Skip to content

feat(studio): move every design value into one theme file - #3621

Draft
miguel-heygen wants to merge 1 commit into
feat/studio-u14-tailwind-v4from
feat/studio-u1-theme-tokens
Draft

feat(studio): move every design value into one theme file#3621
miguel-heygen wants to merge 1 commit into
feat/studio-u14-tailwind-v4from
feat/studio-u1-theme-tokens

Conversation

@miguel-heygen

Copy link
Copy Markdown
Collaborator

Lands unit U1 (theme file and semantic tokens) of the Studio design-system foundation. Stacked on the Tailwind v4 PR (#3618). No ratchet yet; the color literals this adds live in the token file, which the ratchet excludes.

What

Lands U1 of the Studio design-system stack: one theme file that owns every design value.

  • packages/studio/src/styles/theme.css is new and holds the semantic colour palette (background levels, surfaces, input, hover, borders, hairline, text levels 0 to 5, accent, selection, playhead, danger, media, container), radii, control heights, the micro type scale, the two font stacks, menu and popover shadows, easings, motion durations, and the icon size and weight.
  • Tailwind's stock palette is cleared with --color-*: initial. The stock colours Studio's markup still names are re-declared in a deprecated block; the studio.* and panel.* names stay as aliases that point at the semantic tokens. Both blocks are removed by the sweep unit that converts the last usage.
  • Motion durations ship as @utility rules (duration-press and siblings) because Tailwind has no duration namespace. Each zeroes itself under prefers-reduced-motion, so a caller cannot forget the reduced-motion case.
  • Phosphor icons get one default size and weight at the app root, read off the theme. Icons that pass their own size or weight still win.
  • The theme is published as @hyperframes/studio/theme.css. The JS preset export stays as a deprecated shim for Tailwind v3 consumers.

Stacked on the Tailwind v4 branch; review that one first.

Why

Studio's design values lived in three places at once: a JS Tailwind preset, an inline @theme block, and raw hex and pixel literals in markup and CSS. Two colour scales overlapped, the same grey was spelled four ways, and a value read from CSS could disagree with the same value read from a class. Tailwind v4 emits every theme entry as both a utility and a real CSS custom property, so a single file can serve markup, plain CSS and canvas code without a second copy.

How

  • @theme static rather than plain @theme: the default behaviour drops theme variables no utility class happens to reference, and several of these tokens are read through var() from stylesheets and from JavaScript rather than through a class.
  • The type scale is keyed --text-step-8 through --text-step-18 because --text-11 would collide with Tailwind's own size scale. text-xs, text-sm and text-lg keep their Tailwind values, so the sweep can be a 1:1 replacement of the text-[11px] style literals.
  • Radii are re-declared at Tailwind's own values, so rounded-md is unchanged today and the theme file becomes the place to change it tomorrow.
  • The icon size and weight are SVG attributes, not styles, so var() cannot be handed to the icon library. readIconTokens resolves them once at the app root, which keeps the token file the only owner instead of pairing a CSS variable with a JavaScript constant.
  • The five font stacks in studio.css collapse to --font-sans and --font-mono. --font-mono takes the code editor's stack, so the timeline timecode and the loader detail line now prefer the same monospace family as the editor.
  • New in this unit, consumed later: --color-selection and --color-playhead are reserved and differ from the accent (the playhead and a selected clip both draw in the accent colour today), and --shadow-menu and --shadow-popover are sized for a near-black canvas rather than Tailwind's light-mode drop shadows. Nothing reads them yet.

Test plan

  • New theme.test.ts compiles Studio's real entry stylesheet with Tailwind and asserts: the semantic palette is emitted both as custom properties and as utilities; an alpha modifier on a plain hex resolves through color-mix; a stock colour outside the deprecated block no longer compiles; selection, playhead and accent are three distinct values; every micro type step exists and the Tailwind sizes are untouched; a duration utility carries its reduced-motion variant; every deprecated stock-palette entry still equals its upstream Tailwind value; the JS preset shim still agrees with the CSS; and an icon with no props of its own inherits the token size and weight. Each assertion was checked against a deliberately broken theme file first.
  • Studio suite: 428 files passed, 1 skipped; 4753 tests passed, 18 todo. No dev server running.
  • typecheck, build, oxlint, oxfmt --check and the fallow audit are clean.
  • Before and after capture with the design-review script, on this branch and on the Tailwind v4 base: the computed-style table is byte-identical, and the six screenshots match apart from a media fixture that loads non-deterministically between runs (the baseline differs from itself the same way, which is why the table is the verdict).
  • Exhaustive check beyond the six screenshots: the built CSS bundle was diffed rule by rule against the base. The complete delta is the five font-family rules above, three utilities pulled in by the new test file, and three classes in components/ui/Button.tsx that previously did not resolve and now do (border-border, enabled:hover:border-border-strong, duration-press). That component was written against a token vocabulary that did not exist yet; its remaining unresolved classes (shadow-btn-primary, bg-surface-hover, bg-accent-red, rounded-button, ease-standard) are left for the unit that rebuilds it. Its border therefore moves from the inherited text colour to the border token, which is the one intended rendering change in this PR.

Not covered

  • No panel is migrated onto the semantic names; every existing class keeps working through the deprecated aliases.
  • The token gate and the hex ratchet are the next unit, not this one.
  • components/ui/Button.tsx is not fixed here beyond the classes that now resolve on their own.
  • Test files are scanned for class candidates, so a class that appears only in a test still reaches the shipped stylesheet. Pre-existing, three rules' worth, left alone.
  • Nothing consumes the selection, playhead, shadow or easing tokens yet.

Studio's design values lived in three places: a JS Tailwind preset, an
inline @theme block, and raw hex and pixel literals in markup and CSS.
theme.css is now the single owner. Tailwind v4 turns it into both the
utility classes and the CSS custom properties, so markup, plain CSS and
canvas code all read the same number.

- semantic colour palette (background levels, surfaces, borders, text
  levels, accent, selection, playhead, danger, media, container), radii,
  control heights, the micro type scale, font stacks, shadows, easings
  and motion durations
- the stock Tailwind palette is cleared with --color-*: initial, so a
  colour that is not a Studio token cannot be reached from markup; the
  entries Studio's markup still names are re-declared as deprecated
  aliases and removed once the sweep converts them
- motion durations ship as @Utility rules that zero themselves under
  prefers-reduced-motion, since Tailwind has no duration namespace
- Phosphor icons get one default size and weight, read off the theme at
  the app root; icons that pass their own still win
- published as @hyperframes/studio/theme.css; the JS preset export stays
  as a deprecated shim for v3 consumers, with a test that fails if the
  two drift apart
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