Skip to content

Explore/dark mode theme - #68

Merged
QA1S merged 8 commits into
mainfrom
explore/dark-mode-theme
Sep 28, 2026
Merged

QA1S merged 8 commits into
mainfrom
explore/dark-mode-theme

Conversation

@QA1S

@QA1S QA1S commented Sep 28, 2026

Copy link
Copy Markdown
Collaborator

No description provided.

QA1S and others added 8 commits September 27, 2026 23:19
Adds a real dark-mode token pass on top of the light "paper cream / navy
ink / blue accent" theme from the updated-ui redesign, following
prefers-color-scheme automatically. Re-tunes every existing hue for a dark
navy surface rather than introducing a new palette:

- Surface: #070c1a / #0b1226 / #101a35 (was cream/paper)
- Ink: the light theme's own paper cream (#f2e9d6), reused as the dark
  theme's text color instead of a separate near-white
- Accent: #3d8fe0 (brightened from the light-mode blue)
- Identity: teal #3fae9c, terracotta #e08a54, violet #a08cf0

Two helper custom properties (--paper-rgb, --ink-rgb) let the one-off
rgba() literals scattered through app-theme.css (glass nav/sidebar
backgrounds, hover washes, texture patterns) flip with the theme instead
of staying baked to the light values -- box-shadows are deliberately left
alone since a shadow should stay dark-hued in both themes, not invert to
a light glow. The logo's recolor filter gets its own dark-mode chain
since it's driven by a filter on a black source image, not --ink.

Also mirrors the token values into globals.css's plain :root block, which
app-theme.css doesn't otherwise shadow for consumers outside
.app-page/.auth-page. --workspace-* tokens are untouched -- the embedded
IDE surface is already permanently dark by design.

Every dark rule is duplicated under both `@media (prefers-color-scheme:
dark)` and `[data-theme="dark"]`. Only the media query is live today (no
theme-toggle UI exists yet); the data-theme selectors are prepared for
whenever one is added, at zero cost if it never is.

Verified by inlining the real CSS into a throwaway static harness (not
committed) and toggling data-theme -- confirmed to render correctly.
Could not verify against the live dev server: middleware fails to build
for every route in this environment on a pre-existing, unrelated
@azure/core-tracing/@azure/core-lro export mismatch (also hit during the
gen2 test run on the control-updates branch), not touched here.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Two real bugs, both from the previous commit's dark tokens only reaching
app-theme.css's own selectors:

