Skip to content

Web UI: env-var catalog, a light/dark design system, and a full redesign#28

Merged
jellologic merged 3 commits into
mainfrom
feat/web-ui-refresh
Jul 24, 2026
Merged

Web UI: env-var catalog, a light/dark design system, and a full redesign#28
jellologic merged 3 commits into
mainfrom
feat/web-ui-refresh

Conversation

@jellologic

Copy link
Copy Markdown
Owner

The shared base for the web-UI work — three commits that evolve the config UI from a bare form into a designed, capability-aware surface. Split out as its own PR because the branding and capability-gating PRs stack on it.

1. Browse every environment variable Claude Code references (6216890). 495 vars extracted from the shipped binary, classified documented / undocumented / internal — the classification is the feature, so an undocumented name is never mistaken for a supported knob. Descriptions are hand-written, never extracted; tests enforce that a non-documented entry cannot carry one. Extraction is a committed script, not a snapshot.

2. A design system with light/dark, enforced by the type checker (74468e1). Tokens measured from Linear and Apple (negative tracking that scales with size; hairlines, not shadows). strictTokens makes a raw #fff or 13px a type error — 241 violations fixed to zero. Light/dark follows the system with an override that doesn't defeat it. Two bugs found by building it, not reading: the theme script blocked by CSP (fixed with a hash-derived CSP, tighter than unsafe-inline) and a status dot that typechecked and rendered nothing (Panda is a build-time extractor — css({bg: map[k]}) emits no CSS).

