Skip to content

Color Lab: ladder inspector — what each rung is and what governs it #604

Description

@mehdibha

Priority: P2 — tooling. Found while diagnosing text-fg-muted vs shadcn (2026-08-04); a working prototype exists.

Nothing in the UI says what a rung is. neutral-900 is "text low-contrast" in the 12-job ladder, and that fact lives in a docstring in www/src/registry/theme/semantics.ts and nowhere a user can see. Nothing shows which rungs are adjustable and which are solver outputs. And the existing ramp visual actively hides the ladder's geometry.

Diagnosing a single muted-text question took a full session of hand-computed APCA arithmetic. It should have been one glance.

The visualization defect

ColorRampsVisual (www/src/modules/create/panel/schema.tsx:167) renders every step as flex-1 — equal width. Every ramp looks evenly spaced whether or not it is. The real spacing at the text end:

800 → 900 gap
light ~7.5 Lc
dark ~21.7 Lc

That 3× asymmetry is invisible today, and it is precisely why the muted step behaves so differently per mode.

What to build

A color-lab section (www/src/modules/color-lab/sections/) — not in the /create panel, which is queued for a full rewrite from an experience spec. color-lab is the sanctioned playground for the color rewrite and already has the section scaffolding plus SystemPreview.

For one palette at a time:

  1. Rungs positioned by real L*, not equal-width, in both modes on a shared axis. The spacing defect must be the first thing you see.
  2. Each rung labeled with its job — 25 app-bg · 50 subtle-bg · 100/200/300 ui rest/hover/active · 400/500/600 borders · 700/800 solid/hover · 900/950 text low/high.
  3. Selecting a rung shows what governs it — not a dial to drag it.

Point 3 is the design idea worth keeping. Seven of twelve rungs are solver outputs, so a per-rung "adjust" affordance promises control that doesn't exist:

rung governed by
25 background axis, via transposeSkeleton
50–300 the skeleton — moves as a group with 25, not individually
400/500/600 border targets — solveBorderTarget, floors BARS.border400/500/600
700 the brand seed (preserveSeed)
800 nothinghoverSolid derives it from 700, shrinking to protect the on-label bars
900/950 solveText vs BARS.text900/950, anchored at TEXT_TARGETS

The "no control, and here's why" cells are the most valuable in the view — they teach the engine's architecture instead of hiding it.

  1. A "Feeds" column: rung → semantic token → consuming components. No component consumes neutral-900; they consume --color-fg-muted (609 call sites / 229 files). The middle link is what makes the first column legible. Same map the relaxed-policy warnings need — build once, use twice.

Prototype

LadderRow exists in Control Lab (www/src/modules/control-lab/rows.tsx + page.tsx, at /internal/panel-lab/controls#ladder-row) — L*-positioned strips for both modes, rung rail with family brackets, inspector with job name, CSS var, per-mode swatch, gap-to-next, "Governed by", and live "Feeds" chips from DEFAULT_SEMANTICS. Not wired into anything.

Two known defects to fix when promoting it:

  • The 900/950 copy is wrong. It says "the bars own this rung — there is no dial." True in dark (Lc 61.5 vs bar 60), false in light, where the anchor binds and both bars have headroom (5.31 vs 4.5, Lc 71.5 vs 60). It teaches the opposite of the actual mechanism. It should distinguish anchor-bound from bar-bound per mode.
  • Gap annotation is in ΔL*, should be APCA Lc — the cross-mode-honest unit (see Color axis: text depth (muted-text darkness beyond the solved minimum) #464).

Best follow-up: make the 900 anchor a live control clamped by the bars. The asymmetry then becomes something you feel — light pulls back a long way before stopping, dark stops in ~1.5 Lc — instead of a table.

Links

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: colorsColor engine and color tokens (@dotui/colors)enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions