Skip to content

✨ Added system appearance mode to Ghost Admin#28911

Merged
9larsons merged 2 commits into
mainfrom
add-automatic-to-admin-darklight-mode
Jun 26, 2026
Merged

✨ Added system appearance mode to Ghost Admin#28911
9larsons merged 2 commits into
mainfrom
add-automatic-to-admin-darklight-mode

Conversation

@weylandswart

Copy link
Copy Markdown
Contributor

Closes https://linear.app/ghost/issue/DES-1288/add-automatic-to-admin-darklight-mode

Why

Ghost Admin only had a light/dark toggle stored as a boolean, so users had no way to follow their operating system's appearance preference. This adds a "system" mode that mirrors the OS and updates live when it changes.

What

  • Adds a third System appearance mode alongside Light and Dark. System resolves to light/dark from the OS and tracks changes live via prefers-color-scheme.
  • Replaces the user menu's dark-mode switch with a Light / Dark / System submenu showing the current choice.
  • nightShift moves from a boolean to a string enum (light / dark / system). Legacy booleans are migrated to strings on read in both the React and Ember layers, so there's no database backfill.
  • New users (owner setup + invited staff) now default to system. Existing users keep their current preference; users with no stored preference continue to resolve to light.
  • Imported users are intentionally left untouched — they reconstruct accounts that already existed elsewhere, so their source preference (or absence of one) is preserved.

Implementation notes for reviewers

  • Ember remains the source of truth for the admin theme (the dark class, the dark stylesheet, and the OS listener). The React menu applies changes through the state bridge (applyAdminThemePreference / preloadAdminThemeStylesheet) for instant feedback, and the existing user-update bridge re-applies the theme after the save round-trips, so editor and UI consumers stay in sync.
  • React's own class-toggle and media-query effects are gated to only run standalone (no Ember bridge present), so they don't fight Ember in embedded admin.
  • nightShift is optional (not eagerly defaulted) so an absent preference isn't written back on unrelated preference saves; the display fallback to light lives at the read site.
  • E2E colorScheme is pinned to light at the Playwright config level so theme state is deterministic across the suite under the new system default.

Tests

  • Backend unit tests for the add (invited staff, importing skip, explicit preference) and setup (owner) default-injection paths.
  • React unit tests for the nightShift migration (legacy booleans, strings, absent, invalid) and non-eager persistence.
  • Ember integration tests for system resolution, live OS change, missing preference, and legacy-boolean migration.
  • Reworked E2E sidebar tests for the Light/Dark/System menu.

Checklist

  • I've read and followed the Contributor Guide
  • I've explained my change
  • I've written an automated test to prove my change works

closes https://linear.app/ghost/issue/DES-1288/add-automatic-to-admin-darklight-mode

- admin previously only had a light/dark toggle stored as a boolean; users had no way to follow their OS appearance preference
- adds a third "system" mode that resolves to light/dark from the OS and tracks changes live via prefers-color-scheme
- new users (owner setup + invited staff) now default to "system"; existing users keep their current preference, with legacy booleans migrated to strings on read so no data backfill is needed
- the user menu's dark mode switch is replaced with a Light/Dark/System submenu
- Ember remains the source of truth for the admin theme (class + dark stylesheet + OS listener); the React menu applies changes through the state bridge so editor and UI consumers stay in sync
@weylandswart weylandswart requested a review from 9larsons as a code owner June 25, 2026 17:38
@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Review was skipped due to path filters

