Tokenize per-theme card/focus/nav/code CSS rules - #6254
Merged
Conversation
Replaces 8 near-identical per-theme selector blocks (card border tint + top wash), 3 (focus glow), 4 (active-nav rule), and a Kestrel-only code glow + caret block with shared html[data-port-theme] rules driven by new tokens (--port-card-border-color/-alpha, --port-card-surface-image/-size, --port-focus-shadow, --port-field-focus-shadow, --port-nav-active-rule/-glow, --port-code-glow), each theme now setting values in its portosThemes.js manifest instead of a CSS selector block — the same pattern the Kestrel PR used for heading tokens. Verified pixel-for-pixel identical across all 10 themes (card border, background-image, focus box-shadow, nav border-left/text-shadow, code text-shadow) via a headless-Chrome harness diffing computed styles between the old and new stylesheets, except the one deliberate addition the issue calls for: caret-color: rgb(var(--port-accent)) now applies to every theme's inputs/code blocks, not just Kestrel's. Caught and fixed one regression during that verification: the new shared nav-active rule unconditionally declared text-shadow, which broke Black ICE Terminal's inherited body-wide glow reaching the active nav link — fixed by defaulting --port-nav-active-glow to `inherit` instead of `none`.
…ering Two cascade regressions caught in local review: the unconditional shared card-border rule out-specificity'd a component's own semantic border class (e.g. MediaCard's `border-port-accent` "selected" state, or an error border) on every theme, not just the ones that used to override it; and folding the per-theme focus-glow override into the bare `button:focus-visible` rule dropped its specificity below `.bg-port-accent`'s interactive shadow, so an accent-colored button lost its focus glow on Lumen Glass/Lumen Glass Day/ Kestrel Neon. Both are fixed by keeping the token-driven values but scoping the rule to the exact theme(s) that use them, restoring the original selector specificity rather than widening it. Re-verified pixel parity across all 10 themes (including a "selected" card and a focused accent button) with the same headless-Chrome computed-style harness.
Codex review round 2 caught a token leak: reusing --port-focus-shadow for both the generic button/a/[role=button]/[tabindex=0] rule and the Lumen Glass/Lumen Glass Day/Kestrel Neon theme-scoped override meant those three themes' stronger glow bled onto [role="button"]/[tabindex="0"] elements the original per-theme selector never matched (e.g. tag chips, the MIDI piano-roll canvas). Split it into --port-focus-shadow (generic rule only, never overridden per theme) and --port-focus-glow (theme-scoped rule only). Also fixed a gap the first round-2 fix introduced: Lumen Glass Day's `.bg-port-card`/`/50` card-border coverage was dropped when the shared card-border rule's theme list was narrowed to fix the earlier clobbering finding — added it back alongside its existing wider bg-port-card/NN selector. Re-verified pixel parity across all 10 themes with the same headless-Chrome harness, now also covering a role="button" element.
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
client/src/index.css(card border tint + top wash), 3 (focus glow), 4 (active-nav rule), and a Kestrel-only code-glow + caret block with sharedhtml[data-port-theme]rules driven by new CSS custom properties.tokensobject inclient/src/themes/portosThemes.jsinstead of a CSS selector block — the same pattern the earlier Kestrel PR used for heading tokens.--port-card-border-color/-alpha,--port-card-surface-image/-size,--port-focus-shadow(generic default) +--port-focus-glow(theme-scoped override),--port-nav-active-rule/-glow,--port-code-glow. Not added toREQUIRED_TOKEN_VARS— they stay optional, as the issue specifies.caret-color: rgb(var(--port-accent))now applies to every theme's inputs/code blocks, not just Kestrel's.docs/themes/README.mdgets a "Surface & control tokens" section.Closes #6248
Review notes
Verified pixel parity across all 10 themes (card border, selected-card border, background-image, focus box-shadow on both a plain input and an accent-colored button,
[role="button"]focus, nav border-left/text-shadow/color, code text-shadow) with a headless-Chrome harness that diffs computed styles between the old (origin/main) and new stylesheets — zero diffs except the intentional caret-color change.Two local reviewers ran (codex, claude), 3 rounds. Codex caught two real cascade regressions in round 1, both fixed and re-verified: an unconditional card-border rule would have out-specificity'd a component's own semantic border class (e.g.
MediaCard's "selected"border-port-accent) on every theme instead of just the ones that had it before; and folding a per-theme focus glow into the genericbutton:focus-visiblerule dropped its specificity below.bg-port-accent's interactive shadow. A round-2 token-leak finding (the theme-scoped focus glow bleeding onto[role="button"]/[tabindex="0"]via a shared token) was also fixed by splitting into--port-focus-shadow/--port-focus-glow. Codex was clean by round 3.Claude repeatedly flagged that removing Lumen Glass Day's dead/redundant
aside nav [aria-current="page"] { color: rgb(var(--port-accent)); }rule would regress the active nav link's text color. I verified this is a false positive: every real render site for that element (SingleNavRow,WorkingSetRow, nestedNavLinkchildren inLayout.jsx) already applies Tailwind's.text-port-accentclass wheneveraria-current="page"is set, which resolves to the identicalrgb(var(--port-accent))value — confirmed with the same harness, adding a.text-port-accentclass to the test markup and diffing computedcolordirectly (zero diff, all 10 themes). Left as-is rather than adding an unnecessary token.Test plan
node scripts/check-themes.js— theme contract OK for all 10 themes.cd client && npx vitest run— 843 files, 10357 passed, 6 skipped.cd client && npm run build— succeeds.