Repository navigation
Description
The workspace claims this is enforced. Nothing checks it.
~/dev/stores/DESIGN-SYSTEM.md opens with:
Source of truth. If this file disagrees with a site's CSS, the site is wrong.
and the workspace CLAUDE.md says the shared design system is "Enforced via CI lint + PR
checklist."
For this repo neither half exists. ci.yml:72 runs biome check --error-on-warnings packages workers/mcp — store/ is not in the scan, which is the entire UI (documented honestly in
1587022/#326, so this is a known boundary, not a surprise). And Biome is a JS/TS linter: there is
no stylelint, no token lint, and no vendored tailwind-preset.js in this repo — the preset the
design system says every Tailwind app copies lives only at ~/dev/stores/scripts/tailwind-preset.js.
So the design system is a document the console has never been measured against. Below is what a
first measurement finds. This is the same shape as the defects this batch keeps turning up — a
surface asserting a guarantee nothing behind it enforces.
1. There is no light theme, and no theme mechanism
store/console/src/index.css:14-34 defines one palette, and it is dark:
@theme {
--color-paper: #0a0a0a;
--color-ink: #fafafa;The design system requires both themes, switched by a data-theme attribute on <html>, with the
preference in localStorage["stores-theme"] so it follows the user between stores. Grepping the
console for data-theme, prefers-color-scheme or stores-theme returns nothing — not in the
CSS, not in the app.
That is a deliberate-looking product decision (a dark developer console) that was never written
down, so it currently reads as 300 lines of unimplemented spec. Either the console is exempt and the
design system should say so, or the light theme is real work. It should not stay ambiguous — the
next person to add a surface cannot tell which rules apply.
It also has live consequences. DataTab.tsx:46,57 sets bg-gray-200 text-gray-600 for a dead
status chip. On a #0a0a0a page that is a light-grey chip with mid-grey text — not a theme bug
waiting to happen, a legibility bug shipping today. 40 raw Tailwind palette classes are in the tree
(text-amber-500 ×8, text-purple-400 ×7, bg-amber-500 ×4, plus violet/orange/gray) against a
standard that says never hardcode these.
2. Status tokens are named for their colour, not their meaning
The console declares --color-green, --color-red, --color-yellow, --color-blue. The design
system declares --danger, --success, --warning, each with a -soft background pairing, and
says explicitly: "Use these for validation, alerts, badges, and state — never hardcode
red/green/yellow."
Naming the token after the pigment defeats the purpose one level up. text-red has 78 uses carrying
at least four unrelated meanings: an error message, a destructive action, the write scope label on
a tool row (SettingsTab.tsx:854), and an offline/failed state. A reader cannot tell which, and a
future decision to make destructive actions orange-not-red becomes 78 case-by-case judgements.
Rename to intent (danger · success · warning · info), add the missing -soft pairings —
several inline bg-red/10 and bg-accent/10 opacity hacks exist precisely because the soft variant
was not available.
3. Nothing measures any of this
Whatever is decided above, it needs a check, or it will drift straight back. Cheapest useful version:
- add
store/to the Biome scan (its own PR — expect existing findings, see [quality] Reduce Biome lint debt: any, !important, unused imports, and unsafe patterns #326); - a token lint that fails on raw palette classes (
*-amber-500,*-gray-200, …) and on
text-[…rem]once [design] The atomic layer under #187: 273 buttons in 14 shapes, 3 card geometries, 17 font sizes #366 has somewhere for those to go; - if the console is exempt from the light-theme requirement, say so in DESIGN-SYSTEM.md rather than
leaving it silently unmet.
Order matters: land #366's primitives first, then lint — a lint with nowhere to put the fix is
just 168 blocked PRs.
Related: #366 (component primitives), #326 (lint scope), #305.
Activity
serge-ivo commentedon Aug 7, 2026
Correction — one of the three criticisms was wrong, and it was mine
I read all 228 lines of
index.cssand checked the shared preset properly. Two claims hold, one does not.Withdrawn: "no vendored
tailwind-preset.js"~/dev/stores/scripts/tailwind-preset.jsis a Tailwind v3 preset —export default { theme: … }, consumed viapresets: [preset]intailwind.config.js. This console is Tailwind v4 (tailwindcss@^4.3.1), which removed thepresetsmechanism entirely in favour of@themein CSS.So the console could not vendor that file if it wanted to, and
index.css:14-34is the correct v4 equivalent — tokens declared in@theme, consumed asbg-panel/text-muted/border-lineexactly as the standard intends. That criticism was mine to withdraw: DESIGN-SYSTEM.md's enforcement section is stale relative to Tailwind v4, rather than the console having skipped it.That is worth fixing upstream in the workspace doc — every store on v4 hits the same thing.
Stands: no light theme, no theme mechanism
Verified across the whole file now, not just the token block.
index.csscontains@theme(one dark palette),@layer base,@layer components(.msg-md), an@utility, a mobile@mediablock and the playback keyframes. There is no second palette, nodata-themeselector, noprefers-color-scheme, and nothing readsstores-theme. Nothing indocs/records a dark-only decision either.Stands: colour-named status tokens, raw palette classes, nothing lints
store/--color-green/--color-red/--color-yellowvs the standard's--danger/--success/--warning; 40 raw palette classes;ci.yml:72scanspackages workers/mcponly. #368 is the concrete instance.Credit where it is due, since this issue is a critique
Two things in this stylesheet are better than the standard asks for and should not be lost in a refactor: the
prefers-reduced-motionfallback on the playback equaliser (:222), and the mobilefont-size: 16px; min-height: 44pxoverride on form controls (:202-206) — 16px is the iOS threshold below which Safari zooms the viewport on focus, and 44px is the Apple touch-target minimum. Both are deliberate and correct.serge-ivo commentedon Aug 7, 2026
Correction — the lint claim is obsolete, verified at
origin/mainI filed this against a working tree 37 commits behind. Re-checked properly:
Withdrawn: "nothing lints
store/".ci.ymlatorigin/mainnow runs:storeandworkersjoined the gate (31e41cc,6483273, #326) — the widening the step's own comment called "the point". So the console is now linted, and this issue should not be read as claiming otherwise. What Biome does not check is design tokens, which is the part below that still stands.Still true at
origin/main:grep -c "data-theme|prefers-color-scheme|stores-theme" index.css→ 0. Nothing indocs/records a dark-only decision. Still the open question: exempt the console in DESIGN-SYSTEM.md, or build it.--color-green/--color-red/--color-yellowvs the standard's--danger/--success/--warning, andtext-redstill carrying four unrelated meanings.DataTab.tsx'sSTATUS_CLASSis byte-identical atorigin/main.Also already withdrawn above: the
tailwind-preset.jscriticism (v3 preset, v4 app).So this issue is now two claims, not four. Retitling would be fair if someone picks it up.
fix(console): DataTab was styled for a light theme this app does not …
serge-ivo commentedon Aug 7, 2026
Premise changed by the owner, 2026-08-07: PAGS is not following the other stores. It gets its own design system.
That reframes this ticket rather than answering it. Measuring the console against
~/dev/stores/DESIGN-SYSTEM.mdwould have produced a long list of non-conformances that are really one deliberate decision:data-themeon<html>stores-theme, shared so a preference follows a user between stores--danger/--success/--warning(+-soft)The workspace docs now say so —
DESIGN-SYSTEM.mdgained a §0 putting PAGS out of scope, andCLAUDE.md§4 andBRAND.mdwere narrowed to match (serge-ivo/stores358b029). Leaving them asserting all six stores share this would have been the fifth stale-doc incident this week.What this ticket becomes
Not "does the console conform?" but "what IS the PAGS design system, and is it consistent with itself?" Document it from what the console actually does — the existing
--accent/--panel/--line/--muted/--freeproperties, the type scale in use, the control shapes — then name what contradicts it. Land it asplatform/DESIGN-SYSTEM.md.Owning a design system is a reason to have named tokens, not a licence to scatter Tailwind literals. The one rule worth taking from the shared spec verbatim is never hardcode red/green/yellow hex — #368 is what its absence looks like, 11 times in one file, on an app with no light theme.
What matters more now
With no external spec to point at, a guard is the only thing that keeps a design system real. A light-theme Tailwind pair in a dark-only app is mechanically detectable; "the spacing feels off" is not. Say which parts are enforceable and which are taste, and build the enforceable half — every convention in this repo that survived did so because something failed the build when it drifted.
Also worth recording: the accepted cost of the divergence is a theme discontinuity for a user moving between a storefront store and the console. Written into the workspace doc so it is not rediscovered as a bug.
serge-ivo commentedon Aug 7, 2026
The light-theme question has been answered — by decision, not by code
~/dev/stores/CLAUDE.md§Standardized conventions was updated 2026-08-07:So the first of this issue's two remaining claims is resolved: the console being dark-only is a decision, not an omission, and the ask I framed as "exempt it in DESIGN-SYSTEM.md, or build the light theme" has been answered with the first option. Everything in the body about
data-theme,stores-themeand the missing second palette should be read as settled, not outstanding.One loose end: does not exist at
origin/main— I checked. The workspace doc now points at a file that has not been written, so PAGS currently has neither the shared system nor its own. Until it exists there is nothing for this issue's remaining claim to be measured against.What is left here
Just one thing, and it is unaffected by the exemption:
Status tokens named for pigment, not intent.
--color-green/--color-red/--color-yellowwithtext-redcarrying four unrelated meanings (error, destructive, thewritescope label, offline). A dark-only system needsdanger/success/warningnaming exactly as much as a two-theme one — the argument was never about themes. #368 is the concrete instance and is still unfixed.Suggest this issue is retitled to that single claim, or closed in favour of #368 plus a line in the new
pags/platform/DESIGN-SYSTEM.mdwhen someone writes it. Two of its four original claims are now withdrawn (the v3 preset, thestore/lint scope) and a third is decided; keeping it open at its current title overstates what is wrong.serge-ivo commentedon Aug 7, 2026
Correction to the comment above — a shell quoting slip swallowed the filename in the "One loose end" paragraph. It should read:
The workspace doc (
~/dev/stores/CLAUDE.md§4) now points PAGS at that file for its own design system, and the file has not been written yet. So as of this commit PAGS has neither the shared storefront system (deliberately, per the 2026-08-07 decision) nor the replacement it was exempted in favour of.Not a blocker for anything — but whoever writes it should carry over the two things worth keeping from this issue's findings: intent-named status tokens (
danger/success/warningrather than--color-red/--color-green/--color-yellow), and the-softbackground pairings whose absence is whybg-red/10andbg-accent/10opacity hacks are scattered through the console.docs(store): write down the design system PAGS actually has, and chec…
serge-ivo commentedon Aug 7, 2026
Measured, under the new premise
The ruler changed while this was being measured:
~/dev/stores/DESIGN-SYSTEM.md§0 now records PAGS as out of scope (dark-only, own tokens, decided 2026-08-07) and points atpags/platform/DESIGN-SYSTEM.md. That was a dangling pointer — the file did not exist. It does now (beccc57), written descriptively: every number in it was measured from the tree, not proposed.Good thing the premise moved. Measuring against the shared document would have produced a long list of "non-conformances" that are one deliberate decision wearing many hats.
What did not change: owning a design system is a reason to have named tokens, not a licence to scatter literals. So the question became what is the system, and is it consistent with itself?
What conforms — more than expected
Confirming the withdrawal in the second comment above: the console is not an app that skipped the standard.
@themedeclares 16 colour tokens + 3 font tokens, and they are genuinely consumed:text-muted415 uses,border-line383,text-accent145,bg-panel120. This is a token system in daily use, not a decorative header.rounded-lgfor controls (239),rounded-xlfor cards (107),rounded-fullfor pills (40). Control padding clusters hard onpx-3 py-1.5(83) /px-2.5 py-1.5(38) /px-1.5 py-0.5for chips (22). Gaps ongap-2(180). Someone was following a system; it just lived in muscle memory.prefers-reduced-motionfallback on the playback equaliser, and the mobilefont-size:16px; min-height:44pxoverride (iOS zoom threshold + Apple touch target). Both deliberate.store/console/srcandstore/admin/srccontain zero light-theme surface classes outside one allowlisted brand button.What does not — three self-contradictions
Not measured against an external ruler; measured against the console's own stated intent.
1. Tokens are named for pigment, meaning is applied nowhere.
--color-greensays what it is.text-redhas 78 uses carrying an error message, a destructive action, awrite-scope label and an offline state — four ideas, one name. #368 fixed the instance by adding an intent layer in code (lib/statusBadge.ts: status → intent → token). The CSS rename is still open, but is now a one-file change.2.
-softexists for the accent and nothing else. Every status tint is therefore a per-call-site opacity guess:bg-red/10×16,bg-green/15×9,bg-yellow/10×8,bg-accent/10×11 — and note both/10and/15are in use for the same idea. That is not style variety, it is the absence of a decision.3. The palette exists in three copies. Console
@theme, admin@theme(a copy-paste, whose comment says "shared with the console"), and the marketing pages' inline:root— redeclared 13 times acrossstore/**/*.html+build-details.js. Console and admin are identical. The marketing pages disagree with both:#141414#171717#303030#262626Plus
--free: #3b82f6(the OFO footer cross-link colour) which the SPAs have no equivalent for, and one page declaring--radius: 8pxwhere the rest say0.75rem. A change to the console palette does not reach the store front page.And the largest single drift, which is not a contradiction so much as erosion: 174 arbitrary
text-[…rem]uses across 17 distinct values —0.55 · 0.6 · 0.62 · 0.65 · 0.66 · 0.68 · 0.7 · 0.72 · 0.74 · 0.75 · 0.76 · 0.78 · 0.8 · 0.82 · 0.875 · 0.95 · 1.1rem. Several are visually indistinguishable (0.65 / 0.66 / 0.68), which is the tell that each was chosen alone rather than from a scale. Effective type scale: ~24 sizes.What PAGS gives up, written down rather than rediscovered
A user who sets light mode on a storefront store and clicks through to the console gets a black page. There is no
data-theme, noprefers-color-scheme, nostores-themeread — verified zero across both SPAs. That discontinuity is the accepted cost of the decision, and it is now stated in both documents rather than waiting to be filed as a bug.The workspace doc already carries the divergence (§0), so the stale-doc failure this issue warned about was closed upstream before I got here. Confirmed, not edited — it is the workspace's file.
Is a guard worth building? Yes — narrowly. It is built.
store/console/src/lib/designTokens.test.ts, in the normal unit suite. No CI step, no new script — those trees belong to other agents this batch, and a design guard does not need its own pipeline stage to be real.Three things, each chosen because it is mechanical and matches a defect that actually shipped:
bg-*-50/100/200) in either SPA. The exact signature of [bug] All 11 DataTab status badges are light-theme Tailwind pairs on a dark-only app, and disagree with the app's own success/error colours #368. Allowlisted:pages/Login.tsx(Google's guidelines mandate a white sign-in button).@themeblocks — making the admin file's "shared with the console" comment true, since it is shared by copy-paste and nothing was keeping it honest.Enforceable vs taste — the honest split
Enforceable (near-zero false positives, each a real past defect): raw palette classes · the light-surface signature · token-copy drift · hardcoded hex outside
@theme, with an allowlist for brand SVGs and chart series.Taste, and a lint would only teach suppression: spacing rhythm · whether a control is a button or a link · information density · when a surface is
panelvspaper· icon choice · whether/10or/15is the right tint.Enforceable but deliberately not enforced yet:
The negative result worth publishing
The obvious guard — scan the built stylesheet for banned utilities — does not work, and the reason is not obvious. Tailwind v4's source scan reads comments and strings, not just JSX. While fixing #368 I quoted the broken class names in the explaining comment, and those exact rules reappeared in the built CSS: the bug's stylesheet surviving its own postmortem. Any output-scanning guard is defeated by a doc comment. Scan source, with a real parser boundary, and never quote a banned utility verbatim while explaining it.
Remaining open, listed in
DESIGN-SYSTEM.md§6: rename pigment tokens to intent + add-softpairings; collapse the type scale after #366; decide whether the marketing pages adopt the SPA palette.This issue's two surviving claims are now one document and one test. Both are worth closing it on, unless the token rename should live here rather than a fresh ticket.
fix(console): a stopping loop run now says it is stopping (#376)
fix(console): 31 colour utilities named tokens that do not exist (#367)
refactor(console): four button shapes and one card, instead of forty-…
refactor(console,admin): a floor under the type scale and a floor und…
fix(voice): a silence hallucination is matched in the form it arrives…
refactor(design): status colours are named for what they mean, and th…
serge-ivo commentedon Aug 8, 2026
Delivered —
b695046, live and verified in productionThis issue's own comment thread had reduced it to one remaining claim: status tokens named for
pigment rather than intent, plus the missing
-softpairings. That is what landed, and I did notbuild a light theme — the 2026-08-07 decision (dark-only, PAGS out of the shared spec) is the
decision of record,
DESIGN-SYSTEM.md§0 already carries it and its accepted cost, and the issuetitle overstates what was ever wrong. Two of the original four claims were withdrawn upstream and a
third was decided; only this one was live.
What shipped
success/danger/warning/infoacross the console, theoperator portal and
agents/coder/web— one commit by necessity, since the two@themeblocks areheld equal by test and the console
@sources the Coder UI.what [bug] All 11 DataTab status badges are light-theme Tailwind pairs on a dark-only app, and disagree with the app's own success/error colours #368 refused when it declined to invent a third status vocabulary. Deleted, they are
undeclared, so rule 1 reports any straggler as
text-red → text-dangerwith a file and a line.That is what made a 76-file rename safe with four other lanes open.
-soft(0.15) and-line(0.4) declared per intent, replacing 123 opacity modifiers. Themeasurement that justifies two pairings rather than one: three different alphas were in use for a
single red tint and five for a single yellow border, because a 1px line at 0.15 is nearly
invisible on
#0a0a0aand the border sites had drifted upward on their own.store/admin's pin of 2 is cleared — both were aninputnaming a background token fromanother design system while the base layer painted it anyway. Every surface is now a gate at
zero on every rule.
lib/statusBadge.tsnow spells its answer (bg-success-soft text-success) instead of computingone; its comment saying the tint was a modifier "because
@themedeclares no-softpairing" wasthe clearest statement of what was missing, and it is what got fixed.
The guard —
check-design-tokens.mjsgains rule 3 (no status colour written as an opacitymodifier), a gate at zero in all three trees, which it can be only because the tokens landed
first. Both new paths were exercised against a deliberately broken tree to confirm they fail with
the file, the line and the replacement.
One rejected fix, because the negative result is the useful part. Renaming away from hue names
gave up an accident:
text-is excluded from the custom-token rule (it collides with the font-sizescale), so a dead
text-<token>was caught only because the status tokens happened to be namedafter Tailwind hues. Widening rule 1 to cover
text-was measured across all three trees and fireson
text-likeinside an English sentence and on an element id whose tail reads as a utility — twofalse positives on day one, which is how a guard gets suppressed rather than fixed. The hole is
closed from the other end instead: the guard asserts the twelve status properties are declared.
If the token cannot vanish,
text-dangercannot go silent. A misspelledtext-token remainsinvisible, and §6 now says so rather than leaving it to be rediscovered.
Verified: all ten CI gates, 6,861 unit tests, the full Playwright suite (347, chromium + webkit),
CI green on
main, and live in production —proagentstore.online/console/serves--color-danger-soft:#ef444426(0.15) and--color-danger-line:#ef444466(0.4), compiles real.bg-danger-soft/.text-danger/.border-warning-linerules, and contains zero pigmenttoken declarations and zero pigment utility rules. The operator portal serves the same palette,
so the copy stayed in sync.
Not done, and not this issue's: the marketing pages still declare their own
:rootand stilldisagree with the SPAs on
panelandline(§4) — resolving that is a restyling decision aboutthose pages, not a lint. The neutral chip's tint is still a modifier because
--color-muted-softisalready a text colour, so there is no pairing for it to point at; the guard is scoped to the four
status intents so it does not pretend otherwise. Both are recorded in §6.
Closing: every claim in the body is now either withdrawn, decided, or delivered.
fix(console): budget panel uses declared bg-panel token, not bg-canvas (
fix(coder-web): the engine-invocation badge names a scale step, not a…