Accent colour #46
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
URL:http://192.168.31.41:5173/operator/personalization
Create that new page for the user to select the accent color. the modal will keep showing system,dark and light, but the accent color can be changed there. like a tint
Claimed — branch
issue/46-accent-colour. Doing: new/operator/personalizationpage that picks the accent (tint) for--primary/--accent, persisted browser-local alongside the existing system/light/dark selector.Issue #46 — Accent colour: plan
Restated goal
Add
dashboard/src/routes/operator/personalization/+page.svelte— the URL the issue names,/operator/personalization— where the operator picks the console's accent (a tint). The accent isa hue that recolours the shipped brand token
--primary(plus the two tokens that must follow itin lockstep,
--accentand--sidebar-ring), so chips, focus rings, links, the active nav pill,--info, and--chart-1all move with it — there is no second brand colour. The pick is stored inlocalStorageand re-applied before first paint, so a reload keeps the tint with no flash. Theexisting system/light/dark appearance modal is untouched and keeps its three options; this page adds
the accent control and nothing else. "Done" for the operator: open the page, click a swatch, the whole
console re-tints immediately, reload, still tinted, and a Reset returns it to Polar Indigo.
Scope
Files changed (dashboard only, no middleware, no agent, no schema, no API):
dashboard/src/app.css--accent-hue: 260to:root; replace the hardcoded hue in the 5 tinted literals withvar(--accent-hue)dashboard/src/lib/theme.tsparseAccentHue/applyAccentHue/setAccentHue; bumpLastdashboard/src/app.htmlgotoi-accentand sets--accent-huebefore first paintdashboard/src/routes/operator/personalization/+page.sveltedashboard/src/lib/nav.tsPersonalization+paletteicon inITEM_ICONS; bumpLastdashboard/src/lib/page-help.ts/operator/personalizationhelp copy; bumpLastdashboard/src/lib/dashboard/destinations.ts['/operator/personalization', paletteIcon]prefixscripts/e2e/nav_selenium.pyNAV_HREFSdashboard/src/lib/usecases-frontend.test.tsLastdocs/use-cases/frontend/UC-FE-98-accent-personalization.mddocs/use-cases/frontend/UC-FE-08-nav.md,UC-FE-80-users-organizations.md,UC-FE-85-selenium-nav-smoke.mdrelated:+## Relatedlink to UC-FE-98 (bidirectional)Explicitly NOT touched: middleware (
apps.ts,types.ts, any route), the database, the agent,dashboard/src/lib/types.ts(no wire shape changes → type-mirror untouched),dashboard/src/routes/settings/+page.svelte(the appearance modal stays exactly as shipped),
dashboard/src/lib/components/aspa/theme/*, thesemantic state tokens
--success/--warning/--destructive(they are state, not brand),--chart-2..5, andplans/.Design decision
One hue custom property, not a JS palette.
--primaryalready fans out everywhere(
--ring,--info,--chart-1,--sidebar-primary,color-mix(in oklch, var(--primary) …)).So the tint is expressed as
--accent-hueand applied with a singledocument.documentElement.style.setProperty('--accent-hue', '230'). Light/dark lightness and chromastays in
app.csswhere it belongs, the CSS cascade does the theming, and the boot script needs nopalette knowledge. Setting the property is idempotent for the default 260 — the shipped token value
is reproduced exactly.
Consequence: dark
--primaryhue moves 262 → 260 (2°), imperceptible, so both themes share one knob.Required by the single-knob design; recorded here rather than adding a second var.
Contrast is safe by construction for every hue 0–360, measured oklch → linear sRGB → WCAG relative
luminance: light
--primary-foreground(oklch(0.99 0.005 260)) onoklch(0.52 0.12 H)bottoms out at4.82:1 (H≈192, AA ≥ 4.5); dark
--primary-foreground(oklch(0.16 0.03 262)) onoklch(0.76 0.10 H)bottoms out at 8.64:1. Low-chroma hues (teal ~190, azure ~230) fall slightly outside sRGB at C=0.12;
the browser gamut-clamps them and the figure above is computed post-clamp, so it still holds.
Palette: 7 curated anchors, no free-hue slider. Indigo 260 (default), Azure 230, Teal 190,
Emerald 150, Amber 75, Rose 15, Violet 300. A slider needs a native
<input type="range">(noinstalled component represents it) and adds a control that keeps the One Voice Rule harder to audit;
deferred, recorded in the report.
UC decision
New UC:
docs/use-cases/frontend/UC-FE-98-accent-personalization.md, copied from_TEMPLATE.md(UC-FE-98 is the next free frontend id; FE-09/FE-10 are in the graveyard).
dashboard/src/routes/operator/personalization/+page.svelte,dashboard/src/lib/theme.tssetAccentHue,dashboard/src/lib/nav.tsNAV_GROUPS.dashboard/src/lib/usecases-frontend.test.tsUC-FE-98.related: [UC-FE-08, UC-FE-80, UC-FE-85], mirrored in the## Relatedheading and back-linked fromeach of those three (the checker rejects one-way relations).
Ordered tasks
python3 $FJ comment 46 --file …/issue-46.plan.mdexits 0app.css:--accent-hue+ 5var()substitutions;theme.ts: accent model;app.html: boot readcd dashboard && bun run checkusecases-frontend.test.ts: accent tests (palette, storage round-trip,applyAccentHuewrites--accent-hue,app.cssdeclares the var and no longer hardcodes the hue)cd dashboard && bun run testcd dashboard && bun run checknav.ts,page-help.ts,destinations.ts,nav_selenium.py; fix the two operator href assertionscd dashboard && bun run test && bun run checkrelatedon FE-08/FE-80/FE-85python3 docs/use-cases/_check.pypython3 scripts/check-ai-headers.py,check-type-mirror.py,rg -n 'Showing lines'/operator/personalization; report + Done commentOpen questions and the assumption taken
/operator/*requiresinstanceOperator(operator/+layout.server.ts303s others to/settings). The issue names that URL, so the page inherits the gate. Assumed intended — aninstance-level personalization page. If it should be per-user instead, that is a one-line move to
/settings/personalization; noted in the report.popup staying as-is, accent moving to the new page. Assumed; the new page does not carry a second
theme selector.
localStorage,same as
gotoi-theme. Assumed.Risk and fallback
usecases-frontend.test.tsassertions pin the exact operator href list and willgo red the moment the nav item lands — updated in the same commit as the nav change (task 5), not
after.
pageHelp('/settings')appears in a test; its body is not asserted, but the copy stays.bun run check.oklch(… var(--accent-hue))fails to parse in the check pipeline, the boringfallback is to keep
app.cssliterals and set the five custom properties fromtheme.tsinline;the store and page API are unchanged, so the fallback is confined to
theme.ts+ one consumer.app.cssis read by three existing tests (control-chat.test.ts×2,plan-editor.test.ts) forunrelated selectors — the substitutions here do not touch those selectors.
DONE —
/operator/personalizationships; operators pick the accent tint, it persists, Reset returns Polar Indigo.Commits (branch
issue/46-accent-colour, onea26498):f44945fdashboard: accent hue token and browser-local accent store9f6730cdashboard: /operator/personalization accent page, nav, and testsc5e0eb7docs: UC-FE-98 accent personalization, linked from nav UCsdca834adashboard: accent copy names the tokens it actually tints7232cbbdocs: index UC-FE-98 in the use-case README37da458dashboard: describe the personalization page header preciselyDesign: one hue custom property
--accent-hue; app.css rebuilds--primary/--accent/--sidebar-ringfrom it, so--ring,--info,--chart-1, the soft wash, focus rings and links follow for free. Default 260 reproduces the shipped Polar Indigo exactly. Dark--primaryhue moved 262 → 260 (2°) so one knob serves both themes. Pick is browser-local (localStorage), applied before first paint fromapp.html.Gates:
python3 docs/use-cases/_check.py— pass (356 UC files, all conform)python3 scripts/check-ai-headers.py— pass (572 ok, 0 missing, 0 malformed)python3 scripts/check-type-mirror.py— pass (exit 0)cd dashboard && bun run test— pass (16 files, 249 tests)cd dashboard && bun run check— pass (2346 files, 0 errors, 7 pre-existing warnings)cd dashboard && bun run build— pass (adapter-node, exit 0)rg -n 'Showing lines'— only pre-existing doc quotes; no source filecd middleware && bun test— RED, pre-existing, unrelated: my diff touches 0 middleware files (git diff --name-only ea26498 HEAD -- middlewareempty).UC-MW-19fails201/500because the gitignoredmiddleware/data/plans/restart-nginx.gitleft on disk makesPOST /plans {id:'restart-nginx'}hit an already-initialized repo ("nothing to commit"). Removingmiddleware/data/→ 10 pass / 0 fail, but the run recreates it, so it is red again next time. Reproduces on the untouched branch base.cd agent && go test ./...— not run (no agent file touched);gofmt -l agentempty.Browser smoke (real Chromium, worktree dev server → middleware): fresh browser shows no inline override and
--primary: oklch(0.52 0.12 260), Reset disabled; click Azure → inline230, computed--primary: oklch(0.52 0.12 230), reload keeps 230 with no flash and no console/hydration errors; Reset removes the inline property; dark theme keeps its own lightness/chroma (oklch(0.76 0.10 230)). Settings still offers System/Light/Dark.Assumptions to confirm:
/operator/*is instance-operator gated so the page inherits that gate (a move to/settings/personalizationis one line if it should be per-user); "the modal keeps showing system,dark and light" read as the Settings appearance control staying exactly as shipped.Refs #46