6.0 KiB
created, status, requirements, system_design, legacy_specs
| created | status | requirements | system_design | legacy_specs | ||
|---|---|---|---|---|---|---|
| 2026-09-09 | done |
|
|
Implementation Plan: Mobile Notification Dark Mode
Overview
Synchronize the shared Sonner wrapper with the application's initial resolved theme so plugin success notifications use dark colors after a dark page load. One sequential work order owns the regression, correction, and focused desktop/mobile verification.
Confirmed root cause
Toaster reads the document class during its initial render. On a cold page
load, the HTML shell has no theme class, so that read yields light.
AppThemeProvider then applies dark in its layout effect. The Toaster's
passive effect subscribes afterward and does not read the current value again.
Its state stays light until a later root-class mutation.
The app shell enables richColors. Sonner selects those semantic palettes
using data-sonner-theme, so the wrapper's application --normal-* CSS
variables do not correct success colors. The same shared defect can affect
desktop and the authentication surface.
Scope
In scope
- Document REQ-UI-TOAST-THEME-001, which fills a missing reusable UI contract.
- Reconcile the document theme when the shared observer subscribes.
- Cover dark startup, explicit preference precedence, and later theme changes.
- Prove a real plugin success notification renders correctly on mobile and desktop.
Out of scope
- Plugin lifecycle, operating-system notifications, new palettes, and toast layout changes.
- Replacing the existing toast APIs or changing theme persistence.
Technical approach
In apps/packages/ui/src/sonner.tsx, preserve the root-class observer and
disconnect behavior, but synchronize immediately after subscribing. Reuse the
same document-theme reader for initial and subsequent synchronization.
Preserve caller-provided props. Keep the theme provider's layout effect;
terminal initialization already depends on that ordering.
Tests
Add apps/web/components/theme/sonner-theme.test.tsx with the real
AppThemeProvider, shared Toaster, and a real notification. Cover:
- The four cases in
uses $expected at cold load with theme=$theme and systemDark=$systemDarkcover AC .1 and .2. preserves an existing notification through preview and discardandupdates existing and subsequent notifications when the system theme changescover AC .3, including notification identity, content, and actions.- Observer cleanup and explicit wrapper
themeprop compatibility.
Retain the existing components/theme/app-theme.test.tsx checks, especially
the layout-effect ordering regression.
E2E tests
Add tests/settings/toast-theme.spec.ts for Chromium and
tests/settings/mobile-toast-theme.spec.ts for mobile Chrome. Share scenario
helpers in e2e/helpers/toast-theme.ts and reuse the packaged plugin install
helpers in tests/plugins/plugin-test-helpers.ts.
On a fresh navigation, set saved dark against a light OS, system against a dark OS, and saved light against a dark OS. Install the isolated fixture via Settings > Plugins, assert the success notification, and compare its rendered background, text, and border to the selected Sonner palette. Do not inject the document's theme class or toggle the theme before the startup assertion.
The mobile spec uses the configured Pixel 5 project, touch activation, a
viewport-contained notification, and a screenshot for visual inspection.
Run the existing mobile theme-toggle spec as preservation coverage. Restore
fixture state with the existing uninstall helper in afterEach.
Work orders
Verification results
- Workspace dependencies installed with
pnpm install --frozen-lockfile. - Temporary component reproduction rendered the application root as dark and Sonner as light for saved dark and system-dark startup. A later root-class change updates Sonner, confirming the missing initial reconciliation.
- Permanent component RED: two dark-start failures and nine passing controls
or provider tests. The expected failure was root
darkwith Sonnerlight. - Production-build browser RED: desktop had two dark-start failures and one passing light case; mobile had the same two failures and three passing controls, including both existing mobile theme-toggle scenarios.
- GREEN: 11 component/provider tests, three desktop browser tests, and five mobile browser tests passed with retries disabled. The work order records the exact commands and the workspace-path discovery workaround.
- Desktop and mobile dark screenshots were inspected. Toast background, text,
border, viewport containment, and absence of horizontal document overflow
passed. Evidence is retained for this session under
/tmp/kandev-toast-evidence.Eb9wFU/(desktop-after.png,mobile-after.png, andmobile-before.png). - Web typecheck, targeted ESLint, targeted Prettier, and
git diff --checkpassed. Specification linter tests passed all 30 tests during planning; full specification lint also passed. - PR review added and passed an explicit shared-UI ESLint invocation and clarified the historical browser commands, including the temporary configuration needed to reproduce this checkout's filename-filter workaround.
- The temporary test configuration was removed. The managed runner completed teardown of its isolated test backends and browsers.
- Public docs: no change needed. This correction changes notification colors; navigation, copy, configuration, and operator instructions are unchanged.
Risks
- Development StrictMode effect replay can hide the bug; preserve the production-style mount regression and use built Vite assets for E2E.
- A document already carrying the desired class will miss the regression.
- Real Sonner notifications render asynchronously. Wait for the notification
before reading
data-sonner-themeor computed colors. - Disabling rich colors would mask the bug while changing notification semantics; keep them enabled.