⛔ Files ignored due to path filters (7)
  • ghost/core/test/e2e-api/admin/__snapshots__/members.test.js.snap is excluded by !**/*.snap
  • ghost/core/test/e2e-api/admin/__snapshots__/pages.test.js.snap is excluded by !**/*.snap
  • ghost/core/test/e2e-api/admin/__snapshots__/posts.test.js.snap is excluded by !**/*.snap
  • ghost/core/test/e2e-api/admin/__snapshots__/users.test.js.snap is excluded by !**/*.snap
  • ghost/core/test/e2e-webhooks/__snapshots__/pages.test.js.snap is excluded by !**/*.snap
  • ghost/core/test/e2e-webhooks/__snapshots__/posts.test.js.snap is excluded by !**/*.snap
  • ghost/core/test/legacy/api/admin/__snapshots__/authentication.test.js.snap is excluded by !**/*.snap

CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including **/dist/** will override the default block on the dist directory, by removing the pattern from both the lists.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6c1dc664-e5fe-48c4-bd93-db769754df2a

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

Admin theme handling now uses light, dark, and system modes across preference parsing and defaults, Ember bridge methods, the React useTheme hook, the sidebar appearance menu, and E2E coverage. The core user model now defaults new accessibility data to {"nightShift":"system"}.

Suggested reviewers

  • EvanHahn
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately highlights the main change: adding system appearance mode to Ghost Admin.
Description check ✅ Passed The description is clearly related to the change and explains the new system appearance mode, migration, and related tests.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch add-automatic-to-admin-darklight-mode

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
apps/admin/src/layout/app-sidebar/user-menu.tsx (1)

53-73: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use radio semantics for the single-select appearance group.

These options form a mutually-exclusive selection, but they render as plain menu items with only a visual (aria-hidden) checkmark, so assistive tech can't announce the active choice. Consider DropdownMenuRadioGroup/DropdownMenuRadioItem (or role="menuitemradio" + aria-checked) so the selected appearance is conveyed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/admin/src/layout/app-sidebar/user-menu.tsx` around lines 53 - 73, The
appearance options in user-menu.tsx are a single-select group but are
implemented as plain DropdownMenuItem entries, so the active theme is not
exposed to assistive tech. Update the theme picker inside DropdownMenuSubContent
to use radio-style semantics, preferably with DropdownMenuRadioGroup and
DropdownMenuRadioItem, and keep the selected value tied to the current theme in
THEME_OPTIONS / setTheme. If you keep the current structure, add the proper
menuitemradio role and aria-checked state on the item rendered for each theme so
the checked appearance is announced.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@apps/admin/src/layout/app-sidebar/user-menu.tsx`:
- Around line 53-73: The appearance options in user-menu.tsx are a single-select
group but are implemented as plain DropdownMenuItem entries, so the active theme
is not exposed to assistive tech. Update the theme picker inside
DropdownMenuSubContent to use radio-style semantics, preferably with
DropdownMenuRadioGroup and DropdownMenuRadioItem, and keep the selected value
tied to the current theme in THEME_OPTIONS / setTheme. If you keep the current
structure, add the proper menuitemradio role and aria-checked state on the item
rendered for each theme so the checked appearance is announced.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 4b0d21fc-6d97-4f17-91dd-315553fa15dd

📥 Commits

Reviewing files that changed from the base of the PR and between 2506a68 and 103ad40.

📒 Files selected for processing (13)
  • apps/admin/src/ember-bridge/ember-bridge.tsx
  • apps/admin/src/hooks/use-theme.ts
  • apps/admin/src/hooks/user-preferences.test.tsx
  • apps/admin/src/hooks/user-preferences.ts
  • apps/admin/src/layout/app-sidebar/user-menu.tsx
  • e2e/helpers/pages/admin/sidebar/sidebar-page.ts
  • e2e/playwright.config.mjs
  • e2e/tests/admin/sidebar/navigation.test.ts
  • ghost/admin/app/services/feature.js
  • ghost/admin/app/services/state-bridge.js
  • ghost/admin/tests/integration/services/feature-test.js
  • ghost/core/core/server/models/user.js
  • ghost/core/test/unit/server/models/user.test.js

@nx-cloud

nx-cloud Bot commented Jun 25, 2026

Copy link
Copy Markdown

🤖 Nx Cloud AI Fix

Ensure the fix-ci command is configured to always run in your CI pipeline to get automatic fixes in future runs. For more information, please see https://nx.dev/ci/features/self-healing-ci


View your CI Pipeline Execution ↗ for commit 2ae2c75

Command Status Duration Result
nx run @tryghost/admin:build ✅ Succeeded 6s View ↗
nx run ghost:build:assets ✅ Succeeded 2s View ↗
nx run ghost:build:tsc ✅ Succeeded 7s View ↗

💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗


☁️ Nx Cloud last updated this comment at 2026-06-25 19:33:25 UTC

ref https://linear.app/ghost/issue/DES-1288/add-automatic-to-admin-darklight-mode

- new users now persist accessibility {"nightShift":"system"} at creation, so every serialised user reflects the new default — including authors embedded in posts/pages responses and webhook payloads, and the owner created during blog setup
- regenerated the affected admin API, webhook, and setup snapshots; every change is limited to the accessibility value and the resulting content-length headers
@codecov

codecov Bot commented Jun 25, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 60.00000% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 74.35%. Comparing base (83e6dd9) to head (2ae2c75).
⚠️ Report is 6 commits behind head on main.

Files with missing lines Patch % Lines
ghost/core/core/server/models/user.js 60.00% 4 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #28911      +/-   ##
==========================================
- Coverage   74.38%   74.35%   -0.04%     
==========================================
  Files        1559     1559              
  Lines      135013   135014       +1     
  Branches    16416    16408       -8     
==========================================
- Hits       100424   100383      -41     
- Misses      33599    33612      +13     
- Partials      990     1019      +29     
Flag Coverage Δ
e2e-tests 76.50% <60.00%> (-0.04%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@9larsons 9larsons merged commit 65d3685 into main Jun 26, 2026
71 of 73 checks passed
@9larsons 9larsons deleted the add-automatic-to-admin-darklight-mode branch June 26, 2026 16:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants