feat(theme): Visual System 2.0 — T1 foundations - #2525
Merged
Merged
Conversation
Visual system 2.0, T1 subsystem 1. Token layer only; no product CSS moves and no old name changes what it resolves to. The audit read "seven names all resolve to one white" as an accident. Most of it was not. The owner's 2026-06-20 decision already put a gray plate behind pure-white content, and that is this model — `--background` was always the CARD, never the page. Two rungs were genuinely missing: - nothing below the canvas, so a sidebar had no rung to recede to; - `--background-elevated` pinned to `--background`, leaving overlays with no surface of their own. So the ladder is named, not rebuilt: sunken / base / raised / overlay, with height mapping monotonically to lightness and the READING surface always on this mode's brightest rung — pure white in light, lightest gray in dark. Measured by painting each rung and reading the pixel back, light is 237/247/255/255 and dark is 9/17/23/27. Light has no room above white, so overlay carries the same fill as raised and is told apart by the overlay recipe; unpinning it is what gives dark a rung to climb and the recipe something to hang on. Every rung derives from `--background`, because palettes override only that token and a hardcoded plate survived every palette switch once already. That makes the offsets CUMULATIVE: base is already -0.025, so a sunken rung written as -0.030 lands 0.005 under base — a step nobody can see. It is -0.065. `--surface-sunken` has no consumer until the sidebar recedes to it in T2, and is registered as a reserved scale rung rather than worked around: an ordered scale reads as a scale only with its bottom rung present.
Visual system 2.0, T1 subsystem 2. Token layer only; the 59 call sites of --muted-foreground are untouched and migrate in T2-T4. Measured against --surface-raised, the audit's diagnosis held for one tier of three. Primary was said to be short of its 15:1 target and was already 19.1:1. Secondary was to drop to ~5:1 to open a 3x gap from primary — but that gap is copied from a reference with only TWO text tiers, and three tiers standing on a 4.5:1 AA floor cannot span 3x without the bottom two colliding. The rhythm is even instead, roughly geometric, which is what three tiers can hold: light 19.14 / 9.84 / 4.84 dark 15.46 / 9.15 / 4.67 The real defect was muted at 50% ink: 4.21:1 in light and 3.89:1 in dark — already under AA in the mode nobody measured. It is 54%. Dark primary needed 0.92 -> 0.95 to clear 15:1, and dark carries its own secondary mix because one percentage cannot land both modes in the 8.5-10:1 band: 74% reads 9.8 in light but 8.2 in dark. --foreground-dimmed becomes an alias. It was written as "80% ink" exactly like --foreground-secondary but mixed in srgb where that one mixes in oklch, so the same words resolved to different colours (10.51:1 vs 12.01:1). The contract test pins the invariant on the source text rather than a rendering, because what must not come back is dimmed having its own definition to drift from.
Visual system 2.0, T1 subsystem 3. Token layer only. Four border strengths were in play: 5% on the sidebar rail (declared locally in sidebar.css), 8% on --card-border-color, 10% on --border, 16% on --border-strong. Two of those are tiers and two are drift. `--border` is the structural line — the edge of a panel, a card, a table. `--border-soft` is the quiet one for seams inside a surface, and it is what the 5% rail and the 8% card edge converge on. Their call sites move in T2-T4; T1 does not touch product CSS. Registered as a reserved scale rung for the same reason as --surface-sunken: the tier exists before its consumers do, which is what defining a scale means.
Visual system 2.0, T1 subsystem 4. Token layer only. The generated theme ships --shadow-low/med/high and product CSS had used them exactly once, reaching for hand-rolled shadows everywhere else. A scale nobody can name is a scale nobody uses, so: `raised` for a card lifting off its plate, `overlay` for anything in a portal, `drag` for something under the cursor. In-flow elements still cast no shadow; this only names the legitimate path. Consumers adopt them in T2-T4. --shadow-minimal-flat is NOT deleted, against the audit. It has five live consumers, two of them in packages/ui/src/styles.css. The audit read its three DEFINITION sites as its only mentions — it searched the desktop renderer and the token lives in a package. It is also not an elevation tier at all: it is a 1px ring wearing a box-shadow, which is why it survived a scale it was never part of. Kept and documented as a ring.
Visual system 2.0, T1 subsystem 5. The only subsystem here that changes existing pixels. Two vocabularies were running in parallel and disagreeing about the card: this file said 8, Astryx's own scale says 10 (--radius-element), and --radius-modal says 12. Three numbers for one idea is how a radius stops meaning anything. The card tier resolves to 10 rather than 8, because Astryx's element rung is what its own components already round to — meeting it there is what stops a card from disagreeing with the controls sitting inside it. 35 call sites read --radius-surface and all of them move together.
Visual system 2.0, T1 subsystem 6. Light mode; dark needs its own pass. The four status hues were four independent choices: measured against white, info 2.82:1, warning 3.29:1, success 4.55:1, destructive 4.81:1 — a 1.7x spread with TWO of them under the 4.5:1 AA floor. Nothing tied them together, so nothing kept them together. One lightness for all four, each keeping its own chroma. At L=0.50 they land 5.54-6.33:1 as a family. The residual spread is hue rather than drift: yellow carries more luminance than blue at equal lightness, and flattening that would mean giving up the shared L that makes them a family in the first place. info and warning move the most (0.68 and 0.66 down to 0.50) because they were the two that failed AA. This is the visible one; it belongs in the preview set.
--warning was the one status hue with no dark override. info, success and destructive each state their own dark value, so lowering the shared light value to L=0.50 dragged dark down with it: dark warning measured 2.83:1, well under AA, in the half of the theme the previous commit claimed not to touch. Restored to its measured 5.44:1 by stating it where its three siblings state theirs. The asymmetry was the trap — a family where one member is defined once and three are defined twice does not behave like a family.
…he shared-definition bug
Astro-Han
added a commit
that referenced
this pull request
Aug 8, 2026
… contradiction DESIGN.md is meant to be the short list of rulings a call site can obey. T1 (#2525) grew it to 207 lines by carrying three things a spec should not: - migration state (T2-T4 queues, the srgb exception, dark's deferred regeneration, the 8/10/12 obituary) - those belong to the task thread, and they go stale the moment T2 lands with nobody coming back to delete them; - inventories - the seven-alias legacy surface list and border-strong's live call-site census, which is what the file's own last Don't forbids copying in; - measured tables - the surface pixel ladder and the ink contrast column. Their caption claimed "the ink/surface contract tests hold them," but ink-ladder-contract only pins two structural invariants (dimmed stays an alias, no srgb in the ink family) and nothing pins a number. So the numbers were an unguarded second source of truth next to maka-tokens.css, wearing a guarantee they did not have. The rhythm and the AA floor survive as rules. The radius chapter also disagreed with its own frontmatter: the table gave "buttons, inputs" to the 6px control tier while button-default and input-default resolve to {rounded.card} = 10px. The frontmatter is right. --radius-control's 29 live consumers are product-drawn compact controls, and Astryx Button/Input take --radius-element, which IS Maka's card value - the two scales are offset by a rung. Documented as The Astryx Offset so the next reader resolves a radius from the box, not from a same-sounding token name. Doc-only; no token or product CSS changes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
T1 of the Visual System 2.0 program (task #168): the six-subsystem foundation, entirely in the token/theme layer. Old names remain as aliases with unchanged resolved values; consumption sweeps land in T2–T4.
--background; the seven white-pinned aliases each resolve to an intentional tier. Measured px: light 237/247/255/255, dark 9/17/23/27.--foreground-dimmeddemoted to alias (contract-tested); muted lifted off the AA floor in both modes.--elevation-raised|overlay|drag) aliasing the theme scale;--shadow-minimal-flatreclassified (1px ring, live consumers in packages/ui) instead of deleted.DESIGN.md rewritten as the 2.0 specification (ratified rulings, measured tables, forbidden list), superseding the 1.x refresh from #2491 while absorbing its link-contrast rule.
Verification
Merging without waiting for CI per owner's directive (frontend changes, fix-forward on red).