1. globals.css, mission-control.css, team-chat.css and
   settings/orca-theme.css each hardcode their own copies of the current
   light-palette hex/rgba values instead of referencing the shared tokens
   -- over 150 occurrences. Toggling the tokens did nothing for any of
   them, which is why gen2 and sign-in (styled mostly through app-theme.css
   directly) went dark while the dashboard, rooms, admin, and mission
   control did not.

   Fixed every occurrence that is a bare, unwrapped literal (verified by
   excluding anything already inside `var(--token, fallback)`, which was
   already correct and needed no change). Two new RGB-triplet helpers,
   --accent-rgb and --teal-rgb, join the existing --paper-rgb/--ink-rgb so
   the many one-off rgba() badges/borders/washes flip too. Modal-backdrop
   scrims are deliberately left as a literal dark navy wash in both themes
   -- a dimmer should stay dark, not invert to a light glow when it
   happens to reuse the ink color.

   Also fixed `html`/`body { background: #f2e9d6 }` in globals.css, which
   applied globally and outside any dark-aware token -- almost certainly
   the dominant reason pages still looked light at their edges regardless
   of what their own content did.

   orca-theme.css's Tailwind v4 `@theme` block can't itself be
   conditional, so its --color-* overrides are written as ordinary
   selector rules after it, following the same pattern as everywhere else.

   Left untouched, and flagged rather than guessed at: a second, older
   layer of literals from at least one earlier redesign
   (rgba(237,238,240,*), rgba(23,59,45,*), #121417, #1a1d21, plus a few
   standalone colors like #46c771/#e3c98a/#86590b) that isn't even
   rendering today's intended light theme correctly -- a pre-existing bug
   independent of dark mode, not safe to fix by blind substitution.

2. There was no way to switch modes -- dark mode only ever followed the OS
   preference. Added `ThemeToggle`, a standalone icon button (not a menu
   item, so it doesn't require opening anything to find) next to the
   profile menu in both AppChrome layouts and on the sign-in page. Clicking
   it stamps an explicit `data-theme` on <html> and remembers the choice in
   localStorage; a blocking inline script in the root layout's <head>
   re-stamps that choice before first paint so a returning dark-mode
   visitor never sees a light flash. Until a visitor ever clicks it, the
   theme still just follows `prefers-color-scheme` with no JS involved.

Verified with the same throwaway static-harness technique as the previous
commit (real CSS inlined, data-theme flipped directly) since the dev
server still can't build on the pre-existing, unrelated
@azure/core-tracing/@azure/core-lro dependency conflict -- confirmed the
dashboard's stat-card/workspace-browser/workspace-card, previously stuck
light, now render correctly in both themes with no regression to the
light-mode screenshot from the prior commit.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
A third, previously-missed theme file: product-theme.css is a separate
Tailwind @theme block scoped to .product-scope/.rooms-scope, hardcoding
its own copy of the light palette independent of both app-theme.css's
plain CSS tokens and settings/orca-theme.css's own @theme block. Rooms
and the dashboard's actual workspace grid (components/workspace/
workspace-grid.tsx) are built entirely through this file's Tailwind
utility classes (text-primary, border-border, bg-card, etc.), so neither
of the previous two commits reached them at all -- confirmed live: this
is why Rooms stayed light after everything else went dark.

Gives product-theme.css the same treatment as orca-theme.css: a dark
override block redeclaring every --color-* custom property as an
ordinary rule after the @theme block (which can't itself be conditional),
duplicated under both the prefers-color-scheme media query and
[data-theme="dark"].

Also updates app-theme.test.ts, which had one assertion hardcoded to the
exact light-mode literal `rgba(255, 253, 247, 0.72)` for .workspace-browser
-- expected breakage from the --paper-rgb fix two commits ago, not a
regression; updated to check for the theme-aware reference instead. Adds
two new tests locking in that both app-theme.css and product-theme.css
now carry a dark override with the right key values.

Verified against the real running dev server this time (previous two
commits could only use a static-CSS harness): dashboard, Rooms, and
Settings all render correctly in both themes, and clicking the toggle
flips every one of them instantly with no reload.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
app-theme.css (plain CSS custom properties), product-theme.css (a
Tailwind @theme block for Rooms/dashboard) and settings/orca-theme.css
(a separate Tailwind @theme block for Settings) each held their own
independent copy of the same cream/navy/blue palette under three
different naming conventions. That duplication is what let Rooms drift
out of sync with everything else going dark two commits ago, and it will
keep happening on every future redesign unless the duplication itself
goes away -- fixing today's instance without fixing the structure just
schedules the next one.

Adds theme-tokens.css as the single file that actually writes a hex or
rgba literal for this palette, as plain --brand-* custom properties (raw
swatches, not role names) with the light default in :root and the dark
override under both prefers-color-scheme and [data-theme="dark"].
Imported once, globally, from the root layout -- CSS custom properties
on :root are visible to every stylesheet regardless of which one declared
them, so app-theme.css, product-theme.css and orca-theme.css don't need
their own copy or their own dark-mode block anymore. Each of them keeps
its own @theme/token names (--codev-*, --color-*) and its own structural
rules (scoping class, radius, layout), now expressed as var() references
into the shared swatches instead of independent literals.

Deliberately did NOT force every surface to identical values: Rooms and
the dashboard sit over an animated gradient backdrop and use translucent
glass-panel cards, while Settings has no such backdrop and uses solid
opaque cards. Both derive from the same --brand-paper-rgb swatch, just at
different alpha, because that's a real design difference between the two
surfaces, not drift to eliminate.

`.workspace-page` (globals.css, the embedded IDE, permanently dark by
design) and `.lp-page` (landing.css, the marketing page's own richer
multi-hue palette) are deliberately untouched -- neither is part of the
authenticated product's shared palette this file governs.

Updated app-theme.test.ts's assertions to match: checks theme-tokens.css
for the actual literals, and checks the three consumer files for var()
references plus the *absence* of any hex/rgba literal, so the test would
catch a regression back into per-file duplication.

Verified against the real dev server (not the static-harness workaround
from earlier commits): Rooms, dashboard, and Settings all render
identically to before this refactor in both themes, confirming this is a
pure internal consolidation with no visible change. Full apps/web suite:
233 files / 1199 tests passed, 1 pre-existing skip.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…e else

OrcaCard (components/settings/orca-style.tsx) -- used across Settings and
the workspace activity feed -- rendered its own visual treatment instead
of the shared shadcn <Card> that Rooms and the dashboard use
(components/ui/card.tsx): border-border/50 + bg-card/50 + shadow-xs,
versus Card's plain border-border + bg-card + text-card-foreground. The
50%-opacity border and background made every Settings card read as a
visibly fainter, muddier surface than a Rooms or dashboard card, even
though both now pull from the exact same theme-tokens.css swatches.

Aligned OrcaCard's border/background/text classes to match <Card>
exactly and dropped the extra shadow; kept its own px-7 py-6 padding,
since that's a real layout choice for settings rows, not part of the
"look" that was inconsistent.

Verified against the real dev server in both themes: Settings' provider
cards and profile cards now render with the same solid card surface as
Rooms/the dashboard.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
OrcaCard (components/settings/orca-style.tsx) became a byte-for-byte
duplicate of components/ui/card.tsx's <Card> the moment its classes were
aligned to match in the previous commit -- at that point it was pure
indirection with no remaining reason to exist as its own component.
Removed it and switched every consumer to the shared <Card> directly:
provider-account-card.tsx, integrations-list.tsx, and
workspace-activity-feed.tsx, plus four page files that used it inline
(settings/personal/{environment,profile}/page.tsx and
workspaces/[workspaceId]/{session-imports/page.tsx,activity/layout.tsx}
-- the latter only for a stale doc comment naming it).

Every call site already passed its own padding override, so this is a
behavior-identical swap, not a redesign -- confirmed against the real
dev server on Profile, Environment Variables, Integrations, and AI
Provider Accounts, all rendering identically to before in both themes.

OrcaPageShell, OrcaPageHeader, and OrcaSubsectionHeader stay -- Rooms and
the dashboard have no equivalent shared components for a page shell or
header (workspace-grid.tsx hand-rolls its own <main className=
"product-scope"> and its own eyebrow/h1/p header inline each time), so
removing these would mean duplicating that markup across seven
settings/workspace-activity pages instead of keeping it in one place.
That would be a step away from consistency, not toward it.

settings/orca-theme.css (the @theme block these components read from)
also stays: it's the only place --color-worktree-sidebar-* is defined,
which components/settings/SettingsSidebar.tsx actually consumes and
product-theme.css has no equivalent for.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The "Integrations"/"Profile"/etc active nav item used bg-worktree-sidebar-
accent (a solid fill) plus ring-1 ring-worktree-sidebar-ring/25 stacked on
top. bg-worktree-sidebar-accent resolves to --brand-paper-tan, which in
dark mode is #101a35 -- a fully opaque dark navy-blue, not the light
neutral tan it is in light mode. Combined with the blue ring, the active
item read as a heavy, solid blue block, visibly heavier than the main
app sidebar's own active state one level up, which just uses a 12%-opacity
accent tint with no fill or ring at all.

Swapped to the same soft-tint convention: bg-primary/10 text-primary, no
ring. Softened the hover state the same way (hover:bg-primary/5) since it
read from the same heavy token and would have hit the identical problem
on interaction.

Verified against the real dev server: the active settings nav item is now
a light, translucent accent wash consistent with the rest of the product,
in both themes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…nt rule

Per feedback that the bg-primary/10 blue wash still read as heavy: active
state is now a neutral bg-foreground/5 background with a 2px border-primary
rule on the left edge, text in --foreground rather than accent-colored.
"Selected" now reads through a precise mark instead of a color wash.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@QA1S
QA1S merged commit fc68554 into main Sep 28, 2026
2 of 3 checks passed
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.

1 participant