3. Redesign every screen, and fix two rendering bugs the type checker could not see (35d545f). Built by 8 agents behind a shared primitive layer, then integrated and adversarially reviewed. The reviewers caught two high-severity bugs invisible to review: every hairline painted in the text colour (a border-shorthand/borderColor race across Panda's flat utility layer — ~16× the intended contrast), and the doctor's status rule grey for exactly the failing statuses. Both found by reading emitted CSS offsets and measuring getComputedStyle in both themes.

Verified in a browser in both themes throughout. ~783 tests, packed size within the 150 kB ceiling.

495 of them, extracted from the shipped binary (2.1.217): 60 documented, 338
undocumented, 97 internal. Searchable by name OR by description, so "output
tokens" finds CLAUDE_CODE_MAX_OUTPUT_TOKENS.

THE CLASSIFICATION IS THE FEATURE, not a caveat attached to it. Reading strings
out of a binary yields names and nothing else — no meaning, no defaults, no
evidence a name is still wired to anything. Presenting 495 strings as if they
were equally supported knobs would invite someone to set an unreleased feature
codename in a profile and wonder why nothing happened, or why it stopped
working after an agent update. So every entry declares how much is known:

  documented    described by hand from Anthropic's docs, `claude --help`, or
                swisscode's own adapter. Safe to act on.
  undocumented  the name is real; the meaning is not. Ships with NO description
                on purpose — a plausible guess is worse than a blank, because
                someone acts on it.
  internal      test hooks, profiling, third-party cloud auth, and two-word
                feature codenames (ALDER_WICKET, BISON_CAIRN). Listed for
                completeness; not knobs.

Descriptions are hand-written and never extracted. `test/adapters/
claude-env-catalog.test.ts` enforces that a non-documented entry cannot carry
one, that every "internal" verdict states its reason, and that every variable
the adapter itself sets appears documented and flagged — a catalog that omitted
those would invite someone to hand-set a variable the launcher already owns.

Those tests immediately earned their keep: they caught the extractor running
its shape heuristics BEFORE the hand-written table, which filed
CLAUDE_CODE_ATTRIBUTION_HEADER — a variable swisscode sets — as an "unreleased
codename" because it is two words. A verified description now outranks a guess
about a name's shape.

Extraction is a committed script (`npm run extract:claude-env`), not a pasted
snapshot: the catalog ships in the package, so the build must not require
Claude Code installed, and without the script it would be a list nobody could
regenerate against a binary that ships weekly. The UI prints the version it
came from.

Served from its own route rather than `bootstrap`: 57 kB that one screen wants,
and a cold start should not pay for a screen nobody opened.

778 tests, 131.9 kB packed (ceiling 150).

Signed-off-by: jellologic <[email protected]>
The UI had a dark-only palette of raw hex, twelve distinct hardcoded font
sizes, and no scale for anything. This replaces it with a token system, and
makes the tokens mandatory.

MEASURED, NOT INVENTED. The type scale, weights, surface ramp and motion are
read out of Linear's shipped CSS custom properties on the live site via
Playwright; the light ramp and the tracking curve out of Apple's. Two things
they agree on, and they matter more than any single colour:

  - Negative tracking that scales with size — Apple runs -0.01em at 12px and
    -0.022em at 17px. Large text at default tracking is the most common tell of
    an unstyled app, so tracking ships inside each textStyle rather than being
    left to call sites to remember.
  - Hairlines, not shadows. Linear's cards are a 12px radius and a 1px
    translucent border with `box-shadow: none`. Shadows exist as tokens and are
    used nowhere.

Specifics worth keeping: weight 590 for titles (Linear's, and visibly less
heavy-handed than semibold on a variable font), 32px controls, 0.1s
transitions, #1d1d1f/#f7f8f8 for text because neither reference ships pure
black or white, and #f5f5f7 for the light canvas so a white panel has something
to sit on.

`strictTokens` IS THE POINT. `color: '#fff'` and `fontSize: '13px'` are now
type errors: 241 of them when the rule went on, now zero. A system nothing
enforces decays into arbitrary values within a release — this codebase proved
that before the rule existed. Genuinely arbitrary layout values remain legal
through Panda's `[...]` escape, which keeps them visible as deliberate choices;
there are 26, all dimensions.

LIGHT/DARK FOLLOWS THE SYSTEM, with an override that does not defeat it.
`data-theme` on <html> always holds a RESOLVED value, so components style
against one condition instead of reasoning about a three-state preference.
`system` is the default and a real choice: a live `matchMedia` listener means
the OS switching at sunset re-resolves immediately. Status colours are darker
in light mode — reusing the dark-mode green and amber on white is the usual way
a light theme ends up illegible.

Two problems found by building it rather than by reading it:

- THE CSP BLOCKED THE THEME SCRIPT. Resolving the theme before first paint
  needs an inline script, and `script-src 'self'` refused it — so the theme
  applied late, via React, which is exactly the white flash the inline script
  exists to prevent. Rather than add `unsafe-inline`, the document's CSP is now
  DERIVED FROM THE DOCUMENT: the exact bytes are hashed and named. That is
  tighter than before, not looser — an injected script still cannot run, and
  tampering invalidates the hash.

- THE STATUS DOT RENDERED TRANSPARENT, and typechecked perfectly while doing
  it. `css({ bg: LOOKUP[tone] })` is a runtime value, and Panda is a build-time
  extractor: it emits classes for the literals it can see in source, so a
  lookup produces no CSS at all. Dot and Badge are `cva` recipes now, where the
  variants are literal at build time and the prop stays typed. Worth stating
  plainly: type safety did not catch this one, and could not have.

782 tests, 134.4 kB packed. Verified in a browser in both modes: tokens
switching, the dot picking #1a7f37 on light and #3fb950 on dark, and no console
errors under the derived CSP.

Signed-off-by: jellologic <[email protected]>
…uld not see

The previous commit gave the UI a token system; the screens were still the old
layouts wearing new tokens. This redesigns all seven web screens and the Ink
terminal wizard against that system — and, in doing so, found that two of the
system's own rules were not actually being applied to the pixels.

TWO HIGH-SEVERITY RENDERING BUGS, both confirmed against the shipped stylesheet
and a running browser rather than by reading:

1. EVERY HAIRLINE WAS PAINTED IN THE TEXT COLOUR. `borderBottom: '[1px solid]'`
   beside `borderColor: 'border.subtle'` is a race, not a pair: `border-bottom`
   is a shorthand, and omitting its colour resets `border-bottom-color` to
   `currentColor`. Panda emits every atomic class into one flat layer at equal
   specificity in EXTRACTION order, so whichever the scanner reached last won.
   Measured in the running app: borders painted rgb(29,29,31) — the element's
   own text colour — where rgba(0,0,0,0.06) was intended, roughly sixteen times
   the contrast, on every panel header, list separator and sidebar divider. The
   visually identical `borderRight` two lines away was correct purely because of
   where it landed in the file, which is why it read as random rather than
   broken. This defeated the one rule panda.config.ts calls the app's structural
   device: hairlines, not shadows.

   Fixed by making the colour ride INSIDE a `borders` token, so there is no
   shorthand and no separate colour to race it. Where the colour varies with a
   variant, recipes now set `borderWidth`/`borderStyle` longhands and leave
   `borderColor` free — longhands reset nothing, so that composition is
   order-independent too.

2. THE DOCTOR'S STATUS RULE WAS GREY FOR EXACTLY THE STATUSES THAT MATTER. Same
   defect on `borderLeft: '[2px solid]'`, and the class ordering happened to
   spare only `ok`. So a passing check drew its green rule and every FAILING
   check — the only ones a reader is looking for — drew grey.

Neither is a type error. Both are invisible to review. They were found by
reading the emitted CSS offsets and measuring getComputedStyle in both themes,
which is the only way this class of bug is ever found.

ALSO REMOVED `jsxFramework: 'react'`. Nothing imports styled-system/jsx, so it
only enabled the JSX prop extractor — which read this app's components as style
props and emitted a literal `.fill_true{fill:true}` and a dead `.ai_start` into
the shipped CSS. Both are gone, and the whole collision class (any prop named
after a CSS property, any component shadowing a Panda pattern) cannot recur.

Contrast measured and fixed rather than eyeballed: `content.tertiary` in light
mode was below 4.5:1 on three surfaces and is now 4.52–5.15; the accent, ok and
warn ramps were re-derived for light; the disabled button went from 1.87:1 to
4.52:1 by dropping opacity for an explicit muted treatment.

THE TERMINAL WIZARD joins the same vocabulary without pretending to share the
mechanism. Ink has no CSS, so `src/adapters/ui/theme.tsx` names the semantic
roles and maps each to an Ink colour in one place — nine bare `color="cyan"`
across three files had no shared meaning. Every role carries a SECOND CHANNEL:
colour is absent under NO_COLOR and through a pipe, is remapped by every popular
terminal theme, and red/green is the most common colour deficiency, so each role
pairs its hue with bold/dimColor and status reporting adds a glyph. Strip the
colour and the wizard still reads. A new test pins that a refused binding
renders in the warn colour, proven to fail when the tone is flipped.

Built by eight agents in parallel behind a shared primitive layer, then
integrated and checked by four adversarial reviewers. The reviewers were not
taken on faith: the repair pass rejected seven findings with evidence, including
a suggested accent colour that measured 4.46:1 against the surface it actually
sits on.

783 tests, 138.9 kB packed (ceiling 150). Verified in a browser in both themes:
zero borders painting as text, all five doctor tones distinct, no console errors
under the derived CSP.

Signed-off-by: jellologic <[email protected]>
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