Skip to content

feat(studio): graphite colour system with a light theme that passes contrast - #4836

Merged
miguel-heygen merged 8 commits into
mainfrom
feat/studio-graphite
Oct 1, 2026
Merged

miguel-heygen merged 8 commits into
mainfrom
feat/studio-graphite

Conversation

@miguel-heygen

@miguel-heygen miguel-heygen commented Oct 1, 2026 •

Copy link
Copy Markdown
Collaborator

What changes for a person using Studio

Studio's colours move to Graphite: a softer, lifted dark (the shell was near-black), and a full light theme. Every text pairing clears WCAG 4.5:1 and every icon, border, dot and focus ring clears 3:1, on every surface and state it can sit on. The player and the audio meter stay dark in both themes.

How

  • One value per colour. Each token in theme.css is light-dark(light, dark), so the color-scheme where a colour is painted picks its half. Studio is dark; data-theme="paper" on the document element (the existing host hook) makes it light; the preview pane and the audio meter set scheme-dark, so they stay dark inside a light Studio with no copied values. The scheme rule sits in the base layer, so a host's own color-scheme wins.
  • Roles, not new names. Existing public tokens keep their names and take Graphite values. Missing roles are added: raised (menus, popovers), press, on / on-hover (toggle on), accent-hover, accent-ink (accent text), on-accent (text on an accent fill), ring, danger-ink, on-danger, warning-ink, text-off (disabled only). States and lines are washes of the text colour (color-mix), so they work on any surface.
  • Stock colours stay stock. white, red-* and amber-* keep their upstream values, so a host's own palette is untouched. --color-neutral-* points at roles by the job each step does in markup (light steps are text, dark steps are surfaces and lines).
  • Call sites that meant a role now say it. Studio text, lines, dots and focus rings that used stock white, red, amber, orange, green or purple on a Studio surface use roles: timeline lane labels and tree lines, caption selection and focus rings, hover labels on inspector buttons, notices (FFmpeg, colour grading, VFX, motion), status dots, invalid-field borders, the ease curve grid and curve, the border-radius preview, the Shortcuts key caps. White stays where it sits on media, a scrim, a solid fill or a colour picker. Accent buttons are the solid accent with its hover step, popovers sit on raised, flat buttons go hover then press; the timeline toolbar has four readable states (idle, hover, on, on+hover).
  • File-type icons keep their brand hue in both themes, with a darker light half that holds 3:1 on a hovered or selected row.
  • The dock follows the theme. dockview's runtime stylesheet pins color-scheme: dark on its default theme class; the shell around Studio's dock now inherits Studio's scheme.
  • Contrast proof in CI. scripts/contrast.ts reads oklch(), light-dark(), color-mix(... transparent) and nested var(), and a theme can reuse another's pairs (pairsFrom). The manifest measures every timeline pair and every chrome role against every fill it sits on, in both themes: 146 pairs pass, and the debt baseline (11 failing timeline pairs on main) is empty. A second test measures every file-type icon colour on a row at rest, hovered and selected, in both themes.
  • Root fixes the ratchet surfaced: trim-handle grips were drawn at 60% of their fade opacity, so they could not hold 3:1 on a hovered clip; the pending-track label was dimmed twice (a muted token plus opacity: 0.5).

Before

Main, dark (main has no light theme for the editor; its paper hook only recoloured the timeline).

Before: dark editor shell, after every thumbnail loaded
Before: dark clip menu
Before: dark Shortcuts panel and property panel
Before: dark code tab and file tree

After

Dark, then light (data-theme="paper"), on the repo's product-promo example.

After: dark editor shell, after every thumbnail loaded
After: light editor shell
After: dark clip menu
After: light clip menu
After: dark Shortcuts panel and property panel
After: light Shortcuts panel, player stays dark
After: light code tab, file-type icons

Three window sizes

The editor shell at 1280x800, 1440x900 and 1920x1080, dark then light. The rendered sweep finds 0 items under target at every size in both themes.

1280x800, dark
1280x800, light
1440x900, dark
1440x900, light
1920x1080, dark
1920x1080, light

Rendered contrast, measured in the built Studio

A headless Chromium 147 sweep of every visible text run (4.5:1) and svg icon (3:1) against the colours actually painted beneath it, at 1440x900. Failing items out of items checked.

View Main, dark This PR, dark This PR, light
Editor shell (timeline, property panel) 40 of 102 0 2 (lint badge)
Clip menu open 43 of 108 0 2 (lint badge)
Shortcuts panel open 36 of 111 2 (lint badge) 2 (lint badge)
Assets tab 34 of 91 2 (lint badge) 2 (lint badge)
Catalog tab 33 of 95 2 (lint badge) 2 (lint badge)
Code tab 74 of 161 42 (code editor) 42 (code editor)

The Layers, Renders and Variables views keep the code tab open beside them, so they show the same 40 to 42 code-editor items and nothing else.

Named exceptions

  • The code editor keeps CodeMirror's One Dark theme in both Studio themes; its gutter and some syntax colours sit at 3.6 to 4.4:1. That theme is third-party and unchanged here; whether the editor should follow Studio's theme is a separate call.
  • The lint count badge pulses its opacity (animate-pulse); at the trough its label dips below 4.5:1, at rest and at the peak it passes. Left as is; the pulse is the badge's signal.
  • The deprecated Tailwind v3 JS preset stays at its old values. v3 cannot read light-dark() or color-mix; theme.css is the source.
  • light-dark() needs Chrome 123, Safari 17.5 or Firefox 120. Verified in Chromium 147 only.
  • A host that imports theme.css and themes by its own attribute sets color-scheme itself (light or dark); tokens it does not override then follow it.

Verification

  • scripts/contrastRatchet.test.ts: 146 pairs, 0 baseline debts; the file-type icon test fails when one icon goes back to its old light colour (1.68:1, the same figure the rendered sweep measured).
  • packages/studio: the full unit suite, 6310 tests pass. The hex ratchet baseline is lowered.
  • tsc --noEmit on packages/studio: exit 0. oxlint on the changed files: 0 warnings, 0 errors. oxfmt: clean. fallow audit --base (the CI command): exit 0.

…ontrast

Studio's tokens take the Graphite values: a lifted dark, a light set behind
data-theme="paper", the player dark in both. Every text role clears 4.5:1 and
every icon and ring 3:1 on the fills it sits on, held by the contrast ratchet.
@github-actions

github-actions Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

Edit accuracy: 557 passing here, 557 on the base branch

The gate passes.
Smoothness is reported in the artifact, not gated. A case fails only if it fails 2 of 3 runs.

Unstable (1)

  • crop-none-px-r0-nested-z200: tracking 0.02, pressJump 0, drop 40.03, reload 0.04, render 40.03, undo true / tracking 0.02, pressJump 0, drop 0.04, reload 0.04, render 0.03, undo true / tracking 0.02, pressJump 0, drop 0.04, reload 0.04, render 0.03, undo true

…contrast

The dock no longer inherits dockview's forced dark scheme, popovers sit on the
raised surface, stock white, red and amber text map to roles, the keyframe
diamonds and track headers use roles, and the contrast ratchet measures every
pair in both themes (150 pairs).
… fills use roles

Round-two review: white, red and amber are colour content as well as text, so
they keep Tailwind's values and hosts keep their palettes. Text, toasts, the
feedback card, the FX popover, destructive buttons, category labels and the dock
take Graphite roles instead; the contrast gate rejects an unparseable colour.
…s contrast

Stock white, red, amber and file-type hex left in Studio chrome now use roles or a light-dark pair; the file tree icons carry a contrast test.
… in both themes

Danger graphics use the danger ink, file tree outlines use the accent ink, the colour-curve well stays dark like the scopes, and six timeline token pins come back.
…tay distinct in both themes

The invalid readout border is dashed as well as red, the curve graph's ring sits inside its dark well, the radius dots and ease handle centre follow the theme.
…hed invalid border has a test

Simplify pass: the ease editor's inline accent styles become stroke and fill classes, the radius dot class is computed once, the ratio check is one expression, and the alias table is named for what it holds.
@miguel-heygen
miguel-heygen marked this pull request as ready for review October 1, 2026 13:33
@miguel-heygen
miguel-heygen merged commit 658f22b into main Oct 1, 2026
248 of 249 checks passed
@miguel-heygen
miguel-heygen deleted the feat/studio-graphite branch October 1, 2026 14:21
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