Skip to content

[design] The console has never been measured against DESIGN-SYSTEM.md — no light theme, no theme mechanism, colour-named status tokens, and nothing lints store/ #367

Closed

Description

@serge-ivo

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:

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

  1. serge-ivo commented on Aug 7, 2026

    @serge-ivo
    ContributorAuthor

    Correction — one of the three criticisms was wrong, and it was mine

    I read all 228 lines of index.css and checked the shared preset properly. Two claims hold, one does not.

    Withdrawn: "no vendored tailwind-preset.js"

    ~/dev/stores/scripts/tailwind-preset.js is a Tailwind v3 preset — export default { theme: … }, consumed via presets: [preset] in tailwind.config.js. This console is Tailwind v4 (tailwindcss@^4.3.1), which removed the presets mechanism entirely in favour of @theme in CSS.

    So the console could not vendor that file if it wanted to, and index.css:14-34 is the correct v4 equivalent — tokens declared in @theme, consumed as bg-panel/text-muted/border-line exactly 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.css contains @theme (one dark palette), @layer base, @layer components (.msg-md), an @utility, a mobile @media block and the playback keyframes. There is no second palette, no data-theme selector, no prefers-color-scheme, and nothing reads stores-theme. Nothing in docs/ records a dark-only decision either.

    Stands: colour-named status tokens, raw palette classes, nothing lints store/

    --color-green/--color-red/--color-yellow vs the standard's --danger/--success/--warning; 40 raw palette classes; ci.yml:72 scans packages workers/mcp only. #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-motion fallback on the playback equaliser (:222), and the mobile font-size: 16px; min-height: 44px override 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.

  2. serge-ivo commented on Aug 7, 2026

    @serge-ivo
    ContributorAuthor

    Correction — the lint claim is obsolete, verified at origin/main

    I filed this against a working tree 37 commits behind. Re-checked properly:

    Withdrawn: "nothing lints store/". ci.yml at origin/main now runs:

    pnpm exec biome check --error-on-warnings packages workers store
    

    store and workers joined 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:

    Also already withdrawn above: the tailwind-preset.js criticism (v3 preset, v4 app).

    So this issue is now two claims, not four. Retitling would be fair if someone picks it up.

  3. serge-ivo commented on Aug 7, 2026

    @serge-ivo
    ContributorAuthor

    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.md would have produced a long list of non-conformances that are really one deliberate decision:

    shared spec PAGS console
    Themes light and dark, data-theme on &lt;html&gt; dark only
    Theme storage stores-theme, shared so a preference follows a user between stores none — nothing to switch to
    Status colours --danger/--success/--warning (+-soft) its own tokens

    The workspace docs now say so — DESIGN-SYSTEM.md gained a §0 putting PAGS out of scope, and CLAUDE.md §4 and BRAND.md were narrowed to match (serge-ivo/stores 358b029). 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/--free properties, the type scale in use, the control shapes — then name what contradicts it. Land it as platform/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.

  4. serge-ivo commented on Aug 7, 2026

    @serge-ivo
    ContributorAuthor

    The light-theme question has been answered — by decision, not by code

    ~/dev/stores/CLAUDE.md §Standardized conventions was updated 2026-08-07:

    Shared design system — storefront stores only. … PAGS is deliberately out of scope (decided 2026-08-07) — it is dark-only and keeps its own system in pags/platform/DESIGN-SYSTEM.md; see §0 of the shared spec for what it does and does not take.

    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-theme and 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-yellow with text-red carrying four unrelated meanings (error, destructive, the write scope label, offline). A dark-only system needs danger/success/warning naming 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.md when someone writes it. Two of its four original claims are now withdrawn (the v3 preset, the store/ lint scope) and a third is decided; keeping it open at its current title overstates what is wrong.

  5. serge-ivo commented on Aug 7, 2026

    @serge-ivo
    ContributorAuthor

    Correction to the comment above — a shell quoting slip swallowed the filename in the "One loose end" paragraph. It should read:

    One loose end: pags/platform/DESIGN-SYSTEM.md does not exist at origin/main — I checked.

    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/warning rather than --color-red/--color-green/--color-yellow), and the -soft background pairings whose absence is why bg-red/10 and bg-accent/10 opacity hacks are scattered through the console.

  6. serge-ivo commented on Aug 7, 2026

    @serge-ivo
    ContributorAuthor

    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 at pags/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.

    • Tokens exist and are used. @theme declares 16 colour tokens + 3 font tokens, and they are genuinely consumed: text-muted 415 uses, border-line 383, text-accent 145, bg-panel 120. This is a token system in daily use, not a decorative header.
    • Fonts are exactly the shared spec — Manrope body, Fraunces display, SF Mono. Same stack, same fallbacks, in all three surfaces.
    • A shape convention exists without being written down. rounded-lg for controls (239), rounded-xl for cards (107), rounded-full for pills (40). Control padding clusters hard on px-3 py-1.5 (83) / px-2.5 py-1.5 (38) / px-1.5 py-0.5 for chips (22). Gaps on gap-2 (180). Someone was following a system; it just lived in muscle memory.
    • Two things better than the standard asks for, as the first comment noted and I confirm: the prefers-reduced-motion fallback on the playback equaliser, and the mobile font-size:16px; min-height:44px override (iOS zoom threshold + Apple touch target). Both deliberate.
    • Dark-only is now consistent, not partial. After [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, store/console/src and store/admin/src contain 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-green says what it is. text-red has 78 uses carrying an error message, a destructive action, a write-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. -soft exists 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 /10 and /15 are 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 across store/**/*.html + build-details.js. Console and admin are identical. The marketing pages disagree with both:

    Token Console / admin Marketing HTML
    panel #141414 #171717
    line #303030 #262626

    Plus --free: #3b82f6 (the OFO footer cross-link colour) which the SPAs have no equivalent for, and one page declaring --radius: 8px where the rest say 0.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, no prefers-color-scheme, no stores-theme read — 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:

    1. Gate, at zero — no pale Tailwind background (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).
    2. Ratchet, not a gate — raw palette classes pinned at 33 (console) and 1 (admin). Pinned exactly, like the file-size ratchet: removing a use must lower the pin in the same commit, so ground taken is not banked as headroom.
    3. Equality between the console and admin @theme blocks — 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 panel vs paper · icon choice · whether /10 or /15 is the right tint.

    Enforceable but deliberately not enforced yet:

    • The type scale. Mechanically trivial to detect; 174 call sites with nowhere to go is 174 blocked PRs. This needs [design] The atomic layer under #187: 273 buttons in 14 shapes, 3 card geometries, 17 font sizes #366's primitives to absorb the small sizes first — the ordering this issue itself argued for, and it is right.
    • The marketing-vs-SPA palette split. A test could assert the three copies agree. It would fail today, and resolving it is a restyling decision about the marketing pages, not a lint. Documented in §4 instead.

    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 -soft pairings; 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.

  7. serge-ivo commented on Aug 8, 2026

    @serge-ivo
    ContributorAuthor

    Delivered — b695046, live and verified in production

    This issue's own comment thread had reduced it to one remaining claim: status tokens named for
    pigment rather than intent, plus the missing -soft pairings. That is what landed, and I did not
    build 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 issue
    title 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

    • 416 utilities renamed to success / danger / warning / info across the console, the
      operator portal and agents/coder/web — one commit by necessity, since the two @theme blocks are
      held equal by test and the console @sources the Coder UI.
    • The pigment names are deleted, not aliased. An alias leaves two names for one idea, which is
      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-danger with 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. The
      measurement 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 #0a0a0a and the border sites had drifted upward on their own.
    • store/admin's pin of 2 is cleared — both were an input naming a background token from
      another design system while the base layer painted it anyway. Every surface is now a gate at
      zero on every rule
      .
    • lib/statusBadge.ts now spells its answer (bg-success-soft text-success) instead of computing
      one; its comment saying the tint was a modifier "because @theme declares no -soft pairing" was
      the clearest statement of what was missing, and it is what got fixed.

    The guard — check-design-tokens.mjs gains rule 3 (no status colour written as an opacity
    modifier), 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-size
    scale), so a dead text-<token> was caught only because the status tokens happened to be named
    after Tailwind hues. Widening rule 1 to cover text- was measured across all three trees and fires
    on text-like inside an English sentence and on an element id whose tail reads as a utility — two
    false 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-danger cannot go silent. A misspelled text- token remains
    invisible, 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-line rules, and contains zero pigment
    token 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 :root and still
    disagree with the SPAs on panel and line (§4) — resolving that is a restyling decision about
    those pages, not a lint. The neutral chip's tint is still a modifier because --color-muted-soft is
    already 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestfrontendFrontend / UI work

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions