Board – six cards closed in one sitting, and the starter kit has a plan
Owner: Torfinn · Updated: 2026-09-17 · Milestone: The repo moved to Forge on 2026-09-10 and the trunk is now code.aleris.ai/aleris/brand. GitHub is history, kept as the github remote. Forge forked at 2a1110d, before that day's ruling pass landed; the two diverged by two commits each way with zero file overlap and merged clean at a91c7fd. Astro wins, ruled 2026-09-10 — Phase C is decided, and it struck three of the five steps in Forge's migration checklist: no schema through the broker, no pg/withClaims seam, no forced RLS, no OIDC, because nothing static reads a row. The Astro build already emits all 26 content routes, llms.txt and both raw endpoints, and touches Supabase nowhere. Card 145 is now a Dockerfile rewrite plus two errors in Forge's generated scan — it reports zero Supabase call sites where there are seven files, and points at a schema directory that holds one JSON seed while the real migrations sit in supabase/. The cutover has a sequence and both its open questions are ruled (2026-09-10): the icon-picker becomes its own Forge app (card 149) and the /sv/ URLs die, because the old site was never operationally in use. Five steps in workspace/plans/forge-cutover-sequence-2026-09-10.md; forgebrand.dev.aleris.ai is provisioned but still serving a Forge placeholder, so step 1 is pressing Deploy there. Extracting the picker makes Brand OS entirely Supabase-free — seven files on 09-09, four now, zero after. Previously: the first Forge deploy was refused — a 409 domain conflict, nothing built, nothing broken — and card 148 is why the offered override should wait: the old Coolify app still owns brand.dev.aleris.ai, the Qlik icon-picker is live on it and answers 200, and every content URL changes shape from /sv/… to no prefix. Card 147 closed by removing the need for the credential rather than fixing it: the Font Awesome kit is imported by nothing, its 3,776 icons are committed, so it became an optionalDependency and the full Forge image now builds and runs with no token at all — proved, not reasoned. The token is still dead and that moved to card 58, which is the only work that needs it. Card 145 landed the same day: the Dockerfile is two stages with a dependency-free static server, verified by building and running the runtime container, and FORGE-MIGRATION.md is corrected in place with its errors struck rather than silently edited. What is left is Torfinn's and it is one button — press Deploy in the Forge console, which also closes card 113. Card 146 opened and closed the same day: the ruling silently switched off in-page commenting, and Torfinn ruled retire with nothing to export — fifteen files gone, the Supabase surface down to the Qlik icon-picker alone, and card 145 unblocked as a result. Previously the same day: A ten-card ruling pass on 2026-09-10 closed six and moved four. Closed: 42 (premise had already stopped being true), 95 (all seven Swedish originals now superseded with pointers, and the seventh was never on the card), 93 (the en-dash convention retired outright — not narrowed; the AI-tell about over-dashing is a different rule and stands), 25 (phase-0 revised accepted, the 2026-05-30 map filed), 105 (pursue gcc), and 136 (a depends_on identifier is a path, because the site has to render it as a link). Moved: 108 gained the definitions its coined words were missing, 62's boundary question is ruled — gated means it needs his pass, not that Claude may not draft it — 124's four answers are reviewed with every figure recomputed, and 99 has an implementation plan awaiting three rulings. Three cards opened: 142 (the 67-file depends_on sweep, scoped 17 live / 50 declared-and-left), 143 (card 124's three Claude halves), 144 (the gcc adapter, blocked on 104). Previously: Tier 1 is ratified in full — nine constitutional rules accepted on 2026-09-07, and the logo page with them; cards 89 and 133 closed. Phase 0 revised ran its mechanical half the same day (PR #27): eleven files in their tiers, no URL moved, two new checks; the map's six ⚑ items and card 136 are Torfinn's.** Previously: Five decisions taken 2026-08-25 against cards 72, 73 and 75, and Torfinn's review pass the same day moved two of them. Card 75 closed in full — emotional-modes.md and its en-draft no longer promise a detailed digital model, because decision 5 retires A/B/C as redundant rather than relocating it, which removed the placement question the card was opened on. Decision 3 stands: balanced enters as a third emotional mode, giving the model a zero point instead of bounding it — Torfinn's to write, and it lands in the table card 53 is waiting on. Decision 1 was withdrawn (card 79): it would have given F6 a named set of five factors, and it collided with documentation/dependency-reciprocity.md, amended 2026-08-24 — the one modified file in the working tree, and the only one the session's checks did not read. That record argues a pre-declared taxonomy is the wrong shape and each decision defines its own reach; Torfinn ratified it as the learning model. Decision 2 is orphaned with it, and card 54's four axes now face an objection about shape rather than membership. Decision 4 lost its equipment permission on review — a passing example, and wrong; imagery.md is not edited at all and its prohibition stands. The exception Torfinn named instead turned out to be a gap: instructional imagery has no category in the corpus, and it is live in sundviktlakemedel and patientguide (card 84). The measurement behind cards 73 and 75 was false as stated — the A/B/C absence claim searched three descriptive names while Baseline refers to the model by label, in two files, one accepted. Corrected in four places; the two references now dangle (card 83). Nine cards opened, 76–84, six of them from the review pass rather than the session. file-manifest.md archived — frozen since 2026-05-30, no entry since 2026-05-07, while CLAUDE.md still called it live. (Previously, 2026-08-21:) Packaging split into a content core and per-consumer adapters, 2026-08-19/20 (cards 67, 68, 71), and both adapters are installed. vibe-coding-seed v0.2 is on add-brand-os/PR #1 in Richard's repo, wired into docs/aleris-standard.md on his review and shipping a seven-check verifier; vendored-app v0.1.1 is in patientguide and sundviktlakemedel, where its provenance check found four workarounds whose reasons had already been fixed upstream. agent-baseline cut twice: v0.6.4 (three colour roles documented for text below the AA floor, found by patientguide) and v0.6.5 (a font-licence claim stricter than the licence — card 69). The finding that reframes the Richard track: main in aleris-vibe-coding has no docs/agent-baseline/ at all — five package versions live only on that branch, so nothing cloned from that seed has ever had a design system. Merging PR #1 is the one thing left there, and it is his. Card 70 opened for Torfinn: are the twelve chart primitives an order to draw in or an inventory of hues? Cards 72 and 73 opened 2026-08-21 from a consumer finding routed in through the return path: imagery.md divides on audience while a product divides on situation, so a patient-facing instrumental surface has no rule; and both emotional modes are defined against a pending event, so a surface revisited weekly for months has neither. Both Torfinn's, both Foundation-level. The finding was triaged 2026-08-24 and is at workspace/archive/finding-patient-facing-instrumental-surface-2026-08-20.md; testing its own two falsifiers against the corpus confirmed both gaps are real, added a measurement to each card, and turned up a third defect as card 75 — emotional-modes.md promises a detailed digital model that nothing published carries. (Previously, 2026-08-12:) Cards 51 and 52 both closed 2026-08-10: the package system is one standard, and it reached Richard. klinfys-baseline/ and bhsv-baseline.zip retired to _packages/_archive/, neither ever regenerable against the manifest rule; agent-baseline v0.5 is now the only current package. Card 52 shipped it to aleris-vibe-coding the same session Torfinn supplied credentials — found the v0.4 hand-off (card 14) had never merged, so the update landed on the still-open PR #1 rather than opening a second one; title and a comment flag the v0.4→v0.5 change for Richard. Merge remains his. A fourth track opened 2026-08-07: the radius contradiction. Card 44 closed 2026-08-10 — the reference-file sweep (committed 2026-08-09, verified today) retired --radius-l and reconciled the eight contradicting rows; the drift check it added was proven against the restored pre-sweep data, not just trusted. Cards 45–47 opened, all [T]. Card 45 closed the same day: cards derive inward from 16px, picked off the prototype, with flush top-of-card media taking the card's own radius at the top and staying square at the bottom. Cards only — panels, modals and tables are untouched and stay --radius-s. Card 47 decided the same day: option A, live sites non-conformant, canonical unchanged. Card 46 closed by executing it: Phase B step 3 ran (card 48) — baseline/tokens/ and the iconography data file moved to data-products/, the reference file split into schemas/design-token.md and principles/design-tokens.md (the corpus's first real use of that layer's question-framed convention), published routes preserved via an explicit move-map in lib/baseline-content.ts. agent-baseline is not regenerated — it is now behind canonical on both the radius model and this move, and the cut itself still hasn't been asked for. colour supersede landed on a branch merged to main 2026-08-03 (PR #10), contrast check green, packages regenerated and verified. agent-baseline v0.4 shipped to Richard, card 14 closed 2026-08-07. v0.3 (2026-08-05) went stale within hours: it missed the same-day 14px accessibility-floor fix (card 18/19) and shipped four dead links to a file card 26 deleted the same day. v0.4 (card 43) closes both, plus two more dead links card 26's own sweep missed, and is now placed at docs/agent-baseline/ in aleris-vibe-coding, PR open (pull-requests/1), merge pending Richard's review. Phase A of the site rebuild (cards 27, 29, 30) closed but sat unmerged on feat/route-manifest-gate — fast-forwarded into main 2026-08-05 before Phase B started, so Phase B branches off a main that actually has Phase A's work. Phase B under way on feat/phase-b-layer-taxonomy: cards 26, 32 and 33 closed (accessibility consolidation, patterns reconciliation, the three constitutional rule statements — the last of these landed to main 2026-08-07, having sat uncommitted since being marked Done the day before). Phase B step 6 (the BASELINE.md split) now has its home in tokens-are-canonical.md and is unblocked, but not yet started. A third track opened 2026-08-06: the physical domain (cards 35–40). It starts from the domain owner's interior guide rather than from a Brand OS plan, and it does not touch the other two tracks — nothing physical publishes, and foundation/colour.md is the only shared file, through card 37.
How this works
This file is the list of record for in-flight cards. _state.md carries state and points here; it does not repeat the card list. One list, not two – see workspace/status/cookbook.md §12.
Pull a card by moving its line to Doing, then to Done with a date. Cards are written so they can be pulled without asking a question first: each carries the file paths, the verified numbers, the traps, and a done-when. [T] is Torfinn, [CC] is Claude Code.
Done means landed. Every Done when: clause ends with and it is on main — merged and pushed, not correct-on-a-branch. This was added 2026-08-06 after the omission was measured: of the 34 Done when: clauses on the board that day, none mentioned merging, landing, pushing or main. Every one defined done as the change is correct — tests green, build clean, routes resolving — so a card could be legitimately, verifiably done while its work sat on a branch. That is how the colour supersede, Phase A and fourteen backup/* branches each came to sit unmerged, the last of those costing 705 lines that had to be rescued out of deleted branches. It is not a memory problem; the step was never in the definition. Enforced by lib/branch-hygiene.test.ts, which fails when a branch is ahead of main and undeclared.
Blocked cards list what they wait on. A card with nothing in Waits on is pullable now.
Ready now
| # | Card | Owner | Waits on |
|---|---|---|---|
| 158 | Delete the retired staging mirror — the Forge app, the domain forgebrand.dev.aleris.ai, and the aleris/forgebrand repository. The 1:1 domain-to-repository constraint it worked around no longer binds: brand.dev.aleris.ai builds from aleris/brand directly. Do the domain first — measured 2026-09-17 the host still served the 2026-09-10 build, so it carried noindex on none of its four surfaces while canonical-tagging every page to brand.dev.aleris.ai, making it the only crawlable copy of the unreleased corpus. The local half is done: scripts/push-staging.sh and the ~/Dev/forgebrand clone are gone. The repository is a force-push mirror holding nothing original but its own FORGE.md and a do-not-commit banner. Done when: the domain answers nothing, the app and repository are gone, and no clone of the mirror is on the machine |
T | – |
| 157 | Replace --measure-text's dead reopening condition with a check that can fire. The token was deferred 2026-09-13 "until data shows us that we need to change it" and declined 2026-09-17, because the condition — a product measuring a readability or layout problem it can attribute to line length — is one nothing is positioned to meet: no product measures line length unless it already suspects a problem, so the measurement that ends the deferral is one nobody would ever run. A deferral nothing can end behaves as a permanent one while looking temporary, which is the defect, not the token. The premise was also wrong — the realization was believed to use --measure-text and, measured, declares it and references it nowhere (lib/canonical-shim.test.ts pins this). The work: a measure check in review/checks/, the same shape as type-floor — walk rendered text blocks, report characters per line at each declared viewport, on real corpus surfaces rather than a fixture. Then the standardise-or-not question is decided on numbers. The trap: ch is not a character — it is the advance width of 0, and Museo Sans has no tabular figures (v0.11's measurement), so a 68ch box does not hold 68 characters of prose and a check comparing the two would be measuring the wrong thing. Report measured characters per line, never ch. Done when: the check runs against review/targets/realization.json and the Brand OS site, reports characters per line per route/viewport, proves it can fail against a deliberately over-wide fixture, and it is on main. |
CC | — |
| 149 | Extract the icon-picker to its own Forge app — extracted, ported and pushed 2026-09-10 to code.aleris.ai/aleris/ikon, provenance-checked the same day (card 151). No longer gates the cutover: nobody is using the old one, so it goes dark when the domain moves and users are invited to the new instance when it is ready. Still blocked on forge/ASK-IKON-001.md — the schema has no documented route to a post-onboarding database |
T→CC | Torfinn: the schema through the broker, then the OIDC credentials and the domain |
| 150 | The intermittent — captured and diagnosed 2026-09-13: lib/branch-hygiene.test.ts, a 33.9 s timeout against a 30 s ceiling, load-dependent. Cost is ~20 fixture git repos, not the ref walk. The fix is fewer fixtures or taking it out of the parallel pool; raising the ceiling a third time chases it |
CC | – |
| 153 | The plain icon picker went dark with the Qlik one, and the brand site still serves 16 MB of icons no page references | T | – |
| 156 | Which token vocabulary is canon — ruled, reconciled, landed and cut as v0.13 on 2026-09-13. 135 export names mapped; three tokens added (two gradients on the cold/warm axis, an informational status with a measured primitive), one deferred with its reopening condition, one withdrawn on a measurement. Shimmer at 1400ms. What is left is the re-vendor, and sundviktlakemedel is the consumer it exists for |
T→CC | Torfinn: the re-vendor, each consumer being on the no-go list |
| 155 | Release gate: the site is noindex until Torfinn says otherwise — live and verified 2026-09-13 on all five surface types. One flag, four surfaces, suite green in both states. Flip INDEXABLE in site/release-state.mjs at release |
T | Torfinn saying it is ready |
| 154 | Baseline has no status tint scale and no dark-mode surface scale — two products invented values to fill the gaps, and one of those products has just been deleted | T | – |
| 152 | A live app uses the petrol step the scale decided against — #003942, exactly where a petrol-600 would be. Add the token, or rule that the app moves to a neighbour |
T | – |
| 141 | An accepted constitutional page states a count that is wrong by fourteen — its own detection of violation describes a directory that no longer exists in that shape |
CC | – |
| 140 | Hard rule 3 is ruled per section and stated three different ways across nine places — ratify the drafted wording, and define what a section is | T | Card 23 — the adjacency invariant is the candidate definition, and per-section is unenforceable without one |
| 106 | 106 title-case headings ship to consumers against a decided sentence-case rule — recount run 2026-09-10 from lib/heading-tells.baseline.json: 101 entries across 15 files, 47 unique heading texts, and exactly 50 of the 101 are the _packages/ mirror, so 51 are real source instances. The seven files the body names did move in Phase 0 — they are now patterns/anti-patterns.md (12), principles/motion.md (9), principles/grids-tables-dataviz.md (9), principles/progressive-enhancement.md (5), with the two baseline/governance/ files and aleris-baseline-images.md staying put |
T | – |
| 107 | Does WCAG 2.4.6 enter the accessibility constitutional node as a fourth non-negotiable | T | – |
| 108 | The two coined heading patterns — ruled 2026-09-10: not acceptable as they stood, because the coined words are not understandable. Definitions drafted the same day (filters/ai-tells-and-filters.md, both patterns gained a The word line; voice-filter-rules.yaml 0.2.2 gained a term field each). Acceptance of the definitions is what is left |
T | – |
| 142 | The depends_on sweep card 136 authorised — 67 files carry a stale identifier, and DEPENDENCY_ALIASES retires with them |
CC | – |
| 143 | Card 124's three Claude halves — the Tailwind sentence, the comment-density rule, and the cookbook entry on guards with holes | CC | – |
| 145 | Conform Brand OS to Forge — two of three parts done 2026-09-10: the container is built and verified, and the generated scan is corrected in place. What remains is not Claude's: the deploy itself, and getting the scanner to read supabase/ so the corrections survive a regenerate |
T | Card 148 — the first deploy attempt was refused with a 409 domain conflict, and the override on offer would take the domain off a live icon-picker and 404 every /sv/ URL |
| 111 | Q1 — the corpus has no rule for digital identity, and that is what blocks shipping an icon set | T | – |
| 112 | Three token values restated where the token should have been named — and one is a ratified exception the constitutional page does not carve out | T→CC | – |
| 23 | The pairing rule – adopt the flow ladder, or not | T | – |
| 34 | The three English voice-guide drafts have sat finished in workspace/authoring/ since 2026-08-06 — his review, and whether they graduate to Baseline originals. Moved out of Doing 2026-09-09: it is a review waiting on him, not work in progress |
T | – |
| 90 | Print has no home in Brand OS — scope what may be lifted from patientguide's document surface |
T | – |
| 92 | "Chrome" is a retired word with 54 uses in what publishes and ships | CC | – |
| 79 | Decision 1 withdrawn — what F6 gains instead, and it is not a list | T | – |
| 77 | Record the one service property that survives A/B/C's retirement | T | – |
| 78 | Moments have no home in the layer taxonomy | T | – |
| 70 | The categorical chart order puts near-identical hues next to each other | T | – |
| 28 | The language and voice-pass inventory – what B2 needs | CC | – |
| 36 | Physical domain ownership, and the three specifics to confirm with Sanna | T | – |
| 86 | Prose blocks D and E — the colour-alone rule, deferred by route 3 — unblocked 2026-08-31 and moved out of Blocked 2026-09-09: constitutional is in CONTENT_DIRS (verified in lib/content.ts), so the rule's pointer resolves rather than dead-linking on an accepted page |
T | – |
| 55 | Rewrite the "For AI systems" idiom rule in voice.md for an English source |
T | – |
| 57 | Design the positive rule replacing "No dark mode" | T | – |
| 58 | Review the icon allowlist — too conservative. Now has a prerequisite (card 147, 2026-09-10): the Font Awesome kit token returns E401, so scripts/build-icon-metadata.py cannot regenerate the icon set until it is re-issued. Nothing else in the repo needs it |
T | A working Font Awesome kit token — npm view @awesome.me/kit-6f31575c9d version answering with a version rather than E401 |
| 62 | Eight tool-surface pattern entries — the boundary question is ruled, 2026-09-10: gated means it needs Torfinn's pass, not that Claude may not draft it. CLAUDE.md § The content boundary now says so and the two-day contradiction is gone. The remaining work is reframed rather than pullable as written: seven of the eight become rendered components in phase 5 of workspace/plans/card-99-starter-kit-implementation-2026-09-10.md, and two stay Torfinn's behind cards 64 and 65 |
T | Card 99's three rulings — the seven only exist as components if the kit does |
| 64 | --border-subtle — a lighter row separator than gray-100 |
T | – |
| 65 | A non-colour status treatment for a genuine third state | T | – |
| 104 | Gröna korset's five-state safety scale — ratified clinical exception, or standing breach | T | – |
| 114 | Nothing fails when the deployed build and main disagree |
CC | – |
| 119 | v0.12 is cut and on main; the three consumers are not re-vendored, and that half is not Claude's |
T→CC | Cut made 2026-09-09. Sixteen of thirty mirrored files changed, fourteen byte-identical to v0.11, verified by the cut script re-reading every destination. The version call is drafted with its reasoning in MANIFEST.md — a whole version, and not close: two tokens added, one retired, one value changed, plus substantial new Baseline content. Revendor, not repin — the first since v0.6.3. The lag gap the card opened is closed by lib/package-lag.test.ts (mutation-verified three ways); run before the cut it reported sixteen undeclared files, both token files among them. Manifest source column rebuilt from the generator map, its pointer debt recomposed 5 → 3 → 5 and the composition recorded. What remains, and why it stopped here: all three consumers are on the no-go list, so re-vendoring is prepared to the gate and left there — _packages/adapters/vendored-app/REVENDOR-v0.12.md carries the per-consumer instructions, the grep that has to run before any copy, and the failure mode (a dangling var() inherits rather than erroring, so there is no build warning). One correction to this card, measured rather than assumed: its blocking prerequisite is mis-diagnosed. It said re-vendoring would make patientguide's print rule a dangling var() on patient-facing printed documents. It will not — that file declares the token itself and is verbatim: false in the provenance record, a hand-maintained mirror rather than a byte copy, so it stays internally consistent. The real consequence is divergence: the byte copy beside it takes petrol-400 while the mirror keeps orange-500, so one patient-facing repo would hold two answers about the same token, and nothing compares them. Two further uses the card does not mention were found; the local --color-text-muted alias is declared once and used nowhere. Also honest about a gap in the hand-off: sundviktlakemedel has not been surveyed for the retired token. Stated in the note rather than glossed. And aleris-vibe-coding is still at v0.6.5 on an unmerged branch, so the count is three consumers behind on v0.12 plus one that never took v0.7. Yours: whether the drafted version call stands (revising it to a point release is one edit in two files), and the three re-vendors — each one a change to a repo on the no-go list. 426 tests green in 21 files. |
| 120 | Baseline has layout numbers and no layout behaviour — the column ladder for both surfaces is undecided | T | – |
| 121 | Controls on a branded ground — five of six variants undefined, and no check can see a gradient | T→CC | Claude's measurement first: white ghost text against the petrol gradient's light stop, so the ruling carries a number rather than a preference |
| 122 | Third parties on an Aleris surface, and onboarding is not a mode | T | – |
| 123 | Seven findings from the Sund vikt component library still open — three pattern pages, a selected state, a compact button (10 and 11 closed 2026-09-08) | T→CC | – |
| 124 | The serving rule forbids subsetting by its letter, and the flip condition has no card — Sund vikt's four answers reviewed 2026-09-10 at workspace/evaluations/card-124-sundvikt-answers-reviewed-2026-09-10.md; every figure recomputed and all five hold (198 unreferenced against the brief's 199, off by one on reach). Yours: §1 the subsetting ruling, with a fourth clause the brief does not have — subset against the union of every consumer, or the starter kit breaks the first time it ships; and §4 the flip condition, where three of the brief's four conditions are tasks rather than conditions |
T | – |
| 126 | Voice findings — wanted or not, by what route, at what evidence bar | T | – |
| 99 | The starter-kit Storybook — implementation plan delivered 2026-09-10 at workspace/plans/card-99-starter-kit-implementation-2026-09-10.md, awaiting-pass. Five phases, the four honesty mechanisms, the hard clinical exclusion, and nine cards it closes or moves. Yours: the three rulings the card's own done-when asks for — substrate (plan assumes HTML+CSS), scope (tier 0+1, no clinical rendering), hosting (brand.aleris.ai, after Phase C). Phase 0 is worth doing even if you rule against the kit |
T | – |
| 98 | Card 62 re-read: eight missing patterns is one missing component layer | T | – |
| 102 | The 2026-05-07 no-framework component decision was reversed in practice with no record | T | – |
| 103 | OQ-12 — framework-agnostic component specs — open since 2026-03-21 | T | – |
| 101 | Two token consumers are provably stale or wrong | CC | – |
| 130 | review/ has never been pointed at an Aleris consumer |
T→CC | Two decisions, named 2026-09-09 while doing the seam cluster. (1) Where a review runs — a deployed URL, a local dev server, or a container as gcc used. The card's own trap says settle this before writing the config, and it is unsettled; both products are patient-facing, so it is not Claude's to pick. (2) The target-size floor per viewport, with its reason — with no declaration the strict 44 stands, and a patient-facing product should be arguing for 44 rather than inheriting 24 by silence. Claude's half is the target files and the run, and it is one sitting once (1) is answered |
| 131 | The corpus quotes a Swedish source it does not hold, cannot version and cannot check | T | – |
| 132 | 94 pointers to nowhere in what publishes and ships, and no check reads an in-repo path | T→CC | – |
| 134 | The typography essay — same shape, and the smallest of the three | T | – |
| 135 | The voice essay — two documents, 256 lines and 145, that were never merged | T | – |
Blocked
| # | Card | Owner | Waits on |
|---|---|---|---|
| 100 | review/ gains a rendered-component target |
CC | Card 99 — the target has to be the component set itself, and card 99 is what decides whether a component set exists and in what substrate. Moved here 2026-09-09: it had been filed as pullable since 2026-08-28 while its own body says it "depends on card 99 producing something to point at". Card 99 is Torfinn's, in sitting B |
| 50 | Full Phase B step 6 — retire BASELINE.md into the layer taxonomy |
T | 2026-08-10 · Scoped in full (3 new constitutional pages, 2 new principles pages, 2–3 new patterns pages, extending accessibility-is-foundational.md, a retirement stub, and a flagged-not-solved question about agent-baseline's own entry-point model) then explicitly paused — too big for a single pass, and v0.5 needed to ship first. Full plan at ~/.claude/plans/refactored-booping-parrot.md (local to the session that wrote it, not part of this repo). Pull when there's room for the whole thing, not piecemeal against a half-retired file. |
| 37 | The palette boundary – sage, varmvit/ljusgrå, and who owns composition |
T | Card 36 – whether sage is Sanna's real choice or a drafting artefact; varmvit/ljusgrå and 70/20/10 already closed |
| 40 | Build the physical source layer, and split wayfinding out of it | CC | Cards 36, 37 – colour/ownership decisions the layer would have to state (38 closed 2026-08-06) |
| 137 | Rename the package folders to the tiers — a breaking cut, taken once, at Foundation v1 | T→CC | Foundation v1 shipping (documentation/versioning-rules.md: packages pin to a version from then, not a date). Card 136 cleared 2026-09-10 — an identifier is a path — so this now waits on Foundation v1 alone, and on card 142 having swept the identifiers so the rename and the sweep do not collide |
| 144 | Generate a vendored-app adapter for gcc, now that pursuing it is ruled |
CC | Card 104 — Gröna korset's five-state safety scale is either a ratified clinical exception or a standing breach, and that decides whether the adapter's checks exempt those colours or flag them. An adapter may not answer it (adapter rule 2: an adapter carries no brand content) |
Superseded
| # | Card | Owner | Why |
|---|---|---|---|
| 12 | AA pass two – add the steps, turn the test green | CC | 2026-07-31 · replaced by card 19. Card 12 was going to add a confirm fill and a petrol step; the brief retires the confirm variant instead, so the two cards were doing one job in different directions. One list, not two – cookbook §12. |
Critical path: 16 and 17 → 19 → 13 → 14, with 18 also gating 14. 16, 17 and 19 closed 2026-08-05. 16 and 17 decided (merge hover into the focus ring; adopt the state model as proposed), 19 rebuilt against those decisions – 131 tests passing, npm run build clean. 13 and 18 closed the same day – see the Done table for both. Critical path closed 2026-08-07: card 14 shipped. It went through two rounds first (13 → v0.3, superseded by 43 → v0.4, since v0.3 went stale before 14 could run) and one blocker mid-flight (Bitbucket app passwords turned out to be fully retired, not just interactive — corrected in card 14's own body). 18 was a gate on 14, not on 13 – card 13 needed only 19, which was done. Cards 25, 26 and 27 are a second track, added 2026-08-04, and it does not touch this one: 25 is the language decision, 26 the accessibility home waiting on it, 27 the route manifest gate. Baseline is the English instruction layer plus tokens and no Baseline file publishes through CONTENT_DIRS, so the shipment path to Richard is independent of the language question. See workspace/decisions/language-exit-decision-brief.md. Cards 21, 22 and 24 closed 2026-08-01 and are off the path: the two control defects are fixed, and the confirm retirement is carried everywhere except the --button-confirm-* values themselves – which card 19 has now closed too. Card 3 closed 2026-07-31: the confirm variant is retired, which simplifies the set to five variants and moves confirm's job onto secondary. Card 20 closed 2026-07-31, ahead of 19 as intended.
The design return arrived 2026-08-01 and changed the shape of three cards. workspace/archive/Button design WCAG evaluation update.zip (moved from workspace/incoming/ since — workspace/incoming/ stays empty at session end, per house rule), unpacked to workspace/archive/button-state-return-2026-08-01/; assessed in workspace/evaluations/button-state-return-evaluation-2026-08-01.md, all twenty-four of its numbers recomputed exact. Its reasoning is the valuable half and its four replacement files should not be applied – they are a snapshot of its own turn 1, built on a base that has since moved. Three findings carry beyond the cards they touch: card 16's option A is dead on the numbers (petrol-100 on sand-100 is 1.13:1, a 1.4.11 failure the board counted only as a text pass); card 16's question was a false dilemma, because the darkness ruling constrains the palette and AA constrains contrast, and the two meet only if hover must be a fill; and turn 2 produced a pairing rule nobody asked for, which is now card 23 and is larger than either open card.
Reading order, originally written for three open [T] cards (16, 17, 23) – 16 and 17 decided 2026-08-05, so this now applies to card 23 alone: open baseline/buttons/index.html for the measured problem, then workspace/evaluations/button-state-return-evaluation-2026-08-01.md, which supersedes the 2026-07-31 evaluation where the two differ. The reference file workspace/archive/button-state-return-2026-08-01/reference/Button States Brief.dc.html (moved from workspace/incoming/ since) opens in a browser and carries all seventeen options with drawn specimens; it is worth the ten minutes, because the specimens are the argument. Every number in the evaluations was recomputed, not quoted.
Doing
| # | Card | Owner | Since |
|---|
Done
| # | Card | Owner | Closed |
|---|---|---|---|
| 113 | The live site has served a 2026-06-16 build for 73 days — redeploy, and find out why it stopped | T | 2026-09-11 · Closed as a side effect of the Forge cutover, at 87 days rather than 73. brand.dev.aleris.ai now serves the Astro build from a Forge-deployed container. Verified independently rather than taken on report: scripts/verify-deploy.mjs reports 26/26 routes byte-identical to the local build, 35 passed, 0 failed, 0 content differences; /health returns pages: and index: 8751 bytes, which is the line that distinguishes our server from the Forge placeholder's bare ok. Why it stopped was never diagnosed and no longer can be — the Coolify application that held it is stopped and its successor is a different platform, so the question the card asked in its title is closed by removal rather than answered. Recorded that way rather than ticked, because the next long-stale deploy will want to know this was never understood. What stops it happening silently again is card 114, still open: nothing fails when the deployed build and main disagree. The cutover added verify-deploy.mjs, which can answer that question but runs only when someone runs it. |
| 148 | The Forge cutover — steps 1 and 2 done 2026-09-10. Staging is deployed and verified: 26/26 routes byte-identical, 35 checks, 0 failures. And the hold on step 4 is lifted the same day: Torfinn confirmed nobody is using the Qlik picker — it is thought to be in dev — so the domain override is safe and step 4 no longer waits on card 149. DONE 2026-09-11 — step 4 landed and is verified. brand.dev.aleris.ai now serves the Astro build: 26/26 routes byte-identical to the local build, 35 checks, 0 failures, /health reporting pages: and index: 8751 bytes. /sv/… 404s as ruled, the picker paths 404 as expected. Card 113 closes with it — 86 days of a stale live site, ended. What remains is step 5, deleting the Next app, which is Claude's and deliberately last |
T→CC | Nothing. Step 5 is Claude's |
| 151 | The extracted picker had a provenance record and no reader — closed 2026-09-10. Adapter v0.3 installed in ikon, five failure modes proved, and two silent passes that had been in the wiring since v0.1 are closed |
CC | – |
| 147 | The Font Awesome token returns E401, and it is the first thing a Forge build runs | T→CC | 2026-09-10 · Closed by removing the dependency on the token rather than by fixing the token, and the token is still broken. Opened as Torfinn's because re-issuing a credential is his. Pulled on his instruction, and the first question turned out not to be how do we get a working token but does the build need one at all. It does not. Measured: @awesome.me/kit-6f31575c9d was a devDependency; nothing in the repo imports it — no .ts, .tsx, .astro, .mjs or .js file, and astro.config.mjs does not reference it. Its only consumer is scripts/build-icon-metadata.py, run by hand, which reads node_modules/@awesome.me/kit-…/icons and writes into public/icon-picker/. All 3,776 outputs are committed and ship through Astro's publicDir. It is also the only private-scoped package in package.json. So it moved to optionalDependencies, which is semantically what it is: absent, the build proceeds; present, the regeneration script has a pinned version. npm skips an optional dependency the registry refuses instead of hard-failing. The Dockerfile now writes no .npmrc at all when the ARG is empty — an empty _authToken is worse than none, because it authenticates as nobody and earns a 401 where an anonymous request earns a skippable 404. Proved by building and running the full two-stage image with no token set: it served /health, /, /identity/colour, /documentation/site-map, /llms.txt, /foundation/raw/colour.md, a font and an icon SVG, with 3,772 icons present, no node_modules and no .npmrc in the image, as user node, 368 MB. That also corrects card 145's "the full image could not be built here", which was true of the file as written and false about the requirement. lib/build-has-no-private-registry.test.ts, 5 tests, mutation-verified four ways — moving the kit back to devDependencies fires two, deleting it entirely fires the one that guards against that fix, restoring the unconditional .npmrc fires the Dockerfile assertion, and a planted import fires the source scan. It also asserts the load-bearing fact underneath all of it: the icons are committed, thousands of them, not a stub. One thing the check found about itself: its own PRIVATE_SCOPES literals made it report itself as an offender, so test files are excluded — with the reason recorded, since they are not compiled into the deploy build anyway. What is NOT fixed, and stays Torfinn's: the token is still dead. npm view @awesome.me/kit-6f31575c9d version returns E401 from the main checkout's .npmrc. Nothing needs it today, and card 58's icon-allowlist review does — regenerating the icon set is impossible until it is re-issued. Moved to card 58 rather than left open here, because it is now a prerequisite of that work rather than a blocker of the deploy. Suite 435 → 440 green in 23 files. |
| 145 | Conform Brand OS to Forge — the container, and the two errors in its generated scan | CC | 2026-09-10 · Two of the card's three parts are done; the third is a button in a console this repo cannot reach. The Dockerfile is rewritten as two stages. The builder installs dependencies and runs npm run site:build; the runtime carries site/dist and site/serve.mjs and nothing else — no node_modules, no .npmrc, no token, no package.json. A secret cannot leak from a layer that is not in the final image, which is the security half of the Astro ruling rather than a side effect of it. Runs as the node user; nothing writes to disk. site/serve.mjs is dependency-free on purpose — 54 pages and assets need no runtime beyond a file server, and adding a package would re-import the supply chain the static cut just removed. It binds 0.0.0.0 on $PORT, resolves Astro's build.format: 'file' URLs (/identity/colour → identity/colour.html — get this wrong and the homepage works while all 54 content pages 404), preserves the raw-markdown and llms.txt content types lifted verbatim from the Next routes it replaces because consumers fetch those URLs, and revalidates HTML while freezing hashed _astro/* assets. That last one is aimed at card 113: a long HTML TTL is one of the ways a four-month-old deploy stays invisible. The health probe's subject is the built tree, not the process, which is what FORGE-MIGRATION.md means by "not a catch-all 200" — an image from a failed build starts perfectly and 404s everything, so it stats site/dist/index.html and answers 503 when that is missing. Proved in a real container, not read off the file: the runtime stage was built and run, and it served /health, /, /identity/colour, /documentation/site-map, /llms.txt and /foundation/raw/colour.md with the right statuses and content types, as user node, with no node_modules present, exiting 0 on SIGTERM. Dockerfile as written and false about what the build actually requires — the kit is imported by nothing and its icons are committed. lib/static-server.test.ts, 11 tests, mutation-verified six ways. Breaking the .html resolution fires 2; making /health always 200 fires the 503 case; breaking path resolution fires 3; pointing ROOT at the repo instead of the build fires 6, including the traversal case. Two of the six are findings rather than confirmations. Deleting the ROOT check from safeJoin failed nothing — new URL().pathname collapses .. before the server's own code runs, so that check is a backstop unreachable through HTTP, and the first version of the traversal test was a check that could not fail. Rewritten to send raw request lines over a socket and to assert the property (no bytes from outside the tree) rather than the mechanism, with the unreachability recorded in place. And the suite originally used describe.runIf(built), which would have silently skipped eight of eleven tests on any fresh clone, since site/dist/ is gitignored — replaced with a build-if-missing, proved by deleting site/dist and watching the suite stay green while rebuilding it. That is the fifth and sixth instance of card 143's defect, found in this session's own work. FORGE-MIGRATION.md corrected in place rather than regenerated, with the original wording struck through and each correction marked, because a generated document silently edited is worse than one that is wrong — the next regenerate would restore the errors unnoticed. Steps 2, 3 and 4 struck as N/A with the reason; step 1 checked off; the Done when list rewritten for a static site. Suite 424 → 435 green in 22 files. |
| 146 | Astro wins, and the annotation feature does not come with it | T | 2026-09-10 · Ruled: retire it, and nothing needs exporting. Executed the same day. Fifteen files deleted — components/comments/ (4), components/auth/ (3), hooks/useAuth.ts, lib/auth.ts, lib/supabase.ts, app/auth/callback/route.ts, app/api/notify-comment/route.ts, supabase/import-annotations-after-first-login.sql — plus CommentWrapper unwired from both layouts, LoginButton from the sidebar, and resend dropped from package.json and the lockfile with its six transitive dependencies. What deliberately stayed: lib/supabase-server.ts and both @supabase/* packages, because the Qlik icon-picker still uses them. The Supabase surface went from seven files to four, and all four are that island. The annotations table is left in supabase/migrations/ and in the database. Rewriting a past migration falsifies the record, and Torfinn said nothing needs exporting — so the table is orphaned rather than dropped, and that is a statement rather than an oversight. Two things caught by checks rather than by reading, and both are the checks working. The out-of-set hex list held two KNOWN entries pointing into notify-comment/route.ts; its anti-rot half asserts an exception is still present, so deleting the file failed the suite until the entries went too — exactly the convention card 143 describes. And hooks/useAuth.ts survived the first sweep because the grep that cleared the tree searched app lib components site/src and that file is in neither; the build found it, not the grep. Second instance in one session of a clean result being a fact about the query. Verified: 424 tests green in 21 files (426 → 424 with the two dead exceptions), npm run build clean, npm run site:build clean at 54 pages. |
| 42 | The colour supersede's English page has no publish path under route B | T | 2026-09-10 · Closed on a premise that had already stopped being true. Torfinn: "ok." The card was opened 2026-08-06 because the English colour page had no route under route B. The premise was re-checked on 2026-09-09 and no longer held, and nothing in this session changed that: principles/colour.md is accepted, lang: en, and routed at /identity/colour in lib/route-manifest.json. Route B was superseded by the publishing-rule widening of 2026-08-31 and the page moved to principles/ in Phase 0 revised on 2026-09-07 — two changes neither of which was made for this card, which is why nobody noticed it had closed itself. The finding worth keeping is the shape: a card whose premise is a wiring fact can be closed by unrelated wiring work and nothing says so. Card 86 closed the same way eleven days earlier. |
| 95 | Seven archived Swedish originals claim accepted while their replacements do too |
T | 2026-09-10 · Ruled and executed: archive them, and update the claim. All seven files in _markets/sv/foundation/ now carry status: superseded and a superseded_by: pointer to the English page that replaced them — colour.md, constant-contextual.md, emotional-modes.md, typography.md to principles/, index.md and voice.md to foundation/, iconography.md to principles/iconography.md. The seventh was not on the card and had the same defect. iconography.md was draft, not accepted, so it never entered ORPHANED_ACCEPTED and the card never counted it — but draft is as wrong for an archived original as accepted is, and it was fixed with the six. The prose of all seven is untouched: an archive that has been edited is not an archive; a status field is the archive's label, not its content. ORPHANED_ACCEPTED in lib/route-manifest.test.ts loses the six entries and gains the reason. Verified both ways — the suite passes at 12 tests with the change, and reverting one file to accepted fires the orphan assertion naming that file, so the check is doing work rather than agreeing with whatever it finds. |
| 93 | The en-dash convention has never held – 9,079 em dashes against 1,405 en | T | 2026-09-10 · Retired, not narrowed. Torfinn: "allow em dashes (let's stop spending tokens on searching for and swapping out dashes)." The convention set 2026-07-28 — en dash never em dash, in brand-os output and in Claude's prose — is withdrawn in full. No sweep runs, no F5 home is needed, and the corpus is left exactly as it is in both directions: nothing is normalised to em dash either. The standing-rule block in workspace/authoring/colour-supersede-scaffold.md now records the retirement with the old wording quoted so the change is visible rather than a deletion. _state.md's Open #3 closes with it. The measurement is what earned the retirement: 9,079 em against 1,405 en, filters/ with not one en dash in 62 opportunities, and an accepted Foundation page flipped to English on 2026-08-27 by a session holding the convention in its own context, adding fourteen more. A rule this comprehensively unfollowed did not slip; it never took. The 2026-08-27 partial answer — that he uses – himself and would like to keep doing so — is superseded by this one; using it remains his preference and is nobody's rule. One thing deliberately not retired, and it is a different rule: filters/ai-tells-and-filters.md § Layer 1 lists em-dash as default punctuation as an AI-tell, with the method count em-dashes per paragraph; if most paragraphs have one or more, the text is over-dashed. That is about frequency, not about en versus em, it is on an accepted published page, and nothing here touches it. |
| 25 | The language exit – choose where English lives | T | 2026-09-10 · Closed on the last open half: go with the latest plan and file the former one. The language decision itself was never the open part — B1 was accepted 2026-08-04 and constitutional/language-policy.md has been accepted since 2026-08-06. What stayed open was the card's second clause, phase-0 accepted or re-scoped, and it stayed open while eleven files migrated under an unratified plan on 2026-09-07. Now: workspace/plans/phase-0-revised-2026-09-06.md is accepted (v0.2, updated 2026-09-10), and phase-0-target-structure-and-migration.md (v0.1, 2026-05-30, proposed for 103 days) is superseded with a pointer and filed to workspace/archive/phase-0-2026-09-06/ beside the three documents that migration already retired. Three live references repointed — documentation/site-map.md (twice, and it publishes), workspace/plans/website-v3-plan.md's depends_on, workspace/research/voice-consolidation-investigation.md's depends_on. Three deliberately left: _state.md's Log entry and changelog.md's 2026-08-12 entry are dated records of what was true then, and the two decision briefs cite the old plan as part of arguments they made at the time. language-exit-decision-brief.md's condition 2 — "phase-0 is accepted or explicitly re-scoped, so nothing else migrates under an unratified decision" — is now satisfied, and is recorded here rather than written into the brief, which is his record. |
| 105 | gcc is profiled as a consumer candidate — decide whether to pursue it |
T | 2026-09-10 · Ruled: pursue. The profile at _packages/adapters/vendored-app/PROFILE-gcc.md was filled with citations on 2026-08-28 per adapter rule 1, and nothing in ~/Dev/gcc was touched then or now. What the ruling does not do is unblock the work, and that is worth being plain about rather than opening a card that cannot be pulled: card 104 gates it. Gröna korset's five-state safety scale is either a ratified clinical exception or a standing breach, and until that is ruled an adapter's checks do not know whether to exempt those colours or flag them — an adapter may not answer it itself (adapter rule 2: an adapter carries no brand content). Card 144 opened in Blocked with card 104 as its only dependency, so pursuing is now a state the board can show rather than an intention. |
| 136 | depends_on identifiers still say foundation/colour after the essays moved — is an identifier a path or a name? |
T | 2026-09-10 · Ruled: a path. Torfinn: "it has to be able to link to the document so that the website can display links between documents, so path." The reader's requirement decides it — the site renders a dependency as a link, so the identifier has to resolve to something the renderer can find. Recorded in documentation/site-map.md § Dependency model, with the cost stated in the same breath: a path-shaped identifier goes stale every time a file moves, so a move now means a sweep, and DEPENDENCY_ALIASES in lib/content.ts becomes a migration device rather than a permanent layer. The sweep measured before it was scheduled, not estimated: 67 files carry at least one stale foundation/… identifier — 17 live corpus, 16 _packages/ mirror, 3 _markets/sv/ archive, 31 workspace/, 0 in foundation/en-draft/. By identifier: constant-contextual 45, emotional-modes 29, colour 20, typography 12, imagery 11, iconography 3, logo-identity 0. Card 142 executes it. And it clears half of card 137's blocker — that card waited on Foundation v1 and on this question; it now waits on Foundation v1 alone. |
| 129 | Every check is shipped or repo-only, and nothing says which | CC | 2026-09-09 · Built first, because it is the frame the other three fit into. lib/check-placement.ts declares every check with its placement and a property-shaped reason; lib/check-placement.test.ts fails when a check file exists with no entry, when an entry names a file that no longer exists, when any of the three placements empties, and when a contributed check sits outside an adapter — which is card 127's rule asserted rather than restated, since the adapter is the only thing with a route into a consumer's suite. The card's own inventory had drifted, which is the argument for the card. It said lib/ held eight check files and thirteen existed in total. Measured: lib/ held thirteen and the total was twenty, now 22 with this branch's additions. Five checks were added in the three days after the decision that placed them and none of them changed a word in it. A decision record cannot tell you it has gone stale; this can. The reason field is recorded as weak, deliberately. The card's trap says the reason must be the property rather than the placement restated, and there is no machine check for that. The test enforces a proxy — a length floor and a ban on the phrases that restate a placement — and says in its own failure message that passing it is not evidence a reason is good. Under CONSTITUTION.md rule 6 a judgement-based check is recorded as weak rather than as a pass, and how-decisions-are-recorded.md §9 obligation 1 keeps the real detection on reading a sample. Mutation-verified two ways: deleting an entry fires the coverage assertion; rewriting a reason as "repo-only because it is about this repo" fires the proxy. |
| 127 | The adapter is the unit that ships wiring, and a consumer's declared checks are unread | CC | 2026-09-09 · The card named one undocumented field; there were six, and the script read none of them. Measured across both instances: assertions and available_not_vendored in sundviktlakemedel, and print_surface, reciprocal, refreshed and vendored_removed in patientguide. All six were real decisions recorded in a format with no reader, which is worse than not recording them — the record looks like a mechanism. So the build widened from document assertions to a field either has a documented meaning or fails. lib/provenance-record.ts carries two lists and the difference between them is whether anything runs on it: DOCUMENTED_FIELDS the wiring reads and acts on, INSTANCE_NOTES written for a person with a reason each for why there is no enforcement behind it. Patientguide's four are substantive records, not junk, and are declared as notes rather than promoted. _packages/README.md gains rule 8 — the package is content, the adapter is wiring, and the three enforcement routes with the property that decides which one a rule takes. A consumer inventing a field is treated as a signal rather than a defect: the failure message asks whether to build the reader it implies or declare it a note, and says not to delete the field. Patientguide's missing assertions block is now a stated gap rather than a silence — declared empty, in Brand OS's own copy of that consumer's record, with the honest reason that nobody has asked and the question that is owed to that repo. Nothing was written into patientguide (CONSTITUTION.md rule 5). One thing caught mid-build and worth keeping: the first draft of that declaration invented a question_owed sub-field inside assertions, which is the exact defect this card is about, committed while fixing it. Mutation-verified: adding a field to an instance fires the rogue-field assertion. |
| 128 | The out-of-set hex scan is the one assertion that needs the consumer's own source | CC | 2026-09-09 · Extracted to _packages/adapters/vendored-app/wiring/out-of-set-hex.mjs, dependency-free node, reading the token set from the vendored file rather than from a list copied into the script — a copied set is a second source of truth, which is the failure the whole provenance mechanism exists to prevent. The known_out_of_set convention carries across with its anti-rot half: an entry asserts the value is still present, so fixing a violation without removing its entry fails. The reconciliation the card asked for turned out to be the interesting part, and it joins this card to 127. sundviktlakemedel already asserts "no raw hex in the component layer" in its own suite, and its record says so — "which is what an adapter of this type should be spreading rather than reinventing." So the contributed check defers rather than duplicating: if assertions.files names a covering file, it reports skipped and names that file. Skipped is printed either way, the same convention brand-provenance.mjs uses, so a deferral is visible rather than silent. That is what makes the undocumented field of card 127 load-bearing rather than cosmetic — without the declaration the check cannot tell already covered from not covered. Proved as a subprocess against a fixture repo with no package.json, no node_modules and PATH only, because importing the module here would only have proved it parses under this repo's toolchain — the one thing nobody doubted. Seven cases: it runs with nothing installed; a planted out-of-set hex fails and names its file; the same tree clean passes; a consumer with a local equivalent is deferred to; a declared exception passes and then fails once it is fixed; a token set too small to mean anything is refused rather than reported clean; and a scan that found no files is refused. Wired into the install path with the reason a second script exists rather than another assertion in the first, and two new verification steps including planting a hex. Mutation-verified two ways: disabling the deferral fires the duplication case; removing the anti-rot half fires the fixed-exception case. |
| 125 | Drift walk for consumers who copy rather than install — extend consumer-contract.test.ts to declared inherits |
CC | 2026-09-09 · Built, and deliberately not as the card specified — the card's premise had moved and this is the departure stated rather than absorbed. It asked for the canonical paths each copy consumer took, from the 2026-08-10 incident where the token file moved and aleris-assistant's four pointers went stale for fifteen days. Read that consumer's own files on 2026-09-09: it closed the class itself on 2026-08-25 by repointing at the package path, with its reasoning written down — package-internal paths are a published contract and held across v0.4 to v0.7.1 through the very move that broke it, while canonical paths are internal layout and did not. It also wrote its own check that resolves the declared path, compares every vendored value, and skips visibly rather than passing when Brand OS is not checked out. Declaring canonical paths would have re-created the dependency the consumer removed on purpose, and asserted a relation that no longer exists. What is live is the opposite risk. Card 137 renames the package folders as a deliberate breaking cut, and until now only the two adapter consumers had a machine-readable record of which package paths they use — aleris-assistant records it in prose and a source comment, aleris-vibe-coding sits on Bitbucket at v0.6.5. _packages/copy-consumers.json closes that gap, with the reframing written at the top of the file so a reader meets it before the data, and three new assertions in consumer-contract.test.ts: the declaration exists at all, every declared package path still resolves, and every row carries a verification note — aleris.se is declared with nothing copied and says plainly that nobody has looked, which is a different thing from a row read out of someone's memory. A side effect worth having: card 137's required per-consumer migration note can now be generated from data rather than recalled. Mutation-verified: renaming a declared package path fires the reshape guard. Nothing was written into any consumer. |
| 109 | branch-hygiene.test.ts measures against local main, and goes false the moment it is stale |
CC | 2026-09-09 · Both defects fixed, and defect 2 turned out to be live rather than latent. Defect 1, the trunk reference. TRUNK was doing two jobs — the logical name that groups refs and excludes the trunk from its own tail, and the ref the arithmetic runs against. Split: the name stays main, and resolveTrunkRef() returns origin/main when that ref exists and falls back to local main when it does not, so a fresh clone or a repo with no remote still gets a working check. The fallback is announced in the report, because a silent fallback is how the stale reading was believed for a month. BRANCH_HYGIENE_TRUNK overrides it, in the same spirit as BRANCH_HYGIENE_TODAY. Defect 2 was not latent — the existing filter never matched anything. The card called it latent on the reasoning that a current trunk leaves the symref zero ahead. That is true, but it means the old filter's own correctness was never exercised: git for-each-ref --format='%(refname:short)' renders refs/remotes/origin/HEAD as the bare name origin, measured here, so .filter(r => r !== 'origin/HEAD') excluded a string git does not print, while its comment stated that it excluded the symref. A filter whose comment asserts what it cannot do — the same shape as ORPHANED_ACCEPTED's comment asserting its own misreading, and cookbook 24 rule 2 now records this as its second instance. The fix asks git what a ref is instead of matching how it prints: %(symref) is non-empty only for a symbolic ref. That cannot rot when a rendering changes, and if the rendering ever does change the premise is asserted in its own test rather than kept on faith. Proved against a deliberately stale trunk, which is the card's own requirement and the reason it was worded that way. The check was green on 2026-08-31 and green afterwards with neither defect fixed, because local main had been fast-forwarded by hand — a fix to the symptom. Testing that needed the arithmetic separated from the live repo, so aheadRows(repo, trunk) now takes both rather than reading them; a function that resolves its own trunk can only ever be tested in whatever state today's tree happens to be in, which is exactly how a month of green runs proved nothing. The fixture holds both defects at once, with real refs rather than mocked output: local main two commits behind its own origin/main, a feature branch merged into the remote trunk but not the local one, and an origin/HEAD symref. Against local main the merged branch reads as work owed; against origin/main nothing does; the symref is a branch under neither. Plus a live-wiring assertion, because the fixture cannot see whether the resolution is actually plumbed into the real check. Mutation-verified three ways: restoring the old string filter fires the symref assertion; reverting the resolution to local main fires both the resolution and the live-wiring assertions; removing the fallback fires the resolution assertion. This file 14 → 20 tests, suite 398 green in 17 files. One consequence, found by running the fixed check in the main checkout and fixed in the same branch. The snapshot test began failing as a 5-second timeout at 6.0s while its own verdict was correct and printed — a red light saying the wrong thing, which is worse than a slow one. Not caused by the trunk change: the cost is the object walk (7,745 objects from trunk) plus a per-path history walk per snapshot blob, and twelve snapshot refs now exist, so it scales with the very backlog the check asks someone to clear. Vitest's 5s default was never chosen for this test and had simply not been reached before. Timeout set explicitly to 30s with the measurement recorded, deliberately generous — a tight ceiling would turn snapshot accumulation into a spurious failure at some unpredictable count, and the signal here is the verdict rather than the clock. And the check is now saying something Torfinn should see: backup/auto-20260906 (3d) holds three files that are superseded with additions — nothing that is not already in main, so it is finished with and deletable. Under the card 116 precedent a remote branch deletion is his, not Claude's. |
| 139 | The pre-push gate detonated the repository it was protecting — an inherited GIT_DIR redirected a test fixture's git init into the real repo |
CC | 2026-09-08/09 · Found by being hit, and the damage is what identified the cause. At 18:11 on 2026-09-08 the repo's own config acquired core.bare = true, extensions.worktreeConfig = true and [user] Fixture <fixture@example.invalid>. bare = true meant no working copy could run any git command — the main checkout and the worktree both dead — and the identity meant the next commit anyone made here would have been authored by "Fixture". The mechanism, proved in throwaway directories rather than inferred. Git sets GIT_DIR in the environment of every hook. The pre-push gate runs npm test, so this file's git calls inherit it, and GIT_DIR beats cwd. lib/branch-hygiene.test.ts's snapshot-classifier fixture builds a throwaway repo with git init and git config user.email in an os.tmpdir() folder; under the hook those two commands operated on the real repository instead — re-initialising it and writing the fixture identity in. The probe reproduced the identity pollution exactly, in two temp dirs, touching nothing real. So the gate that exists to stop bad code leaving the machine is what broke the machine, and it fires only when the suite runs from the hook — which is why two direct npx vitest run invocations the same session were harmless and the push was not. Two defects from one cause, which is why the fix covers all three helpers rather than the fixture's alone. git() reads process.cwd(); with a GIT_DIR inherited from a worktree push it would read the parent checkout's git directory and report on a tree that is not the one being pushed. The hook's own header records fixing exactly that for the working tree — git rev-parse --show-toplevel rather than BASH_SOURCE, measured twice in patientguide — and the environment was the half nobody looked at. A gate that measures another repository is worse than none, and this one could damage that other repository too. The fix: scrubGitEnv() removes GIT_DIR, GIT_WORK_TREE and GIT_INDEX_FILE, and all three git helpers pass it. Written as a pure function on purpose — the constant snapshots process.env at import, so a test that set GIT_DIR afterwards would have passed whatever the scrub did. That is the shape of a check that cannot fail (cookbook 24) and it was caught before it was trusted. Proved in both directions, because a test that only shows the scrub working is indistinguishable from a test that shows nothing happening. One probe reproduces the incident — git init and git config in one temp directory reaching into a repository somewhere else — and the next runs the same two calls through the scrub and asserts the other repository is untouched and that the identity landed where it was aimed. A fourth assertion reads this file's own source and fails if a git helper is added without the env, since the two probes call execFileSync directly and cannot see the wiring. Mutation-verified two ways: unwiring one helper fires the wiring assertion; dropping GIT_DIR from the scrub list fires both the naming assertion and the end-to-end probe. Suite 10 → 14 tests in this file. The durable half is not in this repo and is owed. The gate should clear these variables before running any suite, in Dev/_tools/harness/template/pre-push.tmpl, so all 31 initiatives are covered rather than whichever one has been bitten. A hand edit to bin/hooks/pre-push is detected by design (harness.json records its hash) and would stop the fix reaching the others, so this is a harness change and this repo cannot make it. Torfinn's to schedule; the repo-local fix means brand-os is safe meanwhile. Restored by Torfinn running three git config --unset/--remove-section commands on 2026-09-09 after the permission classifier — correctly — refused the write to Claude. No commit was misattributed: the last commit before the pollution is 324383d, authored correctly, and nothing was committed while it stood. |
| 138 | The drop zone is invisible to the path check, and Phase 0 left four stale pointers in briefs queued for execution | CC | 2026-09-08 · Scoped as recommended, and the three non-defect classes are handled by two rules rather than 38 pins. workspace/skills joined RULE_SURFACES under full strictness — a skill file states rules and speaks about this repo alone, and it was already clean. workspace/incoming joined as a new kind of surface, BRIEF_SURFACES, because a brief is a document about two repos and its paths sit in two namespaces. Rule 1, the namespace rule: a brief's reference is held only if its first segment is one of this repo's own top-level folders, derived from the tree so a new folder widens the check with no edit. That drops the consumer's own paths, package-relative shorthand and the strings the regex invents — 38 of them — into a returned outOfScope bucket the script prints (--out-of-scope), because the rule's blind spot must be inspectable rather than silent. It left exactly nine instances claiming this repo and missing, which was the whole signal. Rule 2, the strikethrough rule, and it was found by doing the work: repointing the four pointers produced four correction notes, each of which had to name the path it retired, and every one reintroduced the dead reference the repoint had removed. A path inside a ~~strikethrough~~ span is now a quotation rather than a citation. Measured before adopting: eighteen strikethrough spans existed across the scanned surfaces and not one wrapped a path, so the rule started free. A path struck and plain on one line is still held to resolve. The forward-reference question has an answer: a new PROPOSED list, whose anti-rot half runs the other way from an exemption — an entry fails when the path starts resolving, because at that moment the brief has been executed. foundation/print.md is its one entry, and the brief now flags that foundation/ no longer holds essays so that candidate needs re-reading, which is surfaced rather than decided. The four stale pointers repointed, and a fifth thing found: four foundation/typography.md → principles/typography.md (two carried line numbers :65/:120 that are :66/:121 today, so they are repointed by heading and quoted phrase per CLAUDE.md § Cite by heading), foundation/logo-identity.md → principles/logo-identity.md, baseline/setup.md → how-to/install-the-tokens.md, documentation/dependency-reciprocity.md → documentation/how-decisions-are-recorded.md § 10 — verified to still carry the claim the citation was made for rather than assumed to. The fifth: the print brief's publishing-rule sentence still said only foundation/ and baseline/ publish, a rule widened on 2026-08-31; corrected. One instance deliberately not repointed: baseline/tokens/aleris-tokens.css in the assistant brief is the stale path that brief reports the consumer as having recorded — repointing it would erase the finding, so it is declared in NOT_CITATIONS with that sentence. Reach is now asserted, not remembered: a required-surfaces test fails if any of the thirteen is dropped, which is the property that actually failed here. Mutation-verified four ways — a dead pointer written into a brief fires the clean-file test; removing workspace/incoming fires the coverage test and the namespace-rule test; forcing onlyStruck true fires the quotation probe; creating foundation/print.md fires the kept-promise test. KNOWN did not change, which is the measure of the pass: its twenty-one entries sum to 81 before and after, against 634 references scanned rather than 569. The raw unresolved count went 84 → 87 and every one of the three new ones is declared — one quoted stale path in NOT_CITATIONS, two instances of one forward reference in PROPOSED. (Figure corrected in the same session it was written: this cell first said "84 before, 84 after", which conflated the raw scan total with the KNOWN sum. Caught by the verification pass, not by the suite — the test asserts each count exactly and would have failed on a wrong number in the record, but the record is prose and nothing reads it.) Suite 388 green in 17 files, +6 tests in this file. workspace/plans and workspace/briefs stay out with their numbers recorded (546 references, 284 unresolved) — records rather than live surfaces, and Torfinn's scope call, not a defect. Cookbook 25. |
| 116 | branch-hygiene's stranding check cannot see superset containment, so a rescued-with-edit blob is flagged forever |
CC | 2026-09-08 · Two tiers, and the card's own live example turned out not to be the case it described. Tier 1 is the object walk, unchanged, because the card was right that it is the correct instrument — it measured five files whose lines existed only on a snapshot and stayed correctly silent on all five, since every old blob is in main's history. Tier 2 sees only what tier 1 flags and asks a narrower question: does trunk's history at that same path hold every line the snapshot has, against the union of every version of the path rather than each version alone. Contained is printed and passes; uncontained fails; a binary blob cannot be proven contained and fails, which is the conservative direction. Proven against a fixture repo rather than this one, per the card — the live repo can only ever demonstrate whichever case it is in today, and it had been stuck in the false positive since 2026-09-02. Four cases, and the pair that matters differs by one added line: rescue-with-edit must clear, genuinely-new must not. Mutation-verified in both directions — forcing containment true fires the genuine-strand case, forcing it false fires the rescue case. SNAPSHOT_GRACE_DAYS reconsidered and left at 2, with the reasoning recorded on the constant: the recurring noise the card named came from the classifier, not the window, and raising it would only delay a real strand while the work is still fresh. backup/auto-20260905 was not the rescue-with-edit case. It held 3 lines in no version of main's copy of workspace/evaluations/cluster-governance-and-mode-2026-09-05.md — the section framing main replaced with the jobs decomposition, which carries a claim the replacement dropped. Landed at its own path rather than archived under a new name, because the check is path-scoped and an archive copy would have preserved the words and left it red forever, which is this same blind spot one move along. Verified after landing: 0 of 97 quote-normalised snapshot lines absent from main's version. Proven on live data as well as the fixture, by running the check forward. BRANCH_HYGIENE_TODAY exists for testing the expiry logic and also answers this: at 2026-09-11, backup/auto-20260906 holds three blobs unreachable from main — baseline/BASELINE.md, baseline/governance/aleris-design-governance.md and data-products/tokens/tokens.test.ts — and tier 2 clears all three as superseded with additions, printing that the snapshot can be deleted. Under the old check that was a hard failure with instructions to rescue three files main already holds. backup/auto-20260907 and -20260908 correctly still fail at that date, on _probe.mjs and on this branch's own unmerged files. So the two tiers separate real repo data, not only the fixture's. _probe.mjs is worth knowing about: it is the untracked Cowork leftover at the repo root, it is on a snapshot and has never been on main, and it becomes a reported strand on 2026-09-12. That is the check working, and the fix is to land it or delete it. Snapshot inventory after this card, measured: auto-20260902-…ac9176 0 unreachable of 4,342 blobs, -20260904 0 of 4,353, -20260906 3 of 4,361 (all superseded), -20260907 1 of 4,386, -20260908 15 of 4,390 (this branch's own work). Six for six on the hook never having captured content that was not already landed, counting -20260905's three lines as the first exception — which is the answer to this card's closing question and is Torfinn's to act on. backup/auto-20260905 deleted local and remote 2026-09-08 on Torfinn's instruction, after re-verification immediately before: 4,359 blobs, exactly 1 unreachable, and all 97 of its quote-normalised lines present on the pushed PR branch. Ref was df47664, blob 192c3e3b. Suite green at 382/382 afterwards. Note the ordering risk that was checked rather than assumed: the three rescued lines are on origin/chore/retriage-incoming-2026-09-08 and not yet on main, so they reach the trunk when PR #31 lands and not before. One limitation documented rather than special-cased: containment is exact on trimmed lines, so a rescue that reformats — a blockquote prefixes every line with > — does not clear tier 2. Stripping markdown markers would fit the check to one file format and the next reformatting would reopen it. The consequence is stated on the function: a snapshot whose content was rescued in a changed shape is finished with, and deleting the branch is what closes it. backup/auto-20260905 is therefore still owed a deletion, and that is Torfinn's — it is provably empty of unique content but a remote branch deletion is hard to reverse. |
| 89 | A rule is applicable where it is read — a seventh constitutional rule, with a check | T | 2026-09-07 · Ratified as worded, together with the other four proposed constitutional pages (a-thinking-tool, colour, typography, voice). The check (lib/external-references.test.ts) had been in force since 2026-08-27; the rule it enforces is now accepted too. Six routes entered the manifest (20 → 26) |
| 133 | The colour essay — principles/colour.md is cited by a constitutional page and has never existed |
T | 2026-09-07 · The essay exists: Phase 0 revised moved foundation/colour.md to principles/colour.md (PR #27), and the constitutional page it serves was ratified the same day. Card 134's remainder — two homeless demoted typography rules — stays open |
| 115 | Three of four semantic text roles are never measured, and one fails AA everywhere | T→CC | 2026-09-04 · --text-tertiary retired, and the survey the card asked for is what decided it. 849 files across the four consumers and this repo hold one text use: patientguide/web/src/app/globals.css:674, inside @media print, on the (url) suffix after an external link. Three more uses of the primitive in sundviktlakemedel are border-color, where 3.05:1 clears the 3:1 boundary floor. No ::placeholder rule exists in any consumer, so --input-placeholder was declared, aliased through this token, and applied by nothing. So the value was never the defect — gray-300 is right for disabled and placeholder, and --text-disabled and --state-disabled-text already hold it. The name was the defect, and renaming would have kept a fourth name for one value. --input-placeholder now points at the primitive, consistent with the position this suite already recorded about placeholders not being required content. No caption tier added, on a measurement: nothing sits between gray-300 and gray-500, and the lightest warm grey clearing 4.5:1 on all three grounds (~#6d6456) is 1.4 ratio steps from gray-500 and would not read as a separate tier — a caption takes --text-secondary. One published page was prescribing it: patterns/empty-state.md listed it as "Dämpad hjälptext", live at /baseline/patterns/empty-state, corrected. Three retirement assertions added, including one on the name shape rather than the value, since primary/secondary/tertiary is a sequence whose third slot reads as a hole and will be refilled by reflex. The card's fourth role, --text-secondary, measured clean (7.94/7.49/6.76). Consumer change owed and not made here: patientguide's vendored copy still declares the token, so nothing is broken until it re-vendors — at which point the print rule becomes a dangling var() that silently inherits. Blocking prerequisite on card 119. |
| 118 | Error text on the communicative ground — recorded as a known failure for a month, and unowned | T→CC | 2026-09-04 · The ground is ruled; the colour is untouched. --text-error is 5.03:1 white, 4.74:1 sand-50, 4.28:1 sand-100. The survey found no consumer renders error text on sand-100: only sundviktlakemedel renders error text at all, App.tsx:163 sets <Page mode="instrumental"> for its whole app, and the rendered ratio measured 4.74:1, passing — taken from the product's own CSS and markup driven in a browser, not computed from tokens. brand-page--communicative has no user. So the failing case is real in the token graph and absent from every product, which is what made the cheapest option right. BASELINE.md § Forms now says error text sits on a white card or an instrumental ground, never directly on the communicative one, where a form goes in a card anyway. Darkening error-500 was rejected because foundation/colour.md cites #C14444 by hex in its colour-vision reasoning and both published ΔE figures would need recomputing; a darker error-text step was rejected as a primitive bought for a case no product has. The check nearly became a defective symptom — dropping sand-100 from the measured surfaces would have turned the test green with nothing fixed — so the permitted grounds and their ratios are read out of the rule in BASELINE.md and the rule's claims are asserted in both directions. That block had a hole, found by mutation-testing it before trusting it: widening the rule to say sand-100 also clears fired nothing, because the branch was keyed on the surface name rather than the rule's claim. The rule now states a verdict per ground and the verdict is measured. Eight mutations run, all eight fire. Suite 200 → 209 in the token file, 273 across 11. |
| 110 | The main checkout held uncommitted work that duplicated commits already on origin/main |
T→CC | 2026-08-31 · The suspicion was right and the risk was smaller than it looked. ~/Dev/aleris-brand-os sat ten commits behind origin/main and refused git merge --ff-only, because its working tree held six uncommitted files colliding with the commits it was missing. Each was compared against origin/main rather than guessed at: lib/logo-minimum.test.ts, scripts/generate-logo-icons.mjs and workspace/evaluations/digital-identity-verification-2026-08-28.md were byte-identical to what PR #14 had already landed; review/SPEC-production-icon-surface.md was the older copy, missing the rescue provenance note that is on main. So five of six were duplicates or stale, and only _state.md held anything unique — a 2026-08-28 Progress entry and three Log entries in no commit, including the correction owed in three files about emotional-modes.md. None of the ten commits touch _state.md, so it survived the fast-forward untouched and was verified byte-identical to its backup afterwards; the correction is still owed. The favicon was the one thing that could have done damage. The working copy held the generated 4258-byte ICO where main carries the provisional 4158-byte one that is byte-identical to what www.aleris.se serves — landing it would have answered Q2 of the digital-identity verification by accident, the exact question the rescue note says was deliberately left open. Restored to 4158 B; the generated set is parked with its reasoning at workspace/drafts/parked-2026-08-31/, which .gitignore excludes so it cannot be committed by mistake. main is current and all 243 tests pass, in 10 files — the first fully green suite in some time. |
| 97 | A Tailwind v4 @theme bridge for the token file |
CC | 2026-08-28 · data-products/tokens/aleris-tailwind.css, generated by generate-tailwind-theme.js, 145 of 363 tokens bridged and every skipped family named in the file's own header with its reason. Served at /baseline/raw/tokens/aleris-tailwind.css (nav entry + MOVED_PATHS), both routes verified prerendered and byte-identical to source. The design decision is a measurement, not a preference: the obvious identity map (--color-petrol-500: var(--color-petrol-500)) is unsafe — against Tailwind 4.2.2 a theme variable matching Tailwind's own namespace is emitted into @layer theme as a self-reference, invalid at computed-value time, surviving today only because aleris-tokens.css is normally imported unlayered. Prefixed names avoid the collision, and Tailwind then inlines them and emits no theme variable at all, so a utility body is var(--color-petrol-500) directly and the bridge adds nothing to :root. The compile test earned its place twice. It caught a silent generator bug on first run — --color-goal-* and --goal-* both mapped to --color-aleris-goal-*, so four tokens quietly lost their utility; the generator now throws on any two-to-one mapping. And mutation-tested against a restored identity map, three of its six assertions fire. A finding fell out that the card did not anticipate: schemas/design-token.md § "Tailwind CSS v4" already carried a hand-written bridge, and 7 of its 25 token references did not exist (--elevation-1/2/3, --font-size-h1..h4) — a consumer copying that block got seven utilities resolving silently to nothing. Replaced with a pointer to the generated file, the measurement recorded, and the namespace change (prefixed and additive, where the old block replaced Tailwind's own scale) flagged as reversible with the condition that would make it wrong. |
| 91 | agent-baseline v0.8 is cut and three consumer records still pin v0.7.1 |
CC | 2026-08-27 · repinned v0.7.1 → v0.10, skipping three intermediate versions that were cut the same day. Verified before the pin moved rather than asserted: both vendored aleris-tokens.css copies byte-identical to canonical, so this was a repin and nothing was re-vendored. Both consumers' own brand-provenance.json edited first, then copied here, per the direction the adapter manifest records — the consumer's copy leads. Each carries a note saying what the four cuts contain, and a repo-specific paragraph: sundviktlakemedel gets injection technique reclassified as instructional imagery and its weekly check-in named as a working surface; patientguide gets preparation steps reclassified. Each repo's own brand-provenance.mjs run and clean, reporting pinned v0.10, source v0.10. The adapter was not versioned, and the reason is a field that was doing two jobs: Pins content read as both what this wiring was authored against and what the consumers run, which parted company the moment content moved without the wiring changing. Renamed Authored against in both the adapter manifest and adapters/README.md, with the rule stated: an adapter needs a new version only when a content cut changes what the wiring has to do. v0.8–v0.10 were content only. |
| 72 | imagery.md splits on audience; a product splits on situation, and a surface can be both |
T→CC | 2026-08-27 · the axis was decided on 2026-08-25 as decision 4 and had never reached the page — the decision record says so in as many words. § "Imagery in professional and internal surfaces" is now § "When the surface is someone's work": whose time it is decides this; a reader's profession does not. Neither candidate the finding offered: instrumental would have put a patient's check-in and a clinician's dashboard on the same side, which is the reading that renders a consumer product as an admin tool. Two consequences land at once — professional readers on communicative surfaces (recruitment, partners, referring physicians) may lead with an image, which the old lumping forbade; and a patient-facing surface can be a working surface, not under restraint but under the new hero rule: the image never holds the hero position on a working surface. Also confirmed here, closing decision 2's orphaned question: an imagery rule records the property it turned on, not a factor from a list — decision 4 is now the worked example how-decisions-are-recorded.md had none of. |
| 84 | Instructional imagery is not a category the corpus names | T→CC | 2026-08-27 · shape 3, a boundary rather than a home, with Torfinn's provision: a conversation with marketing, judged case by case, deliberately not a named role because who should judge depends on the image. The page now scopes itself to identity imagery in a line under the heading — which it always did without saying so, and the omission had teeth: read literally the old page forbade the image an injection instruction needs, since its founding choice is "the person at the centre… equipment serves as support, never as the main subject". Nothing was loosened to make room. The "Not clinically documentary" prohibition and § "Why the images look the way they do" are untouched; the instructional image sits outside the page rather than as an exception inside it, which is why answering 84 before 72 mattered. The argument for it being a different kind of object rather than an uncovered category: an instruction needs even light and deep focus, the opposite of two of the three visual choices. |
| 96 | imagery.md waits on a reader-state model for professional readers that now exists |
T→CC | 2026-08-27 · dissolved rather than answered, which was the more useful outcome. All three shapes offered assumed the question was how restraint relates to the four dispositional states. Torfinn's answer said the premise was false: "professional" was never a category on the right axis. Some professional surfaces are communicative and always were — "industry peers or insurance partners, material intended for physicians, HR (recruitment)" — and what separates them from a dashboard is whether the surface is someone's work, not the reader's stance. So the content landed inside card 72's section, no per-state table exists to maintain (his constraint: two or three rules, max five, affording direction rather than prescribing), and the stale until then clause came out because the thing it waited for was not the joint. The quick test gained "is this surface someone's work?" as question 1, which can stop the sequence — asking category and reader state first is how an image ends up leading where it should not. Deliberate silence, named as one: the page never points at the dispositional family. |
| 94 | worry is still the mode name in two accepted pages after the rename |
CC | 2026-08-27 · three mode labels corrected (imagery.md's table column head and its "the table works from the patient's modes" line; constant-contextual.md's Reader state row) and one rewrite that was not a substitution — imagery's Avoid cell read "they don't answer the worry", which depends on there being a worry to answer, so swapping the word alone would have left a claim about an emotion the page no longer names. Now "they don't answer the uncertainty"; Claude-drafted table-cell text per CLAUDE.md's utility-text calibration, not routed through a decision loop. Nine ordinary-noun uses deliberately survive — the card's own warning held: the mode is not called Worry, and worry is still something we can cause. Package cut to v0.9.1, a point release that fits rule 6 without argument. Two findings the card did not have, both from running the query wider than the card's five (card 83's lesson applied): constant-contextual.md:30 and imagery.md:84, now cards 96 and a note below. And one honest limit: this fix has no assertion behind it, unlike card 83's — what was retired is a sense of an ordinary word, which no word-boundary match separates from the nine legitimate uses beside it. |
| 73 | Both emotional modes are relative to a pending event; a habitually revisited surface has neither | T→CC | 2026-08-27 · balanced written, with its own table row – lead with relevant, clear options; dial down general information; support understanding consequences and choosing a next step. Not the midpoint of the other two, so decision 3's falsifier passes. The second constraint resolved more simply than the card asked: Torfinn confirmed balanced describes the person, same kind of claim as the other two, so the table mixes nothing and the falsifiability paragraph is unnecessary rather than missing. The third constraint was struck – the pace rule is deliberately out, because once balanced is about a service going smoothly rather than a person setting her pace, "the surface does not push" no longer follows from the definition; friction only where it earns its place is what constrains a builder instead. Decision record amended underneath rather than edited. A structural finding fell out, and it was nobody's mistake: the table and the mode list were never the same taxonomy – the Swedish original listed Oro / Desorientering in the table and Oro / Nyfikenhet as the modes, so disorientation was never a mode and curiosity was never in the table. Invisible while each had two entries. Resolved by declaring the table a worked example of three occasions, not by expanding the model to four modes. |
| 53 | emotional-modes.md — resolve the reader-state rename before English can flip |
T→CC | 2026-08-27 · the last of its three questions answered rather than deferred: next of kin gets its own family, practical states – the person is helping someone else and the state is set by the job in front of them rather than by a feeling of their own. Option 3 of three; the corpus rejects the helplessness framing throughout, which is what ruled out filing a helper under a feeling. The emotional family's definition trimmed to for patients. The umbrella is stated by structure – Three families of reader state – rather than by a separate sentence. The page's own "not canonical" banner is gone with the en-draft, which is deleted; foundation/en-draft/ now holds no markdown at its root and the English flip of Foundation is complete. Also here, and not something the card foresaw: the first mode renamed worry → uncertainty, Torfinn's call, because worry presumes distress most arriving patients do not feel. |
| 39 | CONTENT_DIRS still lists physical against the ratified publishing rule |
CC | 2026-08-27 · the card's own second clause is what made this more than a string edit. physical removed from lib/content.ts, which is what publishes; but the same array in lib/route-manifest.test.ts feeds the orphan gate, which is what gets checked — so narrowing one would have narrowed the other and the gate would have gone green by looking at less, the exact failure the card names. Split into CONTENT_DIRS (['foundation']) and AUDIT_DIRS (ten directories, every one that can hold corpus markdown), with routed unioned across both manifests so the nine files publishing at /baseline/* through resolveBaselineSource are not counted as orphans. ORPHANED_ACCEPTED went from empty to seven — four constitutional/ rules in force and unreadable, two filters/ files, one documentation/ page. Its 2026-08-04 comment claimed the emptiness was "a real result rather than an unpopulated list"; it was true of the two directories it walked and false about the repo, and it stayed that way for three weeks. lib/route-manifest.json did not change — 8 routes, all from foundation/, and physical/ never existed, so fs.existsSync had always skipped it. Mutation-verified: an accepted probe under constitutional/ fails the orphan gate and produces no route, both halves in one probe. 229 tests. |
| 83 | Two A/B/C references survive the retirement, one in an accepted page | CC | 2026-08-27 · both resolved, and the sweep got the row that makes the next one unnecessary. baseline/governance/4-den-nara-experten-digital-behavior-v1.md:122 lost the (Mode C in the design working document) parenthetical — a correction, which an accepted page allows, and the sentence carries its own substance without it. baseline/iconography/curation-session-b.html:535 now reads "Pröva mot ytor där patienten väntar på ett besked" instead of "Pröva mot Mode A-ytor" (Claude-drafted utility text in an unpublished curation file, per CLAUDE.md's calibration). Then three rows in lib/retired-terms.test.ts, one per label, since each has a different replacement: A was anxiety-driven, B aspiration-driven, C latent anxiety, mapping confirmed from workspace/archive/tone-construction-plan.md:214. Needed one new field — a wholeWord?: true flag, because plain substring matching on mode a hits 14 lines in the published surface where 2 are real ("dark mode as", "the mode and"). Both references mutation-verified: restored, seen failing with file and line, reverted. Zero \bmode [abc]\b left in what publishes or ships. Does not close card 77 — the surviving service property is still Torfinn's to record. |
| 15 | Update the records | CC | 2026-08-27 · board, changelog and _state.md. Also corrected card 86's Waits-on, which named card 39 as its unblock and had the direction backwards; opened cards 90 (the print brief, thirteen days unexecuted and now carded so parked and forgotten stop looking alike), 91 (the v0.8 repin fan-out) and 92 ("chrome", a retired word with 54 uses in what publishes and ships, found while correcting one of them). work.md deleted from root — tracked, one byte of content, against the root-only rule. |
| 88 | A moved file breaks a consumer silently — the staleness mechanism, first build | CC | 2026-08-27 · lib/consumer-contract.test.ts reads the generated manifest's file table and asserts both columns resolve — nothing hand-maintained, so the list cannot rot separately from what it describes. Reverting the token row to its pre-move path reproduces the 2026-08-10 failure and fails the suite in the session that would have caused it. Four assertions, all mutation-verified. The three original statements of the gap (_markets §Open, _packages rule 6, §10) are untouched |
| 82 | Nothing carries a finding from a repo to Brand OS | CC | 2026-08-25 · /wrap turned out to ask one question where CLAUDE.md specifies two, so the missing assertion question went in alongside the new consumer-findings step. Built as an unskippable check rather than a third question — a question asked every session stops being heard. GAPS.md §3, the undecided-conventions half, stays open |
| 81 | aleris-assistant has no findings file and no brand-provenance.json |
CC | 2026-08-25 · file installed and adapted; the card's premise that the repo vendors nothing was wrong and its own CLAUDE.md caught it — tokens are hand-copied into src/index.css. Provenance record deliberately not created, reason recorded. First entry is a live finding: the vendored token path has pointed at a file moved on 2026-08-10 |
| 80 | A fourth finding kind, and the field that makes a finding's reach computable | CC | 2026-08-25 · template plus both live instances carry new context — placed fourth and highest, above contradiction — and the what made this situation different field. Every findings file now records Last filed to Brand OS, honestly: sundvikt 2026-08-12, patientguide never |
| 54 | constant-contextual.md — confirm the contextual-axes framework before English can flip |
CC | 2026-08-27 flipped and closed · the four axes were superseded, not amended — documentation/how-decisions-are-recorded.md settles it: F6 stays a stance, adjacency is per-decision, no taxonomy up front. English page prepared in en-draft; only a voice pass remains, which is Torfinn's. Also retires F8: site-map.md had kept Expression calibration as core while the phase list had it demoted |
| 76 | Communicative/instrumental is used in three Foundation pages and defined in none | CC | 2026-08-25 · closed by fixing the inversion card 79 reframed it as. Baseline defining the term is the layer model working; the defect was two accepted Foundation pages consuming a branch's vocabulary. Six instances swept, two of them in the Swedish market copy. Mechanical in every case — each rule already named the property it turned on, so the borrowed term was carrying nothing. principles/, patterns/ and schemas/ deliberately untouched: branch documents using branch vocabulary |
| 87 | The backup hook and the branch-hygiene check cancel each other out | CC | 2026-08-25 · option (b) with the age condition. Snapshot branches now checked on whether they hold content that exists nowhere in main, not on being ahead of it — a tree comparison would have flagged every old snapshot forever, since main moves on. Grace of 2 days. Proven against five cases including the two that must stay silent; 216/216 green with a snapshot present. Hook unchanged |
| 85 | Rescue three records from colour-swedish-port before the branch goes |
CC | 2026-08-25 · the ΔE figures recomputed a second time from scratch and reproduced (76.87 / 14.55), now re-runnable at scripts/delta-e-check.py with sanity pairs and mutation verification; route 3 and the block-C confirmation recorded on card 42; plan-swedish-colour-port-session.md's "unverified" line corrected; card 86 opened for the deferral the branch promised and never tracked. Branch deleted. Measured on the way in: all three of the branch's mechanical fixes were already on main, and its page was three lines behind — the pre-v0.6.4 colour roles |
| 75 | emotional-modes.md promises a detailed digital model that nothing published carries |
CC | 2026-08-25 · closed in full, both halves — Torfinn took bucket 0 option (b) rather than folding the Swedish half into the balanced pass. Clause deleted from foundation/emotional-modes.md and its en-draft, and the false baseline/ pointer deleted with it. Opened as [T] on the assumption that the model needed a destination; decision 5 retired it instead, which made the card mechanical. The measurement it was opened on was itself wrong — see card 83 |
| 66 | Cut agent-baseline v0.6.3: the three mechanical closes from the second build feedback |
CC | 2026-08-13 · --button-min-height-pointer, the button text-zoom/11.7 fix, and the pre-auth token-access documentation, all closed without a new design decision; --border-subtle and the non-colour status treatment deliberately left out, pending cards 64/65 |
| 63 | Fix a build-breaking comment bug in aleris-tokens.css, cut v0.6.2 |
CC | 2026-08-12 · a stray */ inside a token-file comment closed it early — silent in browsers, fatal to lightningcss (Vite's default minifier); comment rewritten to name the real chart tokens instead of ones that were never implemented |
| 61 | BASELINE.md corrections from build feedback: confirm-button contradiction, stale 14px entry, target-size relaxation, density reframe |
CC | 2026-08-12 · confirm-button entry given a superseded note pointing to hard rule 5; 14px conformance row corrected after tokens.test.ts confirmed it now passes at the real 16px root; target size relaxed to 24×24 CSS px AA for dense instrumental desktop surfaces (Torfinn, OQ2); density switcher reframed from requirement to note-to-builders (Torfinn, OQ4) |
| 60 | Fix imagery.md regression and its mis-filed market-package copy |
CC | 2026-08-12 · root-caused via git log --follow to a June 2026 migration the 08-11 language sweep mistook for still-pending; canonical restored, mis-filed _markets/sv/foundation/imagery.md copy removed, _markets/sv/MANIFEST.md corrected |
| 59 | Package status tab — computed drift, not hand-written notes | CC | 2026-08-12 · verified against today's own example — agent-baseline v0.6 correctly shows foundation/colour.md behind, citing the actual "No dark mode" retirement commit |
| 56 | Regenerate agent-baseline to v0.6 and ship it to Richard |
CC | 2026-08-11 · read Richard's repo's wiring first (CLAUDE.md/AGENTS.md/aleris-standard.md), confirmed nothing there needed to change; 5 pages flipped to English, iconography.md newly included, all 31 files verified byte-for-byte, PR #1 updated |
| 52 | Ship agent-baseline v0.5 to aleris-vibe-coding |
CC | 2026-08-10 · Torfinn supplied an Atlassian API token; pushed to the existing open PR branch (add-brand-os) rather than a new PR, title and a comment updated to flag the change to Richard, all 30 files verified byte-for-byte |
| 51 | Retire klinfys-baseline and bhsv-baseline.zip, make agent-baseline the single standard package |
CC | 2026-08-10 · moved to _packages/_archive/, not deleted; consumer outreach left to Torfinn |
| 1 | Rewrite 2G – the print-absence note | T | 2026-07-29 · drafted by Claude, Torfinn to correct on sight |
| 2 | Role wording for the reinstated orange-400 and orange-200 | T | 2026-07-29 · drafted by Claude, Torfinn to correct on sight |
| 5 | §0 Safety – snapshot, branch, freeze | CC | 2026-07-30 · tag accepted-corpus-2026-07-30, recovery verified on 4161 files, branch colour-supersede |
| 4 | Patch the handoff (Patches 1–3) | CC | 2026-07-30 · Patch 4 left void, and its trailing scaffold correction void with it |
| 8 | §4 Token and rule ripple | CC | 2026-07-30 · 5 tokens added, 3 repointed, inverse variant specced, Anchor retired in the 3 source locations |
| 9 | §4b Pre-existing errors | CC | 2026-07-30 · items 1–3 done; item 4 landed with card 6. Two targets deferred to §6 under the freeze and package rule 6 |
| 10 | §4c Contrast fitness check | CC | 2026-07-30 · 42 assertions, 3 known AA failures and 6 out-of-set hexes documented as still-failing |
| 7 | §3 De-duplicate the colour-alone rule | CC | 2026-07-30 · 10 of 12 locations; the 2 Swedish rows deferred to the twin sync. Handoff §8 landed here too |
| 6 | §1–§2 English colour page | CC | 2026-07-30 · renumber applied, accepted prose carried verbatim, 2E placed, dash convention honoured |
| 11 | §6 Wire the loader | CC | 2026-07-30 · renders at /sv/identity/colour-en; the card's premises were both wrong, see below |
| 3 | Confirm – retire the variant, or recolour it | T | 2026-07-31 · option A: the variant is retired. Confirm becomes secondary plus a required check glyph, to simplify the button set. The green keeps its value and narrows to indicator use |
| 20 | Fix the fitness check's per-variant state blind spot | CC | 2026-07-31 · longest-match parser with a hard failure on unknown suffixes; 17 new assertions on synthetic names, so the fix is proven without the brief applied. Verified against the brief: all four active fills now measured, no phantom variants |
| 22 | The token parser is fooled by a comment | CC | 2026-08-01 · comments stripped before matching, plus a divergence guard that parses twice and fails if the maps differ. Both guards seen falling by name before being trusted |
| 21 | The boundary test measures rest fills only | CC | 2026-08-01 · every fill a variant paints is now measured against every surface it may sit on, outline and ghost included. Three known boundary failures landed failing. The page corrected itself in the same change: 15 of 30 becomes 17 |
| 24 | Carry the confirm retirement to its remaining locations | CC | 2026-08-01 · six locations plus the token annotation. BASELINE.md hard rule 5 is the rule of record; the rest reference it |
| 16 | Secondary hover – and whether hover is a fill at all | T | 2026-08-05 · merge hover into focus ring, the board's own recommendation. Hover draws the outer ring only; focus draws both |
| 17 | Button state set – adopt the brief, or amend it | T | 2026-08-05 · adopted as proposed: per-variant active fills, dual focus ring, no disabled state. Blanket focus rule in BASELINE.md § Accessibility amended |
| 19 | Rebuild the button state change from the design return | CC | 2026-08-05 · rebuilt from the return's reasoning, not its edits/. 131 tests passing, npm run build clean. Visual surface 17/30 failing → 6/30 (5 disabled-policy, 1 new: ghost/active, the same defect ghost/hover carried before the merge). Confirm's token values now alias secondary. Four disabled buttons in the live app surfaced as non-conformant, not fixed |
| 13 | §5 Regenerate all three packages and verify the mirrors | CC | 2026-08-05 · agent-baseline/ only, not all three. Regenerated to v0.3, all 27 manifest files verified byte-for-byte against canonical. klinfys-baseline/ and bhsv-baseline.zip predate the manifest rule and have no recorded file list – regenerating them would mean inventing scope, so left unregenerated with the reason recorded in _packages/README.md |
| 18 | Extend the test surface beyond buttons, as a conformance surface | CC | 2026-08-05 · baseline/conformance/index.html + tokens.test.ts Tests 7–13. 169 tests passing. Eight new failures surfaced, none fixed: input error text (4.28:1), disabled input boundary (1.28:1), goal-borderline (1.73:1), table row hover and selected (1.01:1, 1.13:1), all three chart pairs (1.29/2.76/2.18:1), the 14px floor token itself at 12.48px (root cause: the type scale's documented 18px root is never set), and 23 sub-14px declarations in one tool file. Full 7-claim accessibility audit in BASELINE.md § Accessibility |
| 27 | Commit the route manifest gate, and extend it to Baseline | CC | 2026-08-05 · two commits (5466f14, 461aebf). Found the gate's own "twenty" count was wrong: the real total is twenty-seven language mismatches, not twenty – all seven pattern-library files (then at baseline/patterns/, moved into patterns/ 2026-08-05, card 32) are Swedish, not just the three voice guides. 174 tests passing |
| 31 | Fix the 14px floor at its root cause (card 18's largest finding) | CC | 2026-08-05 · Torfinn decided both open questions: rewrite the token rem values (not set a global root override), and wire .prose to the tokens rather than leave it duplicating raw numbers. aleris-tokens.css's font-size scale corrected to hit its documented px targets at the real 16px root. Second, independent defect found while fixing it: the live app never loads aleris-tokens.css at all – app/globals.css has its own separate, hand-duplicated @theme inline token set (colours, shadows, fonts, no font-size). Added a matching --font-size-* set there too and verified live in a running dev server (getComputedStyle on real rendered markdown: h2 27px, h3 22px, p 18px, table header 14px – all exact). Cookbook §19 records the lesson: a build passing proves nothing about whether the app that ships to users ever reads the file that was fixed |
| 29 | Write the publishing rule and the transit rule into CLAUDE.md | T | 2026-08-05 · drafted for sign-off, approved, committed (53b6aeb). Fixed the two stale folder names and gave all eight layer-taxonomy directories a recorded publish state |
| 30 | Create workspace/, move the administrative material, take the eighteen routes out |
CC | 2026-08-05 · workspace/ and documentation/ created, tutorials/ deleted, how-to/→filters/. Eighteen routes out, plus seven more en-draft/ files the status-filtering fix caught that nobody had cross-checked – known language mismatches narrow from 17 to 5. Two file-pair collisions with _archive/ preserved under disambiguating names, not silently merged. Commits ff2a8c0, 7df2d26 |
| 26 | Reconcile the two accessibility homes | CC | 2026-08-05 · pulled without waiting on 25. Re-read the wait: it was about whether en-draft/ survives as a published tree, which affects the source location, not the destination – constitutional/accessibility-is-foundational.md was always the target regardless of how card 25's B2 half resolves. Moved the real content in from foundation/en-draft/constitutional/_accessibility-shared-draft.md via git mv, reformatted to the sibling constitutional convention, repointed eight live [[_accessibility-shared-draft]] references across foundation/en-draft/ and baseline/. _packages/ copies left untouched, regenerate on next package build. Commit 0d08aa8 (+2 follow-ups fixing a bad git add pathspec that silently committed the pre-rewrite content). 174 tests passing, npm run build clean |
| 32 | Phase B, sequencing step 4: reconcile patterns/ with baseline/patterns/ |
CC | 2026-08-05 · moved the 7-file live pattern library in via git mv. Routes are unchanged (/baseline/patterns/*) – added resolveBaselineSource() to lib/baseline-content.ts (baseline/-first, falls back to the layer-taxonomy folder), wired into getBaselinePageData, the /baseline/raw/[...slug] route, and the route-manifest test's own baseline manifest builder. First attempt put the resolver in lib/baseline-nav.ts and broke the build – that module is also imported by the client-side Sidebar, and fs/path can't ship in a client bundle. Verified against a running dev server, not just the build classification: all 7 moved routes return 200, /baseline/raw/patterns/hero.md serves the real moved file. 174 tests passing, npm run build clean |
| 35 | Triage the domain owner's interior guide; record what it decides | CC | 2026-08-06 · not absorbed as a page, following the Vårdförsäkringsdagen precedent. Reframed mid-session: the first pass judged it against _TEMPLATE.md as a candidate corpus page, which was the wrong test for a derived guide by the domain owner. Five owner positions recorded in workspace/decisions/decision-record-physical-interior-2026-08-06.md (wording carried verbatim, proposed not accepted – owner attribution needs a role, and two of the five touch F1). The palette boundary resolved from an existing rule rather than negotiated: composition is the domain's, membership is Foundation's. Two June 2026 research sources git mv'd out of the rescued archive into workspace/sources/, and the per-country accessibility floors extracted to workspace/sources/physical-accessibility-floors-2026-08-06.md – the guide's "gällande krav" had nothing to point at because the figures were on a backup branch until three days before it was written. Three specifics found unsourced and referred to card 36 |
| 33 | Write the three constitutional rule statements | T | 2026-08-06 · all three accepted, by three different routes – group-scope.md decided from scratch across two reasoning passes, language-policy.md and tokens-are-canonical.md extracted from decisions already live elsewhere. Card 34 split off for the Baseline voice-guide translations this surfaced |
| 38 | Teal's position in Norwegian physical environments | T | 2026-08-06 · touching is narrow (only the renovated surface), signage inverts that on purpose – a full signage programme triggers petrol, a spontaneous single-sign swap reuses the existing design to avoid an unplanned mixed style. Recorded in the interior decision record §4 and pointed to from foundation/colour.md's Legacy-färger section |
| 41 | Close the four-branch tail | T | 2026-08-07 · all four gone, locally and on origin; feat/font-hosting's one unique file retrieved first; IN_FLIGHT empty, suite green. Board row moved late – the commits landed 2026-08-07 morning, this table wasn't updated until card 43 |
| 43 | Regenerate agent-baseline to v0.4 |
CC | 2026-08-07 · v0.3 had gone stale within hours of being cut on 2026-08-05: shipped the pre-fix 14px accessibility-floor token values (card 18/19 fixed the same day) and four dead links to a file card 26 deleted the same day. Two more dead links in aleris-tokens.css comments, missed by card 26's own sweep, found and fixed in canonical while cutting this version – not patched in the package only. All 27 files re-verified byte-for-byte; 179 tests passing (was 174), npm run build clean. Also landed two things that had sat uncommitted since being marked Done on this board: card 33's three constitutional rule statements, and the physical-domain colour.md notes (cards 35, 38) |
| 14 | Ship agent-baseline v0.4 to Richard | CC | 2026-08-07 · Torfinn cloned via an Atlassian API token, once the app-password auth in the original brief was confirmed dead rather than just interactive. Placed at docs/agent-baseline/ in aleris-vibe-coding (30 files verbatim, .DS_Store excluded), on branch add-brand-os. CLAUDE.md/AGENTS.md wired with the always-on/on-demand pointer; the standing "Designriktlinjer tillhandahålls av Torfinn" TODO in the repo's own docs/aleris-standard.md filled with a reference to the package instead of left open. PR opened via the Bitbucket API using the same token – the git-clone username (x-bitbucket-api-token-auth) doesn't authenticate against api.bitbucket.org, which wants the Atlassian account email instead; found by trying, not by documentation. pull-requests/1, addressed to Richard, carrying the font-CORS flag and the wiring-wording note as open confirmations, not blockers. Default branch untouched – merge is Richard's. Brief archived, _packages/README.md updated with aleris-vibe-coding as a downstream consumer. |
| 44 | Card A — retire --radius-l, sweep the reference radius model, verify |
CC | 2026-08-10 · Sweep committed 2026-08-09 (f60db76, branch radius-sweep-and-check) but never run — made from an arm64 Linux shell where vitest's native binding failed. Verified today on this machine: 163 tests pass in tokens.test.ts, full suite 187 passing (the one failure, branch-hygiene, correctly flags this branch itself as unmerged and is expected until it lands), npm run build clean. The drift check proven, not just trusted: temporarily restored the pre-sweep --card-radius row (radius-l (12px)), confirmed the new reference-drift assertion fails with the exact contradiction named ("--card-radius: reference says 12px (\"radius-l (12px)\"), tokens say 4px"), then reverted. KNOWN_NESTING_INVERSIONS['image-in-card'] still asserts the 8-inside-4 inversion, unfixed, pending card 45. Prototype built for card 45 at baseline/conformance/radius/index.html — four candidate corner models over seven component cases, unpublished (not in lib/route-manifest.json, confirmed no new build route). Cards 45–47 opened from this brief's findings. |
| 45 | Decide the corner-radius default (D3/D4): base radius and direction of derivation | T | 2026-08-10 · Cards only, for now. Torfinn picked Derived inward from 16 off the prototype, with one refinement: a flush top-of-card image keeps a square bottom edge in every model — only the top corners share a corner region with the card and take its radius (concentric, zero gap); the bottom is an internal division. Landed in canonical: --radius-l reinstated at 16px (not its old, retired 12px), --card-radius repointed to it. BASELINE.md, the governance doc (via a Superseded blockquote, not a silent rewrite, per its own convention) and the reference file all reconciled — the reference's last_verified was bumped too, having sat at 2026-03-21 through every prior correction despite card A's own scope calling for it. Corrected within the same day, on Torfinn's question: the first pass added --card-media-radius-top/-bottom to hand-match the image's corners to the card's. Unnecessary — a card that clips its own contents (--card-media-overflow: hidden) renders a plain, radius-less flush image with the identical result for free, since the clip is the card's shape and there is nothing left to keep in sync. The token pair came back out everywhere it had gone in (tokens, JSON, BASELINE.md, governance, reference, prototype), replaced by the one --card-media-overflow token. tokens.test.ts Test 15: KNOWN_NESTING_INVERSIONS['image-in-card'] is gone, replaced by an assertion that --card-media-overflow is hidden and a retirement guard on the reverted token pair. 190 tests total (was 187), full suite green apart from the expected branch-hygiene flag, npm run build clean. Explicitly scoped to cards — panels, modals and tables were never measured live and stay --radius-s; that gap is still open, not decided by omission. The prototype carries a "Decided, card 45" badge and points at what actually landed. |
| 47 | Reconcile Baseline's white-page hard rule against all three live sites shipping sand-inside/white-page | T | 2026-08-10 · Decided: option A — the live sites are non-conformant, canonical is unchanged. Unlike card 45's radius default, hard rules 1/2 (page is sand, never white; cards are white, never sand) trace to reasoned Foundation content (foundation/colour.md: sand exists specifically so the page doesn't read clinical — "inte kliniskt vitt"), not an unexamined Baseline default, so the production measurement doesn't carry the same weight it did for radius. A second tell pointed the same way: the same three sites also ship a primary button colour (#F58C61 coral) that matches neither canonical orange-600 nor alerisgroup.com's own petrol — three divergent facts on one property, which reads as drift off an older or different system rather than a considered "white works better for patients" call. No token, hard rule, or reference file changes. Not a Brand OS package or file this touches — aleris.se/.no/.dk aren't tracked as a downstream consumer anywhere in _packages/README.md, so routing this to whoever owns those sites is Torfinn's, not a board card here. |
| 46 | Decide what the reference layer is for | T | 2026-08-10 · Decided by executing: split by kind (option 3), per card 48. |
| 48 | Execute Phase B step 3 — tokens and design data to data-products/, reference file split to schemas/+principles/ |
CC | 2026-08-10 · baseline/tokens/{aleris-tokens.css, aleris-fonts.css, baseline-tokens.json, generate-tokens-json.js, tokens.test.ts} → data-products/tokens/. baseline/iconography/{allowlist.json, README.md} → data-products/iconography/allowlist.json and schemas/icon-allowlist-entry.md (the four curation HTML tools stay in baseline/iconography/ per the migration map's own "TOOLING → renderer islands, not corpus" disposition — only index.html's one fetch() and two footer paths needed fixing). baseline/reference/aleris-design-tokens.md + baseline/reference/baseline-token-architecture.md (the latter's own MERGE target, aleris-token-governance-frameworks.md, was already dead — archived and superseded before this session, so skipped) split into schemas/design-token.md (shape, architecture, framework/Figma integration) and principles/design-tokens.md (17 decision records, reframed as the questions they answer per that layer's own documented convention in principles/_index.md — the first real file to use it). Both archived originals kept as historical record, marked superseded, not deleted. Published routes preserved, not just filesystem-moved: lib/baseline-content.ts's resolveBaselineSource gained an explicit MOVED_PATHS map (patterns/'s 2026-08-05 move got this for free because dropping the baseline/ prefix left the same relative shape; tokens/reference don't share that shape, so this move needed the explicit version). vitest.config.ts gained data-products/**/*.test.ts in its include list — the same silent-collection failure mode baseline/**/*.test.ts was added to catch on 2026-07-30, now with a fresh location to reintroduce it in. Test 14 (tokens.test.ts, "the token reference does not contradict the token file") retired by construction, not by choice: there is no restatement left anywhere in the corpus to check for drift against, which is a stronger guarantee than the check itself ever was. One real bug caught by reading, not by the 200-OK check: the baseline landing page's "Learn the system / understand the principles" card linked to /baseline/reference/aleris-design-tokens, which after the move served the schema (the shape), not the principles (the why) — the mechanical route check passed while the promise the card made to a reader was wrong. Repointed to /baseline/principles/design-tokens. Verified against a running server on an alternate port (this session's own dev server was already in use elsewhere): every moved and new route returns 200 with the correct content, not a stale cache — confirmed by grepping rendered body text, not just status codes. 187 tests, full suite green apart from the expected branch-hygiene flag, npm run build clean. agent-baseline is not regenerated — this is a second structural change since v0.4 (radius plus this move), on top of the one card 45 already left it behind on. |
| 49 | Regenerate agent-baseline to v0.5 |
CC | 2026-08-10 · Narrow fix, on Torfinn's explicit call — a fuller Phase B step 6 plan (card 50) was scoped and then paused as too big for a single pass; this is what "just cut v0.5" needed instead. Closes both gaps card 48 opened: the radius default (card 45) and the token/reference-file move (card 48). BASELINE.md's own File Reference table was still pointing at tokens/aleris-tokens.css under the old baseline/ prefix and at reference/aleris-design-tokens.md, which no longer exists in that form — fixed in canonical first, not patched in the package copy only. reference/aleris-design-tokens.md dropped from the package rather than replaced with either half of its split; whether schemas/design-token.md belongs in the next cut stays an open question (_packages/README.md), deliberately not resolved here so this cut could stay narrow. All 27 files re-copied and verified byte-for-byte. Full suite green apart from the expected branch-hygiene flag, npm run build clean. Not yet placed in aleris-vibe-coding — a separate hand-off from cutting the package itself. |
| 68 | The vendored-app adapter, written and installed in both repos |
CC | 2026-08-19 |
| 69 | Correct the font licence claim, cut v0.6.5, close the PDF-embedding question | CC | 2026-08-19 |
| 71 | Rework the seed adapter to Richard's structure, install it, ship the wiring test | CC | 2026-08-20 |
| 67 | Split packaging into content core and adapter, and write the first adapter | CC | 2026-08-20 · The split, consumer-profile.md and adapters/vibe-coding-seed are on main; the adapter is installed at 7156dda on add-brand-os with PR #1 updated. Sat in Blocked on a credentials claim that was never true — Torfinn has had write access throughout. Card 71 carries the v0.2 rework that Richard's review forced. |
| 74 | Cut agent-baseline v0.7 — the logo, with its rules ratified |
CC | 2026-08-21 |
Amendments from the 2026-07-30 run
Corrections to cards, found by executing them. Recorded here because the board is the list of record.
- Card 7 lost two of its twelve locations to the freeze. Card 5 freezes
foundation/colour.md, and card 7 listed:149and:239as dedup targets. Beyond the freeze, repointing:149would aim an accepted page at a constitutional node that does not publish yet. Both rows moved to the Swedish twin-sync carry list in handoff §6, along with the sRGB fix. Card 7's done-when is amended to rows 3–12. - Card 8's "five places" for the hover Anchor listed six, and three of them are package mirrors that
_packages/README.mdrule 6 forbids editing in place. Source count is three; the mirrors are card 13's verification list. - Card 9 item 2 was already covered by card 8's consistency sweep, which also found a third reference-doc drift the card did not list: the doc claimed the primary button radius was
radius-full (pill), contradicting both the token file and the no-full-pill hard rule. - Card 10's "Tests 2 and 3 pass" was wrong. Test 3 finds six real out-of-set hexes. They land documented, the same way Test 1's failures do. Also:
vitest.config.tsscoped collection toapp/**, so a test underbaseline/would have collected nothing and reported green. - Card 11's premises were both false.
foundation/en-draft/is a subdirectory offoundation/and was never excluded, so all 17 files there have been publishing all along at folder-path URLs. What was missing was an intentional route, not a scan directory. - Handoff §8 and authoring 2E had no card. §8 landed with card 7, 2E with card 6. Worth a check for other unassigned handoff sections before the next run.
Two of these became durable anti-patterns in workspace/status/cookbook.md: §14 a card built on a misreading of the code inherits the misreading (card 11), and §15 tests that never run report green (the vitest.config.ts scoping). The rest stay here as amendments to specific cards.
Amendment from the 2026-08-01 return
- A bundle of complete replacement files carries a silent revert. The design return was built from the branch at 16:22 on 2026-07-31; card 20 landed at 16:25. Copying
edits/tokens.test.tsover the current file would have deleted card 20 with no conflict, no warning and a green-looking suite, because the replacement is internally consistent – it is only inconsistent with what happened after its base. The bundle's README says "if those files have moved since, diff rather than overwrite", which is the right instruction and is easy to skip when the same note also says the files are final. Diff every incoming replacement file against its target before reading it as a proposal, not only before applying it: the diff is what tells you which branch state the author was reasoning about. Cookbook §16.
Decisions waiting on Torfinn, beyond cards 16 and 17
Surfaced by the run, none resolved.
- There is no English locale.
app/[locale]/layout.tsx:6isSUPPORTED_LOCALES = ['sv'], and the content map is one slug to one file with no locale awareness. The English source page therefore renders under/sv/withlang="sv"on an English page – an accessibility defect on the page that argues for accessibility.identity/colour-enis a placeholder until this is decided. Real options: per-page language from frontmatter, or a genuineenlocale. Still open, now tracked as card 25's B2 half, informed by card 28 – not resolved, this item is subsumed rather than answered. FourRESOLVED, in two unrelated passes. The first three moved to_-prefixed working files publish as pages –_translation-notes,_examples/voice-examples,_principles-restated,_accessibility-shared-draft.workspace/authoring/as part of card 30 (2026-08-05) – they were three of the eighteen routes it took out, published accidentally through hand-written nav entries._accessibility-shared-draftmoved toconstitutional/accessibility-is-foundational.mdthe same day, card 26 – its own Open #3 (whether the filename still fit) is what that move answered. No open items remain from this one.ANSWERED 2026-09-04, repointed to--text-accentis orange-500 as a text colour. Roughly 1.6:1 on sand-100. Out of scope for the supersede, not covered by the fitness check, which tests buttons.--color-petrol-400on Foundation's precedence (card 115's amendment). Two notes on how this item aged. Its ratio was wrong: orange-500 on sand-100 measures 2.03:1, not 1.6:1 — a number written once in prose and never recomputed. And the item was right about everything else and still sat here five weeks, because nothing depended on anyone re-reading this list; the join it describes as missing now exists as a test, which is the durable difference.- Two constitutional pages still carry
Status: decided, the vocabulary retired on 2026-06-16. Same class as theMANIFEST.mdquestion card 13 surfaces; belongs to the corpus-wide pass.
Cards
1 · [T] Rewrite 2G – the print-absence note
Files: workspace/authoring/colour-authoring-2026-07-28.md ← edit here · foundation/en-draft/colour.md ← lands here · foundation/colour.md ← Swedish twin
Block 2G in workspace/authoring/colour-authoring-2026-07-28.md. Currently: "Some colour steps are digital only and lack physical colour descriptions."
It keeps its home under the secondary-orange Pantone/CMYK table, but its subject narrows and splits in two. After the retirement reversal the only steps with no print counterpart are orange-600 and orange-700, because interaction is a screen event. Separately, orange-300 and orange-200 have no Pantone because no corresponding Pantone colour exists – a fact to state, not a dash to leave, and marked differently from the legacy teal, which is genuinely unmatched and awaiting a press check.
Register model on the page, line 147: "No dark mode. Aleris uses light interfaces. Care products are built on trust conventions that belong in light environments…" – bold statement, one sentence of reason. Same shape as 2D and 2F.
Constraint: a reader here is looking for a value to hand a printer, so nothing may read as forgotten or as coming later. Petrol and sand keep full secondary tables, so orange's partial one needs accounting for.
Two surfaces, English canonical: foundation/en-draft/colour.md, then the Swedish twin.
CLOSED 2026-07-29. Drafted by Claude at Torfinn's instruction and landed in the authoring doc. Reads: "Not every step has print values. Interactive orange – orange-600 and orange-700 – exists to hold contrast on screen, so it has no print counterpart by design. Where a Pantone cell is empty, no Pantone match exists at that tint. In print, the brand orange is orange-500." Correct it on sight if it reads wrong on the page; no approval loop. One dependency noted in the authoring doc: the line is silent on CMYK because orange-300 still lacks a CMYK build (card 9).
2 · [T] Role wording for the reinstated orange-400 and orange-200
Files: workspace/authoring/colour-supersede-scaffold.md ← ramp table, two SLOT markers
Two [SLOT – Torfinn] markers in the ramp table in workspace/authoring/colour-supersede-scaffold.md.
The Role column now carries two distinctions, not one: brand versus interactive, and print-and-digital versus digital-only. Existing rows for register: orange-500 is "Brand – accent, decorative, print. The brand orange"; orange-300 is "Brand – light accent backgrounds. Print and digital".
Both reinstated steps are brand tints used in both media. orange-400 additionally needs its non-interactive status legible, because it is the exact hex the retired hover pointed at.
CLOSED 2026-07-29. Drafted by Claude and landed in the scaffold's ramp table. orange-400: "Brand – accent tint, backgrounds and surfaces. Print and digital. Never an interactive state." orange-200: "Brand – soft tint. Print and digital." SLOT markers removed. Correct on sight.
3 · [T] Confirm – retire the variant, or recolour it
Files: workspace/evaluations/button-state-brief-evaluation-2026-07-31.md ← read this first · baseline/tokens/aleris-tokens.css ← current values · workspace/status/changelog.md ← record the decision
DECIDED 2026-07-31 (Torfinn): option A. The confirm variant is retired, to simplify the button set. Confirm becomes the secondary cluster plus a required check glyph; confirm-500 keeps its value and narrows to indicator use. Card 19 applies it, including the five locations the brief does not patch. One consequence to carry forward: secondary now holds two jobs, "go here" and "I'm done", so whatever card 16 decides for secondary's hover applies to confirm as well. That raises the cost of option B on card 16, which would fold secondary into the outline variant and remove a hierarchy level that now has more traffic through it.
Reframed 2026-07-31, then decided the same day. The card asked which two values to give the confirm button. The incoming brief answered a different and better question: whether the variant should exist as a colour story at all.
Option A – retire it, as the brief proposes. CHOSEN. Confirm becomes the secondary cluster plus a required check glyph. "I'm done" is carried by the glyph and the verb rather than by a green fill, which is what the status-message rule already demands of success states. confirm-500 keeps its value and narrows to indicator use (--status-confirm). Numbers: white on petrol-500 10.27:1 at rest, petrol-600 12.14:1 on hover, petrol-700 13.88:1 active – all comfortably clear.
Option B – recolour it, the original recommendation on this card: keep the green and add a darker interactive fill below confirm-500 (rest #4B7F69 4.63:1, hover #406D5A 5.91:1). Requires two new hex values whose only job is holding contrast for one variant, and a check that they stay distinct from --color-goal-achieved #2E8540.
What decides it. Option A costs the green a job and buys back two palette values and one whole state set. Option B keeps the green signal a clinician may already read as "done" in Hälsodeklarationer, at the price of two values that exist only to pass a contrast check.
Trap in option A: confirm's retirement is a rule of record in five more places than the brief patches – governance:195 and :197, reference/aleris-design-tokens.md:141 and :148, patterns/button-label.md, plus hard rule 5 in BASELINE.md, which the brief ships while still asserting confirm-500 is interactive. That is card 19's job, but it is a cost of option A, not a free consequence.
CLOSED 2026-07-31. Option A chosen. Recorded in workspace/status/changelog.md and carried into workspace/briefs/handoff-button-states-claude-design.md as settled context, so the design exploration factors in that secondary carries two jobs.
19 · [CC] Rebuild the button state change from the design return
Files: workspace/archive/button-state-return-2026-08-01/ ← the return, unpacked (moved from workspace/incoming/ since) · workspace/evaluations/button-state-return-evaluation-2026-08-01.md ← read this first · workspace/evaluations/button-state-brief-evaluation-2026-07-31.md ← the earlier pass · baseline/tokens/aleris-tokens.css · baseline/BASELINE.md · baseline/governance/aleris-design-governance.md · baseline/reference/aleris-design-tokens.md · patterns/button-label.md ← moved from baseline/patterns/ 2026-08-05, card 32 · baseline/tokens/tokens.test.ts · baseline/buttons/index.html
Retitled 2026-08-01. The instruction changes from "apply" to "diff and rebuild", and the corrections go from four to seven.
The return ships four complete replacement files under edits/ and its README says to copy them over. Do not. They are a snapshot of the return's own turn 1, produced from the branch at 16:22 on 2026-07-31, and both the reasoning and the branch have moved past them:
edits/tokens.test.tsreverts card 20. Commite719b41landed at 16:25, three minutes after the bundle's base. Overwriting deletessplitButtonToken, the seventeen synthetic assertions and the hard failure on an unknown suffix. It is also unnecessary – the card-20 file already classifies every token the new CSS adds and measures all four active fills, giving 62 passed with only the threeKNOWN_TEXT_FAILURESentries breaking. Port Tests 4, 5 and 6 onto the current file. They are real additions; the file they arrived in is not.- The edits assert three things the return's own turn 2 reopens – petrol-600 as the secondary hover, the confirm-aliases-secondary model, and the required check glyph. Two of the three are asserted in the new Test 6, so applying them writes open questions into CI as invariants.
- The bundle's own test fails on the bundle's own tokens, 2 of 57. Cause and fix are card 22.
Take the values from edits/ once cards 16, 17 and 23 are decided; take the structure from the current files.
The seven corrections. The first four are unchanged from 2026-07-31.
- outline and ghost need an active state, or a stated reason for having none. The brief retires the generic
--state-activeand adds per-variant active tokens to four variants only. Those two are left with neither a token nor a fallback – before the brief they at least resolved to sand-500. A regression inside an improvement. Invert hard rule 5 inDone 2026-08-01, card 24.BASELINE.md.Carry the confirm retirement to the four locations the brief does not touch.Done 2026-08-01, card 24 – six locations plus the token annotation. What remains here is only the--button-confirm-*values, which depend on cards 16 and 23. Do not redo the prose; check it instead, and note that the return'sedits/BASELINE.mdstill ships the old hard rule 5, so a careless diff will reintroduce it.- Remove the three
KNOWN_TEXT_FAILURESentries intokens.test.ts. Test-applying the brief broke all three, because confirm at rest, confirm on hover and secondary on hover now pass – the allowlist doing exactly its job. Deleting the entries in the same change is what turns the suite green. - Amend the blanket focus rule in
BASELINE.md§ Accessibility – "focus-visiblewith petrol ring for keyboard navigation". The return amends the identical sentence in § Forms and leaves this one standing. Card 17 names it as the rule that cannot survive the dual ring unamended. - Port Tests 4, 5 and 6 rather than replacing the test file – see above. Card 22 landed 2026-08-01, so
--state-focus-ringis visible to Test 5 now; the port itself is still to do. Note that Test 2 has widened since the return was written, so its Test 6 arrives into a file that already measures more than it expects. - Rewrite the retirement comments so a comment cannot look like a declaration. Card 22 fixes the parser; this is the other half.
/* RETIRED: --state-active: var(--color-sand-500). */is the exact shape that breaks it, and the return uses it twice. - Regenerate
baseline/tokens/baseline-tokens.json–node baseline/tokens/generate-tokens-json.js. The JSON is generated from the CSS and its own header says "CSS is source of truth. Regenerate if JSON and CSS disagree", but nothing enforces it: the fitness check reads the CSS only, so a forgotten regeneration drifts silently and ships to_packages/in card 13. In sync as of 2026-08-01, verified by round-tripping – the only diff is the date stamp. Worth one assertion of its own: every--button-*and--color-*value in the JSON resolves to the same hex as the CSS. Add it with card 21 or 22, whichever lands second.
Also handle, and do not paper over: the four disabled buttons in this repo – app/tools/icon-picker/qlik/CreateFlow.tsx:100, components/comments/CommentPanel.tsx:125, components/comments/SelectionPopover.tsx:120, components/auth/OnboardingForm.tsx:51. "Buttons do not disable" makes all four non-conformant on arrival. Three are disabled={!text.trim()}, the empty-form case, where the only honest explanation restates what the user can already see. Surface whether that case is a named exception rather than deciding it here.
Card 20 lands first, or the brief's own new active tokens go untested.
Done when: the brief is applied with all four corrections, npm test passes with no allowlisted failures, baseline/buttons/index.html shows no failing cell, and the disabled-button question is surfaced rather than silently resolved.
CLOSED 2026-08-05. Rebuilt from the return's reasoning per the evaluation's recommendation, not from its edits/ – those reverted card 20 and failed on the bundle's own tokens. Applying board decisions 16 (hover merges into the focus ring) and 17 (per-variant active, dual ring, no disabled state, adopted as proposed):
- Every
--button-*-hover-bgtoken is gone, and--state-hover,--state-activewith it.--color-petrol-600was never added – the design return proposed it as a hover step, and hover no longer has one. - Every filled variant declares its own active fill. Primary and primary-inverse reuse what used to be their hover value (orange-700, petrol-100); secondary and confirm take the one new primitive this card adds, petrol-700 (13.88:1); outline and ghost take petrol-100.
- Confirm's token values now alias secondary (
bg,text,active-bg) plus--button-confirm-icon: check, required – the half of card 3's decision that card 24 deliberately left open pending this card. baseline/tokens/tokens.test.ts:KNOWN_TEXT_FAILURESis now empty (all three entries fixed by construction).KNOWN_BOUNDARY_FAILURESnarrowed to one:ghost/active, the same defectghost/hovercarried before the merge retired that state – ghost has no border, so its press fill (petrol-100, reused from outline) is the only thing marking the control at 1.13:1. Not fixed here: a border would close it but changes what distinguishes ghost from outline, a design call. Three new describe blocks port the return's Tests 4–6 onto this file's structure, widened where the merge changed scope (the outer-ring check now covers all six variants, not only the filled four, since hover's ring applies everywhere).baseline/buttons/index.htmlrebuilt: hover renders as the rest fill plus the outer ring only, focus as both rings, active as the per-variant fill plus a neutral press shade. 17 of 30 cells failing → 6: five are the disabled cell (now policy, not a gap – see below) and the oneghost/activefailure above.BASELINE.md§ Buttons rewritten with the state model; § Accessibility's blanket rule amended – "petrol ring" was one rule for six variants on two surfaces, which cannot hold, now split between a single ring for inputs and the dual ring for buttons.- Reference docs corrected:
aleris-design-tokens.md's token tables andaleris-baseline-animation.md's button code sample, both of which named tokens this card retired.
Verified: 131 tests passing (was 128), npm run build clean, no app/ code referenced any of the retired tokens.
Disabled-button question surfaced, not resolved, per the done-when. Four disabled buttons exist in the live app – app/tools/icon-picker/qlik/CreateFlow.tsx:100, components/comments/CommentPanel.tsx:125, components/comments/SelectionPopover.tsx:120, components/auth/OnboardingForm.tsx:51 – and are now non-conformant under "buttons do not disable." Three are disabled={!text.trim()}, the empty-form case, where explain-on-click would restate what the user can already see. Whether that is a named exception is Torfinn's call.
The literal done-when ("no failing cell") is not quite met, on purpose. Five of the six remaining failing cells are the disabled state, which card 17 made deliberate policy rather than a gap – the page still renders the old generic disabled tokens so there is something to show, but nothing asserts them. The sixth, ghost/active, is a genuine documented failure carried forward from a defect this card inherited rather than caused. Closing either further would mean either inventing a disabled treatment nobody decided or a ghost border nobody decided – both out of scope for a token rebuild.
20 · [CC] Fix the fitness check's per-variant state blind spot
Files: baseline/tokens/tokens.test.ts ← the variant-discovery regex, ~line 88
Pullable now. A defect in the test regardless of what happens to the brief, and it must be fixed before card 19 or the state the brief adds is the one state nothing verifies.
Variant discovery uses /^--button-(.+?)-(?:hover-bg|bg|text)$/. Against --button-primary-active-bg that yields a variant named primary-active, which has no -text token and is then skipped silently by if (!text) continue. Four phantom variants appear – primary-active, primary-inverse-active, secondary-active, confirm-active – and four real active fills go unchecked.
The only thing that caught this was the coverage assertion at the end of Test 2, which failed with the phantom list. Without that assertion the suite would have reported green over an untested state. Worth noting in the fix comment: the assertion earned its place.
-active-bg has to be recognised as a state, alongside bg, text and hover-bg, not as part of the variant name. Check the same trap for any other state suffix a future brief might add.
CLOSED 2026-07-31. Fixed as a named pure function, splitButtonToken, doing longest-suffix match against a declared list rather than a regex alternation – bg will always eat the tail of active-bg otherwise. Suffixes are classified as state-bearing or layout, non-variant tokens are listed explicitly, and an unknown suffix now fails the suite instead of quietly becoming part of a variant name.
Seventeen new assertions run against synthetic token names, not just today's file, because the tokens that broke this do not exist on this branch yet – a fix verified only against current data would be a fix nobody had run. Cases include the regression itself, the two-word-variant-plus-two-word-suffix shape (--button-primary-inverse-active-bg), focus-ring-inner beating focus-ring, and a deliberately unknown suffix that must return null.
Verified against the brief by test-applying it: all four active fills are now measured and pass – primary 5.92:1, primary-inverse 7.73:1, secondary and confirm 13.88:1 – no phantom variants, coverage assertion green. The only remaining failures are the three known-failure entries, which is card 19's job.
One limit recorded in the file rather than fixed: the suite measures states that exist and cannot flag a state that is absent. outline and ghost have no active fill, and that produces no assertion at all rather than a failing one. Absence is visible on baseline/buttons/index.html; whether a variant may have no active state is a design question, so it is not asserted. Explicitly not closed with a fallback – a generic fallback for a per-variant state is what hid the original failure.
21 · [CC] The boundary test measures rest fills only
Files: baseline/tokens/tokens.test.ts ← Test 2, SURFACE_RULES and the loop under it · baseline/buttons/index.html ← the same gap, rendered
Pullable now. Card 20's defect in a second place, found the same way – by running the design return rather than reading it. Test 1 measures text against every state's fill. Test 2 measures figure-ground against --button-*-bg only. So a fill that dissolves into the page passes CI as long as its label does not.
Two live failures, computable all along, asserted by nothing:
| Fill | vs sand-100 | vs sand-50 | vs white |
|---|---|---|---|
| secondary hover, petrol-300 | 2.19 | 2.42 | 2.56 |
| outline / ghost hover, petrol-100 | 1.13 | 1.25 | 1.33 |
The first is the sixteenth button failure; the matrix counted secondary's hover once, as a text failure, and it is two. The second is the defect that killed card 16's option A, already sitting in the token file on a variant with no instances – outline is rescued by its border, ghost is not.
SURFACE_RULES has no entry for outline or ghost at all: they are transparent at rest, so the loop skips them, and their hover fill is therefore on no list anywhere. A transparent variant needs a rule about the fills it paints on states, not an exemption.
This has to land before card 19, or the return's new fills arrive unmeasured on the axis the return itself proved matters. Land it failing on the two values above, the way card 10 landed – the fix is card 19's, and a check that goes green on arrival proves nothing.
Done when: every fill a variant can paint is measured against every surface it is allowed to sit on, outline and ghost are covered rather than skipped, and the two known failures are documented as still failing.
CLOSED 2026-08-01. The rule as landed: if a state paints a fill, either that fill or the variant's border must reach 3:1 against the surface. That is data-driven rather than a per-variant exemption – outline passes its 1.13:1 hover tint because --button-outline-border is petrol-500 at 8.75:1, and ghost fails the identical tint because it has no border token at all. A transparent state with no fill is not asserted; a text-only affordance has no boundary to measure and its label is Test 1's business.
Three known boundary failures, not two. confirm/hover #7ba492 is 2.36:1 against sand-100 – I had counted secondary and ghost and missed it, because it was already on the text allowlist and I read that as it being counted. All three land asserted-to-still-fail, and each was seen falling in both directions before being trusted: a live assertion catches a broken boundary, and the known-failure entries break when the value is fixed, so the allowlist cannot rot.
The page was wrong in the same place, and correcting it is the more interesting half. baseline/buttons/index.html applied the boundary floor to every variant except the transparent ones, reasoning that outline and ghost are identified by border and label rather than fill. True of outline, false of ghost. Two cells that read green now read red, and the page count goes from 15 of 30 to 17 of 30. What caught it was widening the test and finding it disagreed with the page – neither control catches the other automatically, which is worth remembering before card 18 builds more surfaces.
One divergence recorded rather than closed: the page counts ghost's active cell as failing too, and the test does not see it, because --button-ghost-active-bg does not exist and the suite measures declared tokens while the page renders what the button actually does, fallbacks included. Neither is wrong. Card 19 closes it by declaring the state. Noted in the test file header so the differing totals do not read as a fault later.
Not asserted, deliberately: outline on a petrol ground. The design return says outline needs two forms and the second has no tokens, so asserting now would either fail on an absence or invent the value. Card 19.
22 · [CC] The token parser is fooled by a comment
Files: baseline/tokens/tokens.test.ts ← the declaration regex, ~line 65
Pullable now, and it must precede card 19's Test 5.
(--[a-z0-9-]+)\s*:\s*([^;]+); matches anywhere in the file, comments included. A comment of the shape --name: value with no semicolon runs to the next semicolon in the file, which can be twenty lines away. Proven against the design return's token file, where one comment produces two failures:
/* RETIRED 2026-07-31: --state-active: var(--color-sand-500).registers--state-activeas declared, so the guard asserting it stays retired fails on the comment recording the retirement.- The same match swallows the real
--state-focus-ringdeclaration twenty lines later, so it never registers and "both rings are declared" fails.
The second is the dangerous half and the third appearance of this shape on this branch: a token that vanishes from the map produces no assertion rather than a failing one. Cookbook §15, and card 20's blind spot in a different mechanism.
Strip comments before matching. Then add the guard that would have caught it: parse twice, once raw and once with comments stripped, and fail if the two maps differ – a name in one and not the other is either a phantom or a swallow. Against today's file both are empty, so it lands green and stays a tripwire.
Done when: comments cannot declare or erase a token, the divergence guard is in place, and the return's token file parses to the same map with and without its comments.
CLOSED 2026-08-01. Comments are stripped before matching, and the file is parsed twice – once raw, once stripped – with the suite failing if the two maps differ. Two assertions: no comment declares a token that is not there, and no comment swallows a real one. Both were seen falling by name before being trusted, which needed two separate injections rather than one: the swallow case fired immediately, but the phantom case did not, because --state-active is still a real declaration on this branch, so a comment naming it produces no divergence. Only a comment naming a token that does not exist proves that half. A guard verified in one direction would have been a guard nobody had run – cookbook §15, again.
The other half is a writing rule rather than code, and it belongs to whoever next writes a retirement note: --state-active (was sand-500), never --state-active: var(--color-sand-500). Carried on card 19 as correction 7.
24 · [CC] Carry the confirm retirement to its remaining locations
Files: baseline/BASELINE.md ← hard rule 5, the rule of record · baseline/governance/aleris-design-governance.md:195, :197, :199 · baseline/reference/aleris-design-tokens.md:141, :148 · patterns/button-label.md:44 ← moved from baseline/patterns/ 2026-08-05, card 32 · baseline/tokens/aleris-tokens.css ← the @usage annotation
Split out of card 19 corrections 2 and 3 and pulled on 2026-08-01, because none of it waits on a decision: card 3 retired the confirm colour story on 2026-07-31, and green is not an interactive fill whichever way cards 16 and 23 go.
CLOSED 2026-08-01. BASELINE.md hard rule 5 inverted and made the rule of record – it had read "--color-confirm-500 is interactive" while the same file's § Buttons retired it, which is the orange-500 mistake card 8 repaired once. Everything else references it rather than restating it, the discipline card 7 applied to the colour-alone rule:
governance:195– four button colours becomes three, with the removed clause preserved in a superseded block rather than deleted. The tight scoping survives the retirement: "Klarmarkera", "Godkänn", "Signera", "Markera som klar" are still what a completion action is, they just no longer name a colour.governance:199– both greens are indicators now, told apart by what they indicate rather than by interactive versus not.reference:141and:148– the row narrowed, the Design Decision Record marked superseded on its interactive half only, with the value, provenance and F1 link left standing. A DDR whose value still holds should not be retired wholesale.patterns/button-label.md:44– Swedish, pointing at hard rule 5.
A seventh location the card did not list: aleris-tokens.css still carried @usage Interactive confirm actions on --color-confirm-500 – the token file, the source of truth, contradicting every document that references it. Exactly the failure being repaired, in the file the repair is measured from. Annotation corrected and baseline-tokens.json regenerated. The --button-confirm-* values were deliberately not touched: those depend on cards 16 and 23 and stay card 19's.
Left alone on purpose: workspace/archive/baseline-implementation-plan.md:73 carries the same claim inside a March 2026 JSON illustration, against the value #27ae60 that was superseded on 2026-06-12. It is a frozen historical artefact, not a live rule, and updating it would make a planning document into a maintenance burden.
23 · [T] The pairing rule – adopt the flow ladder, or not
Files: workspace/archive/button-state-return-2026-08-01/reference/Button States Brief.dc.html ← turn 2, open it in a browser (moved from workspace/incoming/ since) · workspace/evaluations/button-state-return-evaluation-2026-08-01.md · baseline/BASELINE.md ← hard rule 3 at :35, § Buttons
New 2026-08-01, from the design return's turn 2. Nobody asked for it and it is the largest idea in the bundle.
The rule. Button form follows flow position, not visual rank. Filled orange instigates – pressed from outside, it puts the user into a flow. Petrol outline continues inside a flow, or is a sibling choice outside one. Filled petrol closes – the flow ends here, confirmation or not. A link is a sibling that is navigation. One invariant: never filled orange beside filled petrol, because they are opposite ends of a sequence, so co-occurrence means one is mislabelled.
The measurement under it is new and holds. Against sand-100 the primary's boundary is 3.85:1 and the secondary's is 8.75:1 – the subordinate control is 2.3× crisper than the one it is subordinate to. And the two fills measure 2.27:1 against each other, below the 3:1 that would let a low-vision user separate two adjacent components. The pair fails twice and neither failure was on any list. The rule makes it structurally impossible rather than discouraged, which is a better answer than tuning either button. The invariant is also enforceable in CI, which is unusual for a composition rule.
What adopting it costs, and none of this is free:
- It amends hard rule 3.
BASELINE.md:35reads "One primary CTA per screen… Do not place two orange buttons in the same view." The return records Torfinn as saying per section, not per screen. Stacked sections mean the pair repeats down a page, so a bad pairing compounds rather than appearing once. - It unwinds the confirm alias. Card 3 folded confirm into secondary; the ladder separates them again – confirm would need its own cluster pointing at the same petrol values rather than aliasing. A token change, not a reversal of card 3.
- It reopens the check glyph. Filled petrol now means "closes a flow", so Avsluta and Radera utkast take the form while confirming nothing, and a required check reads wrong on them. Either the glyph belongs to the sign-off subset rather than to the form, or it goes. (
checkis already in the F11 allowlist, so nothing turns on icon availability.) - It leaves one slot empty. Inside a flow, outline is taken by Fortsätt, so Tillbaka and Avbryt have no form. Two candidates: a link, which is honest for Tillbaka and wrong for Avbryt in a modal, since Avbryt is an action; or reviving ghost, whose first real job this would be – at which point ghost's press state stops being theoretical and card 17's gap (a) reopens on a variant with no instances.
- Two edge cases decide whether it survives contact. "Bekräfta och boka ny tid" ends one flow and opens another. A one-step action instigates and closes in the same press, and the invariant forbids drawing both – the return proposes choosing the form from the user's position before the press, which makes it filled orange, and says explicitly that this needs adopting or rejecting rather than inferring.
Why it is its own card and not part of 17. Card 17 decides the state set – what a button does when you focus, press or are blocked. This decides which button you are looking at in the first place, and it amends a hard rule. Nothing in 16 or 17 depends on it; it depends on neither.
Done when: adopted or declined in the changelog. If adopted, the rule gets its own section in BASELINE.md § Buttons, hard rule 3 is reworded, and the four costs above each get an answer or a card.
29 · [T] Write the publishing rule and the transit rule into CLAUDE.md
Files: workspace/decisions/publication-and-workspace-decision-brief.md ← §1 and §4 are the text to adopt · CLAUDE.md ← the repo-structure section, which has drifted · lib/content.ts · lib/baseline-nav.ts
Waits on 27 – the rule should be written against a gate that can enforce it, not ahead of one.
The rule to adopt, in Torfinn's own framing rather than a model: these folders publish, those don't. foundation/ and baseline/ publish. Everything else does not. And a file inside a publishing folder publishes only if it also carries status: accepted and declares a lang matching a served locale – today the folder is the only condition, and it is implicit in where the file happens to sit.
Why the root-level discipline needs a test and not care. The repo root and the URL root are the same namespace, which is why foundation/en-draft/ became sixteen URLs without anyone publishing it. So workspace/ takes three guards: absent from CONTENT_DIRS, absent from the Baseline nav, and listed in SKIP_DIRS so a future subfolder cannot repeat the accident.
The transit rule, which is the half you asked for. A file in workspace/ never publishes. Transit is a move into a publishing folder plus status: accepted plus lang. The move is a git mv in a diff and it fails the route manifest gate until the manifest is regenerated, so publication is always something a person accepted rather than a side effect of creating a directory. Claude proposes; Torfinn moves.
Two things to fix while in there. CLAUDE.md names _inbox/ and arc - previous thinking/; neither exists (workspace/incoming/ and _archive/ do). And the eight layer directories – constitutional, principles, patterns, qualities, schemas, data-products, how-to, tutorials – none of them publishes, while three internal planning documents do. That silence is what let the drift happen, so each layer needs either a route or a recorded reason it has none.
Done when: the publishing rule and the transit rule are in CLAUDE.md, the repo-structure section matches the repo, and every top-level folder has a publish state on the record.
CLOSED 2026-08-05. Both rules written into CLAUDE.md's repo-structure section, drafted for sign-off and approved by Torfinn before committing (53b6aeb). The two stale names fixed (_inbox/→workspace/incoming/, arc - previous thinking/→_archive/); all eight layer-taxonomy directories now carry a real, current publish state in the table rather than going unlisted. The workspace/-specific guards (absent from CONTENT_DIRS, absent from Baseline nav, listed in SKIP_DIRS) are card 30's to add when workspace/ is created, not written here ahead of the folder existing.
30 · [CC] Create workspace/, move the administrative material, take the eighteen routes out
Files: workspace/decisions/publication-and-workspace-decision-brief.md ← §3 is the tree, §5 is the route list · lib/baseline-nav.ts ← six nav entries to delete · lib/route-manifest.json ← the proof · app/proxy-test/ ← removed by this card, not missing
Waits on 27 and 29 – 27 gives the manifest that proves the change, 29 gives the rule it is applying.
Both halves in one pass, because both are moves and one manifest diff verifies both. Doing them separately means updating the same references twice.
workspace/ is the administrative root (decided 2026-08-04; production/ explicitly ruled out – in software it names the live thing, which is the opposite end of the pipeline). Twelve subfolders typed by document kind, not topic, because kind determines lifecycle: decisions/ plans/ briefs/ evaluations/ research/ authoring/ status/ sources/ incoming/ drafts/ archive/ skills/. Absorbs planning/, baseline/planning/, baseline/source/, _sources/, _incoming/, _drafts/, _tooling/ and the root working documents. CLAUDE.md and _state.md stay at root.
documentation/ is a new root-level folder, not a workspace subfolder (Torfinn, 2026-08-04). Documentation is a consumable – CLAUDE.md already defines its job as letting an outsider understand what the site is and how it works. This resolves the architecture/ split that two earlier drafts declined: the line is not shape versus route, it is describes the system to a reader (→ documentation/) versus plans our route to it (→ workspace/plans/). So site-map.md, the three architecture notes, aleris-meta-alignment.md, versioning-rules.md, dependency-reciprocity.md, site-spec-v2.md and slash-commands.md go to documentation/; phase-0, brand-os-refactor-plan and migration-checklist-llm-first go to workspace/plans/. values-layer.md and constitutional-core-test.md need reading before placing. documentation/ is created as not-publishing with recorded intent to publish – the notes are currently written to each other rather than to an outsider, and every top-level folder now needs a stated publish state.
Two taxonomy calls land here too (Torfinn, 2026-08-04). tutorials/ is deleted – free, it has never held anything but a 157-byte index describing content nobody wrote, and it is the §2 failure in its purest form: a directory claiming a category, cited as real work in three planning documents. how-to/ is renamed filters/, not deleted – removal was considered and rejected because it holds two accepted files, three of the four declare depends_on: how-to/ai-tells-and-filters, and data-products/voice-filter-rules.yaml names two of them in a machine-readable generated_from: field under the tokens discipline, so deletion would orphan a data product. The rule from card 29 also forbids it: workspace/ never publishes and accepted means in force, so accepted files cannot live there. The name was the problem, not the layer – filters/ is the word the corpus already uses in four places. Cost: three depends_on, four type:, one layer:, four yaml references. Do not name the folder to accommodate the unwritten communication-genres.md – that is the tutorials/ mistake repeating. Layer count is now seven, not eight.
Use git mv – history is preserved and the rename shows as a rename. The leading underscores drop on the way in: inside workspace/ the root carries the not content meaning those prefixes were doing.
Reference cost, measured rather than estimated. Roughly 330 occurrences of planning/… paths across 54 unique targets, but only four files need hand-editing: board.md (37, all in **Files:** lines), CLAUDE.md (13, and card 29 is rewriting that section anyway), file-manifest.md (12, and its whole job is file paths), _state.md (7). board.html (71) regenerates for free. changelog.md (80) must not be touched – its references are statements about the past. No code reads planning/ file paths; the three code hits match the directory name and keep working.
The five Python scripts are not a bucket. tools/ is taken by the live icon-picker, and these are situational. build-board-html.py → workspace/status/, beside the board it generates. The four audits – contrast-check.py, hover-check.py, hue-check.py, button-audit.py – go to workspace/archive/ if their logic is confirmed absorbed into the suite; baseline/tokens/tokens.test.ts:15 says the contrast maths was ported, so check the other three the same way before moving them.
Eighteen of sixty routes, none of it a content edit. Six working documents published deliberately through hand-written nav entries under Steward and Source research (baseline/planning/ ×3, baseline/source/ ×3) – that is a reversed decision, not a bug. Three working files published accidentally (_translation-notes, _examples/voice-examples, _principles-restated). One tombstone at a URL (en-draft/imagery, status: superseded). Four draft pages the gate should stop serving. Two prerendered test routes (proxy-test, proxy-test/test123).
Two need Torfinn's call before moving: en-draft/viewports/voice-for-ai-systems (no status – content or working file?) and tools/icon-picker (a working tool at a public URL – legitimately public or internal?).
Not in scope, deliberately: the eight English twins of accepted Swedish pages. They are the canonical source rather than working files, and where they go is card 28's answer, not this card's.
Done when: workspace/ exists and holds the administrative material; no route resolves to a file inside it; every top-level folder is on the record as publishing or not; and the manifest diff is the reviewable artefact that proves it.
CLOSED 2026-08-05. workspace/ created with all twelve subfolders; documentation/ created. tutorials/ deleted. how-to/ renamed filters/, twelve references fixed. _sources/, _incoming/, _tooling/, _drafts/, _archive/, baseline/planning/, baseline/source/, and every classified planning/ file absorbed. Two flagged root working documents (ALERIS-DESIGN-WORKING.md, aleris-patient-product-design-guidelines.md) moved to workspace/authoring/, their disposition still Torfinn's to decide. Two open calls answered by Torfinn before executing: voice-for-ai-systems treated as a working file (moved), tools/icon-picker confirmed public on purpose (untouched). Commits ff2a8c0, 7df2d26 (a nesting fix caught immediately after).
The eighteen routes came out, plus more the card didn't anticipate. The six nav-published working docs and the two proxy-test routes went as planned. The status-filtering fix (card 29's rule, enforced in lib/content.ts for the first time) removed the four draft pages the card named — and seven more en-draft/ files nobody had cross-checked against their own frontmatter: four with no status field at all, three proposed or draft. identity/colour-en disappeared with them, since its source (colour.md) is proposed. The known-language-mismatch list narrowed from seventeen entries to five — the ones still genuinely accepted English content under lang="sv".
Two file pairs need Torfinn's read, not resolved here. _archive/'s copies of aleris-design-system-open-questions.md (282 lines vs 117) and aleris-product-design-skill-workplan.md (missing a frontmatter block the other copy has) collided with the baseline/planning/ copies on the same filename. Both _archive/ variants landed in workspace/archive/ under disambiguating names (-arc-variant-282-lines, -arc-variant-no-frontmatter) rather than being silently overwritten or merged.
Classification of the ~50 former planning/ files into workspace/'s kind-typed subfolders is a judgment call, not a verified fact — done from filename and content signals, not confirmed with Torfinn. Correctable by git mv later; flagged here so a wrong placement doesn't read as decided.
Reference sweep: CLAUDE.md, _state.md, workspace/status/board.md, and workspace/status/file-manifest.md updated. changelog.md's own content untouched (statements about the past); its historical mentions of old paths in other files were left alone too, only live references were repointed. file-manifest.md is itself a declared historical snapshot (superseded 2026-05-30) — its paths were updated anyway so links resolve, with a note added distinguishing that from the status/category content, which is unchanged.
174 tests passing, npm run build clean, verified live: accepted pages return 200, the newly-excluded draft page 404s.
28 · [CC] The language and voice-pass inventory – what B2 needs
Files: foundation/*.md · foundation/en-draft/** · workspace/authoring/_translation-notes.md ← the machine-assisted-translation caveat (moved from foundation/en-draft/ 2026-08-05, card 30) · documentation/site-map.md ← the no-fallback stance
Pullable now. B2 – whether the published local site is English or Swedish – is currently a guess about which corpus is real, and the guess may be inverted. The Swedish pages are the finished ones: six genuinely-Swedish accepted Foundation pages, voice-passed and frozen. The English set is five accepted plus one proposed, and _translation-notes.md describes the whole of it as "unverified, machine-assisted translations – starting points for Torfinn to revise", with five voice-sensitive passages flagged for his pass. So the two languages are at rough parity in count, and the real unknown is how many English pages carry prose Torfinn has actually passed versus prose marked accepted by inheritance.
Note the trap: foundation/imagery.md is English sitting in the Swedish tree at identity/imagery, so a count of Swedish accepted pages that trusts directory position returns seven when the answer is six.
Second half of the card. B2 turns on the fallback policy – what an English reader sees when a page exists only in Swedish. site-map.md records a no-fallback stance, meaning gaps rather than silent Swedish. That is what makes a second locale cheap or expensive and it should be decided with B2, not after it.
Done when: every Foundation page has language, status, and voice-pass state on one table, the fallback options are costed, and B2 is a decision rather than an estimate.
25 · [T] The language exit – choose where English lives — DONE 2026-09-10
Files: workspace/decisions/language-exit-decision-brief.md ← read this, it is the whole card · lib/route-manifest.json ← the measured published set · constitutional/language-policy.md ← empty, and it is where the answer belongs · i18n/routing.ts ← locales: ['sv']
New 2026-08-04. The decision was already taken – 2026-05-30, Phase 0: English is the Aleris Group language and the source language for the whole corpus… renderer default locale en. It was never ratified (phase-0 has been proposed for nine weeks), it has no home (language-policy.md is six pending Torfinn markers), and the renderer never implemented it (locales: ['sv']). The corpus then migrated page by page under it, and each page migrated differently.
The measured damage. Twenty of sixty published pages carry the wrong lang attribute, in both directions: seventeen of twenty-six locale routes serve English under lang="sv", and three of thirty-four Baseline pages serve Swedish under lang="en". WCAG 3.1.1 is Level A. Also live: a superseded page, an internal working note whose glossary proposes a term retired on 2026-06-16, and two Swedish draft pages.
Three routes, costed in the brief. A ratify English-canonical and build the en locale; B English becomes unpublished source and the site serves Swedish only; C govern the mixture with lang and publish: keys. The brief recommends B, on five grounds – the exposure is on the published site, B is A's first half rather than a detour, it obeys the LLM-first guardrails, it collapses card 26 at no extra cost, and it is honest that today's readers are Swedish-speaking.
Blast radius of B: a directory move out of CONTENT_DIRS, a loader change, the packages' "English-canonical" wording restated, and identity/colour-en retired rather than promoted. No i18n build, no content edit, no accepted prose touched.
Decided 2026-08-04 (Torfinn): route B, and it splits in two. B1 – publication becomes explicit is accepted and is now cards 29 and 30: nothing publishes because of where it sits, working material leaves the published zone, and the archive takes what is retired. B2 – which language the published site serves – is deferred, and that deferral is right rather than a hedge: B1 needs no language decision, and an explicit gate with a declared lang is what a two-locale site needs anyway, so B1 forecloses nothing. Two things recorded with the decision: English is the group language and a Brand OS reachable only in Swedish is not a group asset, so the destination is a published English site with Swedish beside it rather than instead of it; and B2's prerequisite is card 28, because the worry about a Swedish translation lag looks inverted – Swedish is the voice-passed corpus and English is the one carrying machine-assisted prose. My brief's "the site serves Swedish only" was one option inside B, not B itself.
Done when: the route is chosen done – remaining: the rule is written into constitutional/language-policy.md (card 29 carries the zone half into CLAUDE.md; the language half belongs here), and phase-0 is accepted or re-scoped so nothing else migrates under an unratified decision.
26 · [CC] Reconcile the two accessibility homes
Files: constitutional/accessibility-is-foundational.md ← the empty stub, at the time this card opened · foundation/en-draft/constitutional/_accessibility-shared-draft.md ← the real node, at the time this card opened – gone as of this card's own closure, see below · baseline/BASELINE.md ← § Accessibility points at the en-draft path
Waits on card 25 – where the node lands depends on whether en-draft/ survives as a published tree.
The problem. Two homes for one binding rule, which is the duplication the corpus exists to remove. The real node is _accessibility-shared-draft.md: normative: true, status: proposed, propagating to foundation/*, baseline/*, communication/*, physical/*, carrying three bright-lines – no meaning by colour alone, no text below 14px, contrast meets AA. The stub in constitutional/ has six pending Torfinn markers and points at [[qualities/accessibility]], which does not exist. Your own document board flagged "one of the two has to go" on 2026-07-28.
Two things it drags with it. The node's own Open #3 is still open – the filename carries _ and -shared-draft, which no longer describes a canonical normative node. And three sibling constitutional scaffolds (group-scope, language-policy, tokens-are-canonical) are equally empty while en-draft/constitutional/ holds real colour and typography nodes, so this is one move done four times, not a one-off.
Done when: one file holds the rule, the other is gone or is a pointer, BASELINE.md § Accessibility cites the surviving path, and [[qualities/accessibility]] either exists or is not referenced.
CLOSED 2026-08-05, pulled without waiting on 25. The "waits on 25" reasoning was about the source location – whether en-draft/ survives as a published tree – not the destination. constitutional/accessibility-is-foundational.md was always where the rule was going regardless of how B2 resolves, so moving it doesn't foreclose anything B2 might still decide. [[qualities/accessibility]] was never referenced by the real content (only by the empty stub it replaced), so that half of done-when was satisfied by construction. BASELINE.md § Accessibility repointed to the new path, along with seven other live references across foundation/en-draft/ and baseline/. Commit 0d08aa8 (+2 follow-up commits – the first git add -A hit a bad pathspec and silently committed the pre-rewrite content instead of erroring loudly).
27 · [CC] Commit the route manifest gate, and extend it to Baseline
Files: lib/route-manifest.test.ts · lib/route-manifest.json · vitest.config.ts · branch feat/route-manifest-gate
Pullable now, and mostly already done. The gate was built and run on 2026-08-04 – KNOWN_LANG_MISMATCH is populated from its own first run, ORPHANED_ACCEPTED came back genuinely empty, and vitest.config.ts now includes lib/**. It is sitting uncommitted in the working tree on a branch at the same commit as main. Finished work that no record points at is the failure mode cookbook §12 describes.
The hole it currently has. It builds from CONTENT_DIRS, so it covers the twenty-six locale routes and none of the thirty-four /baseline/ pages, which publish through their own route group and nav table (lib/baseline-nav.ts). Three of the twenty language mismatches are in that blind spot, so the gate reports seventeen when the number is twenty.
Do not fold the Baseline extension into the same commit as the existing work – commit what has already been run and verified first, so the reviewable diff stays the diff that was actually tested.
Done when: the gate is committed with its manifest; a second commit extends it over the Baseline nav table; the suite runs green under npm test rather than only when the file is named.
CLOSED 2026-08-05. Committed as two commits exactly as instructed — 5466f14 (the gate as already built), 461aebf (the Baseline extension). The count in this card's own second paragraph was itself wrong. "Three of the twenty" undercounted: language is detected from a stop-word ratio (no Baseline file carries a lang key), and running it found the whole seven-file pattern library (hero, button-label, confirmation, empty-state, error-message, chat-bubble, chat-response-simple – then at baseline/patterns/, moved into patterns/ 2026-08-05, card 32) is Swedish, not just the three voice guides. The real total is twenty-seven language mismatches, not twenty – ten in Baseline (all served under app/baseline/layout.tsx's hardcoded lang="en"), seventeen in foundation. 174 tests passing, all green under npm test.
4 · [CC] Patch the handoff (Patches 1–3)
Files: workspace/briefs/handoff-colour-supersede-claude-code.md ← edit this · workspace/briefs/handoff-colour-supersede-corrections.md ← the patch list · foundation/en-draft/constitutional/colour-is-the-aleris-palette.md ← Patch 1 target
Mechanical edits to workspace/briefs/handoff-colour-supersede-claude-code.md per workspace/briefs/handoff-colour-supersede-corrections.md.
Patch 1: §4c misattributes the token-conformance claim to baseline/governance/aleris-design-governance.md; a full grep of that file returns nothing. It lives at foundation/en-draft/constitutional/colour-is-the-aleris-palette.md:39. Batch with the §8 contrast-note edit, same file.
Patch 2: §3's dedup list carries five targets; regenerate to the twelve-location table in the corrections brief.
Patch 3: repoint stale citations to authoring "§11" and "§12" (handoff lines ~81, ~83, ~93, ~103). AA failures → authoring §4B; bright-line and fitness-function reasoning → §4A.
Patch 4 is void – it instructed the Pantone collapse, cancelled with the retirement reversal. Handoff §1 carries the current instruction. Leave the void marker in place.
Re-confirm line numbers before editing; earlier edits shift them.
Done when: all three patches applied, Patch 4 left marked void.
5 · [CC] §0 Safety – snapshot, branch, freeze
Files: (git only – tag, branch, freeze)
Migration-checklist principles 3 and 4. Tag or snapshot the accepted corpus and confirm it is recoverable. Work on a branch; nothing lands directly in the connected repo. Freeze the accepted Swedish foundation/colour.md until the last step – English en-draft/ is the edit surface.
Git run from the Cowork sandbox leaves stale .git/*.lock files; clear with rm -f .git/*.lock.
Done when: tag exists, recovery confirmed, branch created.
6 · [CC] §1–§2 English colour page and the constitutional rule
Files: foundation/en-draft/colour.md · foundation/en-draft/constitutional/_accessibility-shared-draft.md ← moved 2026-08-05 (card 26) to constitutional/accessibility-is-foundational.md · documentation/versioning-rules.md ← version bump rules
§1. Author on foundation/en-draft/colour.md. Carry Torfinn's authored English prose verbatim – no edits to brand prose. Apply the 0–900 renumber across the page: petrol-100..500, sand-50..500, gray-100..500, orange-100..700; Slate → gray-100, Sand dark → sand-500. Drop in the scaffold's final tables – the orange ramp at seven steps, the Pantone/CMYK table with orange-600/700 marked digital-only and the reinstated orange-400/200 rows present, and the updated contrast pairs. Frontmatter: status: proposed while in review, depends_on gains the constitutional accessibility node, version bumped toward a Foundation major per documentation/versioning-rules.md.
§2. Confirm foundation/en-draft/constitutional/_accessibility-shared-draft.md (now constitutional/accessibility-is-foundational.md, card 26, 2026-08-05) as the canonical home for "no meaning by colour alone" – do not create a parallel page. Set normative: true, carry the rule text verbatim. colour.md references it via depends_on plus an inline pointer and keeps only the CVD justification, not a restatement.
Done when: both pages updated on the branch, all brand prose carried verbatim.
7 · [CC] §3 De-duplicate the colour-alone rule across twelve locations
Files: foundation/colour.md · foundation/en-draft/colour.md · foundation/en-draft/constitutional/_accessibility-shared-draft.md ← moved 2026-08-05 (card 26) to constitutional/accessibility-is-foundational.md · baseline/BASELINE.md · baseline/governance/aleris-design-governance.md · baseline/reference/aleris-design-tokens.md · baseline/reference/aleris-grids-tables-dataviz.md · baseline/tokens/aleris-tokens.css
The rule statement lives once, at the constitutional node. Domain implementations reference it rather than restating it – that is inheritance, not duplication.
Verified set: foundation/colour.md:149 becomes the constitutional seed · foundation/colour.md:239 Snabbtest → reference · foundation/en-draft/colour.md:145 → justification plus pointer · _accessibility-shared-draft.md:16 → canonical, normative: true · baseline/reference/aleris-grids-tables-dataviz.md:173 · baseline/reference/aleris-design-tokens.md:155, :163, :76, :135 · baseline/tokens/aleris-tokens.css:80, :296 – all keep as implementation and reference the rule · baseline/BASELINE.md:193 → reference · baseline/governance/aleris-design-governance.md:87 → reference, and note this is one sentence inside a five-sentence paragraph, so it is an edit not a replace · :232 → reference, the data-viz line, found in analysis and absent from every source doc.
workspace/authoring/ALERIS-DESIGN-WORKING.md:43 is out of scope – absorbed when that doc is absorbed.
Done when: every location either holds the canonical rule or references it, and none restates it.
8 · [CC] §4 Token and rule ripple, with the retirement reversal applied
Files: baseline/tokens/aleris-tokens.css · baseline/BASELINE.md · baseline/governance/aleris-design-governance.md · baseline/reference/aleris-design-tokens.md
Read §1's reversal note first – it overrides every "retired" instruction in §4.
Add --color-orange-600: #d14811 and --color-orange-700: #b23c0e; confirm orange-100. Keep --color-orange-400; add --color-orange-200: #fbd1c0. Add petrol-200 #abc7c9 and petrol-400 #4f868e for scope B.
Rewrite @usage and @constraint: orange-500 loses "Primary CTAs" and becomes accent/decorative/print – both halves invert, since the live constraint reads "Never for decoration" and the new role is decorative; editing only @usage leaves a self-contradicting token. orange-400 loses its button-hover claim but keeps the token – give it an explicit interactive-use ban matching orange-300's "Decorative/background only, not for interactive states", or the bug walks back in.
Repoint --button-primary-hover-bg off orange-400 → orange-700. Repoint --state-hover (line 281) off orange-500 → orange-700; orange-500 is brand-only now and cannot carry an interactive state.
Retire the hover Anchor in five places, not two: aleris-design-governance.md:201, BASELINE.md:122, aleris-tokens.css:473, plus three _packages/agent-baseline mirrors. Use the ADR vocabulary – superseded with a pointer to the replacement, recorded where the Anchor sat. No new retirement procedure is needed: Fixed/Anchor was retired as a vocabulary on 2026-06-16. Replacement wording is authoring §1C, verbatim.
Also: BASELINE.md supersedes "hover lightens, never darkens" with the H2 contrast-direction wording; add the semantic status pattern (icon plus label on error and success); spec the inverted primary button as a new variant – rest = white fill with petrol-500 text at 10.27:1, hover = var(--color-petrol-100) fill at 7.73:1, reuse the existing token, never place the orange-600 fill on petrol.
Do not fix secondary or confirm here – that is card 12.
Done when: tokens, comments and rule statements are consistent and no page or comment asserts a retired rule.
9 · [CC] §4b Fix the four pre-existing errors
Files: foundation/colour.md · foundation/en-draft/colour.md · _packages/agent-baseline/foundation/colour.md · baseline/reference/aleris-design-tokens.md · baseline/tokens/aleris-tokens.css · app/globals.css · app/proxy-test/client.tsx ← removed by a later card (30, 2026-08-05), not missing
None are caused by the supersede; all sit in files being edited anyway.
- The brand-orange sRGB triplet.
#F58C61is 245, 140, 97 but the row records 248, 124, 86. Settled 2026-07-29 by measuring the swatch Torfinn confirmed correct – Display P3 converted to sRGB gives exactly 245, 140, 97, zero distance to the hex. Fix the triplet, keep the hex, infoundation/colour.md:77,foundation/en-draft/colour.md:79, and the shipped_packages/agent-baseline/foundation/colour.md:77. The CMYK is not a data error – whetherC0 M56 Y62 K0reproduces the hex at press is print validation. - Reference doc contradicts the token file on primary hover.
baseline/reference/aleris-design-tokens.md:427says orange-300;baseline/tokens/aleris-tokens.css:425says orange-400. Mismatch dates to 2026-03. Both become orange-700 – update the reference table, not only the token. - This repo's own app.
app/globals.css:13--color-accent-80and:15--color-accent-40, withhover:bg-accent-80inapp/proxy-test/client.tsx. Narrowed after the reversal – these are live tints, not dead values. Repoint the hover usage to the orange-700 equivalent and keep both custom properties. Audit otheraccent-80/accent-40uses for interactive contexts, not for deletion. - Pantone absence. Mark orange-300 and orange-200's missing Pantone as a fact – no corresponding Pantone colour exists – not a bare dash, and not the way the legacy teal is marked. Wording comes from card 1. Report that orange-300 also lacks CMYK; that value is derivable but do not compute it into the page unilaterally.
Done when: all four addressed, item 4 using Torfinn's wording.
10 · [CC] §4c Land the contrast fitness check, failing on secondary and confirm
Files: baseline/tokens/tokens.test.ts ← NEW FILE · workspace/archive/contrast-check.py ← port the maths from here · baseline/tokens/aleris-tokens.css ← parsed by the test · foundation/en-draft/constitutional/colour-is-the-aleris-palette.md ← the false claim, line 39
The item that stops the next one – the three AA failures were computable from the token file the whole time.
Add baseline/tokens/tokens.test.ts; vitest is already wired (npm test → vitest run). Port the WCAG relative-luminance and contrast maths from workspace/archive/contrast-check.py – forty lines, already verified against the published pairs.
Test 1, the one that matters: every button variant, every state, text against its own fill, must meet 4.5:1. Parse --button-*-bg, --button-*-text, --button-*-hover-bg, resolve the var() chains to hexes, assert. This fails today on secondary and confirm – that is correct. Land it with those two as documented known failures (test.fails or an allowlist with an expiry), never by loosening the threshold.
Test 2: any fill placed on another surface must meet 3:1 per WCAG 1.4.11 – catches orange-600-on-petrol structurally rather than by remembering the rule.
Test 3, token conformance: every hex in app/ and baseline/ appears in the token set. Point it at a defined token-set source – the --color-* declarations in baseline/tokens/aleris-tokens.css – or it will flag app/globals.css's own valid custom properties as violations.
Either build this or correct the claim at colour-is-the-aleris-palette.md:39 that the check already exists. Do not leave a page asserting a control that is not wired.
Done when: npm test runs, Test 1 fails only on secondary and confirm as documented, Tests 2 and 3 pass, and no page claims an unwired control.
11 · [CC] §6 Wire the loader so the English page publishes
Files: lib/content.ts ← CONTENT_DIRS line 20, ROUTE_OVERRIDES lines 9–17
Two edits, not one. lib/content.ts:20 is CONTENT_DIRS = ['foundation', 'physical'] – en-draft/ is not scanned. And there is no route mapping for the en-draft colour page in ROUTE_OVERRIDES (lib/content.ts:9–17). The English page needs the scan directory and a path mapping before it can publish. Check SKIP_DIRS and SKIP_FILES for interactions.
Done when: the English colour page renders at its intended route in a local build.
12 · [CC] AA pass two – add the steps, turn the test green
Files: baseline/tokens/aleris-tokens.css · baseline/tokens/tokens.test.ts · baseline/reference/aleris-design-tokens.md
Deliberately a second pass, not folded into the supersede (decided 2026-07-29): the contrast test lands first with secondary and confirm failing, then this card fixes them, which makes the fix provable rather than asserted.
Add the petrol step and the confirm interactive fill at the values from card 3. Repoint --button-secondary-hover-bg off petrol-300 to the new darker step. Add the confirm interactive fill below confirm-500 and repoint --button-confirm-bg and --button-confirm-hover-bg to it, leaving --status-confirm on confirm-500 so non-button uses are untouched. Remove the "Derived value – validate when a confirm scale is added to F1" comment at aleris-tokens.css:473 along with its Anchor justification.
Then remove the known-failure markers from tokens.test.ts – the suite goes green. Update baseline/reference/aleris-design-tokens.md to match; that table has drifted from the token file before. Confirm the new confirm fill stays visually distinct from --color-goal-achieved #2E8540, which the token constraint requires.
Done when: npm test passes with no allowlisted failures and the reference doc agrees with the token file.
13 · [CC] §5 Regenerate all three packages and verify the mirrors
Files: _packages/agent-baseline/ · _packages/klinfys-baseline/ · _packages/README.md ← rule 6 · _packages/agent-baseline/MANIFEST.md ← retired vocabulary
This is the gate for sharing Brand OS again, not the supersede.
Regenerate _packages/agent-baseline/ and _packages/klinfys-baseline/, and record a regeneration path for bhsv-baseline.zip, which predates the manifest rule. Then verify rather than assume: the agent-baseline mirrors of governance:201, BASELINE.md:122 and tokens.css:473 are verbatim copies, and _packages/README.md rule 6 says a shipped snapshot cannot be corrected in place, only regenerated and re-sent.
Confirm each of these is gone from the regenerated package: the retired hover Anchor, the wrong sRGB triplet, the orange-400 button-hover claim, and the failing secondary and confirm values. Also check agent-baseline/MANIFEST.md, which records its own provenance in the decided/fixed/anchor vocabulary retired on 2026-06-16.
Surface, do not resolve: whether the shipped governance doc keeps its ~18 Fixed/Anchor rule headings for one more release or has them removed in the package copy only. The corpus-wide answer belongs to Phase 0.
Done when: all three packages regenerated, every mirror verified by inspection, and the vocabulary question surfaced.
CLOSED 2026-08-05 for agent-baseline/, with klinfys-baseline/ and bhsv-baseline.zip explicitly not regenerated – see below, this is not the full "all three" the done-when asked for.
agent-baseline/ → v0.3. All 27 files in its MANIFEST.md's provenance table re-copied verbatim from canonical and verified file-by-file with diff – zero mismatches. Confirmed gone: the retired hover Anchor as a live rule (survives only inside two "Superseded" blockquotes, which is correct – they're quoting history), the orange-400 interactive-hover claim, and the failing secondary/confirm values (fixed outright by card 19's rebuild). One known-stale value is unchanged, deliberately: the brand-orange sRGB triplet in foundation/colour.md (248, 124, 86 instead of 245, 140, 97) is still wrong, because canonical itself still carries it – parked behind card 5's freeze pending the Swedish/English twin sync (_state.md, "carried under the freeze"). A verbatim mirror of a canonical defect is not a mirroring defect; recorded in the manifest so it isn't mistaken for one. MANIFEST.md bumped to v0.3, dated 2026-08-05, contents note rewritten.
The vocabulary question, surfaced rather than resolved, as asked. governance/aleris-design-governance.md carries 32 mentions of the retired Fixed/Anchor/Hypothesis vocabulary in the shipped copy, unchanged since v0.2. Recorded in MANIFEST.md's limitations section as a Phase-0 question – whether the package strips this vocabulary ahead of the corpus doing so, or waits – not decided here.
klinfys-baseline/ and bhsv-baseline.zip – not regenerated, and the reason is itself the finding. agent-baseline/ could be regenerated mechanically because its MANIFEST.md names the exact 27 files it mirrors. Neither other package has that: both predate the manifest rule, and nothing in the repo records which canonical files they're supposed to contain. Regenerating them by guessing a file list would be inventing scope, not executing a recorded one – the same failure mode the manifest rule exists to prevent. _packages/README.md's table now says so explicitly for both, with the same fix: confirm the file list before the next regeneration, rather than assuming agent-baseline/'s list applies. This is Torfinn's call, not made here.
14 · [CC] Ship agent-baseline v0.4 to Richard
Files: workspace/incoming/2026-06-20-brand-os-into-vibe-coding-repo-brief.md
Runs workspace/incoming/2026-06-20-brand-os-into-vibe-coding-repo-brief.md, blocked since 2026-07-28 because it would have shipped v0.2 carrying the retired hover Anchor, the wrong sRGB triplet, the retired orange-400 claim and the retired Fixed/Anchor vocabulary into a repo that exists to be read by LLMs as current.
The brief predates the colour decision by five weeks and carries no dependency on it – read it against the current state before executing, do not follow it blind. Needs an interactive Bitbucket clone. Version the package v0.4; marking the brief filed or archived is Torfinn's call.
Done when: v0.4 is in Richard's repo and the brief is resolved.
Checked 2026-08-05: this is genuinely blocked, not just next. The brief's own §"Source and destination" says it plainly: "The clone is interactive (Bitbucket app password) – if the session can't authenticate non-interactively, stop and hand the clone step back to Torfinn, then resume." No Bitbucket credentials are available in this session, and cloning a private external repo plus pushing a branch is exactly the kind of cross-boundary action that needs a human hand regardless. Handoff: Torfinn clones https://tiffotofu@bitbucket.org/aleris-engineering/aleris-vibe-coding.git into ~/Dev/aleris-vibe-coding (or grants access another way), then this card resumes – branch, place the v0.4 package per the repo's own convention, wire CLAUDE.md/AGENTS.md, commit, push, open the PR. Everything upstream of the clone is done: agent-baseline/ is at v0.4, verified, ready to copy in verbatim (card 43).
Updated 2026-08-07: still blocked, same reason, package now at v0.4. v0.3 had gone stale before this card could run — see card 43. Nothing about the blocker changed; only the version being handed off did.
Updated 2026-08-07, later the same day: the blocker itself was mis-diagnosed. Attempted the clone directly (GIT_TERMINAL_PROMPT=0, fails fast rather than hanging) – confirmed non-interactive auth is impossible from here, as expected. But Torfinn's own follow-up attempt with his account password also failed, and the cause isn't identity or ownership: Bitbucket app passwords were fully removed 2026-07-28 (brownout started 2026-06-09), so the brief's tiffotofu@… HTTPS-plus-password flow no longer authenticates for anyone. Corrected in the brief itself. Two live routes, either Torfinn's to set up: SSH (git@bitbucket.org:aleris-engineering/aleris-vibe-coding.git, needs a public key added under his Bitbucket personal settings, unaffected by the deprecation) or an API token over HTTPS (username literally x-bitbucket-api-token-auth, password an Atlassian API token). Still genuinely [T] — the credential lives in his account, not something a session can supply — but now for the right reason.
15 · [CC] Update the records — DONE 2026-08-27
Files: workspace/status/changelog.md · _state.md · workspace/status/board.md ← next decision goes here
Struck 2026-08-25 — the manifest is archived (workspace/status/file-manifest.md for files that moved or changed status.workspace/archive/file-manifest-superseded-2026-05-30.md); it had declared itself frozen on 2026-05-30 and taken no entry since 2026-05-07. File and card state live on this board. workspace/status/changelog.md – the single changelog; append, and note the Foundation major bump per documentation/versioning-rules.md. _state.md at the initiative root. Do not create a second changelog: workspace/archive/baseline-changelog.md was merged in and superseded on 2026-07-28 after dying two days into its life. Mark resolved briefs in workspace/incoming/.
Then propose the next decision on this board: Phase 0 sign-off, plus whether scoping documentation/dependency-reciprocity.md moves ahead of it, since this session's rework came from exactly the gap that document names.
Done when: manifest, changelog and _state.md reflect reality, and the next decision is on the board.
16 · [T] Secondary hover – and whether hover is a fill at all
What this card actually is, added 2026-08-04.
aleris.sepublishes atillgänglighetsredogörelsedeclaring partial compliance with DOS-lagen, and one of the defects it names is missing focus indicators on interactive elements, hindering keyboard navigation – self-assessed with internal resources, last assessed August 2025. Cards 16 and 17 are that defect. The measured evidence agrees: focus is at 1.0:1 on secondary and primary-inverse – a petrol ring on a petrol fill – so on those two variants the indicator is not weak, it is absent. This is remediation of a declared non-conformity, not a preference between hover treatments. DOS-lagen reaches Aleris through publicly financed care (Digg's definition of offentlig aktör extends to private actors running healthcare under HSL with public financing), which is presumably why the statement exists at all.Which is an argument for the merge. Of the live options, the hover-equals-focus merge is the only one that fixes the declared defect and simplifies at the same time: no new hex, and every
--button-*-hover-bgtoken plus--state-hoverbecomes redundant. Its one cost – the keyboard-and-mouse user losing the caret – has a one-line answer already in the return: hover draws the outer ring, focus draws both. Recommendation, not a decision: confirm the merge, hover draws one ring.
Reframed again 2026-08-01 by the design return, and the reframing is the useful part. workspace/evaluations/button-state-return-evaluation-2026-08-01.md is the assessment; the specimens are in workspace/archive/button-state-return-2026-08-01/reference/Button States Brief.dc.html (moved from workspace/incoming/ since).
Two things below are now wrong.
Option A is dead. petrol-100 fill on a sand-100 page is 1.13:1. The label passes at 7.73:1 and the button loses its boundary – WCAG 1.4.11, the clause this whole exercise is built on. The option traded a text failure for a figure-ground failure and the card counted only the first. It survives only with a permanent petrol border present at rest, which makes the collision with outline mandatory rather than incidental, and on hover secondary becomes pixel-identical to outline's hover. That repaired version is option 1a in the return, and it is not the option written below. The reason the move works on primary-inverse is that primary-inverse sits on petrol, where petrol-100 is 7.73:1 against its ground.
The question was a false dilemma. The ruling constrains the palette; AA constrains contrast. Neither requires hover to be a fill change. They collide only under the unexamined assumption that the fill is the hover channel. Drop it and both hold with no new hex.
The live option space.
| Option | Numbers | Cost | |
|---|---|---|---|
| 1b | The fill holds, the edge arrives – 2px petrol-500 ring at 3px offset. This is option C below, made buildable | label 10.27:1 unchanged · ring on sand 8.75:1 · no new hex | Collides with the dual focus ring: hover and focus both become concentric outlines. Must be resolved deliberately |
| 1e | The hybrid the handoff asked to be costed – 1b for hover, petrol-700 for press only | hover 10.27:1 · press 13.88:1 · press change vs rest 1.35:1 | One new hex, not two. Splits the mechanism – hover is a shape, press is a colour – so a designer reading the tokens will not see them as siblings. Name the value press, not active |
| 1a | Invert, with a mandatory border – option A repaired | label 7.73:1 · border on sand 8.75:1 · state change 8.75:1 | Loudest hover available. Three roles then converge on one appearance, and the fill/ground relationship inverts mid-interaction |
| 1d | Darken to petrol-600 – the earlier brief's answer | label 12.14:1 · boundary 10.34:1 · state change 1.18:1, ΔL* 4.9 | One new hex, and it contradicts the ruling on the ruling's own terms. Its defence – hover is pointer-only, so a weak signal is tolerable – argues the hover may be weak, not that it must be a fill |
And the return records an answer that may delete the card. Turn 2 has Torfinn saying hover and focus cannot co-occur and should be the same treatment. If they merge, hover on secondary becomes the dual ring: no new value, no fill change, and the darkness ruling is never tested. It also makes every --button-*-hover-bg token redundant, which is a larger simplification than anything else on this board. The return raises the caveat itself: hover and focus can be visible simultaneously on different buttons – focus on the submit button while the mouse rests over Avbryt – and a keyboard-and-mouse user then loses the caret. Its proposed fix is one line: hover draws the outer ring only, focus draws both. Same vocabulary, one a subset of the other, and it survives greyscale.
So the decision is now: confirm the merge and pick whether hover draws one ring or two, or decline it and choose between 1b, 1e and 1d.
Correction, 2026-08-01, to a line first written on this card on 2026-07-31 and repeated when it was reframed: "nothing else depends on this" was true of petrol-600 and is not true of the merge. The two branches have very different blast radii, and that difference is part of the decision:
- 1b, 1e or 1d touch
--button-secondary-hover-bgand nothing else. Card 16 stays a one-token decision, and cards 16 and 17 stay independent. - The merge deletes the whole hover column – all six
--button-*-hover-bgtokens, primary, primary-inverse, secondary, outline, ghost and confirm – plus--state-hover, and it makes card 17's dual-ring spec load-bearing for hover as well as focus. So the two cards stop being independent: whether hover draws the outer ring or both is simultaneously the answer to this card and an amendment to 17's. Downstream, every token change here regeneratesbaseline/tokens/baseline-tokens.json, updates the--button-*-hover-bgrows inbaseline/reference/aleris-design-tokens.md, and touchesbaseline/reference/aleris-baseline-animation.md:79, which names--button-primary-hover-bgin a code sample.
The merge is the larger simplification and probably the better answer. It is not the cheaper one, and it should not be chosen on the belief that it is contained.
Reframed 2026-07-31 by the first brief, kept because the numbers are the record: the brief does option D – the one ruled out below – and the hover step it picks is measurably weaker than the value that was rejected.
| ΔE76 from petrol-500 | L* | L* drop | |
|---|---|---|---|
petrol-500 #004851 |
– | 27.4 | – |
petrol-600 #003c44 – the brief's hover |
5.4 | 22.5 | 4.9 |
#003A42 – rejected on this card |
6.3 | 21.7 | 5.7 |
petrol-700 #003238 – the brief's active |
10.0 | 18.2 | 9.2 |
The brief has a real counter-argument, though it never states that it is reversing a decision: under Mobile first orders the states, hover is pointer-only and matters least of the three touch states, so a weak-but-compliant hover is acceptable because the press affordance carries the interaction. A hover that is weak but clears AA is strictly better than one that fails it at 2.56:1.
So the question is narrower than the original card: not "how should secondary hover", but "does the ruling that petrol-500 is as dark as the brand goes survive contact with a hover that has to pass AA?"
Three ways out, all consistent with the numbers:
- Accept petrol-600/700. The ruling bends because hover is the least important state. Everything in the brief then lands unchanged.
- Hold the ruling and take option A – invert on hover to petrol-100 with petrol text, 7.73:1, ΔE 63.8. The strongest signal of any option, and the system already does exactly this on the inverted primary. petrol-600/700 then still land, but for the active state only, where a 9.2-point L* drop is a real change.
- Hold the ruling and take option C – move the hover signal off colour entirely, which leaves the petrol ramp untouched at five steps.
Nothing else in the brief depends on this. The dual focus ring, the per-variant active model, the confirm retirement and the disabled rule all stand or fall on their own.
Note added 2026-07-30, still true. Secondary's focus ring is invisible independently of whatever is decided here – a petrol-500 ring on a petrol-500 fill is 1.0:1, and focus is not hover. The brief's dual ring fixes that separately, on card 17.
Original card, 2026-07-29, kept because the ruling and the options are the record:
Files: baseline/tokens/aleris-tokens.css ← --button-secondary-* lines 439–446 · baseline/reference/aleris-design-tokens.md · baseline/BASELINE.md ← button roles
Split out of card 3 on 2026-07-29, because the recommendation there was wrong. It proposed adding a petrol step below petrol-500 for the hover. Torfinn: petrol-500 is as dark as the brand goes, and nothing below it is distinguishable in any practical sense. Correct conclusion; the numbers refine the reason. #003A42 measures ΔE 6.3 from petrol-500, so a meter can tell them apart – but ΔE76 overstates differences in the dark region, and a 5.7-point L* drop on a small dark button, seen with no side-by-side reference, is a weak hover signal even when it is technically visible. So the option is out because it signals poorly, not because it is invisible.
Current state: rest is petrol-500 fill with white text at 10.27:1 and passes. Hover moves to petrol-300 and drops to 2.56:1 – worse than the white-on-orange failure this whole supersede exists to fix.
The options, with numbers. This needs design thinking, not a value pick, which is why it is its own card.
| Option | Numbers | Note | |
|---|---|---|---|
| A | Invert on hover | petrol-100 #D9E1E2 fill, petrol text, 7.73:1; ΔE 63.8 from rest |
Strongest signal of the three, and the system already contains the move – the primary button inverts exactly this way on petrol surfaces. Reuses an existing token. |
| B | Secondary becomes outline-only | rest petrol text on sand 8.75:1; hover petrol-100 fill 7.73:1 | Uses --button-outline-hover-bg, which already exists. But it collapses secondary into the outline variant and removes a level from the button hierarchy. |
| C | Keep the fill, move the hover signal off colour | contrast never changes, so the AA rule is untouched by construction | Border, shadow, underline, or scale. Needs a spec for what the signal is, and a check that it survives prefers-reduced-motion. |
| D | Darken the fill | ruled out – see above |
Worth noting alongside: card 4C in the authoring doc records that the whole button set is missing state specs, so whatever is decided here probably wants to be decided for all variants at once rather than for secondary alone.
Done when: an option is chosen and recorded in the changelog, with enough detail that card 12 can implement it – for A or B that means the two fills and the text colour; for C it means what the non-colour signal actually is.
Note added 2026-07-30. The button state matrix confirms the closing line above with numbers, and adds one thing: secondary's focus ring is invisible independently of whatever is decided here. A petrol-500 ring on a petrol-500 fill is 1.0:1. Choosing option A (invert on hover) does not touch it, because focus is not hover. So this card can be decided on its own merits without waiting for card 17, but it does not fix secondary on its own.
DECIDED 2026-08-05 (Torfinn): merge hover into the focus ring. Hover draws the outer ring only, no fill change; focus draws both rings. This was the recommendation added to the card 2026-08-04, and it is the only live option that fixes the declared DOS-lagen non-conformity (missing focus indicators) and simplifies at the same time – every --button-*-hover-bg token and --state-hover are gone, and --color-petrol-600, which the design return had proposed as a hover-only step, was never needed. Cost accepted: a keyboard-and-mouse user loses a separate hover cue on a button their mouse rests over while focus sits elsewhere, mitigated by hover drawing the outer ring only rather than both. The coupling with card 17 flagged on 2026-08-01 became real: the dual ring from 17 is now load-bearing for hover as well as focus, and 17 was decided in the same session so the two landed together. Applied in card 19.
17 · [T] Button state set – adopt the brief, or amend it
Files: workspace/evaluations/button-state-brief-evaluation-2026-07-31.md ← the assessment · baseline/buttons/index.html ← the evidence, open it first · workspace/archive/Button design WCAG evaluation.zip ← the proposal (moved from workspace/incoming/ since) · baseline/tokens/aleris-tokens.css ← where the tokens would land · baseline/BASELINE.md · baseline/governance/aleris-design-governance.md ← the blanket focus rule
Updated 2026-08-01 by the design return. Three things to fold in before deciding.
The press model has a principle behind it now, and it argues against what the brief shipped: states that signal role may vary by variant; states that confirm an act may not. Hover is about hierarchy, so it may legitimately differ across variants. Press is about physics – it says this object received my touch – and a user pressing a toolbar icon and a user pressing Boka tid ask the identical question. On that reading, outline and ghost aliasing their hover fill is not a fix, and the return adds a fifth job to the four in the handoff: on touch, press is the only proof the object is interactive at all. A patient who has never met this button has no hover to teach them. That makes press an onboarding channel, and onboarding channels cannot be optional on two of five variants.
It also reads the phrase back: "not load-bearing for conformance" is true and is being used to mean "not important". Count the channels for a reduced-motion pointer user pressing primary – fill unchanged from hover, translate suppressed, inset shade remaining. One channel, and the inset is measured against orange-700, a dark fill where a black shade moves least.
Two candidate mechanisms that reach the transparent variants, neither needing a new hex: press as a uniform geometry change (radius tightens to --radius-s, 1px inset on every side – contrast untouched by construction, second carrier is shape, survives greyscale and prefers-reduced-motion intact), or press as one compositing rule, a black overlay at 0.25 over whatever is beneath. The second is uniform in mechanism but not in effect: over petrol-500 it barely moves, so the variant that most needs the signal gets the weakest version of it.
Two rules in BASELINE.md are still unamended in the return's own edits. Hard rule 5 at :37 still asserts confirm-500 is interactive, in the same file whose § Buttons retires it. And § Accessibility still reads "focus-visible with petrol ring for keyboard navigation" – the return amends the identical sentence in § Forms and leaves this one, though it is the rule this card names as unable to survive unamended.
Reframed 2026-07-31: this is no longer an open design question, it is a proposal to accept or amend. The brief in workspace/incoming/ answers all fifteen measured failures, and its four fifths that hold are stronger than a standing start would have produced.
The part worth accepting on its merits – the dual focus ring. 2px white inner and 2px petrol-500 outer, both always drawn, swapped on primary-inverse. Its argument for drawing both rather than choosing per surface is that a surface-aware ring fails silently when a button lands on the wrong ground, which is the failure mode being fixed rather than a milder version of it. Verified across seven configurations, including a button on a white card and an outline button on petrol – two the brief never mentions – and at least one ring clears 3:1 in every one. That is a construction, not a set of tuned values.
Also sound: retiring --state-active rather than repointing it, because sand-500 is --surface-strong-neutral – a surface tint doing a fill's job at 1.93:1 under white text. The general lesson the brief draws is worth keeping: a generic fallback for a state that varies per variant is the mechanism that hides failure, not a safety net.
The two decisions inside it that are separable and have their own cards: petrol-600/700 (card 16) and the confirm retirement (card 3).
What this card decides: whether the state model is per-variant tokens plus a dual ring plus no disabled state, as proposed – and whether the blanket rule in the governance doc, "focus-visible with petrol ring for keyboard navigation", is amended or explicitly kept. It cannot survive unamended: one ring colour has to work against six fills on two surfaces.
The four gaps card 19 must fix are listed there and in the evaluation; none of them changes the shape of the model.
Original card, 2026-07-30, kept as the measured record of the problem the brief answers:
The state of play. Six variants define bg, text and hover-bg. None defines focus, active or disabled. All six therefore fall back on the generic --state-* tokens, and those fail:
| Finding | Numbers |
|---|---|
| Focus is invisible on two variants | secondary: petrol-500 ring on a petrol-500 fill, 1.0:1. primary-inverse: petrol-500 ring on the petrol-500 page it sits on, 1.0:1. primary 2.27:1, confirm 2.43:1. Only outline and ghost pass, because their fill is transparent. |
| Active breaks every white-text variant | --state-active is sand-500. White on it is 1.93:1 – primary, secondary and confirm together. The token is also --surface-strong-neutral; it was chosen as a surface tint, not a button fill. |
| Disabled has no boundary | gray-100 on sand-100 is 1.28:1 on all five light-surface variants. The label at 2.03:1 is a legibility choice, not a violation – WCAG 1.4.3 exempts inactive controls. |
The structural finding, which may matter more than the values. The blanket rule "focus-visible with petrol ring for keyboard navigation" cannot hold: one ring colour has to work against six fills on two surfaces, and any single colour collides with something. And the two variants that pass most states – outline and ghost – pass because they are transparent, so their boundary is the border and the label. Transparency is doing accessibility work that no written rule acknowledges.
Also worth deciding knowingly: the only variant passing four of five states is primary-inverse, the newest, and it passes because petrol contrasts with everything rather than because it has a spec.
Not proposed here. Whether states become per-variant tokens, whether the focus ring becomes surface-aware or offset-based, and what a disabled button should look like are design decisions. The matrix is the input.
Done when: a direction is recorded in the changelog for focus, active and disabled, at whatever granularity is decided – per variant or a smarter generic set – with enough detail for card 12 to implement, and with the blanket focus rule in the governance doc either amended or explicitly kept.
DECIDED 2026-08-05 (Torfinn): adopted as proposed. Per-variant active fills for every filled variant, the dual focus ring (2px white inner, 2px petrol-500 outer, swapped on primary-inverse), and no disabled state – buttons do not disable at all, rather than a per-variant disabled treatment. The blanket rule in BASELINE.md § Accessibility is amended, split between a single ring for inputs (§ Forms, unaffected) and the dual ring for buttons: one ring colour cannot serve six variants on two surfaces, which is exactly the structural finding above. Applied, together with card 16's merge, in card 19 – 131 tests passing, npm run build clean. Disabled buttons already in the app are now surfaced as non-conformant (four instances, see card 19's closure note) rather than fixed, per this card's own "not proposed here."
18 · [CC] Extend the test surface beyond buttons, as a conformance surface
Files: baseline/buttons/index.html ← the pattern to follow · baseline/tokens/tokens.test.ts ← the other half of the control · _packages/README.md ← rule 6, why this precedes shipping
A gate on card 14, not a nicety (Torfinn, 2026-07-30): the visual design instructions get tested before any of this is shared. Card 13 regenerates the packages and card 14 ships them, so this sits in front of the shipment.
Why it exists. The fitness check tests token values and is the right control for "does white on this fill clear 4.5:1". It cannot catch a rule that is arithmetically fine and visually broken: a focus ring specified as petrol on a petrol fill fails no test, because no test asks the question. Twelve of the fifteen button failures survived exactly there – computable all along, never rendered.
Buttons are done. The same treatment is owed to the rule sets that carry contrast or figure-ground claims and currently have no rendered form: form inputs and their focus and error states, tables and row states, status and feedback messages including the new icon-plus-label rule, surface temperature combinations, and the chart palette against its backgrounds.
Scope note: this is an evaluation surface, not a component library, and it stays out of app/. It specifies no values – it renders what the tokens already say and measures the result. Where it finds a gap, the gap becomes a card.
Rescoped 2026-08-04, same work, harder done-when. The card was written as a visual test surface, which measures what a person can see. The claims in BASELINE.md § Accessibility are wider than that: 44px minimum touch target, focus-visible with a petrol ring, prefers-reduced-motion: reduce disabling all animation, aria-invalid + aria-describedby on form errors, and the 14px floor at component level rather than token level. None of those has a test today. The contrast fitness check covers colour and token conformance only, so five of the seven claims in that section are asserted and unmeasured.
Why this rescope and not a new card. aleris.se publishes a tillgänglighetsredogörelse that declares partial compliance with DOS-lagen and names missing focus indicators on interactive elements and unlabeled interactive elements and unclear status messages as known defects, self-assessed August 2025. Those are this card's subject matter. A surface that renders what the tokens say and measures the result is the instrument that turns that declaration from a statement into a fact.
Done when: each rule set above either has a test surface or a recorded reason it needs none; every claim in BASELINE.md § Accessibility has either a test or a recorded reason it cannot have one; and nothing ships in card 14 that has never been looked at.
CLOSED 2026-08-05. baseline/conformance/index.html (new, same pattern as baseline/buttons/index.html) plus tokens.test.ts Tests 7–13 cover forms, status/goal colours, tables, page surfaces, the chart palette, the 14px floor, and the 44px touch-target token. 169 tests passing (was 153 before this card, 116 before card 19), npm run build clean.
Eight genuine failures surfaced, none on any list before this card, seven left unfixed here (the eighth, the 14px floor, is fixed in card 31, the same session):
- Input error text, 4.28:1 on sand-100 – below AA, though it clears the floor on white (5.03:1).
- Disabled input boundary, 1.28:1 against sand-100 – the button-disabled shape recurring somewhere it isn't policy-exempt: inputs legitimately disable.
goal-borderline, 1.73:1 against white – fails even the 3:1 boundary floor its three sibling goal colours all clear.- Table row hover, 1.01:1 against sand-100 – a hovered row gives no perceivable feedback at all.
- Table row selected, 1.13:1 against sand-100 – the identical petrol-100 tint that ruled out card 16's option A, now failing as a row state.
- All three
--chart-pair-*tokens, built for two-series comparison charts, fail 3:1 against each other: 1.29:1, 2.76:1, 2.18:1. - The 14px floor token itself renders at 12.48px. The larger finding of the card: the type scale's own documentation assumes a root font-size of 18px that nothing in
app/ever sets, so every size in the scale – not just the floor – renders ~11% smaller than documented. One root cause, not seven; which fix is Torfinn's call, not made here. app/tools/icon-picker/qlik/qlik.module.csshas 23 literal sub-14px font-size declarations, unrelated to the root-cause finding above.
Two of the seven BASELINE.md § Accessibility claims are structural claims about components that don't exist in app/ yet (colour-alone meaning for status messages, aria-invalid/aria-describedby on form errors, prefers-reduced-motion) and are correctly left unasserted rather than faked – recorded in the audit table added to that section. The full seven-claim audit lives there, not duplicated here.
32 · [CC] Phase B, sequencing step 4: reconcile patterns/ with baseline/patterns/
Files: patterns/ ← 7 files, moved in · lib/baseline-content.ts · app/baseline/raw/[...slug]/route.ts · lib/route-manifest.test.ts
Part of Phase B (~/.claude/plans/joyful-petting-hopcroft.md), pulled ahead of the rest of the sequencing because it needed no content decision — a pure duplicate-name reconciliation between an empty stub and the real, live library.
CLOSED 2026-08-05. Moved the 7-file pattern library into patterns/ via git mv. Routes are unchanged — still /baseline/patterns/* — via a new resolveBaselineSource() in lib/baseline-content.ts: checks baseline/<path> first, falls back to the item's own path from the repo root once a category's files leave baseline/. Wired into getBaselinePageData, the /baseline/raw/[...slug] route, and the route-manifest test's own baseline manifest builder (which had baseline/ hardcoded onto every item path and would otherwise report all 7 as missing files).
One wrong turn, corrected before it shipped. First attempt put the resolver in lib/baseline-nav.ts, which seemed like the natural home since all three call sites already import from it. Broke the build: that module is also imported by the client-side SidebarInteractive.tsx, and fs/path can't ship in a client bundle. Moved the resolver to lib/baseline-content.ts, which is server-only.
Verified live, not just via the build's static classification — started a dev server and curled all 7 moved routes plus the raw endpoint: all return 200, and /baseline/raw/patterns/hero.md serves the actual moved file's content, not a cached or stale copy.
Done when: patterns/ holds the real library, baseline/patterns/ is gone, every route that served from it still resolves. All three true — 174 tests passing, npm run build clean.
33 · [T] Write the three constitutional rule statements
Files: constitutional/group-scope.md · constitutional/language-policy.md · constitutional/tokens-are-canonical.md
Not pullable by Claude. Phase B's sequencing puts "constitutional core first" — filling these before the rest of the corpus moves in, so later moves can declare depends_on against a real rule rather than a stub. Card 26 (accessibility) and the earlier voice-is-the-present-expert.md show what "filled" looks like for this layer, and both are the same shape: real, already-decided content, extracted and reformatted. These three are different. Each is a template with the rule statement itself — not the surrounding scaffolding — marked ⚑ pending Torfinn, and each names Author: Torfinn in its own frontmatter. There is no already-written source lying around to extract from the way there was for accessibility and voice; the rule has to be decided and stated, not found.
What each one is asking, concretely (from the stub's own Related field and audience line):
group-scope.md— the jurisdictional scope claim: what "Aleris Group" means for this corpus across se/no/dk, and what follows from it.language-policy.mdis written to depend on this being settled first.language-policy.md— overlaps card 25's still-open B2 half (which language the published site serves) but is not the same question; B2 is a publishing decision, this is the constitutional statement of why. Likely the two get decided together rather than in either order.tokens-are-canonical.md— the rule that token values are the single source of truth, notBASELINE.md's hard-rules list restating them. This is also what Phase B step 6 (theBASELINE.mdsplit) needs a real home to move hard rules into — that step is blocked on this card, not the other way round.
What Claude can do once each is decided: format it to the sibling constitutional convention (frontmatter, Author/Audience/Supersedes/Related, Detection of violation, Origin, Open, Related) the way accessibility and voice were formatted — that part is mechanical. The rule statement itself is not.
Done when: all three read like voice-is-the-present-expert.md — no ⚑ pending Torfinn markers left — and tokens-are-canonical.md specifically gives Phase B step 6 (the BASELINE.md split) a home to move into.
CLOSED 2026-08-06. All three accepted, but by three different routes — not one mechanical pass across three identical stubs.
group-scope.md— genuinely decided from scratch, across two reasoning passes in conversation. First narrowed to Sweden-only, after a "this corpus binds Aleris Group across se/no/dk" framing failed the constitutional-core-test's scoped-boundary-vs-override distinction (it described a future organisational state, not a present fact). Then widened back to Aleris Group once the narrower version was judged to bake today's organisational politics into the rule itself, rather than state the authority the corpus is meant to have. Landed as an authority claim, deliberately decoupled from present compliance — Detection of violation carries that distinction, not the rule text. The market-inclusion roadmap (how Norway and Denmark actually get eased in) was kept fully off the page, not even anOpenpointer, and flagged as a doab.md candidate for TAKS instead.language-policy.md— extraction, not fresh authorship. The rule was already decided 2026-05-30 (workspace/status/changelog.md) and only lacked a home; this page is that ratification, not a new decision. One question stayed genuinely open on the page rather than being forced either way: whether this rule interacts with card 25's site-language publishing decision (route B) — unresolved on purpose, not guessed at.tokens-are-canonical.md— also extraction: generalises a principle already live inBASELINE.md's Token system section and already enforced incident-by-incident by cards 8, 21, 22 and 24. Gives Phase B step 6 the home it was blocked on — not yet executed, but no longer blocked.
One piece of work surfaced while drafting language-policy.md, and was split out rather than folded in. The three Swedish Baseline voice guides need English "originals" authored for them to depend on — reverse engineering, not translation, since the Swedish is accepted and accurate as it stands and there was never an English source to translate from. That became card 34, still in Doing: three full Claude-drafted English texts delivered to workspace/authoring/, unverified, awaiting Torfinn's voice pass.
34 · [T] Author English Group-level originals for the three Baseline voice guides
Files: baseline/voice/1-den-nara-experten-karnguid.md · baseline/voice/2-den-nara-experten-dokumenttyper.md · baseline/voice/3-den-nara-experten-digital-rost-v2.md ← the three Swedish sources, active and accurate · workspace/authoring/ ← English drafts land here
New 2026-08-06, surfaced while drafting constitutional/language-policy.md (card 33). The route-manifest gate (card 27) already flagged these three as a language mismatch — Swedish content under Baseline's hardcoded lang="en" — and the language-exit brief left "whether Baseline's three Swedish voice guides get translated or relabelled" explicitly open (§5).
Not a translation problem, a provenance problem. The three guides were authored directly in Swedish and are accurate as they stand — nothing about them is wrong. They simply have no English original to point to, and language-policy.md (card 33) requires one. Torfinn's framing: authoring the missing English "originals" that the Swedish is retroactively "based on" is reverse engineering, not translation. The Swedish stays exactly as it is; English is constructed to stand behind it as the Group-level source.
What Claude can do: produce a high-quality English draft of each guide as a starting point — not a rough machine pass, the best version achievable short of Torfinn's own voice pass. Same shape as the existing _translation-notes.md precedent: flagged, unverified, his to correct on sight. What Claude cannot do: finalize it as the canonical original. These three become the source every future reference points to, so the voice pass is not optional the way it might be for a lower-stakes page.
Done when: all three have an English draft in workspace/authoring/ for Torfinn's review, and a decision is recorded on whether they graduate to real Baseline originals — with the Swedish repointed as depending on them — or stay flagged as drafts.
35 · [CC] Triage the domain owner's interior guide; record what it decides
Files: workspace/sources/interior-fysisk-miljo-v2-2026-08-06.md · workspace/sources/interior-fysisk-miljo-v2-2026-08-06.docx · workspace/evaluations/interior-v2-compliance-2026-08-06.md · workspace/decisions/decision-record-physical-interior-2026-08-06.md · workspace/sources/physical-accessibility-floors-2026-08-06.md · workspace/sources/wayfinding-signage-standards-research-2026-06-15.md ← git mv'd out of the rescued archive · workspace/sources/arrival-phygital-interior-research-2026-06-15.md ← same
New and CLOSED 2026-08-06. Interiör och fysisk miljö.docx arrived from Sanna, who owns the physical domain.
The reframe is the card's main content, and it happened mid-session. The first pass read the document as a candidate physical/interior.md and judged it against _TEMPLATE.md — no frontmatter, no depends_on, bullet-dominant, noun headings, no negative form, no Origin/Open/Related. Torfinn's correction: it is a guide for humans, a derived rendering of the physical layer in the same category as the downloadable kits, and it is written by the domain owner, so her rules carry weight rather than awaiting correction. Both halves of that change the answer. The template tests do not apply to a derived document, and the substantive content is not draft prose but decisions.
The precedent that fits, and it already exists in the corpus: workspace/archive/playbook-vardforsakringsdagen-2026-04-23.triage.md. Sanna's event playbook was evaluated, deliberately not absorbed (roughly 80% operations, 20% brand), referenced as a worked example instead, and the gap it exposed — no home for in-person presence — became new scope. Same three moves here: don't absorb the guide, take what it decides, fix what it exposes.
Five owner positions recorded, wording carried over unchanged, in workspace/decisions/decision-record-physical-interior-2026-08-06.md: a fifth principle (Ansvar och säkerhet — hygiene zones, cleanability, accessibility), the level model with content named per tier, the four variation factors, a form language with an explicit undvik list, and reception-as-welcome. All five are new relative to all three archived interior drafts. The level model closes v1's open B1 at the level a guide can close it. The record is proposed, not accepted, for two reasons stated in its Open: owner attribution needs Sanna's role rather than her name (_TEMPLATE.md's rule), and two of the five touch foundation/colour.md, which she does not own.
The palette boundary was resolved from an existing rule, not negotiated. foundation/constant-contextual.md already says "Paletten är konstant. Kompositionen är kontextuell." Applied: 70/20/10 is composition and is the physical domain's by right — recorded as decided, needs no ratification. sage is palette membership and is not hers — it appears nowhere in the corpus, and F1's hard rule is that every colour has a name, a value and a role. varmvit/ljusgrå most likely resolve by wording. That test settles every future physical-domain colour question without another conversation; the two live items go to card 37.
Three debts found running the other way — the corpus owing the domain, not the reverse. The guide asserts "miljön uppfyller gällande krav på fysisk, visuell och kognitiv tillgänglighet" with nothing to point at, and the reason is timing: the per-country floors were researched 2026-06-15, sat on a backup branch, and reached workspace/archive/ on 2026-08-03 — three days before the guide was written. She could not have cited them. Both research sources git mv'd into workspace/sources/, and the floors extracted to workspace/sources/physical-accessibility-floors-2026-08-06.md with the two caveats promoted to the front: they are web research, not primary sources, and the SE ≥0,40 (NCS lightness difference) and NO ≥0,8 (contrast value) figures are on different scales and must never be cited interchangeably. Teal has no physical position at all (card 38) and Ingen dark mode is not carried through, though F1 says it covers fysiska rum.
The duplication question is answered and it changes the reading. The guide is a strict subset of the three archived drafts — gone are reception acoustics and sightlines, waiting-room zoning, the logo placement table, the wall-communication rules with an owner and update cycle per element, staff areas, sound design, and all 21 of v1's open questions. interior-arrival-phygital-draft.md (27 KB, the most developed) opens by declaring itself a draft for Torfinn and Sanna to write together, with six [!utkast] slots marked skrivs av Torfinn/Sanna. Torfinn confirmed 2026-08-06 that he shared it with her over Teams and she has seen it. So the brevity is a choice, not an accident — and the corpus should stop treating those six slots as pending work until that is said either way. Recorded as Open 5 on the decision record.
Three specifics are unsourced and go to card 36. sage, the 70/20/10 ratio and the supplier names (Input Interiör, RP Möbler, IKEA) appear in no earlier draft and in neither research source. The document is LLM-assisted, so each needs confirming as a decision rather than a plausible detail. The supplier list matters most: v1's open B3 asked exactly that question and left it open; three names with no owner and no date is not an answer to it.
Done when: the guide is triaged rather than absorbed, its decisions are recorded somewhere the corpus can declare depends_on against, and the accessibility floors are citable from one page. All three true. Not done and deliberately not attempted: building physical/ — see card 40 for why that waits.
36 · [T] Physical domain ownership, and the three specifics to confirm with Sanna
Files: workspace/decisions/decision-record-physical-interior-2026-08-06.md ← Open 1 and 2 · documentation/site-map.md ← open decision 3 · workspace/archive/interior-fysisk-miljo-v1-inbox.md ← open question B3
New 2026-08-06 out of card 35. Two things, one conversation.
One: record the owner as a role. documentation/site-map.md's open decision 3 is "Per-domain ownership — who owns communication, physical, and each per-market core." Sanna owning physical answers a third of it for free. But _TEMPLATE.md's rule is that owner is a role, not a personal name — "People change; the repo's load-bearing facts must survive" — and Claude does not know her title. One field, and it unblocks promoting the decision record past proposed.
Two: confirm three specifics before they enter the corpus. Each appears only in the 2026-08-06 guide — not in any of the three archived drafts, not in either June research source. The guide is LLM-assisted, which makes the distinction between decided and plausibly generated worth one explicit pass:
sage— is this a colour she chose, or a word that arrived with the drafting? Determines whether card 37 is a palette-extension decision or a rewording.- 70/20/10 — recorded as hers by right (composition is the domain's), so this is confirmation rather than approval. But if the ratio came from the model rather than from her, it should not be recorded as a decision at all.
- The suppliers — Input Interiör, RP Möbler, IKEA. v1's open B3 was "Finns det befintliga leverantörsavtal som påverkar möbel- och materialval?", unanswered. If these are real agreements, that is a decision record and a changelog entry of its own. If they are illustrative, they should not sit in a document that reads as policy. Worth putting the tension to her directly: IKEA against the guide's own undvik trendmaterial, its hög kvalitet, and its sustainability line möbler som håller länge. That is not a gotcha — it is the kind of thing that is cheap to reconcile now and expensive to reconcile after a unit has been fitted out.
Also worth raising in the same conversation, though it is not a decision: she has seen interior-arrival-phygital-draft.md, so the six [!utkast] slots are either declined or deferred. Either answer is fine; the corpus just needs to know which, so it stops carrying them as live work.
Done when: the decision record carries a role in owner:, the three specifics each have a yes or no, and documentation/site-map.md's open decision 3 records physical as settled.
37 · [T] The palette boundary – sage, varmvit/ljusgrå, and who owns composition
Files: foundation/colour.md ← accepted, and the freeze/twin-sync history applies · workspace/decisions/decision-record-physical-interior-2026-08-06.md ← §3 · foundation/constant-contextual.md ← the rule that settles it
New 2026-08-06 out of card 35. Most of this card is already answered; what remains is one decision and one rewording.
The framing, so this doesn't get re-litigated. foundation/constant-contextual.md says "Paletten är konstant. Kompositionen är kontextuell." That draws the line between what a domain owner may decide and what belongs to Foundation, without needing a judgement call each time:
- 70 % bas / 20 % naturmaterial / 10 % accent is composition. The physical domain's, by right. Recorded as decided in the decision record §3. Nothing to approve — only to confirm it is really hers rather than the model's (card 36).
sageis membership. Not hers. F1's hard rule: "Använd aldrig godtyckliga HEX-värden. Varje färg i paletten har ett syfte och ett namn."sageappears nowhere in the corpus — not in F1, not in the tokens, not in the three archived interior drafts. This is the decision. Two routes: it enters F1 properly, documented the way every other member is — Pantone, CMYK, sRGB, HEX, a stated role, and a contrast row — or the intent is expressed through existing palette names. Route one is a real palette extension with a real ripple (tokens, the fitness check's out-of-set hex assertion,agent-baseline, the Swedish twin). Route two costs a sentence.varmvitandljusgråare probably neither — they read as material descriptions rather than palette claims, and F1 already hasvit(#FFFFFF),sand(#F2ECE4),sand 50(#FAF8F6) and named warm greys with roles. Likely a rewording, not a decision. Confirm and close.
The trap. foundation/colour.md is status: accepted and carries the freeze and twin-sync history from cards 5, 7 and 9 — the two Swedish rows that were deferred to the twin sync are still deferred. If sage goes in, it goes in through the same discipline: not a direct edit to an accepted page as a one-liner.
Why this is worth deciding rather than deferring: the guide is currently the only statement of Aleris interior policy, it is in circulation, and it names a colour the brand does not have. Every week that stands is a week someone can specify sage on a wall and be right to think they were following the guide.
Done when: sage has a route and, if it enters, a full F1 row plus the token ripple; varmvit/ljusgrå are either reworded or documented; and the decision record's §3 open half is closed.
Two of three closed 2026-08-06. 70/20/10 needed no ratification, per the framing above — only card 36's confirmation that it's really hers. varmvit/ljusgrå resolved as wording, not decision: they map to Sand 50 (#FAF8F6) and Slate (#D7D2CB), both already named and roled in F1's Neutrala toner table. A pointer note added there rather than restating the mapping — same discipline tokens-are-canonical.md just formalised. Decision record §3 updated to match. sage alone remains, and it's blocked on card 36 — moved to Blocked on the board rather than left in Ready now with nothing actually pullable.
English twin synced 2026-08-06, and it surfaced a bigger finding. foundation/en-draft/colour.md is not a translation of the Swedish page — its own frontmatter says translation_status: source, and its content is materially ahead: the post-supersede semantic naming (sand-50, gray-100, the seven-step orange ramp with interactive/brand split) that the Swedish page never received. Its own banner calls the Swedish page "outgoing... frozen until this one publishes," which per card 25's route B (Swedish stays the served site) may never happen as originally framed. The pointer note added there uses the English file's current names (sand-50, gray-100), not the Swedish page's Sand 50/Slate — so the two pointer notes are equivalent in meaning but not byte-identical, on purpose. Not resolved, flagged for Torfinn: whether the colour supersede's English-only content is stalled, abandoned, or still pending a publish path is a live question this session didn't raise or answer.
38 · [T] Teal's position in Norwegian physical environments
Files: foundation/colour.md ← Legacy-färger, and the Norway row · workspace/decisions/decision-record-physical-interior-2026-08-06.md ← §4
New 2026-08-06 out of card 35. The corpus has been describing this problem for months without ever telling the physical domain what to do about it.
What F1 already says: teal is legacy, not removed; petrol is primary in all markets including Norway; teal's status is identical everywhere and what differs is how strongly it survives in reality. The Norway row reads "Stark kvarvarande närvaro — tung i fysiska miljöer (skyltning, interiörer) och förekommer fortfarande på sätt som motsäger den nuvarande profilen." The handling rule is byt vid beröring — no campaign, replaced when a surface is updated anyway. workspace/archive/rescued-from-backup-branches-2026-08-03/physical/README.md lists this as a declared inbound dependency on Physical, and adds that the Pantone/CMYK values "valideras vid tryck — främst mot den norska tryckprofilen, eftersom det är där merparten av teal finns."
So: F1 says most of the remaining teal is in Norwegian interiors and signage, and that replace-on-touch will mostly be executed through physical updates. The 2026-08-06 interior guide names Land och lokala krav, names Norway, and says nothing about teal. That is not an omission on her part — nobody has given the physical domain a position, and it cannot be inferred from F1's market table alone.
What needs deciding, concretely. Replace-on-touch is clear for a printed template. In a building it is not: does a waiting room being refurnished count as "touching" the teal signage in the corridor outside it? Does a single teal door sign get replaced when the reception is redone, or does it wait for a signage programme? The rule works where the unit of change is a file; interiors need the unit of change named. Related and cheaper: whether teal specifically may not be specified for anything new in a Norwegian environment, which F1 already implies for all markets but has never said in physical terms.
Done when: the physical domain has a stated teal position — at minimum what counts as "touching" in a built environment, and an explicit no-new-specification line — recorded where a Norwegian interior brief would find it.
CLOSED 2026-08-06. Torfinn's ruling: touching is narrow — only the surface actually being renovated counts, so refurnishing a waiting room does not touch the corridor signage outside it. Signage inverts that on purpose: the touching unit for signage is the whole programme, not the individual sign, precisely because a lone petrol sign in an otherwise-teal system is an unplanned mixed style with no harmonisation date — a full signage replacement triggers the switch, a spontaneous single-sign swap reuses the existing design instead. The no-new-specification line was already general in F1 ("teal specificeras inte för nytt arbete"); restated in physical terms rather than newly decided. Recorded in full in the interior decision record §4, and made findable from where a brief would actually look: a pointer added to foundation/colour.md's Legacy-färger section rather than restating the policy there. English twin synced 2026-08-06 — see card 37's note on the same edit for what that sync surfaced about the two colour pages having diverged in substance, not just language.
39 · [CC] CONTENT_DIRS still lists physical against the ratified publishing rule — DONE 2026-08-27
Files: lib/content.ts · lib/route-manifest.test.ts
New 2026-08-06, found while working card 35. Independent of everything else on the physical track — it is a code/rule mismatch, not a content question, and it is pullable now.
Both files read const CONTENT_DIRS = ['foundation', 'physical']. The ratified publishing rule (Torfinn, 2026-08-04, route B1) is that foundation/ and baseline/ publish; everything else does not. physical/ does not currently exist, which is the only reason this has been harmless — and _state.md already records the observation from the backup-branch cleanup that restoring the rescued physical/ content to its original path "would publish three pages, one of them a draft."
The live risk, now that the physical track is open: the moment card 40 creates physical/interior.md with status: accepted, it publishes — through a leftover the publishing rule was written to close, with no diff anywhere saying "this page is now public." That is precisely the implicit-publication failure B1 replaced with an explicit one.
The trap. Do not just delete the string. lib/route-manifest.test.ts builds its manifest by walking CONTENT_DIRS (:240), and the gate is armed in both directions — it asserts that every accepted file under those dirs has a route, and that no route exists without a file. Removing physical changes what the gate covers, so the manifest needs regenerating in the same commit and the test needs to be seen passing for the right reason, not passing because it now checks less. Cookbook §19's lesson applies: confirm against something that actually renders, not just a green build.
Done when: CONTENT_DIRS matches the ratified rule in both files, the route manifest is regenerated in the same commit, and the gate is verified still armed — a deliberately-added accepted file under a non-publishing folder must not produce a route, and must not be silently ignored either.
40 · [CC] Build the physical source layer, and split wayfinding out of it
Files: physical/ ← does not exist yet · workspace/archive/rescued-from-backup-branches-2026-08-03/physical/{interior.md, interior-arrival-phygital-draft.md, README.md} · workspace/archive/interior-fysisk-miljo-v1-inbox.md · workspace/decisions/decision-record-physical-interior-2026-08-06.md · workspace/sources/physical-accessibility-floors-2026-08-06.md
New 2026-08-06 out of card 35. Waits on cards 36, 37 and 38 — all three are decisions the layer would have to state on its first page, and writing it first means writing around them.
What this is not. The first pass of card 35's evaluation recommended reconciling the four interior drafts into a single physical/interior.md. That recommendation followed from reading the 2026-08-06 guide as a candidate page, and it went away with the reframe. The source layer gets built from the archived drafts plus the recorded decisions — not by merging a human-facing guide into a corpus page.
The material to build from, and what each contributes. interior-arrival-phygital-draft.md (27 KB) is the base: most developed, and already structured along the patient journey — ankomst, incheckning, väntan, mötet och avskedet — with emotional mode as an explicit lens and a digifysiskt lager section. interior.md and the v1 inbox file carry the operational detail the guide dropped: reception acoustics and sightlines, waiting-room zoning, the logo placement table, the wall-communication rules with a function, an owner and an update cycle per element, staff areas, sound design. The decision record carries the five owner positions. The consolidation rule applies: source text carries over verbatim, nothing paraphrased, and anything the guide changes is marked as changing it rather than silently rewritten.
Two things to preserve that are easy to lose. v1's open A4 — reformulate self-check-in from a solution into a principle (patienten ska kunna ankomma utan att behöva verbalisera sitt ärende offentligt) so terminals and apps become implementation examples and the principle survives technology change. And the guide's undvik list, which is the closest thing in any of the four documents to the negative form the corpus requires of its own pages; it should become that section rather than staying inside a form-language subsection.
Wayfinding splits out in the same pass. The guide itself says of it "Ett eget kapitel." It has its own standards research (workspace/sources/wayfinding-signage-standards-research-2026-06-15.md), its own governance logic — follow established standard rather than choose from our list, which is the inverse of every other iconography arena — and its own open decision: one signage line across three countries or local adaptation, which Norway's reversing universal-design stance makes a design question and not only a compliance one. foundation/iconography.md already declares the skyltning & vägfinning arena OPEN and pointing here. Keeping it as a section inside interior is what has kept it unwritten.
Also fold in: physical/README.md's five collected inbound dependencies — it was written as a requirements inventory for exactly this moment and says so ("When Physical is planned for real, this table is the requirements inventory — start there"). And F1's Ingen dark mode, which states it covers fysiska rum and has never been carried through.
The trap: card 39. Do not create any physical/*.md with status: accepted until CONTENT_DIRS is fixed, or it publishes silently. Card 39 is [CC] and pullable now, so it should simply go first.
Done when: physical/ holds an interior page and a separate wayfinding page, both declaring depends_on against the decision record and the constitutional accessibility node, both non-publishing until a deliberate transit; the archived drafts are marked superseded with pointers rather than left as parallel copies; and every open question from v1 that is still open is carried forward rather than dropped.
41 · [T] Close the four-branch tail
Files: lib/branch-hygiene.test.ts ← IN_FLIGHT declares two of these · workspace/status/board.md
New 2026-08-06, from measuring the tail rather than guessing at it. The current work is not the problem — Phase B is committed straight to main and pushed, nothing unmerged. The tail is four old branches, and the ages are the finding:
| Branch | Ahead of main |
Last commit | Verdict |
|---|---|---|---|
origin/claude/thirsty-diffie-0cf507 |
0 | 2026-05-06 | Fully merged. Delete — nothing is owed. |
basepath-test |
1 | 2026-04-07 | A reverse-proxy experiment (basePath, robots.txt, sitemap.ts, next.config.ts). Four months old. Decide or delete. |
teal-legacy |
4 | 2026-06-08 | Superseded in substance. Teal-as-legacy reached main through the colour supersede; foundation/colour.md carries the full Legacy-färger section. Expected outcome is deletion, not a merge. |
feat/font-hosting |
30 | 2026-06-16 | The real one. Museo Sans hosting did land (baseline/fonts/*.woff2 and public/fonts/ are both on main). Still only here: the Supabase brand-schema infra, the crossborder + vårdförsäkring DB migrations, and one WIP refactor commit. |
Method, and its limit: commits were matched against main by message subject, so a cherry-pick or rebase reads as landed correctly — but it is indicative, not proof. feat/font-hosting's DB migrations are the ones worth opening properly rather than trusting the heuristic.
Why it is [T] and not [CC]: ref deletion cannot be done through the device bridge — unlink is denied under the mount, and an attempt on 2026-08-06 left two stale .lock files in .git that also could not be removed from there. The bridge has no network either, so git push origin --delete is out. This one is a terminal on the Mac.
rm -f .git/packed-refs.lock .git/refs/heads/basepath-test.lock # clear the stale locks first
git push origin --delete claude/thirsty-diffie-0cf507
git branch -D basepath-test && git push origin --delete basepath-test
Done when: the two safe branches are gone locally and on origin; feat/font-hosting and teal-legacy each have a recorded outcome (landed, or abandoned with a reason); their IN_FLIGHT entries in lib/branch-hygiene.test.ts are deleted — and the suite is green, which it cannot be while an entry outlives its branch. And it is on main.
CLOSED 2026-08-07. All four gone, both locally and on origin (confirmed by git fetch --prune; git branch -a shows only main). feat/font-hosting's one unique file, foundation/en-draft/imagery.md, was retrieved before deletion. teal-legacy confirmed superseded in substance, as expected. IN_FLIGHT in lib/branch-hygiene.test.ts is empty; suite green. This card's own commits (a1dce9c, 62b939d) landed the same morning the "Done means landed" rule did — the board itself was the one thing left un-updated, caught while cutting agent-baseline v0.4 (card 43).
42 · [T] The colour supersede's English page has no publish path under route B — DONE 2026-09-10
Files: foundation/en-draft/colour.md · foundation/colour.md · workspace/decisions/language-exit-decision-brief.md · workspace/status/changelog.md
New 2026-08-06, surfaced while syncing the physical-domain pointer notes onto the English colour page for cards 37 and 38.
What's actually there. foundation/en-draft/colour.md is not a translation of the Swedish page — its own frontmatter says translation_status: source, translation_source: null. Its content is a materially different, more advanced colour system: semantic token naming (sand-50, gray-100, petrol-500...) against the Swedish page's older names (Sand 50, Slate, Petrol 80); a seven-step orange ramp split into interactive and brand roles, against the Swedish page's four flat steps. This is the colour-supersede project (cards 1–11; milestone: merged to main 2026-08-03). The English page's own banner, dated 2026-07-30, says explicitly: "the Swedish foundation/colour.md is the outgoing page and is frozen until this one publishes."
The problem. Route B (card 25, accepted 2026-08-04) decided the served site stays Swedish-only; English becomes the unpublished source/authoring layer. That decision landed three days after this banner was written, and nothing has reconciled the two since. The banner's plan — publish the English content, retire the Swedish page — assumed a publish path that route B may have quietly closed. If Swedish stays the served page indefinitely, the supersede's actual content (the new orange ramp, the new naming) never reaches the site unless it's ported into Swedish by hand — a different, unscoped piece of work from "let the English page publish."
What's not yet known, and worth checking before deciding anything. Whether baseline/tokens/aleris-tokens.css — which merged 2026-08-03 — already reflects the new naming and ramp, independent of which Foundation page describes it in prose. If the live tokens already match English, only the Swedish Foundation page is stale, which is a much smaller problem than the whole supersede being stranded.
Done when: one of — (a) the Swedish page is ported to the new system and the English banner's plan is executed, route B or no route B, since Foundation content and site language are different questions; (b) the supersede is declared stalled or superseded itself, and the English page's status and banner are corrected to say so; or (c) some other resolution Torfinn names. Not Claude's to decide — this is a call about which colour system is actually current, and it touches the accepted Swedish page's freeze/twin-sync discipline directly.
Partly answered 2026-08-11, recorded 2026-08-25 (card 85) — route 3, Torfinn's decision. It sat unrecorded for two weeks on the unmerged colour-swedish-port branch, which is why this card still reads as fully open.
The decision. The English page points its colour-alone hard rule at [[constitutional/accessibility-is-foundational]]. That page is accepted, but constitutional/ is not in CONTENT_DIRS, so it does not publish — and a pointer to it would be a dead link on an accepted, published page. Route 3: port everything except that rule, and leave it as it stands until constitutional/ publishes. Not a resolution of this card's underlying question, and deliberately not one.
What it defers: prose blocks D and E — the colour-alone rule's expanded reasoning, and "Why colour cannot carry meaning on its own". Now card 86, which the branch promised to open and never did.
Second question answered the same day: the Orange section's new frame (block C) is Torfinn's own writing, not lifted from a deck despite the register shift against the rest of the page. It ports as-is, with no rewrite. Worth recording because it is expensive to re-ask and easy to assume the wrong way.
43 · [CC] Regenerate agent-baseline to v0.4
Files: _packages/agent-baseline/ (all 27 manifest files) · _packages/agent-baseline/MANIFEST.md · _packages/README.md · baseline/tokens/aleris-tokens.css ← two dead links fixed in canonical · foundation/colour.md · foundation/en-draft/colour.md · constitutional/group-scope.md, language-policy.md, tokens-are-canonical.md
New 2026-08-07, from Torfinn asking directly: the OS has been cleaned up all week — is anything actually gating a new package version, separate from card 14's Bitbucket blocker?
It was not just old, it was wrong. v0.3 (2026-08-05) had gone stale within hours of its own regeneration, because canonical kept moving the same day:
- The 14px accessibility floor. Card 18 found
--font-size-xsrendering at 12.48px, not 14px, because no root font-size was ever set for the rem scale to compute against. Card 19 fixedaleris-tokens.css/baseline-tokens.jsonthe same day. v0.3 shipped the pre-fix values — wrong on the exact claim the package exists to guarantee. - Four dead links. Card 26, same day, deleted
foundation/en-draft/constitutional/_accessibility-shared-draft.mdand consolidated the rule intoconstitutional/accessibility-is-foundational.md.BASELINE.md,governance/aleris-design-governance.md, and bothreference/*.mdfiles still pointed at the deleted file in v0.3. - Two more dead links, found here.
aleris-tokens.csshad two comment-block references to the same deleted file that card 26's own sweep missed — a defect in canonical, not just the package. Fixed inbaseline/tokens/aleris-tokens.cssdirectly, then re-mirrored, per_packages/README.mdrule 1 (packages are generated, never hand-patched). BASELINE.md's accessibility conformance table (card 18's seven-claim audit) postdated v0.3 entirely.
Two things were also sitting uncommitted, landed here rather than left for later: the physical-domain colour.md notes (cards 35, 38) and card 33's three constitutional rule statements — both marked Done on this board on 2026-08-06, neither actually on main until now. constitutional/ isn't in agent-baseline's scope, so card 33 didn't block the package's content, but it was the same "Done means landed" gap card 41 exists to catch, on the card that most directly produced Torfinn-authored content this week.
All 27 files re-copied verbatim from canonical and verified byte-for-byte (full sweep, not spot checks). 179 tests passing (was 174 – card 33's landing plus the constitutional pages' own coverage). npm run build clean. MANIFEST.md bumped to v0.4, "what changed since v0.3" rewritten, provenance table's patterns row corrected to patterns/ (card 32 moved it there). _packages/README.md's table row updated. The known-stale brand-orange sRGB triplet is carried forward unchanged, same as v0.3 – still parked behind card 5's freeze in canonical itself.
Done when: agent-baseline/ is at v0.4, every mirror verified, and both records (MANIFEST.md, _packages/README.md) reflect it. Not done, deliberately: committed locally only, not pushed to origin/main – pushing is Torfinn's call, flagged separately.
51 · [CC] Retire klinfys-baseline and bhsv-baseline.zip, make agent-baseline the single standard package
Files: _packages/README.md · _packages/_archive/README.md (new) · _packages/_archive/klinfys-baseline/, klinfys-baseline.zip, bhsv-baseline.zip (moved) · _packages/_archive/agent-baseline-pre-manifest-2026-06-17.zip (moved, renamed)
New 2026-08-10, Torfinn: consolidate the package system to one standard, now that agent-baseline is the only package with a working manifest/regeneration discipline behind it.
What was actually there. Two tracked-but-dead packages: klinfys-baseline/ (Klinfys redesign, nskyttberg/klinfys) and bhsv-baseline.zip (BHSV product team), both unversioned, both pre-dating the manifest rule, neither with a recorded file list to regenerate against — flagged as stale in _packages/README.md since 2026-08-05 with no path back to current. Also found in the same pass, not previously tracked in the README table at all: _packages/agent-baseline.zip, a stale pre-manifest zip from 2026-06-17 sitting alongside the current agent-baseline/ folder under the same base name — old enough to predate the folder's own manifest system, confusing to find next to the live package.
Moved, not deleted. All three retired artefacts (klinfys-baseline/, klinfys-baseline.zip, bhsv-baseline.zip) plus the stale zip (renamed agent-baseline-pre-manifest-2026-06-17.zip so its age is legible without opening it) go to a new _packages/_archive/, with its own README recording former consumer, last version, and retirement reason per package. _packages/README.md's current-packages table now carries only agent-baseline and its aleris-vibe-coding downstream mirror, with a standing line declaring agent-baseline the single standard and any future consumer — Klinfys or BHSV included, if either comes back — gets pointed at it rather than a bespoke cut.
Explicitly not done here, on Torfinn's own scoping: telling Klinfys or BHSV anything. This card is file consolidation and documentation, not consumer outreach — whether and how those teams are notified, and what (if anything) they're offered instead, is Torfinn's.
Done when: _packages/README.md shows one current standard package, the retired packages are archived with a recorded reason, and it is on main.
52 · [CC] Ship agent-baseline v0.5 to aleris-vibe-coding
Files: (in aleris-vibe-coding, not this repo) docs/agent-baseline/* · PR #1
New 2026-08-10, the hand-off card 49/51 both left open: agent-baseline had been at v0.5 since card 49 closed, but the downstream mirror in Richard's repo was still v0.4 (card 14, 2026-08-07). Torfinn supplied an Atlassian API token the same session it was raised — same credential shape card 14 needed (git clone auth and the Bitbucket REST API authenticate the token two different ways: x-bitbucket-api-token-auth as username for git's smart-HTTP, the Atlassian account email for the REST API).
One thing checked before touching anything, and it changed the plan. v0.4 was never merged to aleris-vibe-coding's default branch — it has sat on branch add-brand-os in open PR #1 since 2026-08-07, unreviewed. Confirmed via the REST API (GET .../pullrequests/1 → state: OPEN) before assuming a fresh branch and a second PR were the right move; they weren't. Opening PR #2 against the same content would have split Richard's review across two open PRs for the same package.
What actually happened: cloned add-brand-os, found one file the manifest doesn't track — FEEDBACK.md, added in the same original commit as v0.4, byte-identical to the current source copy — so a full sync (rsync --delete) was safe rather than selectively patching. Replaced docs/agent-baseline/ wholesale with the current _packages/agent-baseline/ (30 files: 27 manifest + README/MANIFEST/FEEDBACK), which correctly dropped reference/aleris-design-tokens.md (retired from the package in v0.5) along with the rest. Pushed to add-brand-os, updating PR #1 in place rather than opening a new one. PR title updated (v0.4 → v0.5) and a comment posted summarizing what changed for Richard, since he's mid-review on an already-open PR that just moved under him. Bitbucket token used only transiently (scratchpad file, chmod 600, deleted at the end of the session) — never written into the cloned repo's git config or committed anywhere.
All 30 files verified byte-for-byte against _packages/agent-baseline/ before and after the push.
Done when: docs/agent-baseline/ in aleris-vibe-coding matches agent-baseline v0.5 byte-for-byte, PR #1 reflects the update, and _packages/README.md's mirror row shows v0.5. All true as of 2026-08-10. Not done, deliberately: merging PR #1 is still Richard's call.
53 · [T→CC] emotional-modes.md — resolve the reader-state rename before English can flip — DONE 2026-08-27
Files: foundation/en-draft/emotional-modes.md · foundation/emotional-modes.md (Swedish, currently live) · foundation/en-draft/constant-contextual.md (card 54, related) · foundation/en-draft/principles/voice.md (its own §"Across audiences" depends on this)
New 2026-08-11, from the language sweep run before flipping English Foundation content live (Torfinn: "I need the english files to be the live and accepted content"). This file's frontmatter says status: accepted; the file itself opens with "Do not treat as canonical or stakeholder-facing" — a real contradiction, not a formality, so it was held back rather than flipped with the other five.
What's actually open, not just untranslated: a 2026-05-31 proposal to broaden the concept from emotional mode to reader state as an umbrella term — emotional modes (worry/curiosity) stay the patient/next-of-kin family, professional audiences get a new dispositional states family (scrutiny, time-pressure). This is additive, not a breaking rename (Baseline component metadata and the 3-mode digital hypothesis are untouched either way), but it has never been confirmed. The file's own Open section adds three more: where next-of-kin actually belongs (emotional, dispositional, or a third "helper/task" framing — the "helplessness" trope is explicitly rejected throughout the corpus, so the current pairing with patients sits awkwardly); confirming the Baseline 3-mode hypothesis is referenced rather than duplicated; and validating the professional-audience table with people who actually do that work, since it's analytical, not researched.
Amended 2026-08-25 — one of the three open questions is moot, and a third mode is landing in this file's table. Decision 5 retires the A/B/C digital model rather than relocating it, so open question 3 — "confirming the Baseline 3-mode hypothesis is referenced rather than duplicated" — has no subject left. Two questions remain, the reader-state rename and where next-of-kin belongs. Pulling against this card also got harder in one respect: decision 3 adds balanced as a third emotional mode, in the same table this card is waiting on (card 73). Whoever writes one should write both.
Decided 2026-08-25 — reader state is the umbrella. Torfinn: "go with reader state." Open question 1 of three is closed. Question 3 was mooted by decision 5 (the Baseline 3-mode hypothesis is retired, so there is nothing to reference or duplicate). One question remains: where next-of-kin belongs — emotional, dispositional, or a third helper/task framing, given the corpus rejects the helplessness trope throughout.
Applied already, so the term does not have to move twice: foundation/en-draft/constant-contextual.md Example 1's table label and quick-test question 2 now read reader state, matching what en-draft/emotional-modes.md already used. The Foundation prose that states the umbrella on the page itself is still Torfinn's, and it lands in the same table as decision 3's balanced mode — one writing session, not two.
Done when: the reader-state rename is confirmed (or rejected) by Torfinn, the next-of-kin placement is decided, and the file no longer carries its own "not canonical" banner — at which point it can move with the other five (colour, typography, index, imagery, iconography) into foundation/, and it is on main.
54 · [T→CC] constant-contextual.md — confirm the contextual-axes framework before English can flip — RESOLVED 2026-08-25
Files: foundation/en-draft/constant-contextual.md · foundation/constant-contextual.md (Swedish, currently live) · foundation/en-draft/emotional-modes.md (card 53, related — this proposal names reader state as one of the axes) · foundation/en-draft/principles/voice.md (its own "fourth contextual axis" language depends on this)
New 2026-08-11, same language sweep as card 53. Frontmatter says status: accepted; the file opens "Do not treat as canonical or stakeholder-facing until Torfinn has revised it."
What's open: a single but load-bearing proposal, dated 2026-05-31 and never confirmed — that every identity element's contextual behaviour resolves into four axes (channel/format, reader state, audience, market/culture), and that each element declares which axes bind it rather than the axes applying globally. The proposal already works out specific cases (voice answers to all four; imagery answers to market; colour does not answer to market as expression, only as legacy/rollout presence) but is flagged as an open author-prompt, not a decision. principles/voice.md already writes as if this is settled ("the fourth contextual axis for voice") — so leaving it unconfirmed means one draft is quietly citing another draft's unconfirmed proposal as fact.
Amended 2026-08-25 — the objection to this card is now about the shape, not the membership. A session on 2026-08-25 proposed replacing the four axes with a named set of five factors the corpus's own rules read (decision 1). That proposal was withdrawn the same day — see card 79 — because documentation/dependency-reciprocity.md, amended 2026-08-24, argues that a pre-declared taxonomy is the wrong shape: "adjacency is per-decision, not global. There is no single adjacency graph over contexts, and a pre-declared taxonomy of context relations will be wrong — each decision defines its own reach."
That argument rules out the four axes and the five factors together, which changes what this card is deciding. The old objection was these are the wrong four. The live objection is there should not be a fixed list at all — a decision instead records the property of the situation its answer turned on, and reach is computed from that. Its evidence: the children's markers case (TAKS brandguide/_state.md, 2026-07-30), where the constraint that reorganised the whole design was that the summons letter names a lettered waiting area — not channel, not reader state, not audience, not market. No pre-declared scheme would have contained it.
Still [T], still open, and deliberately not closed for you. Rejecting the four-axis model looks like a consequence but it is a decision, and it decides how F6 works. The measurement from the withdrawn session stands and is worth having either way: five rules in force (colour.md's two page backgrounds, iconography.md § Function breaches, BASELINE.md's 44/24px target, emotional-modes.md's mode, card 73's return frequency) each turn on a different property, and Sund vikt läkemedel's weekly check-in reads five ways at once. Note also that principles/voice.md still writes as if the four axes are settled, so leaving this open keeps one draft citing another draft's unconfirmed proposal as fact.
Done when: the four-axis model is confirmed, amended, or rejected by Torfinn, the per-element binding logic is stated as decided rather than proposed, and the file no longer carries its own "not canonical" banner
Resolved 2026-08-25 — rejected, and by a document that already existed. TAKS/1-projects/brandguide/context-architecture.md, updated 2026-08-24, is the answer to this card and was written before the card was looked at. (That document is now documentation/how-decisions-are-recorded.md, merged with dependency-reciprocity.md later the same day — card 79. The rule that generalises this card's outcome is constitutional/a-thinking-tool-stays-a-thinking-tool.md.) It supersedes language-architecture-hypothesis.md and it settles the axes question on its own terms:
- §9: "F6 is unchanged and stays a stance. This document does not enumerate constants and does not propose to."
- §3: "adjacency between contexts is per-decision, not global… a taxonomy of context relations authored up front will be wrong. Each decision defines its own reach, through the property it turned on."
- § "What this does not change": "Nothing here proposes a context taxonomy authored up front."
(Cited as "§169" until 2026-08-25 — that was a line number, and it had already moved. Governing documents are cited by section heading plus a quoted phrase; a line number goes stale on the next edit above it.)
So the four axes are not amended and not confirmed. They are superseded — by a model that keeps what they were reaching for (per-element reach) and drops the mechanism that failed (a global list). Torfinn's §4 is the honest replacement: a table of boundaries the system has actually met, "observed, not designed… open at the bottom", earning entries the way doab.md does.
What that document also settles, which this card could not have known: the position this session spent the day deriving — F6 stays a stance, structure lives in instances declaring their own reach — was already written down on 2026-08-24, with the same children's-markers evidence. Card 79's first open decision is therefore closed, not open.
The English page is prepared (commit d244107): proposal removed, Quick test heading restored, Example 3's audience framing dropped, and the dangling propagates_to: foundation/expression-calibration removed. Nothing blocks the flip except a voice pass on machine-assisted English, which is Torfinn's and not delegable.
And it retires F8. This card's proposal was F8's work arriving at F6 — brand-os-inventory.md §F8 asks "decision inputs: what determines where a context sites on the spectrum?", which is the same list in the same shape. documentation/site-map.md had kept Expression calibration as one of four core thinking tools while CLAUDE.md's phase list recorded it demoted; corrected 2026-08-25, and its naming question is moot. The digital instance in BASELINE.md § Surface temperature is enough, and the physical domain has its own.
Done when, restated: the flip lands after Torfinn's voice pass, and it is on main. ✔ 2026-08-27.
The flip, done. Swedish archived verbatim to _markets/sv/foundation/constant-contextual.md (sixth page in that package, sixteen days after the other five). English promoted to foundation/constant-contextual.md with lang: en, status: accepted, last_verified: 2026-08-27, the scaffolding banner stripped and the translation fields removed. foundation/en-draft/constant-contextual.md deleted — the draft is consumed. The route path is unchanged, so all 46 files that reference foundation/constant-contextual keep resolving; that is why this flip was cheap.
Two gates were crossed on purpose rather than worked around. lib/route-manifest.json now records about-the-brand/constant-and-contextual as lang: en, hand-edited, which is the deliberate acceptance card 27 built the gate for. And the flip failed the suite first: KNOWN_LANG_MISMATCH still listed this route as a known Swedish-under-English mismatch, and its own guard fired — "is no longer Swedish — remove it from the list". The list's comment had predicted the exact closing condition ("both close when their card closes and the English promotion lands") and the entry is now gone with the reason recorded. That check caught the stale entry before anything else did.
What the English page carries that the Swedish never did, and neither correction was back-ported to the archived copy: the Present Expert in place of the close expert, retired 2026-06-16 and missed here for two months precisely because this page never flipped; and Example 3 no longer framing the colour rule by audience.
The package mirror goes behind, deliberately. _packages/agent-baseline/foundation/constant-contextual.md is still the Swedish page and MANIFEST.md still lists this row as Swedish. Not patched — _packages/README.md rule 1: packages are generated, never edited. Drift shows in the board's Package status tab. The next cut picks it up, and a cut for a language flip is worth doing on its own rather than bundled.
One page left unflipped: emotional-modes.md, card 53. Its umbrella is confirmed but two questions remain open on it, both Foundation prose. — at which point it can move into foundation/, and it is on main.
55 · [T] Rewrite the "For AI systems" idiom rule in voice.md for an English source
Files: foundation/voice.md (after promotion) · viewports/voice-for-ai-systems.md (the generated adapter this section feeds, per principles/voice.md's own Open item 4)
New 2026-08-11, from the English-flip language sweep. voice.md's "For AI systems" section bans literal-translation artefacts — "Vi är här för dig", "tveka inte att höra av dig" — explicitly as a rule about Swedish source text. Now that this page is the canonical English source rather than a translation of it, the rule is self-referential and doesn't name any real failure mode in English.
Torfinn's decision (2026-08-11): rewrite it, not cut it. The gap: rewrite is voice content — deciding what the English-language equivalent failure looks like (stiff corporate-translation phrasing? AI-hedging boilerplate? something else) is a brand-voice call, not translation.
Not blocking: the rest of voice.md is reviewed and approved; this one bullet ships with a flag rather than holding up the page. See the bullet's own inline note for where it sits.
Done when: the bullet states a real English-language failure mode Torfinn has written, and it is on main.
56 · [CC] Regenerate agent-baseline to v0.6 and ship it to Richard
Files: _packages/agent-baseline/* (regenerated, 28 files + new foundation/iconography.md) · _packages/agent-baseline/MANIFEST.md, README.md · _packages/README.md · (in aleris-vibe-coding, external repo) docs/agent-baseline/*, PR #1
New 2026-08-11, Torfinn: "Ship it to Richard now too — but read Richard's repo first for instructions on how that repo works so that the package is cut in a way that wires into that software correctly." Follows directly from cards 51/52/53/54/55 — the English flip changed five of the package's Foundation pages and made a sixth (iconography.md) finished for the first time.
Read Richard's repo before cutting anything, per the brief. Cloned add-brand-os again. CLAUDE.md, AGENTS.md, and docs/aleris-standard.md's "Look and feel" section all point agents at docs/agent-baseline/BASELINE.md and the token files by path — none of that is affected by a content-language change, only by files moving or being renamed, neither of which happened. Confirmed there's nothing to fix in Richard's own wiring — this was wrong, and it is the reason card 68's adapter exists. Corrected 2026-08-19. The language reasoning here is sound as far as it goes: his Swedish instruction layer pointing at English content is fine, the same way a Swedish teacher's guide can point at an English textbook. But the brief asked whether the package wires into that software correctly, and this pass answered a narrower question — whether the language flip broke his wiring (it didn't) — and then recorded the narrow answer as a general one. Two real defects were in the files it read and passed over: the refresh command in docs/aleris-standard.md does not refresh docs/agent-baseline/ at all, and BASELINE.md is called always-on in three files while nothing loads it. Richard's review of this very PR said the package needed wiring to fit how his repo works — he was right, and Brand OS had told him in a PR comment that it already did.
What changed in the cut itself: index, colour, typography, voice, imagery now carry English content (reviewed, not machine-translated-and-shipped — see the changelog's 2026-08-11 entry for the language sweep that established which files were actually ready). iconography.md added — every prior version's manifest excluded it as "draft, ships once authored"; it's accepted now with Torfinn's own closing paragraph, so the exclusion reason no longer holds. constant-contextual.md/emotional-modes.md stay Swedish, matching canonical (cards 53/54 — their English drafts aren't decided). Everything else re-verified byte-for-byte unchanged: BASELINE.md, both token files, all governance/reference/voice/patterns content.
Shipped the same way as v0.5 (card 52): pushed onto the still-open add-brand-os branch rather than a new PR — PR #1 was still unmerged from the v0.5 update. Title bumped to v0.6, and the PR comment calls out specifically that this version changes language, not just values, since that's a different kind of change for Richard to notice than a token tweak.
Done when: docs/agent-baseline/ in aleris-vibe-coding matches agent-baseline v0.6 byte-for-byte, PR #1 reflects the update, and _packages/README.md's mirror row shows v0.6. All true as of 2026-08-11. Not done, deliberately: merging PR #1 is still Richard's call, same as every version before it.
57 · [T] Design the positive rule replacing "No dark mode"
Files: foundation/colour.md (the retirement note lives at the old rule's position) · foundation/en-draft/constitutional/colour-is-the-aleris-palette.md (same retirement, plus a resolved Open item) · documentation/site-spec-v2.md (corrected, no longer cites the retired rule as evidence of on-brand discipline)
New 2026-08-11. Torfinn: respecting a user's local/personal display settings — dark mode, high contrast, dynamic text size — is a legal requirement, the same class of obligation as the WCAG floors already on this page, not a brand preference Aleris can override. "No dark mode" retired the same day across both places it was stated as a non-negotiable (canonical and the still-draft constitutional page), plus the site-spec line that cited it as evidence the Brand OS site follows its own rules.
What's retired, and what isn't. The prohibition is gone. Nothing has replaced it — there is no dark-mode token set, no dark-surface contrast verification, no rule about what "on-brand in dark mode" even means. The Brand OS site itself is still light-only in practice; that's now an implementation gap, not a demonstration of discipline (site-spec corrected to say so).
Why this is [T] and can't be executed mechanically. A positive rule here is real design work — a dark palette (or a rule for how the existing palette inverts), contrast re-verification against dark surfaces, and a decision about whether high-contrast and dynamic-text-size get their own rules or ride on existing accessibility floors. None of that exists yet to port or verify; it has to be designed.
Done when: a positive rule exists in foundation/colour.md (or wherever the constitutional split lands it) stating what Aleris does in dark mode, high contrast, and at larger text sizes — and it is on main.
Scope question raised 2026-08-12 (consumer feedback, workspace/briefs/findings-from-sundviktlakemedel-build-2026-08-12.md §6/OQ3): does the eventual dark-mode rule cover instrumental surfaces (tools, admin, dashboards) the same way it covers communicative ones, or does instrumental get its own, separately-reasoned treatment? Torfinn: explicitly TBD — this needs a real design pass, not a quick mathematical or logical extrapolation from the light-mode rule. Don't let a builder (or Claude) infer an instrumental dark-mode answer from the eventual communicative one; it hasn't been designed.
58 · [T] Review the icon allowlist — too conservative
Files: data-products/iconography/allowlist.json (the patient-facing list itself) · foundation/iconography.md (the four-context model and the governance rules each context follows) · baseline/iconography/index.html (the visualisation)
New 2026-08-11. Torfinn: the allowlist is too conservative right now.
What's already on record about it. foundation/iconography.md (accepted the same day, card 51/56 sequence) states the model this list is supposed to implement: patient-facing gets the strictest selection because the cost of a wrong icon is highest there — "editors choose icons from a reviewed allowlist rather than freely... the friction is the point." That's the right shape; the question this card raises is whether the current contents of the list match how strict that actually needs to be, or whether it's stricter than the cost-of-mistake principle it's supposed to implement actually calls for.
Why this is [T]. Which icons clear the bar is a judgment call about what reads as trustworthy to a patient — not something to loosen mechanically without a reason for each addition.
Done when: the allowlist reflects a deliberately reviewed scope, not last review's default, and it is on main.
Progress 2026-09-05 — the data defects are fixed; the content half is still this card. sundviktlakemedel found (brief 2026-09-01) that all 78 entries carry __FILL_IN__ in both label fields, that the note claimed 79 icons and 55 ui-primitive entries while the array held 78 and 54, and that no test read the file. The note is corrected, and data-products/iconography/allowlist.test.ts now derives both counts from the entries, checks categories and families, and holds the placeholder total as a KNOWN figure of 78 — so filling labels moves a number the suite sees, and a new placeholder cannot hide under it. Eight mutations, eight fire. Still yours: the 156 label strings, and the scope review this card was opened for — the same brief found a payment icon is now a live need (Swish) and the list has none in any category.
Reframed 2026-09-08 (Torfinn). The list is not too conservative by accident: it was curated once, on 2026-04-30, for one purpose — the patient-facing context, second-pass clinical trim, 78 icons — and has been read as the whole system's allowlist since. Widening it is the job. What must survive the widening is the floor, which is documented and sound: principles/iconography.md § the floor — weapons, logos and trademarks, religious symbols, anything aggressive or alarmist, never in any context — plus the curation decisions the allowlist's own note records (no person-iconography, people stay in photography per imagery; anatomy reduced to heart-pulse; specialty iconography deferred to custom extensions). So the review has two halves: state the floor as the allowlist's exclusion rule in schemas/icon-allowlist-entry.md where it can be checked, then widen the list per context (the four-context model gives internal tools "a generous selection list" already, on paper). .font-awesome.md (2026-09-08) tells /add-icon to refuse anything off the list until then, so the narrowness now costs every icon insertion. The file references above predate Phase 0: foundation/iconography.md is principles/iconography.md.
59 · [CC] Package status tab — computed drift, not hand-written notes
Files: workspace/status/build-board-html.py (the PACKAGES config, package_status(), render_package_card(), the tab HTML/CSS) · workspace/status/board.html (generated) · _packages/README.md (rule 7, pointing here)
New 2026-08-12. Torfinn: a status page on the board, under a separate tab, showing which active packages are behind current main and which features — naming the exact example: v0.6 doesn't have the dark-mode retirement yet.
Computed, not asserted. Every prior drift note in _packages/README.md (the sRGB triplet parked behind a freeze, the radius default, now the dark-mode retirement) was hand-written prose someone had to remember to add and could forget to update — the same "measurable half taking over the analysis" failure mode (cookbook §13) in reverse: the prose half went stale because nothing checked it, mirroring how earlier sessions found the measurable half winning by default. This tab computes drift instead: PACKAGES declares which directories a package mirrors (seven rules for agent-baseline, not a 28-file list — a new file dropped into an already-mapped directory needs no script change to be tracked); the generation commit is the last commit to touch that package's own MANIFEST.md, which every regeneration this session has already updated in the same commit as the file copy, so there's no second date field to keep in sync; everything after that commit touching a mirrored canonical path is drift, shown with its actual commit hash and subject line — which, because commit messages in this repo already explain themselves, doubles as the human-readable "what changed" with nothing separate to write.
Verified against today's own example. agent-baseline v0.6 (generated commit 1681658) correctly shows foundation/colour.md as 1 of 28 files behind, citing commit 8169bdf, "Retire 'No dark mode' -- legal requirement, not a brand preference" — the exact case named in the brief.
Tabs are native radio+label, no JavaScript — both panels are always in the DOM, CSS decides which shows, and the page degrades to both-stacked rather than broken if CSS fails, matching this page's existing "readable without JS" pattern (cookbook pattern 1). Verified interactively: served locally (this file isn't part of the Next.js app, so file:// alone renders as a static snapshot in the preview tool used to check it) and clicked between tabs for real.
What this doesn't do. No alerting — nobody is notified when a package goes behind; the tab has to be opened to see it. Retired packages aren't tracked (no file list to check them against, same reasoning as _packages/README.md rule 1). A file matching no dir_map rule is flagged rather than silently skipped, so a future package with a different layout fails loud, not quiet.
Done when: the tab exists, computes real drift from git log, and is on main. True as of 2026-08-12.
60 · [CC] Fix imagery.md regression and its mis-filed market-package copy
Files: foundation/imagery.md · _markets/sv/foundation/imagery.md (removed) · _markets/sv/MANIFEST.md
New 2026-08-12, from consumer feedback: workspace/briefs/findings-from-sundviktlakemedel-build-2026-08-12.md §1/§2. The 2026-08-12 language sweep treated foundation/imagery.md as part of the uniform Swedish-canonical/English-draft flip it was doing for five other pages, when this page had already flipped to English canonical months earlier (2026-06-17, "Land F10 imagery"), a separate and unrelated migration. Net effect: the sweep overwrote the correct, already-accepted canonical body with an inferior draft that had been tombstoned since June and was accidentally revived by an intervening commit, and archived the actual correct content into _markets/sv/foundation/imagery.md mislabelled as "the Swedish original."
Root cause, traced via git log --follow: commit c46bc2f (2026-06-17) promoted foundation/imagery.md to English canonical and tombstoned foundation/en-draft/imagery.md as superseded. Commit 62b939d, part of unrelated branch-tail cleanup, un-tombstoned that draft file without checking its status. The 2026-08-11 flip (0ae89ff) then swept it up as if it were a live draft still needing promotion.
Fix: foundation/imagery.md restored to the fuller, correct body (Group Image Guidelines framing, the "Imagery in professional and internal surfaces" section, the fuller AI-generated-images and image-bank paragraphs, the market-variation paragraph, the three-layers closing paragraph) — the version that had been sitting, mislabelled, at _markets/sv/foundation/imagery.md. That file removed from the market package; _markets/sv/MANIFEST.md corrected to list five archived pages, not six, with a note explaining the removal.
Done when: foundation/imagery.md matches its pre-regression 2026-06-17 content plus today's date, the mis-filed market copy is gone, and the manifest reflects it. True as of 2026-08-12.
61 · [CC] BASELINE.md corrections from build feedback: confirm-button contradiction, stale 14px entry, target-size relaxation, density reframe
Files: baseline/BASELINE.md
New 2026-08-12, from workspace/briefs/findings-from-sundviktlakemedel-build-2026-08-12.md §3/§4/§6, and Torfinn's OQ2/OQ4 answers.
Confirm-button contradiction (§3). Hard rule 5 (line ~37) states confirm green is an indicator only, the button variant retired 2026-07-31, and calls itself "the rule of record." The Buttons section (line ~121) still listed a Confirm (green) button variant with no supersede note — the two directly contradicted each other with no pointer between them. Fixed the same way the hover-direction rule was already handled (line ~130's template): struck the entry, added a superseded note pointing back to hard rule 5.
Stale 14px conformance entry (§4). The accessibility conformance table said the 14px floor was "tested, and failing" because the token scale assumed an 18px root font-size the app never sets. Verified directly: tokens.test.ts's ROOT_FONT_SIZE_PX is 16 (confirmed nothing overrides it), and --font-size-xs is 0.875rem — exactly 14.0px at that real root. The test that checks this passes. Corrected the table row to say so, while keeping the still-real, still-unfixed qlik.module.css sub-14px literal-value note, which is unrelated to the token scale.
Target-size relaxed (OQ2, Torfinn: relax it). The blanket 44px minimum touch/click target is now 44px for patient-facing and touch contexts, and 24×24 CSS px (WCAG AA 2.5.8) for dense instrumental surfaces on desktop pointer input — the AAA-level 44px (2.5.5) was never a requirement everywhere, just where it was originally reasoned for touch. Conformance table row updated to match; no token yet represents the new floor, recorded as a gap rather than asserted.
Density switcher reframed (OQ4, Torfinn: note to builders). "Density is user-selectable in instrumental surfaces — persists as preference" read as a hard requirement every table must ship with. Reframed as an expectation to build toward — a first cut shipping only the default density is a known gap, not a violation.
Done when: all four corrections are in baseline/BASELINE.md and on main. True as of 2026-08-12.
62 · [T] Six tool-surface pattern files — content-boundary conflict, not yet resolved
Files: none yet — this card is the flag, not the work
New 2026-08-12, from workspace/briefs/findings-from-sundviktlakemedel-build-2026-08-12.md §5 and Torfinn's OQ5 answer ("write them now"). The brief identifies six missing tool-surface pattern-library entries (the gaps a real build hit with nothing in the pattern library to point to). Torfinn's instruction is to write them now rather than wait.
A seventh candidate arrived in the second deliverable (workspace/briefs/findings-from-sundviktlakemedel-build-part2-2026-08-12.md, predicted pattern #2): a confirm-with-attestation control, built by that consumer as Button variant="complete" — petrol plus a required check glyph, direct application of hard rule 5's confirm retirement. Unlike the third-state status indicator (that same brief's predicted pattern #1, which needs board card 65 decided first), this one has no open decision behind it — same shape as the other six. Folded into this card's scope rather than opened separately.
An eighth candidate arrived 2026-08-19, from a different direction — a consumer's written standard rather than a build's findings. aleris-vibe-coding's docs/environments.md:131 makes an environment badge mandatory in every app it seeds: the interface must show whether it is running DEV, STAGE or PROD, driven by an environment variable, never hardcoded to PROD. Brand OS has no pattern for it, so a component every app in that consumer must ship is improvised in every app — colour role, placement, whether it survives greyscale, and whether PROD should be badged at all or only the non-PROD environments. Folded in here rather than opened separately, the same way the seventh was. It is arguably the strongest of the eight: the requirement is mandatory, universal in that consumer, and predates every build that produced the other seven. A second consumer confirmed it 2026-08-28, and differently. gcc builds an environment marker of its own, inventing three colours Brand OS has never seen. So the eighth candidate now has two independent instances — one a written standard requiring the component, one a build that needed it and improvised — and the two do not agree on what it looks like. That is the shape the other seven candidates do not have. See _packages/adapters/vendored-app/PROFILE-gcc.md §7. Found while writing _packages/adapters/vibe-coding-seed/, and recorded in that adapter's GAPS.md — an adapter may not answer it, because inventing brand content in wiring is a hand-edited package one layer out.
The conflict. CLAUDE.md's content-creation boundary is explicit and was restated after an earlier miscalibration this same project: Claude does not "produce pattern examples for the pattern library" — that's listed as still-gated even after the 2026-07-29 calibration narrowed the boundary to exclude pure utility text. Six new pattern-library entries read as exactly what that sentence names, not utility text like a table cell or column header.
Not resolved by proceeding anyway. Raising this here rather than either silently writing the six files or silently sitting on the instruction. Options: (a) Claude drafts structure/scaffolding — headings, the problem statement, the token references, the "done when" — and Torfinn writes the pattern-defining prose and examples into that scaffold; (b) Torfinn writes the six files directly, using the brief's §5 gap list as the brief; (c) Torfinn decides these six specifically read as mechanical enough (they document existing token/rule combinations already decided elsewhere in Baseline, not new brand argument) to fall on the utility side of the boundary, the same recalibration that freed table cells and footnotes on 2026-07-29.
Done when: Torfinn picks an option above, or restates the boundary doesn't apply here and why — not before then.
63 · [CC] Fix a build-breaking comment bug in aleris-tokens.css, cut v0.6.2
Files: data-products/tokens/aleris-tokens.css · _packages/agent-baseline/tokens/aleris-tokens.css · _packages/agent-baseline/MANIFEST.md · _packages/agent-baseline/README.md · _packages/README.md
New 2026-08-12, from a second real build's feedback the same day, mid-shipment of v0.6.1. The Data Visualization comment block read "Use chart-cool-*/chart-warm-* for intentional temperature grouping" — the */ inside chart-cool-*/chart-warm-* closes the CSS comment early. Browsers recover silently; lightningcss (Vite's default minifier) does not, and fails the build outright. The consumer worked around it with cssMinify: false rather than editing the byte-for-byte token file — exactly right, and exactly the case for reporting it back rather than living with the workaround.
Root cause, not just the syntax. The comment named tokens that were never implemented — chart-sequence, chart-cool-*, chart-warm-* — none of which exist anywhere in the file. The real tokens are --chart-1 through --chart-12 (sequential) and --chart-pair-N-a/-b (paired). Rewriting the comment to name the real tokens fixed both problems at once: the syntax bug and the stale documentation that caused it. A full sweep of the file found no second occurrence of a mid-comment */. No token value changed — comment-only, verified by diff.
Confirms rather than duplicates the same feedback's second point: the 14px-floor fix from v0.6.1 (BASELINE.md's conformance table) is independently verified correct — "resolved, by measuring rather than reading." No further action there.
v0.6.2 cut rather than amending v0.6.1 in place, even though v0.6.1 hadn't yet reached Richard — each real defect gets its own dated, tracked version, matching how v0.3 going stale within hours was treated as a problem to fix forward from, not to silently patch over. All 28 package files verified byte-for-byte; 1 changed since v0.6.1 (the token file), 27 identical.
Done when: the fix is in canonical, agent-baseline is at v0.6.2, and it is on main. True as of 2026-08-12.
64 · [T] --border-subtle — a lighter row separator than gray-100
Files: data-products/tokens/aleris-tokens.css (a new colour + border token pair) · baseline/BASELINE.md § Tables
New 2026-08-12, from workspace/briefs/findings-from-sundviktlakemedel-build-part2-2026-08-12.md §3. --border-default (gray-100, #d7d2cb) is the only container border colour, so a dense worklist's row separator and its panel edge are forced to the same value — flattening the table's structure at scale (the consumer measured this at 250 rows). The build used --border-default for both, deliberately, rather than invent a value.
Checked whether an existing token already covers this — it doesn't. sand-50 (the instrumental page background) is close in lightness, but using a page-background colour as a row-separator border would make the rule nearly invisible against that same page, which isn't what a "lighter but still present" rule needs. A genuinely new value between gray-100 and the sand surfaces is required.
Why this is [T]. Same shape as the corner-radius default (card 45): a new value on the palette needs a design call, not a mechanical wiring — there is no "lighter gray" already decided elsewhere to point to.
Done when: a --border-subtle value is chosen and lands in aleris-tokens.css, BASELINE.md § Tables documents when to use it over --border-default, and it is on main.
65 · [T] A non-colour status treatment for a genuine third state
Files: data-products/tokens/aleris-tokens.css (possibly a new token, depending on the decision) · baseline/BASELINE.md § Status messages · foundation/colour.md (the colour-alone rule this depends on)
New 2026-08-12, from workspace/briefs/findings-from-sundviktlakemedel-build-part2-2026-08-12.md §5. A real status state — "could not assess", not a degraded version of either "clear" or "flagged" — had no token to build from: every existing status value (--status-confirm, --status-error, --status-warning) is a colour, and colour-alone is exactly what the constitutional rule forbids relying on. The consumer built it from border-style: dashed plus a distinct glyph — it works and survives greyscale, but "the shape that means uncertain" is now a decision made once, by one consumer, rather than something Brand OS owns.
Why this is [T]. What visual convention reads as "uncertain" system-wide (dashed border, a specific glyph, a hatch pattern, something else) is a visual-language decision, the same character as the button state model (cards 16/17) — not a token to wire mechanically.
Depends on this, once decided: the StatusChip three-state pattern named in the same brief's predicted patterns (distinct from card 62's six/seven, since it can't be written as a pattern until this decision exists — right now it would just document one consumer's improvisation).
Done when: a system-wide non-colour treatment for a third, genuinely-uncertain status state is decided, documented in BASELINE.md § Status messages, and it is on main.
66 · [CC] Cut agent-baseline v0.6.3: the three mechanical closes from the second build feedback
Files: _packages/agent-baseline/BASELINE.md · _packages/agent-baseline/tokens/aleris-tokens.css · _packages/agent-baseline/tokens/baseline-tokens.json · _packages/agent-baseline/MANIFEST.md · _packages/agent-baseline/README.md · _packages/README.md
New 2026-08-13, on Torfinn's request following the mechanical fixes from workspace/briefs/findings-from-sundviktlakemedel-build-part2-2026-08-12.md. The package status tab flagged agent-baseline v0.6.2 as 3 files behind main the moment those fixes landed in canonical — exactly the drift it exists to catch. This cut closes it.
Contents: --button-min-height-pointer (24px), the button text-zoom/EN 301 549 11.7 fix, and the pre-auth token-access documentation — all three items closed mechanically in the prior session, none requiring a design decision. --border-subtle (card 64) and the non-colour status treatment (card 65) are deliberately not in this cut — both are still open decisions, and shipping a package ahead of them would be inventing scope the same way _packages/README.md rule 1 exists to prevent.
All 28 files verified byte-for-byte; 3 changed since v0.6.2 (BASELINE.md, both token files), 25 confirmed identical.
Done when: agent-baseline is at v0.6.3, the package status tab shows it in sync with main again, and it is on main. True as of 2026-08-13.
67 · [CC] Split packaging into content core and adapter, and write the first adapter
Files: _packages/consumer-profile.md · _packages/adapters/README.md · _packages/adapters/vibe-coding-seed/ (MANIFEST, PROFILE, INSTALL, GAPS, six wiring artefacts) · _packages/README.md · CLAUDE.md
New 2026-08-19, from Torfinn's question about packaging for a growing number of consumers: as consumers multiply, do they need different kinds of packages, including wiring that complies with each consumer's own way of working? Answered by comparing the three that already exist. All three — aleris-vibe-coding, patientguide, sundviktlakemedel — take near-identical content and wire it three different ways: a copy in docs/ with prose pointers, an absolute path reference plus a vendored token file, and a token file copied into src/styles/ with a build workaround. Content is one package; wiring is one adapter per consumer type, and the adapter is the half that grows.
Two wiring defects found while profiling aleris-vibe-coding, both invisible from inside Brand OS because neither is content. The refresh command the consumer's own standard documents — cp /tmp/aleris-std/docs/*.md docs/ — does not refresh docs/agent-baseline/ at all: the glob does not recurse, so every project that follows the ritual keeps a frozen design system while believing it just updated, and two lines later an agent is told to remind the developer to run it. And BASELINE.md is called "alltid-på" in three files (CLAUDE.md:9, AGENTS.md:9, docs/aleris-standard.md:125) while nothing loads it — the @ import that would is present and proven at CLAUDE.md:5 for a different document. The adapter fixes both: an import for Claude Code, two numbered imperatives for Codex, which has no import mechanism, and a refresh script that replaces rather than merges, reports the version transition, and aborts without touching the project if the fetch is incomplete (tested against a local clone: restores a deleted subtree, removes a file the new version dropped, exits 1 on an incomplete fetch).
The highest-leverage piece is four lines on someone else's checklist. docs/production-checklist.md is the gate that actually blocks production in that consumer, filled honestly per project — and nothing on its 17 items touches brand, tokens or accessibility. Three Brand OS obligations are offered for it, worded to be verifiable by measurement, plus one of the consumer's own mandatory rules that had no line. Offered, not committed: it is Richard's gate.
Deliberately not answered: the environment badge pattern (folded into card 62), an owner for the per-hostname font allowlist, and adapter-drift detection. All in _packages/adapters/vibe-coding-seed/GAPS.md.
Done when: the split is on main and adapters/vibe-coding-seed/ is installed in the consumer's repo. The first half is done; the second is blocked on the same Bitbucket credentials as card 52, which now hold back two content point releases and an adapter.
68 · [CC] The vendored-app adapter, and the four expired workarounds it found
Files: _packages/adapters/vendored-app/ (MANIFEST, PROFILE, INSTALL, GAPS, wiring/ with six artefacts, instances/ with two filled records) · _packages/adapters/README.md
New 2026-08-19, immediately after card 67, on Torfinn's request: write the adapter for patientguide and sundviktlakemedel. Both profiled by reading the repos. They are one consumer type — an app on the forge that vendors package values into its own build — and the adapter is nothing like the seed one.
The finding that shaped it: for a seed repo, wiring is instruction; for an app with a build, wiring is assertion. Neither of these repos has the seed's problem. The token file is what their build compiles, their component layers declare no literals and a test asserts it, noRawElements.test.ts stops a screen bypassing the component layer, and both suites cite Brand OS's own tokens.test.ts as the mechanism they borrowed. What they had no mechanism for is the package's own provenance — whether the vendored file is unedited, whether a version lag is still deliberate, whether a local workaround still has a reason.
Run against both repos before installing, the check found four expired entries and one shared live defect. In sundviktlakemedel: cssMinify: false, whose own comment reads "revert this once the token file is fixed upstream" about a defect fixed in v0.6.2 seven days earlier; the foundation/colour.md read-from-canonical override, made unnecessary by v0.6.1; and var(--target-min, 44px), standing in for the token v0.6.3 added. In patientguide: the CLAUDE.md note stating the 14px floor renders at 12.48px, corrected upstream in v0.6.1 by measuring. A condition written in a code comment cannot fire — that is the whole argument for the record. And both vendored copies still carry the */ comment defect; patientguide inlines that file into every rendered page and PDF, where Chromium's leniency is the only reason nothing has broken.
Two decisions came out rather than being answered. Fonts split into two cases and only one is drift: measured against the font host, both patientguide.aleris.ai and sundviktlakemedel.aleris.ai are on the CORS allowlist, so patientguide's self-hosted woff2 on the screen path has no remaining reason — but the PDF path embeds a raw TrueType font program in a document sent to patients, and _packages/README.md rule 4 speaks only to linking. That needs the licence read, not reasoned about (GAPS.md item 1). Second: patientguide's most consequential finding, the colour roles that produced v0.6.4, reached Brand OS with no artefact on either side — nothing in workspace/briefs/ records it, while sundviktlakemedel's findings file produced two filed briefs and four releases. Shipping the file closes the writing half; the carrying half is unowned (GAPS.md item 4).
Done when: the adapter is on main and installed in both repos, with the four expired entries cleared. True as of 2026-08-19 — installed at 84de52b (sundviktlakemedel) and 7fc2ed6 (patientguide), 245 and 351 tests green, both checks clean.
What installing changed, beyond the four entries. sundviktlakemedel refreshed to v0.6.4 and dropped cssMinify: false; its build is clean with lightningcss minification on for the first time, and --target-min now falls back to var(--button-min-height-touch) rather than a literal — which closes the missing-token half without lowering anything, since whether that console uses the 24px AA floor is still Torfinn's ratification.
patientguide stays at v0.4 by acknowledged decision, and the reason is now measured rather than assumed. A token-by-token comparison found only two changed values between v0.4 and v0.6.4, neither referenced from the forms pipeline — but --card-radius now resolves to --radius-l (16px) per card 45, while screen.css:17 aliases --radius-card to --radius-s (4px). Adopting it changes every printed form and PDF, so it wants a render pass. Review by 2026-09-15.
The install's largest finding was not on the list. patientguide/web/src/app/globals.css hand-transcribes 388 tokens into its own :root, calling itself "Source of truth — all values come from aleris-tokens.css". 271 overlap the package and 23 hold different values — confirm green as #27ae60, the whole type scale one step small, --button-confirm-* still wired to a retired variant. One is a conformance defect: --font-size-xs: 0.78rem is 12.48px at that app's 16px root, its own comment reads "14px — accessibility floor", and it feeds helper text, validation errors and badges on a patient-facing surface. This is also where the 12.48px in that repo's CLAUDE.md actually came from — the note blamed the package's type scale, which v0.6.1 had already re-measured and fixed. Not repaired in place: --font-size-sm is 0.89rem, so raising xs alone collapses the bottom of the scale, and reconciling the scale is a visible decision across a whole app. Torfinn's; full list in that repo's BRAND-OS-FINDINGS.md, and the mechanism that would catch this class is GAPS.md item 6.
69 · [CC] Correct the font licence claim, cut v0.6.5, and close the PDF-embedding question
Files: baseline/setup.md § Step 1b · _packages/README.md rule 4 + version table · _packages/agent-baseline/README.md § Fonts · _packages/agent-baseline/MANIFEST.md · _packages/adapters/vendored-app/{GAPS.md, wiring/font-loading.md, instances/patientguide.brand-provenance.json}
New 2026-08-19. Torfinn: the Fontspring licence texts are public and Museo Sans is fully licensed. Read from the primary text rather than from this repo's paraphrase of it, and the answer inverts — the thing that was wrong was ours.
What the licence actually says. §2e expressly permits embedding the webfont in reports generated by the website, provided they are not sold for profit. §2a's prohibition is on linking the desktop font — the full CFF OpenType or TrueType built for desktop installation — not on font files generally; patientguide's embedded files are webfont-kit TTFs, name table Museo Sans 500 Regular Webfont. §2d limits use to websites the licensee owns or controls with no domain count, and Fontspring's Worry-Free terms state unlimited domains. §5 still requires separate licences for desktop, applications, ebooks/epubs and templates — a patient document an app generates is none of those.
So two claims this repo made were wrong, and both were stricter than the licence. baseline/setup.md said the EULA "licenses @font-face linking on Aleris-controlled domains only" — presenting our own CORS configuration as a legal term. Rule 4 said it grants "linking, not file distribution" — which reads as forbidding exactly what §2e permits. A compliant consumer pipeline sat flagged as a licensing question for weeks because of it, and a developer reading the shipped package would have avoided generating a branded PDF that is licensed.
Cut as v0.6.5 rather than edited in place, per rule 6 — the wrong claim shipped, and the mirror in aleris-vibe-coding still carries it at v0.6.2. No content file changed: all 28 verified byte-for-byte, 28 identical, 0 differing. Only the package's own README.md and MANIFEST.md moved.
The lesson is not about fonts. The rule paraphrased an external document nobody had on hand, and the paraphrase was stricter than the text. A rule that restates an external document should cite the clause, so the next reader can check it instead of inheriting it. Worth applying to every other rule in the corpus that summarises something legal or contractual.
Done when: canonical, rule 4 and the package all state the licence correctly with clause citations, v0.6.5 is cut, and the adapter's gap 1 is closed. True as of 2026-08-19.
70 · [T] The categorical chart order puts near-identical hues next to each other
Files: data-products/tokens/aleris-tokens.css (--chart-1…--chart-12) · whichever reference file carries dataviz guidance
New 2026-08-19, from reconciling patientguide's token transcription. Every value matched after correction except two — and the two that didn't are the interesting ones.
The chart primitives in sequence read: teal, mint, blue, blue-light, purple, purple-light, terracotta, terracotta-light, marine, olive, warm-grey, dark-sand. A five-series chart drawing --chart-1..5 in package order therefore gets blue immediately followed by blue-light; at six series it also gets purple followed by purple-light. Two near-identical pairs out of five, before considering colour-vision deficiency.
patientguide maps its five semantic slots to positions 1, 2, 3, 5, 7 instead, and that skip is almost certainly the better choice. It was left in place rather than aligned to the package, because aligning would have made adjacent series harder to distinguish — a regression wearing conformance's clothes.
Why this is [T]. Whether the twelve-step list is an order to draw in or just an inventory of available hues is a visual-language decision, the same character as cards 64 and 65. The light/dark pairs look designed for paired comparison — before/after, two-series — which --chart-pair-N-a/-b already serve; if that is what they are for, then --chart-1..12 is listing primitives rather than recommending a sequence, and saying so is the whole fix.
Done when: the dataviz guidance either states a categorical order that keeps adjacent slots distinguishable, or confirms sequential order with its reasoning — and patientguide's deviation is resolved against whichever it is.
71 · [CC] Rework the seed adapter to Richard's structure, and ship the wiring test he asked for
Files: _packages/adapters/vibe-coding-seed/ (v0.2 — two stanza files deleted, wiring/aleris-standard-patch.md rewritten as the whole wiring, wiring/verify-aleris-setup.mjs new, MANIFEST/INSTALL/PROFILE/GAPS) · _packages/adapters/README.md (new rule 4) · workspace/briefs/feedback-from-richard-pr1-2026-08-11.md (new)
New 2026-08-19, from Richard's actual review of PR #1, which Torfinn supplied and which had never been written down on this side: "Kolla en sväng till på strukturen. Claude.md och agents.md pekar på aleris-standard.md. Så jacka in där. Inte i Claude eller Agents." Plus: "kör några egna tester (eller låt Claude testa) och se att det hänger ihop också."
He was right, and the reason is stronger than the one he gave. docs/aleris-standard.md:119 says the refresh updates docs/ and not the project's own CLAUDE.md/AGENTS.md, so project-specific content is never overwritten. Wiring in those two files is therefore unrefreshable by that repo's own design — frozen at clone time in every child, forever. v0.1 put the wiring exactly there, in the same cut that fixed the refresh command for not updating the package. Demonstrated both ways against a copy of the repo: wiring in the thin files survives a refresh untouched and permanently stale; wiring in the standard is reverted by a refresh from an unpatched seed, which is the same mechanism working usefully once the seed carries it.
The second ask became a script rather than a test run, because a run answers once and the wiring has to keep holding in every child. verify-aleris-setup.mjs: seven checks, Swedish output, no dependencies — import chain, referenced paths, dead links in the package, strict CSS parse of the token file, wiring in the refreshable file, readable version, no font files. Run against the repo as it stands it reports three failures, exactly his three complaints, without being told what to look for.
Two process findings, both filed rather than just fixed. Richard's comment is now a brief in workspace/briefs/ — it reversed an adapter's central decision and had lived only in a PR for eight days, which is the same asymmetry adapters/vendored-app/GAPS.md item 4 names one layer out. And adapters/README.md gained rule 4: find the consumer's refresh path before choosing where wiring goes, because an unrefreshable instruction is worse than none — it looks maintained.
Done when: v0.2 is on main, installed on add-brand-os, PR #1 updated, and the verifier green in that repo. True as of 2026-08-20 — 7156dda, 12 paths changed, seven checks green, package v0.6.2 → v0.6.5.
One thing verified rather than assumed, in a scratch copy rather than the live repo: main carries no docs/agent-baseline/ at all, because PR #1 has never been merged. So the documented refresh, run in a project today, clones main and finds no package — and the script's completeness guard aborts before writing anything, leaving the project intact. Confirmed. It also means the wiring reaches child projects only once the PR lands, which is now the strongest argument for merging it.
72 · [T→CC] imagery.md splits on audience; a product splits on situation, and a surface can be both — DONE 2026-08-27
Files: foundation/imagery.md (the mode table and § "Imagery in professional and internal surfaces") · possibly foundation/constant-contextual.md and foundation/iconography.md, which carry the parallel boundary
New 2026-08-21, from workspace/archive/finding-patient-facing-instrumental-surface-2026-08-20.md (triaged there 2026-08-24, from workspace/incoming/) — a finding routed here from Sund vikt läkemedel after that app's visual design entered MVP scope. Arrived through the return path rather than being solved locally, which is the path working.
The gap. imagery.md divides on audience: patient-facing communication follows the mode table (Life / Care / Portrait by emotional mode), while professional and internal surfaces get restraint, function leading, often no hero image. The consuming product divides on situation: entry, pricing and waiting are communicative, while onboarding and the treatment loop are instrumental, because the patient is managing her own plan rather than being informed. Those are different axes, so a surface can be patient-facing and instrumental — and that surface is most of the app by time spent. The page gives no rule for it.
Both available readings fail on the page's own terms: reading it as patient-facing puts a lifestyle photograph above a weekly weight-submission screen; reading it as internal renders a consumer product the patient pays for as an admin tool.
Why this is [T]. Whichever axis wins is a Foundation-level decision that propagates. The finding names two candidate shapes without recommending either: (a) audience stays the axis and patient-facing-instrumental becomes a third case with its own rule; (b) situation becomes the axis and "professional and internal" is named as one instance of instrumental. (b) is the larger change and reaches constant-contextual.md and the iconography boundary.
Checked at triage, 2026-08-24 — the corpus already carries the other axis, in the page this card names as possibly affected. foundation/iconography.md § "Function breaches" divides on situation rather than audience: number icons are ruled out because "in a dashboard they compete with actual data and risk becoming the sole carrier of meaning. This applies specifically to instrumental surfaces; in a presentation they are unproblematic." So instrumental is already a live term in accepted Foundation content, used as a property of the surface and not of the reader. That is evidence for candidate (b) rather than a neutral fact — and either way, two pages that cross-reference each other divide on two different axes today.
Related and not in scope here: the same page records that a standalone asset specification is still missing and that the shared image bank is an ambition. A consumer needing real photography for a service with no existing image set meets all three at once.
Session 2026-08-25 — the axis question is still open, and the card got smaller. Five decisions were taken against this card and cards 73/75 (workspace/decisions/decision-record-context-factors-and-emotional-modes-2026-08-25.md), then two of them moved on Torfinn's review pass the same day. What reaches this card:
- The axis is not decided. Decision 1 would have replaced "situation" with a named set of five factors in F6. It is withdrawn — see card 79. So neither candidate (a) nor (b) above has won, and this card still holds the question it was opened with.
- Decision 2 is orphaned with it. It said imagery declares which factors bind it, with a behaviour for each, and there is now no factor list to declare from. The instinct survives and the open question becomes this card's: does an imagery rule name a factor from a list, or record the property that rule turned on? The second is the shape
documentation/dependency-reciprocity.mdargues for. - What is decided (decision 4). A patient-facing instrumental view may carry imagery inside components that have their own job; the restraint rule in § "Imagery in professional and internal surfaces" is not driven by instrumentality but by whether the person's time on the surface is their job, and it moves there; and the image never holds the hero position on a working surface — the top of the screen belongs to the task. That last rule is what blocks the failure this card names, a lifestyle photograph above a weekly weight-submission screen. Recorded position, Torfinn 2026-08-25, not a research finding: a patient's instrumental view may be less efficient and more emotional than a clinician's view of the same kind.
The equipment image is not part of this, and there is no contradiction to settle. The session briefly permitted it and Torfinn struck that on review: it was a passing example and it was wrong. imagery.md § "What Aleris images are not" — "Not clinically documentary. Images of equipment with no person, sterile corridors, data screens with graphs" — stands unchanged, unscoped, untouched. Worth recording why permitting it would have been expensive as well as wrong: the prohibition is not the only sentence that would have had to move. § "Why the images look the way they do" carries "The person at the centre. Images of equipment, premises, or processes serve as support — never as the main subject" — one of the three choices the whole page rests on. Scoping the "is not" bullet alone would have left the founding choice contradicting it. The exception Torfinn did name — clinically appropriate instructional material — turns out to have no category anywhere in the corpus, and is card 84.
Done when: imagery.md states which axis governs and gives the patient-facing instrumental surface a rule, or records why it does not need one — and it is on main.
73 · [T→CC] Both emotional modes are relative to a pending event; a habitually revisited surface has neither — DONE 2026-08-27
Files: foundation/emotional-modes.md · foundation/constant-contextual.md (its worked example has the same shape)
New 2026-08-21, same finding as card 72.
The gap. emotional-modes.md names oro and nyfikenhet, and both are defined relative to something pending — oro before a result or procedure, nyfikenhet toward a choice not yet made. The page's own examples are the waiting room before and the recovery room after: two bounded occasions. Nothing in the model describes a surface someone returns to weekly for months, where nothing is pending and nothing is being decided. The app's home screen and its weekly check-in are exactly that.
Structural, not a tone question. Both pages state that emotional mode governs information architecture rather than style — what leads, what is played down, how much is explained. So an unnamed mode leaves the information architecture of the app's most-used screen with nothing to reason from.
The page also says digital products work with a more detailed model, and that model is not in the package. Whether this gap is that missing model or something beside it is part of the question.
The finding's own falsifier, tested at triage 2026-08-24 — this is a corpus gap, not a packaging gap. The finding said finding 2 would be the cheaper problem if the detailed digital model existed outside the package. It exists nowhere that publishes: the three-mode model (anxiety-driven, aspiration-driven, latent anxiety) appears only under workspace/ — authoring/ALERIS-DESIGN-WORKING.md, five files in archive/, sources/baseline-web-view-research.md, research/brand-os-inventory.md and the changelog. Corrected 2026-08-25: that last sentence is false. It holds for the three descriptive names, which is what the grep searched. Two files under foundation/, baseline/, patterns/ and principles/ carry no trace of any of the three.baseline/ carry the labels — governance/4-den-nara-experten-digital-behavior-v1.md:122 and iconography/curation-session-b.html:535. Neither publishes and neither ships, so the conclusion above survives; the sentence does not. Card 83.
And the gap survives the richer model, so finding it would not have answered this card. All three of its modes are also defined against a pending event: A is a condition or upcoming procedure, B a proactive choice, C an entry in B that a result may shift to A. None of them describes a surface with nothing pending.
A second defect found in the same check, cheaper and now card 75: foundation/emotional-modes.md promises that detailed model in accepted, published content, and nothing published carries it.
One candidate direction, Torfinn's own, recorded as exploration rather than proposal: visual material that may read as abstract and still connects to something emotional — offering something to feel toward without asserting what the person feels, which is structurally a different move from inferring a mode and selecting an image category from it.
Worth knowing about the finding's own history: an earlier draft argued this from a claim about what patients on this kind of service feel, built on a market read of 38 GLP-1 providers. Six companies choosing the same message is a fact about the companies, not about the people they address. That was removed on Torfinn's objection, and what survives is a claim about what the model covers.
Decided 2026-08-25 (decision 3) — closed by completing the model, not by bounding it. Oro and nyfikenhet gain a third mode: balanced — nothing pending, nothing being decided, the person moving ahead with her task at her own pace. The alternative on the table was to bound the model, declaring that emotional mode governs occasions and routing habitually revisited surfaces elsewhere. Bounding it leaves the model with no zero point, so oro and nyfikenhet could only ever be described against each other; a baseline gives it an origin and makes the other two describable as departures from it. Writing it is Torfinn's — Swedish, accepted page, Foundation prose.
Three constraints it must carry to be real, all open:
- Its own row in the existing table (Vi leder med / Vi tonar ner / Formatet stödjer), filled with a positive consequence rather than the midpoint of the other two. The model earns its place because choosing wrong is a mistake; a mode with no consequence of its own becomes the lazy default.
- A stated kind. Oro and nyfikenhet describe the person. If balanced describes what the design assumes in the absence of a pending event, that is a different kind of statement sitting in the same table — the page has to say so, and say the assumption is falsifiable. It is not a diagnosis of anyone.
- "At her own pace" is the load-bearing half, and it yields the concrete rule: the pace is hers, so the surface does not push. No urgency, no streak, no nudge as a default. Testable against Sund vikt läkemedel today.
Collision, unchanged: card 53 is open on this same file and this same table — the reader-state umbrella, marked RESOLVED in the en-draft on 2026-05-31 and never closed as a card. A third mode lands inside what 53 is waiting on. Card 53 is now partly relieved: decision 5 moots the third of its three open questions.
Done when: the model either covers a habitually revisited surface with no pending event, or says explicitly that it does not and what to do instead — and it is on main.
74 · [CC] Cut agent-baseline v0.7 — the logo, with its rules ratified
Files: _packages/agent-baseline/logo/ (4 new files) · _packages/agent-baseline/{BASELINE.md, README.md, MANIFEST.md} · _packages/README.md · foundation/logo-identity.md · baseline/BASELINE.md · _packages/adapters/vendored-app/instances/sundviktlakemedel.brand-provenance.json
New 2026-08-21. Torfinn, after a test of the seed wiring found the package had no logo: cut a version that includes it in a way any project cloning Richard's repo can use directly. Then, mid-cut, he stopped the obvious version of it: "Logo without rules is trouble."
He was right, and reading foundation/logo-identity.md showed how right. That page is a draft skeleton. Its factual rules are quoted from Aleris Brand Guidelines 2025 and marked "ratificera eller revidera", so they existed without being in force — and three of them could not be followed with what was on hand.
Ratified 2026-08-21, unchanged from the source: clear space at 50% of logo height (p. 6), the horizontal variant as primary (p. 5), the eight-item prohibition list verbatim (p. 8), and the sender rule from the ordlistan (2026-04-21). Recorded in the page against each item. The Varför and Test prose stays Torfinn's — a consumer placing a logo needs the rule and the threshold, not the argument.
One rule is new and derived: a 24px minimum height. No minimum size existed anywhere in the corpus — searched foundation, baseline, principles, constitutional and the package — and it is the number a digital consumer needs most. Derived from the accepted 14px type floor by rendering the SVG at 1930px and measuring ink rather than parsing paths, after a crude path parse gave bounding boxes exceeding the viewBox. The wordmark's letter height is 0.4933 of total logo height, so the measured floor is 20.4px and 24px is the rounded rule with margin. Same shape as v0.6.3's target-size token: mechanical completion of a rule already decided, and it moves if the 14px floor moves.
Three gaps stated in logo/README.md rather than hidden. No vertical variant file, so the space-reason exception in the ratified rule cannot be exercised. No symbol-only asset, and whether the symbol may stand alone is still open in the page. Digital identity past the favicon — app icon, profile image, notification sender name — is absent from the 2025 source and unassigned between Foundation and Baseline (that page's own decision point 3).
The favicon is provisional and says so: from TAKS/3-resources on Torfinn's word that it serves for now, 31×32 rather than square, so it resamples imperfectly to 16px.
A whole version, not a point release, per _packages/README.md rule 6 — it adds a package member. All 30 content files verified byte-for-byte against canonical, 30 identical.
Found while cutting: sundviktlakemedel renders no logo and has no favicon link. A patient-facing Aleris product with no Aleris mark, and the first consumer v0.7 could fix. Recorded in its provenance record as available-not-vendored; adding it is a UI change and Torfinn's to ask for.
Done when: v0.7 is on main and in aleris-vibe-coding on the PR branch, so a project cloning from there gets the logo and its rules. Brand OS side done; the consumer install is a push.
75 · [T→CC] emotional-modes.md promises a detailed digital model that nothing published carries — DONE 2026-08-25
Files: foundation/emotional-modes.md (the closing line of the two-mode section) · foundation/en-draft/emotional-modes.md (the same claim, plus a pointer) · workspace/authoring/ALERIS-DESIGN-WORKING.md § Mode A/B/C, where the model actually is
New 2026-08-24, found while triaging the finding behind cards 72 and 73 — not raised by the finding itself, which had no reason to look.
The defect. foundation/emotional-modes.md says "i digitala produkter arbetar vi med en mer detaljerad modell": a forward reference, in accepted and published Foundation content, to something a reader cannot reach. The en-draft version is more specific and therefore more wrong — it names the model (anxiety-driven, aspiration-driven, latent anxiety), calls it a hypothesis living in Baseline, and instructs "Reference it; don't restate it here. See baseline/ patterns." Nothing under Corrected 2026-08-25 — false as written, see the close below. The model lives only in baseline/, patterns/, principles/ or foundation/ mentions any of the three modes.workspace/ — chiefly authoring/ALERIS-DESIGN-WORKING.md, the working reference still being absorbed, plus five files in archive/, sources/baseline-web-view-research.md, research/brand-os-inventory.md and the changelog.
Why [T] rather than mechanical. The measurement is settled; the fix is a placement decision the record has already answered twice, differently. The 2026-03-26 session note puts the third mode "deeper in the system, linked from F7 when relevant"; sources/baseline-web-view-research.md puts the framework in Patterns, "not Foundations"; card 53's own open question 3 asks whether Baseline's hypothesis is referenced or duplicated. Where it lands is Foundation-level. The cheapest honest interim is a sentence that stops promising a page that does not exist.
Related: card 73 (the gap this model would not have closed), card 53 (the reader-state umbrella, whose resolution names this model), card 62 (the same question about absorbing from ALERIS-DESIGN-WORKING.md).
Done when: the claim in foundation/emotional-modes.md either resolves to something a reader can open, or is narrowed to what the corpus actually holds — and it is on main.
Closed 2026-08-25, in full — both halves, and it turned out to be [CC] after all. The card was opened [T] because the fix looked like a placement decision the record had answered twice, differently. Decision 5 removed the question by retiring the model rather than relocating it, which left nothing to place and made the card mechanical.
Bucket 0 decided (b), Torfinn's call. The brief offered folding the Swedish half into the eventual balanced-mode pass (one edit to an accepted page instead of two) or doing the card now in full. He took the second, knowing it means foundation/emotional-modes.md is edited twice in one cycle — once now, and again when balanced lands and "de två grundlägena" stops being true.
What was done. The middle clause deleted from the Swedish canonical sentence, leaving "Det finns fler nyanser, men de två grundlägena räcker för de flesta kommunikationsbeslut." The same clause deleted from the en-draft's (verbatim) line, which would otherwise have claimed a source sentence that no longer exists and kept promising the model this card is about — the card's own done-when covers the en-draft, so leaving it would have closed the card falsely. The bracketed author instruction with the baseline/ pointer deleted. _packages/agent-baseline/foundation/emotional-modes.md deliberately not touched (_packages/README.md rules 1 and 6); the mirror goes behind and the drift shows in the Package status tab.
The measurement this card was opened on was itself wrong, which is the more useful outcome. The absence claim searched the three descriptive names; Baseline refers to the model by label, in two files, one of them status: accepted. Neither publishes and neither ships, so nothing above changes — but the en-draft's "a hypothesis living in Baseline" was half right rather than fabricated, and the deletion stands on the other reason: decision 5 retired the model, so the pointer points at nothing. The two references now dangle. Card 83.
76 · [T→CC] Communicative/instrumental is used in three Foundation pages and defined in none — DONE 2026-08-25
Files: foundation/colour.md (rows 75–76 and the cream note at 180) · foundation/iconography.md (§ Function breaches) · foundation/imagery.md (by cross-reference) · baseline/BASELINE.md § Surface temperature (line 56, the only definition of record) · foundation/constant-contextual.md (F6, which never names either term)
New 2026-08-25, from the decision session behind cards 72/73/75. No card existed for this, and it is the mechanism behind card 72 rather than another instance of it.
The measurement. Three accepted Foundation pages use the communicative/instrumental distinction as though it were defined upstream. colour.md sets two page backgrounds by it — sand-100 for communicative contexts, sand-50 for instrumental. iconography.md rules out number icons "specifically to instrumental surfaces". imagery.md inherits it through the pages it cross-references. F6 never names either term anywhere.
The only definition of record is baseline/BASELINE.md § Surface temperature, and it opens by saying it applies F6: "Baseline applies F6 (foundation/constant-contextual.md) to surfaces through two modes: communicative and instrumental. Mode is determined by the situation, never the user's role — a patient managing their treatment plan in MyAleris is in instrumental mode." So Baseline is carrying a Foundation-level distinction on Foundation's behalf, and that is what let an accepted Foundation page contradict it silently: there was nothing upstream to contradict.
Two things that make this sharper than it looks.
- That
BASELINE.mdsentence has already answered card 72's question, in favour of situation, and has already named the patient-instrumental case as instrumental. An accepted Baseline page decided a Foundation-level question by writing it down first. - It uses the word "situation", which is the word the 2026-08-25 session set out to remove as unexaminable. It ships, in
_packages/agent-baseline/BASELINE.md, to every consumer. Whatever this card decides has to say what happens to that sentence.
Under the learning model (card 79), the fix may not be a Foundation definition at all. If reach is recorded per decision rather than looked up from a taxonomy, then the honest repair is that each rule using the distinction says which property it turned on — colour.md's backgrounds turn on receiving-versus-doing, iconography.md's on whether real data is on screen — and the shared term becomes shorthand rather than an axis. That is a different fix from defining it in F6, and choosing between them is the card.
Related: cards 54, 72, 79 · decision 1 (withdrawn) and the measurement that survived it · documentation/dependency-reciprocity.md § "Context is not a thing in Brand OS today", which had already recorded this scattering.
Amended 2026-08-25 from card 79 — half of this card's framing does not survive, and what is left is worse.
This card reads "Baseline is carrying a Foundation-level distinction on Foundation's behalf" as the defect. Card 79's measurement says otherwise: declaring a local instance of F6 is exactly what a branch is for. The physical domain does the same thing — four factors, explicitly "the physical domain's instance of foundation/constant-contextual.md", accepted 2026-08-06, and nobody has called it a defect. Baseline's two surface modes are the same move one branch over. Three attempts to hoist that kind of list onto F6 have now been declined or withdrawn; two instances a layer down are accepted and shipping. So Baseline holding the definition is the pattern working, not the pattern breaking.
What is actually wrong is narrower, and it inverts the layer model. Two accepted Foundation pages consume Baseline's local vocabulary: colour.md sets two page backgrounds by it (rows 75–76 and the cream note at 180), and iconography.md rules out number icons "specifically to instrumental surfaces". Foundation therefore depends downward on a branch's instance. The dependency model has Foundation depending on nothing and branches declaring depends_on: foundation/* — here the arrow runs the other way, undeclared, and that is what let an accepted Foundation page contradict Baseline silently: not that the term was defined in the wrong place, but that Foundation borrowed a term it does not own and cannot see.
So the question is no longer "where should this be defined". It is: what do those two Foundation pages say instead? Three shapes — name the property each rule turned on (colour.md's backgrounds turn on receiving-versus-doing, iconography.md's on whether real data is on screen), which is the learning model's own answer and needs no shared term at all; or promote the distinction to Foundation and have Baseline declare it as inherited, which is the move already refused three times in its general form; or leave the pages as they are and record that Foundation may borrow branch vocabulary, which is a real position and should be written down if it is the one taken.
BASELINE.md's sentence is unaffected by that choice and stays as written — "Mode is determined by the situation, never the user's role" is a branch describing its own instance, which is legitimate. It only becomes a problem if decision 1 is ever revived, and decision 1 is withdrawn.
Done when: the distinction is either defined in Foundation, or explicitly delegated to Baseline in writing, or replaced by per-decision properties the two Foundation pages either stop depending on a branch's vocabulary, or the corpus records that they may — and it is on main.
Closed 2026-08-25 by fixing the inversion, and it turned out to be mechanical.
Why it needed no new prose. Every borrowed use already explained itself by other means, so the branch term was carrying nothing. iconography.md said "In a dashboard they compete with actual data and risk becoming the sole carrier of meaning. This applies specifically to instrumental surfaces; in a presentation they are unproblematic" — the rule names its own discriminating property (competes with actual data) and its own contrast pair (dashboard / presentation). Deleting the middle clause loses nothing and reads better. colour.md's cream note already gave the examples (workspaces, tools against landing pages, patient material). Only the two table cells needed words, and role labels in a table are the utility text CLAUDE.md's calibration explicitly releases.
Six instances, all found before any were fixed — the sweep mattered, because two of the six were in the Swedish market copy and would have been missed by reading foundation/ alone:
| File | Before | After |
|---|---|---|
foundation/colour.md |
Page background (communicative contexts) |
Page background — pages someone reads |
foundation/colour.md |
Page background (instrumental contexts) |
Page background — surfaces someone works in |
foundation/colour.md |
cream note, both terms | same examples, terms dropped |
foundation/iconography.md |
"applies specifically to instrumental surfaces" | clause deleted |
_markets/sv/foundation/colour.md |
Sidbakgrund (kommunikativa sammanhang) |
Sidbakgrund — sidor som någon läser |
_markets/sv/foundation/colour.md |
Sidbakgrund (instrumentella sammanhang) + cream note |
property, terms dropped |
Table cells and the cream note are Claude-drafted under the utility-text calibration; the iconography.md change is a deletion. Verified: the grep found 6 at HEAD and 0 in the working tree, so it can fail. 216/216 tests, no _packages/ path touched — the mirrors go behind, per rule 1.
What was deliberately left alone, and why it is not the same problem. Ten further uses live in principles/design-tokens.md, patterns/hero.md, patterns/chat-bubble.md and schemas/design-token.md. All four publish at Baseline routes and are branch documents by origin — a branch using its own vocabulary is the layer model working. Sweeping them would have destroyed the distinction this card exists to protect. BASELINE.md § Surface temperature stays exactly as written, including "Mode is determined by the situation, never the user's role": a branch is entitled to describe its own instance.
One thing found and not fixed: the distinction is now defined twice in the branch layer, in BASELINE.md:56 and principles/design-tokens.md:37, in near-identical words. That is a duplication inside Baseline, not an inversion, and which one is canonical is a Baseline call. Worth its own card if it bites.
What Foundation gained by losing the term. Each rule now states the property it turned on, which is the learning model's own answer applied to the smallest possible case — and it removes a dependency that the layer model had no way to declare, so nothing has to be added to make it legible.
77 · [T] Record the one service property that survives A/B/C's retirement
Files: baseline/governance/4-den-nara-experten-digital-behavior-v1.md (line 122, where it already sits in other words) · workspace/authoring/ALERIS-DESIGN-WORKING.md § 3.1 (where A/B/C is) · destination undecided
New 2026-08-25, from decision 5.
The property. Can this service deliver news the person did not ask for? Screening can. An ablation pathway will. A weight subscription mostly will not, until a lab value or a side effect arrives. It decides whether a results experience must be designed for the full emotional range at all.
Why it is the only part worth keeping. A/B/C is retired as redundant — too high level to decide anything, identifying a patient's emotional state from the service she bought, and broken by its own examples ("Patientguide spans A and C", "a bariatric patient may move between Mode C and Mode A within the same flow"). Everything else in it reduces to a flow state, which accumulates across products rather than per product type. This one does not: it is a property of the service, not of a moment, and no flow-level record would hold it.
It is already written down, in Baseline, in different words — found on the 2026-08-25 check and not known when decision 5 was taken. baseline/governance/4-den-nara-experten-digital-behavior-v1.md (status: accepted) lists as priority area 3: "AI behavior on unexpected results (Mode C in the design working document) — how the product acts when a patient shifts from calm to anxious." So this card starts from an existing sentence rather than a blank page. Note that the same line is one of the two dangling references in card 83 — resolving them together is cheaper than twice.
The trap. This is not a mode, and it must not be recorded as one. It answers a yes/no question about a service before design starts; it does not describe anyone's state.
Related: cards 73, 78, 83 · decision 5.
Done when: the property is recorded somewhere a page can depend on, or explicitly discarded with a reason, and it is on main.
78 · [T] Moments have no home in the layer taxonomy
Files: patterns/ (7 files, all component-level) · principles/ (one real file) · qualities/ (empty) · documentation/site-map.md (the layer model)
New 2026-08-25, from decision 5, which implies a unit of accumulation the corpus has nowhere to put.
The gap. Decision 5 retires A/B/C on the grounds that learnings accumulate by flow and state across products, not per product type. That names a unit the taxonomy does not have. patterns/ is component-level — hero, empty state, error message, confirmation — and a moment such as receiving a result spans components and constrains their order, so it is not a pattern. principles/ holds one real file. qualities/ is empty.
What is lost meanwhile, concretely. The best material in A/B/C is results sequencing — lead with the next step, context before clinical detail, human contact always visible. Filed under Mode C it reaches only products classified as C. Filed as a moment it reaches the health check, the screening, and a lab value inside the weight product. § 3.2's trust sequences have the same shape: they describe an entry moment and are filed by product type.
A partial answer already exists and should be read first. documentation/dependency-reciprocity.md (2026-08-24) proposes a fourth finding kind, new context — on the grounds that the existing three (missing token, missing pattern, contradiction) force situations into the wrong box, and "the box determines what survives". That is the same problem arriving from the consumer side. Whether a moment is a context, or a second thing beside it, is part of this card. Card 80 carries the finding-kind half.
Related: cards 50 and 62 (both about what the layers are for), 77, 79, 80 · decision 5.
Done when: there is a decided home for moment-level material, or a recorded decision that patterns absorb it, and it is on main.
79 · [T] Decision 1 withdrawn — what F6 gains instead, and it is not a list
Files: documentation/dependency-reciprocity.md (the 2026-08-24 section, now on main) · foundation/constant-contextual.md (F6, unchanged and to stay unchanged) · workspace/decisions/decision-record-context-factors-and-emotional-modes-2026-08-25.md § 1
New 2026-08-25. Records a withdrawal and the work that replaces it.
What happened. The 2026-08-25 session decided (decision 1) that F6 gains a named set of five factors, each identity element declaring which bind it. Torfinn withdrew it on review the same day. It collided with documentation/dependency-reciprocity.md, amended 2026-08-24 — the one modified file in the working tree, and the only file in that tree the session's checks did not read.
Why it lost. That record had already put a version of the same proposal to Torfinn and had it declined: F6's job, per documentation/site-map.md, is to let someone reason about a decision no branch or leaf covers yet, and a list cannot do that job — it answers covered cases and goes quiet on new ones. Hence "F6 stays a stance" and "Where enforcement goes instead — not on F6", but on the nodes where a constant is already stated as a value. Then the part that decides it: "adjacency is per-decision, not global. There is no single adjacency graph over contexts, and a pre-declared taxonomy of context relations will be wrong — each decision defines its own reach."
Its evidence, which the session had no answer to. The children's markers case (TAKS brandguide/_state.md, 2026-07-30): the constraint that reorganised the whole design was that the summons letter names a lettered waiting area. Not medium, not audience, not emotional mode, not surface temperature — nothing a pre-declared scheme would have contained, and found by asking Sanna. A closed list of five factors misses it exactly as the four axes would.
What replaces it. Not a list. The field a decision records is the property of the situation the answer turned on, in the words of whoever made it, so reach is computed per decision. Torfinn, 2026-08-25: that addition is the correct one, and it is the learning model. The concrete work is cards 80, 81 and 82 — carded rather than executed, on his scope call.
What this card is for, since the withdrawal is already recorded. Two things are still open. (1) F6 is confirmed to need no change — worth stating positively somewhere, because three separate proposals have now tried to give it structure and the answer has been the same each time. (2) documentation/dependency-reciprocity.md is status: proposed and sits in documentation/, which does not publish. If it is the learning model, its status and placement are a decision nobody has taken.
The method lesson, which generalises past this card. git status is part of reading the working tree. The record listed twelve files it had read and carried the caveat that it read the working tree rather than main — it named the right risk and missed the actual one. The ground is newest exactly where a session is most confident.
Pulled 2026-08-25. Both halves are now one decision each rather than open questions, and the first half's premise was wrong.
(1) F6's position — measured, and the framing inverted
This card said three proposals had tried to give F6 structure and been turned down each time. Checking the corpus corrected that. Five attempts exist, and they split by scope rather than failing uniformly:
| Attempt | Date | Scope | Outcome |
|---|---|---|---|
| Four contextual axes (channel/format, reader state, audience, market/culture) | 2026-05-31 | global | Never confirmed — still PROPOSED in F6's en-draft, still card 54 |
| Enumerate the constants, contextual as residual | 2026-08-24 | global | Declined, Torfinn's objection. "F6 stays a stance" |
| Five context factors | 2026-08-25 | global | Withdrawn same day (decision 1) |
| Physical domain's four factors — kliniktyp, patientgrupp, byggnadens förutsättningar, land och lokala krav | 2026-08-06 | one domain | Accepted, and named "the physical domain's instance of foundation/constant-contextual.md" |
| Baseline's two surface modes — communicative, instrumental | before 2026-06 | one branch | Accepted and shipping. BASELINE.md § Surface temperature opens "Baseline applies F6…" |
So the position is not "F6 resists structure". It is: F6 stays a stance, and structure lives in instances that declare their own reach. Three attempts to put a list on F6 failed; two attempts to put one a layer down succeeded and nobody has argued with either. The corpus settled this in practice and never said so, which is why the same proposal keeps arriving — this is the third time.
The physical instance proves a global list would have been wrong, not just unnecessary. Its four factors overlap card 54's four axes on two (patientgrupp is audience-shaped, land is market-shaped) and diverge on two no global list would ever carry: kliniktyp and byggnadens förutsättningar. A building's constraints decide an interior and mean nothing for a screen. A list big enough to hold them is useless; one small enough to be useful excludes the factor that actually reorganised that domain's guide.
The decision left, and it is one sentence's worth: Closed 2026-08-25 — it was already written. TAKS/1-projects/brandguide/context-architecture.md, updated 2026-08-24, states it in §9 ("F6 is unchanged and stays a stance"), argues it in §3 with the same children's-markers evidence this session re-derived, and says in its own § "Relationship to other documents" why it exists: "F6 teaches the reader to tell constant from contextual, and says nothing about how the system learns what the contexts are." So the position has a home, it is not F6, and it predates the question. Nothing to write.
This session's measurement is still worth keeping — five attempts split three-global-refused against two-local-accepted — because that document argues the position from first principles and does not count the corpus's own history of trying the alternative. Recorded in workspace/decisions/decision-record-context-factors-and-emotional-modes-2026-08-25.md § "What F6's position actually is".
What this card still holds is only the second half: the status of documentation/dependency-reciprocity.md. Note that context-architecture.md §5 asks the sibling question — "does this document live in TAKS or move to Brand OS? Two documents on one subject is the shape that has drifted four times before" — so the two placement questions are one decision, not two.
(2) dependency-reciprocity.md — one status field over two different states
The card asked for a status and a home. Reading the file gives the reason it has neither: it is two documents with one status: field, and they are not at the same level of ratification.
- The original 2026-07-28 half proposes a dependency graph. Genuinely still
proposed— its own Next step asks for a scoping session that has not happened. - The 2026-08-24 half is the learning model, and Torfinn ratified it on 2026-08-25. That half is accepted.
One field cannot say both, so the file currently understates the ratified half and overstates the proposed one. type: planning is also now wrong for the second half — a ratified model is not planning.
Same shape as card 74's finding on foundation/logo-identity.md: ratified rules inside a draft page, where the page's badge described the weakest thing in it. The fix there was to separate ratification from reasoning rather than to relabel the whole file.
Three options, and the third is what card 74 did:
- (a) Mark the whole file
acceptedand leave it indocumentation/. Cheapest, and it promotes an unscoped graph proposal to accepted along with the model. - (b) Leave it
proposed. Honest about the graph, and leaves the ratified model looking provisional — which is what let decision 1 be taken against it in the first place. - (c) Split. The learning model becomes its own accepted record (decision record, or a constitutional statement if "reach is recorded per decision" is a rule rather than a position); the graph stays here as
proposedplanning with a pointer. Most work, and the only option where both halves are described correctly.
Not done here, deliberately: both remaining decisions are yours, and neither is a formality — (1) decides whether Foundation gains a sentence, and (2) decides whether the corpus gains a constitutional rule. This card stays open until one of each is taken.
Also produced by pulling this: card 76 amended. Its reading of Baseline defining communicative/instrumental as the defect does not survive the pattern above — declaring a local instance is what a branch is for. The real defect there is narrower and worse.
Related: cards 54, 72, 74, 76, 78, 80–82 · decision 1 (withdrawn), decision 2 (orphaned).
Done when: F6's position is stated as decided rather than repeatedly re-proposed, and dependency-reciprocity.md has a decided status and home, and it is on main.
Closed 2026-08-25 on both halves, and the second one dissolved rather than being decided.
(2) The status question had no answer because the file held two subjects. The coherence pass found that context-architecture.md and dependency-reciprocity.md were not model and mechanism — they stated one argument twice, five days apart, with the same target-size example and the same three evidence cases in the same order. git log gives the direction: written 2026-08-24 in dependency-reciprocity.md, restated 2026-08-25 in context-architecture.md under a header claiming neither restated the other. The drift shape, on consecutive days.
The two are merged into documentation/how-decisions-are-recorded.md — when we decide something, how do we record it so the next person can find it and knows what it covers? Both old paths are pointer stubs with a section map. The merged file is accepted and carries an in-force map at the top: §1–4, §8–10 in force; §7 (the language question), §11 (the graph), §13, §14 carried open. That is card 74's move — separate ratification from reasoning inside the file rather than relabel the file to its weakest part — applied to the file that ended up holding the ratified half. No split was needed, because once the model moved, everything left in the graph proposal was uniformly unratified.
(1) F6's position now has a rule, not just a record. constitutional/a-thinking-tool-stays-a-thinking-tool.md, status: proposed. Its Detection of violation is the five-attempt table from this card; its Origin says outright that it exists so the fourth proposal meets something. The rule sentence and Implication are Claude-drafted and marked as such — both argue, so both are Torfinn's to rewrite before it moves to accepted. That is the one thing left on this card.
Also produced. workspace/status/observations.md — a doab.md-shaped ledger for situations the brand has met, which gives F6's quick-test question 3 ("flagga den") somewhere to point for the first time. It starts near-empty on purpose, with one Claude-proposed candidate awaiting Torfinn's wording (instructional imagery, card 84). Entries are earned, not generated; the discipline is TAKS/doab.md 2026-05-14 and is pointed at rather than restated.
And a detection statement for the obligation that had none. Every constitutional rule carries one; "every decision records the property its answer turned on" did not. The failure it needs to catch is not an empty field but a filled one — a label in a property-shaped box, "applies to the console", which looks complete. The test is doab.md's own: could someone who was not in the situation have written this? Recorded in the merged file § "What holding a situation commits you to", obligation 1.
Not made constitutional, deliberately: a decision records the property its answer turned on. It is in force on consumer findings and nowhere else — not on board cards, not on decision records. A rule nobody follows on the day it is written becomes decoration. Graduation trigger recorded in workspace/decisions/decision-record-context-documents-coherence-2026-08-25.md § 5.
Reasoning, alternatives, and what would make it wrong: workspace/decisions/decision-record-context-documents-coherence-2026-08-25.md. Still open on this card: Torfinn's wording of the rule sentence. ✔ otherwise.
80 · [CC] A fourth finding kind, and the field that makes a finding's reach computable — DONE 2026-08-25
Files: _packages/adapters/vendored-app/ (the findings template and consumer profile) · patientguide/BRAND-OS-FINDINGS.md and sundviktlakemedel/BRAND-OS-FINDINGS.md (the two live instances) · documentation/dependency-reciprocity.md § Capture
New 2026-08-25, from the learning model (card 79). Two changes, both cheap, both named in that document as the first things to do.
1 — a fourth kind: new context. The kinds today are missing token, missing pattern, contradiction. Several of the strongest entries received are none of the three: the pre-auth page that renders before the built stylesheet is reachable (§7), the 250-row worklist wanting a lighter row rule than section rule (§3), a button at 200% text on a 320px viewport (§6). All are situations Brand OS had not met, filed as missing tokens or patterns because those were the available boxes. The box determines what survives: filed as a missing token, what reaches Brand OS is a request for --border-subtle, and what is lost is that a dense worklist is a context with needs of its own.
2 — one field on the template: what made this situation different. The discriminating property, in the consumer's own words, written while they still remember. This is the field the reverse query reads — given this artefact in this context, what constraints bear on it — and it is the difference between a queue of fixes and knowledge about contexts. Sundvikt wrote it voluntarily once, unprompted: "any consumer with a server-rendered pre-auth page (a login screen, an error page, a maintenance page) has the same problem." The template should ask every time.
Why this is [CC] and safe to pull. Neither change touches brand content or a Foundation rule. Both are a byproduct of work the consumer is already doing rather than a separate authoring act, which is the constraint the upkeep objection imposes. The capture mechanism works better than its reputation — the last four to five package point releases came from consumer builds — and nothing here should disturb the working half.
Trap: do not design a context schema first. That document's own conclusion is to collect the contexts before designing the schema for them.
Related: cards 78, 79, 81, 82.
Done when: the template carries the fourth kind and the discriminating-property field, both live instances have been updated, and it is on main.
Closed 2026-08-25. The template at _packages/adapters/vendored-app/wiring/BRAND-OS-FINDINGS.md now carries four kinds and the new field, and both live instances are updated in their own idiom rather than overwritten with the template's.
New context is placed fourth and highest, above contradiction. The reasoning is stated in the file: it is the only kind that produces knowledge rather than a fix. A contradiction found by building yields a correction; a situation the system has never met yields a category. That is a reordering of the template's own "ascending value" list and is flagged as such.
The field is what made this situation different, with guidance that names the failure it prevents: "the console" records where you were, "pointer input, and the same people use it all day" records what the answer turned on. sundviktlakemedel's finding 7 wrote it unprompted and is quoted in its own file as the model.
Also added, because card 82 needs something to compare against: every findings file now carries **Last filed to Brand OS:** <date>. The values are honest rather than convenient — sundviktlakemedel 2026-08-12, patientguide never, which is uncomfortable given that file produced v0.6.4.
Done when: the template carries the fourth kind and the discriminating-property field, both live instances have been updated, and it is on main. ✔
81 · [CC] aleris-assistant has no findings file and no brand-provenance.json — DONE 2026-08-25
Files: aleris-assistant/ (its LEARNINGS.md is the existing right-shaped thing) · _packages/adapters/vendored-app/
New 2026-08-25, from the learning model (card 79).
The gap. It is the third consumer under our control and the one whose context differs most — conversational, LLM-mediated, with the brand carried almost entirely by voice rather than by surface. That makes it the likely richest source on the half of Brand OS that is thinnest, and it is silent by construction: no findings file, no provenance record, so nothing it learns has a route here.
Most of the work is already done there. Its LEARNINGS.md has the right form — numbered tension, reasoning, implication, dated edits — and it correctly routes brand material to TAKS, where it lands in a project LEARNINGS file rather than in Brand OS. Installing the adapter's findings file is the cheap fix; the question of whether voice findings should reach Brand OS directly or continue through TAKS is worth asking while doing it, not assuming.
Related: cards 79, 80, 82.
Done when: aleris-assistant carries a findings file and a provenance record, and it is on main.
Closed 2026-08-25, and the card's premise needed correcting first.
The card said this repo has no findings file and no provenance record, and that it should get both. The first half is right. The second half was wrong, and checking is what found it. A grep for brand-provenance.json, a vendored agent-baseline and aleris-tokens.css returned nothing, and the obvious conclusion — this repo installs nothing, it consumes Brand OS only as voice — was written into the file and then contradicted by the repo's own CLAUDE.md. It does vendor tokens, hand-copied into src/index.css under @theme, governed by its constitution rule 7 and recorded in docs/architecture/inheritance.md. Corrected before committing.
No brand-provenance.json was created, deliberately. It pins a package version and carries a file hash; neither applies to a hand-copy. inheritance.md already does that job in the shape this repo uses, and names its own graduation trigger: a second consumer (aleris.se) → published package, semver-pinned. Recording the reason in the file beats leaving an absence that reads as an oversight.
The file's first entry is a live finding, produced by installing it. inheritance.md and the source comment in src/index.css both record the vendored tokens as coming from baseline/tokens/aleris-tokens.css. That path has not existed since 2026-08-10, when Phase B step 3 moved tokens to data-products/ (cards 46/48). So the one row whose stated job is "when an upstream thing changes… what in this repo depends on it?" points at a moved file, and "keep in sync with brand-os" names an empty place to look. Token values were not checked against canonical — the entry reports a broken pointer, not confirmed drift, and says so.
The property that decides its reach, in the entry's own words: the seam is a hand-copy recorded in prose, not an installed dependency. A package fails loudly via a hash check; a path in a comment fails silently and stays wrong until somebody follows it by hand. That applies to every inherit recorded the same way, and to any future consumer that copies rather than installs.
The route home is left open on purpose. CLAUDE.md routes strategic material to TAKS and technical patterns to COOKBOOK.md; voice findings fit neither. Both candidate routes are written into the file with their risks, for Torfinn.
Done when: aleris-assistant carries a findings file and a provenance record, and it is on main. ✔ — with the provenance record deliberately not created, and the reason recorded.
82 · [CC] Nothing carries a finding from a repo to Brand OS — DONE 2026-08-25
Files: _packages/adapters/vendored-app/GAPS.md §§3–4 (where this is already named) · ~/Dev/_commands/ and ~/.claude/commands/ (the /wrap definition)
New 2026-08-25, from the learning model (card 79). Named as a known blocker in GAPS.md and worth doing on its own account — every improvement to the capture template is worth nothing while the carrying step is unowned.
The gap (GAPS.md §4). "Nothing carries a finding from a repo to Brand OS — the file is not the whole path." The writing half works; nobody owns noticing that a findings file has content. Measured consequence: patientguide's most consequential finding produced agent-baseline v0.6.4 with no artefact on either side.
The fix GAPS.md names: a third question at /wrap, alongside the COOKBOOK question and the assertion question. ~/Dev/CLAUDE.md § Close already runs two; this is the third, and it fires while the work is fresh for the same reason the assertion question does.
The other half, §3, and it is harder. The provenance check detects resolved upstream states, not undecided ones, so three visual conventions now live in one repo that Brand OS does not own. Undecided conventions are the residue this mechanism structurally cannot close, and they need somewhere to sit that is not a board card nobody reads. Worth scoping with this card even if only the /wrap half ships.
Related: cards 79, 80, 81.
Done when: a session that ends with content in a findings file cannot close without that content being routed, and it is on main.
Closed 2026-08-25, and it turned out /wrap was behind its own specification.
GAPS.md §4 says "/wrap already asks two questions, and 'does any consumer's findings file have new content?' would be a third." It asked one. ~/Dev/CLAUDE.md § Close specifies two — the COOKBOOK question and the assertion question — and only the first was in ~/Dev/_commands/wrap.md. So the fix was not one addition but two: the missing second question, and the new third. Same class as this morning's file-manifest.md finding, and as the three-definitions-of-/wrap problem that file's own header records from 2026-08-04. A command drifting from the ritual that documents it is this project's most repeated defect.
Built as a check, not a third question, which is a deliberate departure from what GAPS.md proposed. A question asked every session becomes a question nobody hears — that is exactly how CLAUDE.md could ask for a frozen manifest to be updated for three months. Step 5c compares each consumer's Last filed to Brand OS: line against the last commit touching its findings file, and speaks only when the file is ahead. Unskippable, and always reported in the summary, like the git check in step 7.
Scope stated rather than hidden: patientguide, sundviktlakemedel and aleris-assistant are sweepable. aleris-vibe-coding is on Bitbucket and is not — checked by hand when Richard is in contact, and the summary line says so.
Not closed, and it is the harder half: GAPS.md §3 — the provenance check detects resolved upstream states, not undecided ones, so three visual conventions live in one repo that Brand OS does not own. Undecided conventions are the residue this mechanism structurally cannot reach. Worth its own card when it bites.
Done when: a session that ends with content in a findings file cannot close without that content being routed, and it is on main. ✔
83 · [CC] Two A/B/C references survive the retirement, one in an accepted page — DONE 2026-08-27
Files: baseline/governance/4-den-nara-experten-digital-behavior-v1.md:122 (status: accepted) · baseline/iconography/curation-session-b.html:535
New 2026-08-25, found on the review pass of the decision session. Not urgent, and worth doing before the trail goes cold.
The references.
baseline/governance/4-den-nara-experten-digital-behavior-v1.md:122— priority area 3: "AI behavior on unexpected results (Mode C in the design working document) — how the product acts when a patient shifts from calm to anxious." This is also card 77's surviving service property in other words, so the two cards should be resolved together.baseline/iconography/curation-session-b.html:535— an icon rationale: "BRAND-BESLUT: kan väcka associationer till smärta/oro hos patient. Pröva mot Mode A-ytor." An instruction to test an icon against a surface class that decision 5 has just abolished. Related to card 58, the allowlist review.
Scope, measured. Neither publishes — the governance file declares no lang and holds no route, and the other is HTML — and neither ships in _packages/. So the retirement is clean downstream and nothing a consumer reads is affected. data-products/, schemas/, filters/, documentation/ and _packages/ were checked and carry no reference at all.
How they were missed, four times. Cards 73 and 75, the 2026-08-24 changelog entry, and the 2026-08-25 brief all assert that nothing under foundation/, baseline/, patterns/ or principles/ mentions any of the three modes. The greps searched the three descriptive names — anxiety-driven, aspiration-driven, latent anxiety — which is the form the en-draft uses. Baseline uses the labels. An absence is a claim about a query, not about the world, and this one was repeated three times without being re-run. Corrected in all four places on 2026-08-25.
Done when: both references are resolved or explicitly kept with a reason, and it is on main.
84 · [T→CC] Instructional imagery is not a category the corpus names — DONE 2026-08-27
Files: foundation/imagery.md (three categories, none of them this) · baseline/reference/aleris-baseline-images.md:154 (the only mention anywhere, as a compression setting) · live in sundviktlakemedel and patientguide
New 2026-08-25, from Torfinn's review pass — raised as the exception to a rule, and it turned out to name a gap.
How it came up. The 2026-08-25 session briefly permitted the equipment image on patient-facing instrumental surfaces. Torfinn struck that: the permission was a passing example and it was wrong, and imagery.md's refusal of the clinically documentary image is correct — unless the image is clinically appropriate as instructional material.
The gap that exception exposes. imagery.md governs identity imagery. It sorts images into Life, Care and Portrait "by the story they tell" — why we do what we do, how we do it, who we are — and all three are identity-bearing. An image showing how to hold an injection pen tells none of those stories and is not trying to. It is not covered by the three categories, not covered by the prohibitions written against identity failures, and not named anywhere else in Foundation. The corpus's only mention of instructional imagery is baseline/reference/aleris-baseline-images.md:154, which says graphic content can take lower compression.
Why it is live rather than theoretical. sundviktlakemedel has injection technique. patientguide has preparation steps. Both are patient-facing, both need images that show a procedure rather than a person, and both currently fall under a page whose founding choice is "the person at the centre… images of equipment, premises, or processes serve as support — never as the main subject." Read literally, imagery.md forbids the image an injection instruction needs.
The shape of the question, and why it is [T]. It is a scope question before it is a rule question: does imagery.md govern all images, or identity images? If the second — which is what the page actually does — then instructional imagery needs its own home and its own criteria (accuracy, sequence, what the hand is doing, when a photograph beats a diagram), and the identity prohibitions stop being the wrong tool applied to it. That is a Foundation-level boundary, not a bullet to add.
Trap: do not solve this by scoping the "Not clinically documentary" prohibition. That was the move Torfinn struck. The prohibition is right about the image it describes — an equipment photograph standing in for a person in identity work — and the instructional image is a different kind of object, not an exception to it.
Related: cards 72, 76 · decision 4 as amended.
Done when: instructional imagery has a stated home and a rule, or is explicitly recorded as out of Brand OS's scope, and it is on main.
85 · [CC] Rescue three records from colour-swedish-port before the branch goes — DONE 2026-08-25
Files: workspace/status/changelog.md (no 2026-08-11 colour-port entry exists) · workspace/plans/plan-swedish-colour-port-session.md:98 (carries a claim that is now false) · workspace/status/board.md card 42 · branch colour-swedish-port, commits 45bdca9, 0a1c779, e4a28ca, dbbc985
New 2026-08-25, from the branch sweep after three backup/auto-* snapshots were deleted. colour-swedish-port is the last branch ahead of main — 4 commits, 2026-08-11, and it is the only thing keeping lib/branch-hygiene.test.ts red.
The branch cannot be merged, and it does not need to be. It ports the Swedish Foundation colour page while foundation/colour.md was still Swedish. Ten days later 0ae89ff flipped six Foundation pages to English and archived the Swedish originals to _markets/sv/foundation/, so the branch edits a file that has since changed identity. Its own IN_FLIGHT declaration expired 2026-08-18, which means merging it as-is would fail the suite on the expiry rule — that mechanism working as designed.
Measured 2026-08-25: all three mechanical fixes are already on main, and the branch is behind it. Diffing the branch's foundation/colour.md against today's _markets/sv/foundation/colour.md gives three lines, and all three are the branch holding the older, wrong text: petrol-400 | Sekundär text, etiketter, petrol-300 | Dämpad text, bildtexter, tertiär, Bekräftelse | Slutfört, godkänt — the three colour-role claims below the AA floor that agent-baseline v0.6.4 corrected. The corrected orange triplet (245, 140, 97), the 0–900 renumber, and the Teal legacy placement are all present in the market copy. So there is nothing to replay. Recorded because the branch's own changelog entry reads as though the work is outstanding, and it is not.
What is genuinely only on the branch — three records, none of them code.
- The ΔE recomputation, with its method. The English page asserts error red
#C14444and success green#4F866Esit ΔE 76.9 apart in normal vision and 14.6 apart under protanopia. Recomputed independently on 2026-08-11: 76.87 and 14.55 — CIE76 in CIELAB, D65 white point, standard sRGB→linear→XYZ→Lab, with Machado 2009 at 100% severity applied to linearized RGB before conversion to Lab. The claim holds under that formula and colour space. - Route 3 on the colour-alone blocker, Torfinn's decision. The English page points its colour-alone hard rule at
[[constitutional/accessibility-is-foundational]], which does not publish (CONTENT_DIRS), so the pointer would be a dead link on an accepted published page. Decision: port everything except that rule, and leave it untilconstitutional/publishes. This defers prose blocks D and E and should become its own card. - The Orange section's new frame (Block C) is Torfinn's own writing, not lifted from a deck despite the register shift against the rest of the page — so it ports as-is with no rewrite. A question asked and answered, and expensive to re-ask.
And main currently asserts the opposite of (1). workspace/plans/plan-swedish-colour-port-session.md:98 says of the ΔE claim: "The arithmetic on top of it is unverified: which ΔE formula, which colour space, and whether the simulation was applied before or after the difference was computed." That was true when written and is false now. Four files on main repeat the 76.9 / 14.6 figures (decision-brief-orange-interaction-colour.md, colour-authoring-2026-07-28.md, the changelog, that plan) and none records that anyone checked them. Correcting that line is the cheapest half of this card.
Why [CC]. Nothing here is a new decision or a line of brand prose. Two of the three records are decisions Torfinn already took on 2026-08-11; the third is arithmetic. The work is writing them down where they can be found.
Trap: do not merge the branch, and do not replay its diff onto the market copy — that would reintroduce three role claims below the AA contrast floor that v0.6.4 exists to have fixed.
Related: card 42 (which route 3 partly answers, and which is [T], so amend it rather than closing it) · card 76 · the deleted backup/auto-* snapshots and workspace/archive/pre-review-2026-08-25/.
Done when: the ΔE recomputation and both Step 1 decisions are recorded on main, plan-swedish-colour-port-session.md's "unverified" line is corrected, and colour-swedish-port is deleted — at which point branch-hygiene goes green for the first time since it was written.
Closed 2026-08-25. All four done-when conditions met.
The ΔE figures were recomputed a third time rather than copied. Recording someone else's verification without running it is the failure this card exists because of, so the arithmetic was redone from the hexes with no reference to the branch's result: 76.87 normal, 14.55 protanopia, matching 2026-08-11 to two decimal places and the documented 76.9 / 14.6 within the precision the corpus quotes. The check is now scripts/delta-e-check.py — it states its method in the docstring, carries two sanity pairs, fails if a documented figure drifts, and was mutation-verified against a deliberately wrong value before being trusted.
One finding from writing the check, worth keeping because it looks like a bug and is not: neither published matrix sums to exactly 1 at the precision it is given. The sRGB→XYZ luminance row sums to 1.0000001 and Machado's protanopia matrix has a row summing to 1.000001, so black against white lands at L* 100.000004 and 100.000012 rather than 100. Found because the sanity assertion was written at 1e-9 first and failed.
Route 3 and the block-C confirmation are recorded on card 42, which had been reading as fully open for two weeks while its central question was half answered on a branch. Card 86 opened for prose blocks D and E — the deferral the branch's changelog promised to track and never did, because the promise itself was only on the branch.
plan-swedish-colour-port-session.md § 2d corrected. It had been asking for a recomputation that had already happened, and asserting the arithmetic was unverified when it was not. It now records outcome 1 of the three it asks for.
The measurement that shrank this card, recorded because it inverts what the branch's own changelog implies. All three mechanical fixes were already on main. Diffing the branch's foundation/colour.md against _markets/sv/foundation/colour.md gave three lines, all of them the branch holding older text — the petrol-400, petrol-300 and confirmation-green roles that agent-baseline v0.6.4 corrected for being below the AA floor. Merging or replaying would have been a regression, not a rescue.
colour-swedish-port deleted, local and origin. lib/branch-hygiene.test.ts is green — the first time since it was written on 2026-08-06.
86 · [T] Prose blocks D and E — the colour-alone rule, deferred by route 3
Files: _markets/sv/foundation/colour.md (the Swedish page the blocks belong to) · foundation/colour.md (the English source they translate from) · constitutional/accessibility-is-foundational.md (the page the rule points at) · lib/route-manifest.json / CONTENT_DIRS (why it cannot point there yet)
New 2026-08-25, from card 85. This card was promised on 2026-08-11 and never opened — the promise lived in a changelog entry on colour-swedish-port, which was never merged, so the deferral had no tracker for two weeks. That is the same failure as the branch itself: a record whose only copy was on a branch.
What is deferred. Two prose blocks from the Swedish colour port:
- Block D — the colour-alone hard rule's expanded reasoning.
- Block E — "Why colour cannot carry meaning on its own", which is where the CVD evidence goes.
Why they were deferred rather than written. Route 3 (card 42): the English page points the colour-alone rule at [[constitutional/accessibility-is-foundational]], which is accepted but does not publish, because constitutional/ is not in CONTENT_DIRS. Porting the rule would put a dead link on an accepted, published page. So the rule stays as it stands until constitutional/ publishes.
This card is therefore gated on a publishing decision, not on writing time. It unblocks when constitutional/ publishes — which is Phase B work and touches card 39's CONTENT_DIRS question — or when someone decides the rule can point somewhere else.
The evidence for block E is now ready and checked, which was not true when this was deferred. Error red #C14444 and success green #4F866E sit 76.87 apart in normal vision and 14.55 apart under protanopia — computed twice independently, and the check lives at scripts/delta-e-check.py with sanity pairs and mutation verification. The structural finding behind block E is the stronger half and it also holds: every Aleris colour is warm, and warm colours collapse onto a single yellow-khaki band under red-green CVD, so hue distinguishes nothing there. Whoever writes block E does not need to recompute anything.
Why [T]. Both blocks are Foundation prose that argues — exactly what the content boundary reserves. The numbers, the method and the gate are all settled; the writing is not Claude's.
Related: cards 42 (route 3, the parent decision), 85 (which opened this), 39 (CONTENT_DIRS), 76.
Done when: blocks D and E are written into the Swedish colour page, or the rule is repointed and the blocks written against the new target, and it is on main.
87 · [T→CC] The backup hook and the branch-hygiene check cancel each other out — DONE 2026-08-25
Files: .claude/hooks/backup-push.sh (the SessionEnd hook) · .claude/settings.local.json (its wiring) · lib/branch-hygiene.test.ts (the check it defeats)
New 2026-08-25, found while clearing the last branches. Both mechanisms are good. Together they produce a check that can never be green, and a team that has learned to ignore it.
The mechanism. .claude/hooks/backup-push.sh runs at SessionEnd, snapshots the entire working tree — committed HEAD plus uncommitted changes plus untracked files — into backup/auto-YYYYMMDD, and force-pushes it. It is careful, well-written work: a throwaway index so the real one is never touched, guarded at every step, always exits 0, and a 30-day retention window added 2026-08-03 after fourteen snapshots had accumulated.
lib/branch-hygiene.test.ts fails when any branch is ahead of main. So the hook creates, at the end of every session, exactly the condition the check fails on.
Measured today, and it is not theoretical. All four branches ahead of main were cleared and the suite went green at 13:20 — the first time since the check was written on 2026-08-06. At 14:04 a session ended, the hook fired, backup/auto-20260825 was recreated, and the suite went red again. Its snapshot is byte-identical to main; it is one empty commit on top, and that is enough to fail.
The cost is not the red light, it is what the red light stopped meaning. The 2026-08-14 changelog entry already reads "one pre-existing unrelated branch-hygiene flag on colour-swedish-port" — the check had become something to note and step past. colour-swedish-port then sat unmerged for a further two weeks carrying a verified number, two decisions and a promise to open a card, none of which existed anywhere else. The check was pointing straight at it the whole time. A check that is always red cannot distinguish the day it matters.
Why this is [T] and not a quick exclusion. The obvious fix is to skip backup/auto-* in the check, since a whole-tree snapshot is insurance rather than work-in-progress. But there is a real argument against it, and it is this repo's own history: snapshot branches have four times held the only copy of something (workspace/archive/rescued-from-backup-branches-2026-08-03/, 705 lines), and they did it a fifth time today — backup/auto-20260825 held the pre-review originals of the 2026-08-25 decision record and brief, which main had never carried, now at workspace/archive/pre-review-2026-08-25/. Excluding them removes the one signal that has ever surfaced that content.
So the question is not "how do we make the test pass". It is what should notice that a snapshot contains something main does not — which is a different check from "is a branch ahead", and the one actually wanted. A snapshot whose tree matches main is noise; a snapshot whose tree does not is the finding.
Three shapes, for Torfinn. (a) Exclude backup/auto-* and accept losing the signal. (b) Replace the ahead-of-main test for snapshot branches only with a tree-comparison — flag a snapshot when its tree differs from main, ignore it when identical, which keeps the signal and drops the noise. (c) Leave both and accept a permanently red check, which is the status quo and has already cost two weeks once.
Related: cards 85 (which cleared the branches and exposed this), 15 · _packages/adapters/vendored-app/GAPS.md §4 and card 82, which are the same shape one level out — a mechanism whose writing half works and whose noticing half is unowned.
Done when: the check is green in the ordinary case and still fires when a snapshot holds something main does not — or it is recorded as deliberately red with what a reader should do about it, and it is on main.
Closed 2026-08-25. Torfinn took option (b) with the age condition, and it is implemented.
What changed in lib/branch-hygiene.test.ts. Branches matching backup/auto-YYYYMMDD — the hook's own naming, so deliberate snapshots like backup/brand-os-full-* are untouched — leave the ahead-of-main rule and enter a new check that asks a different question: does this snapshot hold content that exists nowhere in main? SNAPSHOT_GRACE_DAYS = 2 keeps it quiet for the ordinary case, since ending a session with uncommitted work is what the hook is for.
One thing the implementation had to get right, and the naive version gets wrong. Comparing the snapshot's tree to main — the obvious reading of option (b) — fails every old snapshot forever, because main moves on and their trees necessarily diverge. That would have changed nothing. The check compares content: it walks every object reachable from main (6,444 of them, ~70 ms) and flags only blobs that appear nowhere in that history. A snapshot whose content has since landed goes silent however old it is and however far main has travelled.
Proven against five cases before being trusted, not just run once. Using the file's own BRANCH_HYGIENE_REPO and BRANCH_HYGIENE_TODAY injection points against a throwaway repo:
| Case | Expected | Result |
|---|---|---|
Ordinary branch ahead of main |
fail | fails, unchanged — the original rule is intact |
| Snapshot 5d old holding a unique file | fail | fails, and names the file |
| Same snapshot, 1d old | pass | passes — inside grace |
Snapshot 5d old, content since landed in main |
pass | passes — the case a tree diff gets wrong |
| This repo, today, snapshot present | pass | passes, 216/216 |
A fixture bug worth recording, because it is a real property of the check. The first failing-case attempt passed when it should have failed: ageDays is computed from the snapshot's commit date, not from the date in its branch name, and the fixture's commit was dated today. The hook makes the two agree in practice, but they are not the same field, and anyone reasoning about this check from the branch name alone will be wrong.
Result. 216/216 green with backup/auto-20260825 sitting in the branch list — the state that was red an hour ago. The check now stays green through ordinary sessions and fires on the one case it exists for.
Not done here, and left deliberately: the hook is unchanged. It is good as written, and the fix belonged on the side that was asking the wrong question.
88 · [CC] A moved file breaks a consumer silently — the staleness mechanism, fourth statement and first build
Files: lib/consumer-contract.test.ts (new) · _packages/agent-baseline/MANIFEST.md (the artefact it reads) · documentation/how-decisions-are-recorded.md § "A staleness relation to the core"
New 2026-08-27, from the aleris-assistant install (card 81, 2026-08-25) and closed the same day it was opened.
The measured failure. Phase B moved the token file from baseline/tokens/ to data-products/tokens/ on 2026-08-10. aleris-assistant pointed at the old path in four places — README.md, the source comment in src/index.css, docs/architecture/inheritance.md, and its own constitution rule 7, the rule that governs where tokens come from. Fifteen days, no signal. It surfaced because someone opened that repo for an unrelated reason.
The asymmetry that decides where the fix belongs. A consumer cannot detect that this repo moved a file. This repo can. The break happens here and is discovered there, weeks later, by accident — so the check belongs in the repo that causes it, in the suite that runs on every change. Putting it only in the consumer means the signal arrives whenever that repo's tests next run, which for a quiet consumer is never.
Why the package consumers were never at risk, which is the design lesson. patientguide and sundviktlakemedel point at agent-baseline's internal paths, recorded in their brand-provenance.json from fields. tokens/aleris-tokens.css has held across v0.4 → v0.7.1 including straight through the move. The package is the indirection layer and it worked. aleris-assistant was the only consumer reaching past it into this repo's own directory layout, and the only one that broke. Fixed there by repointing at the package path — the general rule being older than this project: depend on a published interface, not on someone's internal structure.
What was built here. lib/consumer-contract.test.ts, same shape as route-manifest.test.ts — which exists to make the published set "an artefact instead of an emergent property… impossible to make silently." The artefact needed already existed and is already generated: the file table in _packages/agent-baseline/MANIFEST.md, rewritten at every cut, whose two columns are exactly the two surfaces worth protecting.
| Assertion | Protects | Would it have caught 2026-08-10? |
|---|---|---|
| Every package path the manifest promises exists in the package | what consumers point into | no — the package was fine |
| Every canonical source path the manifest names still exists | where the package was cut from | yes, in the session that did the move |
| The parsed row count is ≥ 15 | that a green run means something | — |
Manifest version agrees with _packages/README.md |
two records of one version | — |
Nothing is hand-maintained, which was the standing objection to a declared path list and the reason this is derived. The list is read from a generated file, so it cannot rot separately from what it describes.
Mutation-verified before being trusted, all four: reverting the token row's source to the pre-move path fails assertion 2 and names the exact pair (tokens/aleris-tokens.css ← baseline/tokens/aleris-tokens.css); renaming a shipped file fails assertion 1; renaming the table header so the parser sees nothing fails assertion 3 rather than passing on zero rows; bumping the manifest version fails assertion 4.
Where this sits in the larger gap. how-decisions-are-recorded.md § "A staleness relation to the core" calls it "one mechanism, three places waiting for it" — _markets/README.md §Open, _packages/README.md rule 6, and its own §10. This is the fourth statement and the first build. It does not model the relation — it fails when a promised path stops existing, which is the cheap 80% and needs no graph.
Still open, deliberately. The three original places are untouched: no market page knows when its English source moves, no shipped snapshot knows when canonical moves past it, and the reverse query is unbuilt. This card closes the narrow case where the artefact already existed. The rest is the scoping session how-decisions-are-recorded.md §10 asks for.
Also not covered: a consumer that points at a canonical path not named in the manifest. aleris-assistant's pointer was exactly that until it was repointed today, and this check would not have seen it. What protects that case is the rule, not the test — consumers reference the package, never the layout.
Done when: a move that breaks a path the package is cut from fails this repo's own suite, and it is on main. ✔ (220/220 with the four new assertions.)
89 · [T] A rule is applicable where it is read — a seventh constitutional rule, with a check
Files: constitutional/a-rule-is-applicable-where-it-is-read.md (new, proposed) · lib/external-references.test.ts (new) · constitutional/_index.md
New 2026-08-27, from Torfinn's question about whether Brand OS depends on anything that exists only on his machine.
The rule. A rule stated in Brand OS must be applicable by a reader who has Brand OS and nothing else. A reference outside the repo may credit or date a rule; it may never be the only place the rule's content lives. Corollary: Brand OS may write outward, never require an outward read.
What prompted it, and the correction that came with it. A dependency sweep reported five local-only exposures as though the product were compromised. Reading each one found every reference in a published, shipped or accepted file was attribution rather than instruction — the favicon ships in the package, the detection test is quoted in full, the presentationsverktyg path is a downstream consumer. The corpus was already compliant and had never said so, which is the same shape as card 79's finding and as [[a-thinking-tool-stays-a-thinking-tool]].
Torfinn's framing is what made it a rule rather than a note: "the collaboration was about how you and I work together, not about how Brand OS learns and collaborates… it's a product and as such shouldn't have dependencies to my work stream." The rule's §2 draws the line where he put it — between the maintenance method, which belongs in the repo, and the collaboration method, which stays in TAKS. The answer is not "move TAKS into Brand OS": most of TAKS is correctly outside the product, and a different maintainer would bring their own collaboration method.
The uncomfortable half. CLAUDE.md's own rule — never paraphrase a governing document, point at it — is what would have produced a breach. It assumes the target is reachable by whoever reads the pointer, which holds inside one repo and fails across a boundary. The rule adds the missing clause: inside the corpus, point; across the boundary, absorb and attribute. documentation/how-decisions-are-recorded.md reached accepted citing an unversioned external ledger for its only stated test, and stayed applicable only because its author quoted the test rather than pointing at it — against the stated rule.
The check, and why it declares rather than judges. lib/external-references.test.ts cannot tell a load-bearing dependency from a credit, so it does not try: every outside-repo path in a rule surface must be declared with a kind and a reason, and an undeclared one fails. Same discipline as KNOWN_LANG_MISMATCH and IN_FLIGHT, including the anti-rot half — a declaration whose reference disappears also fails. Nine references across five files, all declared, all attribution or provenance or downstream. Four assertions, mutation-verified: a new ~/TAKS path in foundation/voice.md fails; removing a declared reference fails the anti-rot check.
Known limits, both on the rule page. The script judges declaredness, not correctness — a dependency mislabelled attribution passes, and only reading catches it, which is why the classification test is stated for humans. And it cannot see the mirror-image failure: a consumer holding a stale path into this repo, which is card 88's half.
Why [T]. The rule sentence, the corollary and the Implication all argue, so all three are Torfinn's to word or reject before this moves to accepted. Three further questions are open on the page, including whether the corollary should forbid the outward write too.
Related: cards 79, 88 · [[a-thinking-tool-stays-a-thinking-tool]] · documentation/how-decisions-are-recorded.md.
Done when: the rule sentence is Torfinn's wording and the page is accepted, and it is on main.
Closed 2026-09-07. Torfinn ratified the page as worded — the Claude-drafted sentence stands as his by acceptance, which is the second of the two routes this card allowed for. Same day and same instruction for a-thinking-tool-stays-a-thinking-tool.md, whose card was never opened separately.
90 · [T] Print has no home in Brand OS — scope what may be lifted from patientguide's document surface
Files: workspace/incoming/brief-print-surface-scope-2026-08-14.md (the brief) · principles/design-tokens.md · would land in principles/ + data-products/tokens/
New 2026-08-27, and the card is the point: the brief had sat unexecuted for thirteen days with nothing on the board, and deliberately parked and forgotten look identical without a card. Torfinn's call was to card it rather than archive it or execute it.
The gap is total, and the brief measured it rather than asserting it. Across foundation/, baseline/, principles/, constitutional/, patterns/, schemas/ and data-products/, in .md, .css and .html: zero occurrences of @page. Zero page dimensions in mm. One @media print — four lines in baseline/index.html hiding the sidebar when someone prints the Brand OS site, which is not a document standard. No header or footer standard, no page-number convention, no margin values, no normative measure. Meanwhile principles/design-tokens.md names print as a context where exact values matter, pointing at a token file that holds no print value of any kind.
Why it is a [T] card and not a lift. patientguide/docs/specs/document-surface.md (2026-08-13) is the first real print specification anywhere in the portfolio — page frame, margins derived from ISO 838 punch clearance, measure, header slot ratios, footer slots, a type floor in points, all measured off rendered output. The temptation is to copy it. The brief's own framing is the reason not to: a value lifted into Brand OS binds every project, not just this one. So the decision is not "what are the numbers" but which of one product's measured numbers are properties of the Aleris brand and which are properties of an A4 patient guide. That is a brand call.
Deliberately not proposed here: page geometry. The brief declines to propose any, and this card should not invent one either.
Done when: the brief has a decision — a scope for a print layer, or a recorded decision not to have one — and the brief is filed to workspace/archive/.
91 · [CC] agent-baseline v0.8 is cut and three consumer records still pin v0.7.1 — DONE 2026-08-27
Files: _packages/adapters/vendored-app/MANIFEST.md · _packages/adapters/vendored-app/instances/patientguide.brand-provenance.json · .../sundviktlakemedel.brand-provenance.json · the two consumer repos themselves
New 2026-08-27, opened by the v0.8 cut rather than found later. This is a repin, not a revendor — no token file changed in v0.8, so every vendored hash still verifies and no artefact moves. Only a pinned version number is stale, in three places.
Why the cut did not just fix them here. _packages/adapters/vendored-app/MANIFEST.md records the direction and it is the right one: "the consumer's copy is the one file a consumer edits, so the instance follows it rather than leading it." Editing the instance JSONs in this repo to say v0.8 while both consumer repos still read v0.7.1 would make this repo's records lead reality — which is the failure mode the instance/record split exists to prevent. The adapter's own pin is a second case of the same thing: vendored-app v0.2 pins content v0.7.1 deliberately, as a statement of what it was authored against, not as a pointer to latest. Moving that pin without cutting an adapter version would falsify it.
So the honest sequence is: repin in each consumer repo, then copy the live record back here. Two working copies are needed, one of which (patientguide) has been held by another session before.
Do not write drift prose into _packages/README.md to track this. Rule 7 of that file is explicit that drift is computed in workspace/status/board.html's Package status tab, from git log, precisely so nobody has to remember to update a note.
Done when: both consumer repos read v0.8, both instance records here are copied from the live files, and the adapter's pin has either moved with a new adapter version or been left with a reason.
92 · [CC] "Chrome" is a retired word with 54 uses in what publishes and ships
Files: measured in baseline/iconography/size-test.html (23), curation.html (13), curation-session-b.html (6), data-products/tokens/aleris-tokens.css (3), _packages/agent-baseline/tokens/aleris-tokens.css (3), data-products/iconography/allowlist.json (2), schemas/design-token.md (1), data-products/tokens/baseline-tokens.json (1), _packages/agent-baseline/tokens/baseline-tokens.json (1)
New 2026-08-27, found while cutting v0.8: _packages/agent-baseline/README.md's language section opened "Chrome is English; brand content is in its authored language." Corrected in that one sentence because it was being rewritten anyway. The other 54 were left, and this card is why.
The word was retired on 2026-08-04 and the reason is exactly what makes the sweep non-mechanical: "the word named both this rule and app furniture in roughly sixty files, and app furniture is the half the rule does not govern." The canonical replacement for the rule half is "Internal names are English." But a large share of the 54 are almost certainly Chrome the browser — size-test.html and the iconography curation files are about rendering — and a blind replacement would corrupt them. The word does two jobs; that is why it was retired, and it is also why a sed cannot close this.
A second thing this card has to settle before it can be asserted. lib/retired-terms.test.ts's third assertion requires every RETIRED row to name a record path that exists in this repo. The chrome retirement is recorded in ~/TAKS/2-areas/collaboration/ and in Dev/CLAUDE.md, neither of which is in this repo — and under the seventh constitutional rule (a rule must be applicable by a reader who has only this repo) citing them is not allowed. So a row cannot be added until the retirement has an in-repo record. That is the card's first step, not an afterthought.
Done when: each of the 54 uses is classified as the rule sense or the browser sense, the rule-sense ones are corrected, the retirement has a record in this repo, and a RETIRED row asserts it — with wholeWord: true, since "chrome" appears inside "Chromebook" and CSS identifiers.
93 · [T] The en-dash convention has never held – 9,079 em dashes against 1,405 en — DONE 2026-09-10
Files: measured across the whole corpus. workspace/status/changelog.md holds the convention's own history (2026-07-28 set it, two later entries record Claude breaking it and sweeping)
New 2026-08-27, and it is a decision rather than a sweep, which is why it is [T].
The convention as stated: en dash, never em dash, in brand-os output and in Claude's own prose. Set 2026-07-28. _state.md has carried "corpus-wide sweep is still Open #3" ever since.
The measurement, which is what changes this from a chore into a question:
| Directory | em | en |
|---|---|---|
foundation/ |
387 | 33 |
baseline/ |
423 | 55 |
patterns/ |
78 | 3 |
principles/ |
61 | 2 |
constitutional/ |
117 | 5 |
schemas/ |
87 | 4 |
filters/ |
62 | 0 |
documentation/ |
280 | 6 |
_packages/ |
1,195 | 88 |
workspace/ |
6,386 | 1,209 |
| total | 9,079 | 1,405 |
Em dash outnumbers en dash 6.5 to 1. filters/ has not one en dash in 62 opportunities. A rule this comprehensively unfollowed is not a rule that slipped; it is a rule that never took.
Two things found while measuring, both of which argue it is stated wrongly rather than merely unenforced.
foundation/constant-contextual.md, flipped to English on 2026-08-27, carries 14 em dashes and 0 en. So the convention was broken fourteen more times, that same day, on an accepted Foundation page, by a session that had the convention in its own context. Nobody noticed, because nothing checks it.
And the incremental fix makes things locally worse. Normalising only new prose leaves a file whose adjacent sentences disagree, which is why this session swept its own changelog entry (a self-contained new block, clean) and deliberately stopped rather than normalising 78 additions inside files holding hundreds of the other kind. _packages/README.md showed the trap concretely: a diff-scoped sweep would have rewritten the v0.7.1 row's punctuation, which is a previous session's prose, because the line registered as "added" when only its prefix changed.
PARTLY ANSWERED 2026-08-27 (Torfinn), and the answer narrows the rule rather than confirming it. Asked which way to take it, he said: "it's just that I always use – myself, so I would like to keep doing that." That is a preference about prose being written now – his own, and Claude's in brand-os output – not a mandate to rewrite 9,079 existing instances. foundation/emotional-modes.md was flipped to en dash throughout on the strength of it (0 em, 30 en), which is what following the rule on a page you are already writing costs: nothing.
So option 2 below has the answer and option 1 does not. What remains open is only the corpus: whether the 9,079 get swept, and whether anything asserts the rule afterwards. Nobody has decided to spend that, and it should not happen by accumulation.
The decision, and it is a fork not a task:
- The convention is real – then it needs one corpus-wide pass and a check, because 9,079 instances is not something attention closes.
retired-terms.test.ts's shape fits: declare the rule, assert the surface, allow named exceptions. Verbatim quotes of superseded wording must keep their original dashes, which the 2026-07-28 sweep already established and which any check has to encode. - The convention is narrower than stated – it governs, say, Claude's prose in working documents and not Foundation pages, in which case the statement in
_state.mdand the changelog is wrong and should say what it actually governs.
Option 2 is not a climbdown. A rule that reads as absolute, is unfollowed 6.5 times out of 7, and has no assertion behind it is worse than a narrower rule that holds – the same argument the corpus made when it moved the no-hard-wrapping rule into a checked register.
Do not sweep before this is decided. That is the third time the sweep would have run without the decision, and the two earlier runs are why the corpus is now mixed rather than either consistent state.
Done when: the convention is either restated with the scope it actually has, or made structural with a check and a corpus pass – and _state.md's Open #3 either closes or names which of the two happened.
94 · [CC] worry is still the mode name in two accepted pages after the rename — DONE 2026-08-27
Files: foundation/imagery.md:74, 78, 84 · foundation/constant-contextual.md:30, 36 · both mirrors in _packages/agent-baseline/
New 2026-08-27, opened by the rename rather than found later. foundation/emotional-modes.md renamed its first mode worry → uncertainty (Torfinn: worry presumes distress most arriving patients do not feel). Two other accepted, published, English Foundation pages still name the mode worry, so a reader crossing the three meets two names for one mode.
| Where | What it says |
|---|---|
imagery.md:74 |
table column head – "Worry — the patient before an assessment or procedure" |
imagery.md:78 |
"Life images as the main subject — they don't answer the worry" |
imagery.md:84 |
"The table works from the patient's modes, worry and curiosity" |
constant-contextual.md:30 |
"the one who is anxious" – a third word for the same state, and stronger than worry, so the objection applies to it more forcefully |
constant-contextual.md:36 |
Reader state row – "Worry, need for control" |
Two of these must NOT change, and getting that backwards is the real risk. constant-contextual.md:38 and emotional-modes.md's own "clinical details that may increase worry" use worry as an ordinary noun, naming an effect to avoid rather than naming the mode. The mode is not called Worry; worry is still something we can cause. That distinction is exactly what makes the rename right, so a find-and-replace would destroy the reason for the change while appearing to implement it.
And imagery.md:78 is not a substitution either. "They don't answer the worry" depends on there being a worry to answer. "They don't answer the uncertainty" parses, but it is a slightly different claim about what an image fails to do – which makes it Foundation prose, not a rename.
Deliberately not bundled into the flip (Claude's call, stated in the v0.9 manifest so consumers know): it is a second and third page's prose and deserves its own read rather than riding along.
Done when: each of the five is classified as mode-name or ordinary-noun, the mode-name ones are corrected, imagery.md:78 has Torfinn's wording, the package is re-cut, and it is on main.
95 · [T] Seven archived Swedish originals claim accepted while their replacements do too — DONE 2026-09-10
Files: _markets/sv/foundation/ (7 files, 6 of them accepted) · lib/route-manifest.test.ts (ORPHANED_ACCEPTED)
New 2026-08-27, and it surfaced because a check started declaring something instead of ignoring it. Adding _markets to AUDIT_DIRS – closing a hole in card 39's own first version, which claimed to walk "every directory that can hold corpus markdown" while omitting this one – put six files into ORPHANED_ACCEPTED.
The question the declaration raises. Each of those six carries status: accepted, and so does the English page that superseded it, at the same name under foundation/. So two files claim to be in force for the same content in two languages. The corpus has superseded for exactly this, and its own definition of the status is retired and replaced, carries a pointer to its replacement – which describes these precisely.
Why it was not just fixed. Two reasons, and the second is the one that makes it Torfinn's. First, an archive that has been edited is not an archive, and these were copied verbatim on purpose. Second and larger: _markets/ is the market-package model's territory, and that model is not settled. The 2026-08-04 language decision recorded that the destination is "a published English site with Swedish beside it, not instead of it". If Swedish returns as a served locale, these are not superseded originals – they are the Swedish edition, and accepted is the right status. So the frontmatter question and the market-package question are one question.
Cheap interim if the larger one stays open: the six could carry a pointer to their replacement without changing status, which is the half of superseded's definition that costs nothing and helps a reader either way.
Done when: either the six carry a status that matches what they are, or the market-package model is settled enough to say accepted is correct – and ORPHANED_ACCEPTED's comment stops posing the question.
96 · [T→CC] imagery.md waits on a reader-state model for professional readers that now exists — DISSOLVED 2026-08-27
Files: foundation/imagery.md:84 · foundation/emotional-modes.md § Three families of reader state
New 2026-08-27, found by querying wider than card 94's five references rather than by looking for it.
The sentence. imagery.md:84 reads: "Once the reader-state model for professional recipients (scrutiny, time-pressure) is settled, this page can point to it. Until then we keep the table at the patient modes."
It was settled earlier the same day. foundation/emotional-modes.md now publishes dispositional states for professional readers – time-pressure, cognitive load, accountability, scrutiny – as one of three families under reader state. So imagery.md is holding a door open for something that has walked through it, and the page pre-authorised the pointer in its own words.
Why it is [T] and not mechanical. Two things, and only the first looks like the sentence.
The pointer itself is one line. But imagery.md has a whole section below that sentence – Imagery in professional and internal surfaces – whose rule is restraint: the image steps back, function leads, often no hero image at all. That section was written when professional readers had no model. Now they have one with four named states, and the honest question is whether restraint is still a single rule or whether it varies by state – whether an image for a decision-maker assessing risk does the same work as one for a practitioner under time-pressure. That is a brand call, not a cross-reference.
And it collides with card 72, which is already open on this same page: imagery splits on audience while a product splits on situation, and a surface can be both. Card 72 is about the patient half of that split; this is the professional half. Whoever opens imagery.md should read both cards – the same argument the emotional-modes sitting made for cards 73 and 53.
Done when: either the page points at the dispositional family and says whether restraint varies across it, or it records that restraint is one rule regardless of state and the until then clause comes out – and it is on main.
97 · [CC] A Tailwind v4 @theme bridge for the token file — DONE 2026-08-28
Files: data-products/tokens/ (a new bridge file) · data-products/tokens/generate-tokens-json.js (if the bridge is generated rather than hand-written) · baseline/setup.md § Step 1 · schemas/design-token.md § Framework bridges
New 2026-08-28, approved by Torfinn the same day. From the portfolio census in workspace/evaluations/sundvikt-component-library-candidacy-2026-08-28.md §10: eight of twelve React apps type hex by hand, aleris-brand-os itself among them — app/globals.css carries a 122-line hardcoded @theme inline block while this repo publishes the canonical file. Ten of the twelve run Tailwind v4 and have no bridge to consume, so the hand-typing is not carelessness; there is nothing to import.
Why a bridge closes it. Tailwind v4 is CSS-first: @theme inline generates utilities that reference a custom property rather than inlining its value. So one canonical file can feed both worlds, and a project gets bg-petrol-500 from the same 363 declarations tokens.test.ts already asserts. Roughly forty lines.
Rule 2 of principles/design-tokens.md §13 constrains what this may be: the bridge is "disposable convenience infrastructure", not governance. It restates no value — it maps names — and if it ever disagrees with the token file, the token file wins. Generating it from aleris-tokens.css rather than hand-writing it is the way to make that true rather than intended, and it is the same shape as generate-tokens-json.js, which already parses that file.
Measured evidence that literals are the wrong medium, worth carrying into the file's own header: aleris-apt/index.html links no token file and carries twelve hardcoded hex including #4F866E and #4F868E. Not a typo — both are real Aleris tokens, --color-confirm-500 and --color-petrol-400, one character apart. A human reader cannot tell them apart and a project using them by literal has no way to know which it meant.
Done when: a bridge file exists in data-products/tokens/, is generated from the canonical CSS rather than hand-maintained, is named in baseline/setup.md as an optional step, has a test asserting it declares no value of its own, and it is on main. All but the last true as of 2026-08-28 — see the Done table for what the build actually found. 235 tests, npm run build clean, both routes prerendered and verified byte-identical to source. Carried forward, not silently absorbed: baseline/BASELINE.md, baseline/setup.md and schemas/design-token.md all changed today, so _packages/agent-baseline is behind on three files and a cut has not been asked for.
98 · [T] Card 62 re-read — eight missing patterns is one missing component layer
Files: none yet — this card is the re-reading, not the work. Depends on card 99.
New 2026-08-28, from workspace/evaluations/sundvikt-component-library-candidacy-2026-08-28.md §11. Does not replace card 62. It proposes a different answer to the question card 62 is stuck on.
Card 62 has been blocked since 2026-08-12 on the content-creation boundary: eight tool-surface pattern entries are missing, and writing pattern-library entries is prose Claude may not produce. It offers three routes, all of which put the writing on Torfinn.
The re-reading. A pattern file argues; a rendered component shows. The rendering half — the component, its states, the tokens it uses, the measurement — is the same category as baseline/buttons/index.html and baseline/conformance/index.html, both of which Claude built and which the board fenced (card 18) as "an evaluation surface, not a component library." So seven of the eight could exist as rendered, checkable components without anyone crossing the boundary, and the prose can follow later or not at all.
Two stay Torfinn's, because a real decision sits behind each. The third status state is card 65. The environment badge needs a colour role, placement, and a ruling on whether PROD should be badged at all — the questions _packages/adapters/vibe-coding-seed/GAPS.md §1 lists.
What this does not claim. It does not argue the boundary is wrong, and it does not ask for a recalibration. It observes that the boundary is about prose that argues in Aleris's voice, and that a rendered specimen measuring itself against the tokens is a different object — the same distinction the 2026-07-29 calibration drew, applied to a different artefact rather than widened.
Done when: Torfinn either accepts that card 62's six-to-eight are better served by rendered components than by pattern files — in which case card 62 narrows to the two with decisions behind them — or says why the pattern prose is the thing that was actually needed and card 62 stands as written — and whichever it is, the board records it and it is on main.
99 · [T] The starter-kit Storybook — scope, substrate and the four mechanisms
Files: none yet · reads workspace/evaluations/sundvikt-component-library-candidacy-2026-08-28.md · touches documentation/site-map.md § Delivery surfaces once decided
New 2026-08-28. The main proposal. It is a knowing revision of decision 2 in TAKS/1-projects/brandguide/design-system-layering-analysis-260824.md — "leave src/brand/ product-local" — taken by Torfinn on the reasoning that Brand OS should do as much of the groundwork as possible for people building in the Aleris style, that visual documentation is a powerful tool, and that a starter kit is less brittle than a lawbook.
What it is. A visual expression of the packages that are already cut: a Storybook that renders from the real token file rather than restating it, the property baseline/buttons/index.html already has. A fourth delivery surface, alongside the three documentation/site-map.md § "Delivery surfaces" lists. Not a package member — _packages/ is untouched, so rule 6 and adapter rule 2 do not bite.
Why the 24 August objections do not survive the reframe. Three were given. Framework choice — none, the artefact is CSS and markup per principles/design-tokens.md §12. Everyone else's upgrade path — none, nobody imports it; what is load-bearing is the token file they vendor, which already carries version identity, sha256 and expiry checks through brand-provenance.mjs. Release cadence — remains, and is the honest residual. The fourth objection, that a copied reference implementation has no version identity, was answered by the same document two sections earlier when it called the token layer "the discipline paying off": that machinery works because the artefact is a CSS file, and a CSS component layer inherits it unchanged.
The seed, measured 2026-08-28. sundviktlakemedel/src/brand/brand.css — 1674 lines, 137 BEM classes, zero hex literals, three data- hooks. The React barrel above it is 534 lines with not a single React hook; its own header calls it "the thin component layer." It is the only implementation of BASELINE.md's component rules anywhere.
Substrate: HTML and CSS, not React. It is the only choice that reaches every Aleris app — aleris-apt, aleris-event and bryning-app are vanilla HTML, aleris-presentation is Vue 3 — and it matches the decision taken at this exact layer on 2026-05-07 and never executed (documentation/architecture-v2-synthesis.md: "No framework (no Lit, no React, no Vue)… with AI as the primary coder, [the DX] value largely disappears, while the dependency cost stays"). The cost, stated: no props table, less ergonomic authoring, and a React developer hand-converts markup — which is the cost AI has actually made cheap. A React binding is a later addition, explicitly not canonical.
The four mechanisms that keep a non-authoritative kit honest, all of which have a working precedent in this portfolio: a review/ target (card 100); a test asserting the stylesheet and the stories agree — 137 classes declared, 92 literal in TSX, 17 constructed dynamically, so no grep can settle it; a declared-divergence list generalising Sund vikt's closed nine; and the no-raw-elements gate widened so it can see inline formatting inside the shared layer.
One hard exclusion, carried from the 24 August analysis and not negotiable: the kit ships no clinical rendering — dose and unit, lab value with reference range and flag, weight trend, provenance. The mitigation is a type, not a convention: shared components accept pre-formatted opaque strings and never numbers-with-units. sundviktlakemedel/src/brand/DataTable.stories.tsx:35 is the proof convention alone fails — the boundary leaked inside the candidate before a shared artefact existed.
Its nav slot is occupied as of 2026-09-18. The header entry reserved for this card — "Kit", rendered as text with "Not built yet" beside it — now links the published reference screens at /design-system/, labelled Reference screens rather than Kit, because the 2026-09-17 ruling declined an API surface. Nothing about this card changed: if it is built, the question is whether it shares that section or replaces it, and that is one line from Torfinn.
Hosting: brand.aleris.ai, built on deploy so it cannot become the "aspirational documentation" risk #5 of architecture-v2-synthesis.md names. Any Aleris git account may hold the source. Sequence against Phase C — a Storybook build is static and composes with the re-platform, so building it before that decision risks building it twice.
Maintenance: Torfinn now, Mohan later, forge-style. Which has to mean generated wiring, a gate that fails rather than a document that asks, and a self-service path for adding a component — or the handover does not survive.
Done when: Torfinn has decided scope, substrate and hosting, design-system-layering-analysis-260824.md §7 records that decision 2 was revised on 2026-08-28 and why, documentation/site-map.md names the fourth delivery surface, and it is on main.
100 · [CC] review/ gains a rendered-component target
Files: review/targets/ (a new target) · review/README.md · possibly review/checks/
New 2026-08-28. Depends on card 99 producing something to point at.
review/README.md opens by saying what it is for: "data-products/tokens/tokens.test.ts asserts what the token file declares. This asserts what a browser actually paints." What it has never had is a target that is the component set itself rather than a product built from it. A rendered component gallery is the ideal target — every component in every state at one URL, no login, no seeded database, no Docker.
Why this matters more than convenience. It is the mechanism that lets the kit be non-authoritative without being unchecked, which is the whole architecture of card 99. And it closes a gap the conformance tiers currently leave open: a product can be checked against Baseline, and the token file can be checked against itself, but nothing renders the component set against the rules it claims to implement.
Two properties of review/ to preserve, both stated in its own README. Targets are declared in review/targets/, never in the reviewed repo — but this target is in this repo, so the distinction that motivated the rule (reviewing work you do not own) does not apply and should be noted rather than silently inverted. And --self-test runs before every real review; a component gallery does not change that.
Contextual floors need declaring. target-size has two, 44px and 24px, and which applies is a fact about the situation. A component gallery renders both densities on purpose, so the target must declare per-viewport with a reason rather than inherit the strict default and report a false failure on every console specimen.
Done when: a target exists, --self-test passes, the run is clean or its findings are declared with reasons, review/README.md records the in-repo-target exception, and it is on main.
101 · [CC] Two token consumers are provably stale or wrong
Files: none in this repo — this card is the finding and the routing · _packages/adapters/vendored-app/ if either consumer turns out to warrant the adapter
New 2026-08-28, found while surveying the portfolio for card 99. Independent of every other card here.
aleris-units-explorer carries src/tokens/aleris-tokens.css at 33,409 bytes against canonical's 50,487 — an older, smaller version with a different hash, imported from src/index.css. It has no provenance record, so nothing in that repo can tell it is behind.
aleris-svar-main vendors a 149-line subset whose provenance comment points at ~/Dev/aleris-brand-os/baseline/tokens/ — a path that no longer exists, since Phase B step 3 moved the token file to data-products/tokens/ on 2026-08-10. Its sibling aleris-assistant, which it is a fork of, points at the current path. So the two forks disagree about where the source is and neither is checked.
Why this is a Brand OS card rather than each consumer's. _packages/adapters/vendored-app/ exists precisely because vendoring without provenance drifts silently, and it found four expired workarounds when it was first run against the two consumers that have it. These two have the same failure and no adapter. Whether they warrant one, or a lighter note, is the decision — but a consumer that cannot tell it is stale is the condition that adapter was built for.
Done when: both consumers either carry a provenance record or are recorded as deliberately unmanaged with a reason, the dangling path in aleris-svar-main is corrected, and it is on main.
102 · [T] The 2026-05-07 no-framework component decision was reversed in practice with no record
Files: documentation/architecture-v2-synthesis.md § Resolved 2026-05-07 · workspace/status/changelog.md § 2026-05-07
New 2026-08-28, found while sourcing the framework-agnostic reasoning for card 99.
documentation/architecture-v2-synthesis.md § "Resolved 2026-05-07" records: "Component framework: TypeScript + vanilla custom elements + Shadow DOM. No framework (no Lit, no React, no Vue)… Lit's main value is developer experience for humans writing many components by hand; with AI as the primary coder, that value largely disappears, while the dependency cost stays." Mirrored in the changelog the same day.
aleris-assistant ships React 19 + react-dom + Vite + Tailwind 4 + @storybook/react-vite + @testing-library/react. No .md in that repo mentions shadow DOM, custom elements, web components, or why React was chosen. The decision was reversed in practice and neither side recorded it.
Two things make this worth a card rather than a note. The synthesis document still reads as current — it says "no remaining architecture-level questions" — so a session reading it is told a decision holds that does not. And the architecture-v2-decisions.md it promises was never written, so there is no other place the reversal could have been recorded.
Why [T]. The question is whether the 2026-05-07 reasoning still holds. Card 99 leans on it — the AI-as-primary-coder argument is the reason an HTML substrate is proposed over a React one — so if it has quietly stopped being the position, card 99 should know before it is decided, not after.
Done when: either the synthesis document records that the component-framework decision was superseded and by what, or aleris-assistant records why it diverges, and it is on main.
103 · [T] OQ-12 — framework-agnostic component specs — open since 2026-03-21
Files: workspace/research/aleris-design-system-open-questions.md § OQ-12 · baseline/governance/aleris-design-governance.md (open_questions: frontmatter)
New 2026-08-28. The tracker entry card 99 answers, still last_verified: 2026-03-21.
OQ-12 reads: "Blocking: Framework-agnostic component specs… The slot pattern (data-slot) is CSS-native and the strongest candidate for a cross-framework standard." The same gap was flagged Priority 1 in workspace/archive/aleris-product-design-skill-workplan.md § "Gaps" — "Existing docs describe everything in Tailwind utility classes (rounded-xl, bg-sand). Action: describe in framework-agnostic terms with Tailwind as one mapping example."
Neither has moved in five months, and the asymmetry is the point. The token layer has been medium-independent since March, ratified three times and re-ratified into principles/design-tokens.md on 2026-08-10. The component layer never was: BASELINE.md's component rules are prose, every consumer implements them independently, and the one place they exist as code is product-local.
Nothing asserts framework independence anywhere. Every test in lib/ and every check in review/ was read; none of them touches it. The only mechanical proxy in the portfolio is _tools/frontend-suite/checks/r4-js-ceiling.mjs, whose docblock quotes the COOKBOOK rule "default to no JavaScript, and make the framework an opt-in with a reason" — and it measures a byte ceiling by audience tier, not a medium. Nothing fails when a component spec is written in Tailwind classes.
Done when: OQ-12 either closes against card 99's outcome or is restated to say what is still open, its last_verified moves, and it is on main.
104 · [T] Gröna korset's five-state safety scale — ratified clinical exception, or standing breach
Files: none yet · reads _packages/adapters/vendored-app/PROFILE-gcc.md §7 and review/reports/gcc-ux-review.html
New 2026-08-28, from profiling gcc as a consumer candidate. This is the question that gates whether an adapter for that consumer can be generated at all, because it decides whether the checks would exempt those colours or flag them — and an adapter may not answer it (rule 2 of _packages/adapters/README.md: an adapter carries no brand content).
Gröna korset is a national patient-safety method with established colour semantics. Its scale is five ordered states — empty → green → yellow → orange → red — and gcc's own collaboration map states the problem in one line: a straight one-to-one mapping onto Baseline's status set does not exist, so the scale has to be designed against Aleris tokens without losing readable gradation. Baseline's status colours mark error, warning, confirm and goal; they do not express ordered severity.
Two facts make this urgent rather than theoretical. The rendered review measured three of the five colours failing AA, including green — which is what a well-run ward sees on most days. And four of the five states are separated by hue alone, which is the classic red-green confusion set: for roughly one in twelve men, risk of harm and harm occurred read as the same band. Screen-reader users are already served, because every cell carries a full aria-label — so an audit that inspects only the accessibility tree passes this, and the group with no path is sighted users with colour-vision deficiency.
The decision, stated as the fork. If the colours are fixed by the method, the scale belongs in the corpus as a documented clinical exception with its own accessibility requirement — most plausibly a glyph per state, which gcc can implement cheaply because label, description and colour already sit in one structure. If they are not fixed, the scale is a design problem to solve against the tokens, and gcc is carrying a breach until it is.
Related, not the same: cards 64 and 65 are the same class — a real state the palette has no answer for — and 65 in particular is about a non-colour treatment for a third status state. Whoever takes this should read 65 first; if the two are answered separately they will disagree.
Done when: the corpus either records the safety scale as a ratified exception with its accessibility requirement stated, or rules that it is not one and says what the scale maps to — and it is on main.
105 · [T] gcc is profiled as a consumer candidate — decide whether to pursue it — DONE 2026-09-10
Files: _packages/adapters/vendored-app/PROFILE-gcc.md · _packages/adapters/vendored-app/PROFILE.md · _packages/adapters/README.md
New 2026-08-28. The profile is filled, with citations, per rule 1 of _packages/adapters/README.md. Nothing is installed and nothing in ~/Dev/gcc was touched. This card is the decision the profile exists to inform.
Structurally it is a vendored-app and nothing argues for a new type — Vite build compiling CSS, forge deploy on an aleris.ai origin, vitest, an agent reading an instruction file in the repo, and a single CSS entry point a vendored token file could reach.
Three things make it unlike the two installed instances, and each changes what installing would mean:
- It consumes nothing today. The palette is declared three times by hand and cited to a document rather than a file, and three values disagree with canonical — the first palette divergence in the portfolio, against a token file that ships byte-identical to five consumers. "Vendored means identical" has been true without exception until now.
- Its gate is empty.
npm testruns one assertion that isexpect(true).toBe(true). The adapter's checks would be creating the gate rather than joining one, which is the opposite of the conditionvendored-appv0.2 was designed for. - Its instruction layer is Swedish, so by the language rule wiring landing there would be Swedish — the
vibe-coding-seedanswer, not this adapter's.
Two blockers are not ours. Card 104 is the first. The second is that neither instruction file is currently a safe place to install wiring: AGENTS.md holds the design guidance but contradicts its own repo (it still describes a Supabase backend and points at directories that do not exist), while CLAUDE.md is what the agent actually loads and says nothing about UI — and has no refresh path. Rule 4 of the adapters README applies with force. Repairing that layer is Niclas's, not ours.
The cheapest useful thing needs no adapter and no decision: gcc already runs a working findings loop pointed at the forge platform, with the convention and the habit in place. It has no Brand OS findings file and no route home. Adding one copies a pattern that already works in that repo.
Also open: whether Torfinn has push access to code.aleris.ai/aleris/gcc at all. Not evidenced in the repo, and everything downstream of installation depends on it.
Done when: Torfinn decides whether to pursue gcc as a consumer, on what sequence, or records that it stays unmanaged and why — and it is on main.
106 · [T] 106 title-case headings ship to consumers against a decided sentence-case rule
Owner: T · Opened: 2026-08-31 · Found by: lib/heading-tells.test.ts on its first run
Sentence case is decided and unambiguous — "no exceptions, not even table headers or nav labels". First mechanical count: 106 title-case headings across 16 files, of which 58 are unique and the rest are the _packages/agent-baseline/ mirror. They concentrate in seven files imported from ALERIS-DESIGN-WORKING: aleris-anti-patterns (12), aleris-baseline-animation (10), aleris-grids-tables-dataviz (9), aleris-privacy-jtbd-analysis (7), aleris-baseline-images (5), aleris-progressive-enhancement (5), aleris-design-governance (3). Everything else is in ones and twos.
The rule was applied to new work and never to the imported material, and nothing counted, so it has been shipping to consumers inside the package the whole time.
Not failing the suite. The hits are frozen in lib/heading-tells.baseline.json and the test fails on a new hit or a stale row, so the debt cannot grow and cannot rot. The cleanup is a decision, not an emergency: the seven files are Baseline reference material, and rewriting their headings is a voice pass, not a find-and-replace. PROPER_NOUNS in lib/heading-tells.ts already exempts real names, including "Present Expert".
Done when: either the seven files are corrected and their rows deleted from the baseline, or the board records a decision that imported reference material keeps its title case and the baseline says so in its note — and it is on main.
107 · [T] Does WCAG 2.4.6 enter the accessibility constitutional node as a fourth non-negotiable
Owner: T · Opened: 2026-08-31
baseline/BASELINE.md commits to WCAG 2.1 AA minimum and constitutional/accessibility-is-foundational.md names WCAG 2.1 AA as its external lineage. 2.4.6 Headings and Labels — headings and labels describe topic or purpose — appears nowhere in the corpus; grep returns zero mentions, while 1.4.1, 1.4.11 and 2.5.8 are all cited by number. The node's three non-negotiables are colour-alone, 14px, and contrast — all visual.
This is what makes "What it is, in one paragraph" a conformance question rather than a taste question: it is a heading that does not describe its section's topic or purpose.
Against: the node describes its non-negotiables as "clean bright-lines and clean fitness functions", and 2.4.6 is only half-checkable — the vocabulary and structure rules are mechanical, but whether a heading describes its topic is judgment. Adding a half-checkable rule to a list whose defining property is checkability is the objection to answer, not to wave through.
Done when: the node either carries 2.4.6 or records why it does not, and it is on main.
108 · [T] The two coined heading patterns — wording passed, acceptance open
Owner: T · Opened: 2026-08-31 · Wording pass done 2026-08-31 at Torfinn's instruction
formrubrik (form instead of content, hard) and etikettrubrik (label heading, soft) sit in filters/ai-tells-and-filters.md and in the rules file at 0.2.1. The card opened as awaiting Torfinn's wording pass; he delegated the pass, so what is left is acceptance, not drafting.
Three things the pass changed, beyond the prose.
- The page's naming claim was false for two of fourteen patterns. "Named in Swedish because these are the established working terms" — these two are not established, they were coined here, because the writing-style-profile has no vocabulary for headings. The claim is now qualified in place and each pattern's provenance says coined, not inherited.
etikettrubrikhas an anchor at least: etikett is the word Digg's guideline 61 uses for a label. - The two patterns were overlapping. "The constituent parts" was listed as a
formrubriktell and is a noun phrase with no verb, which makes itetikettrubrik.formrubrikis now strictly the heading reports the text's form;etikettrubrikis the heading names a topic and says nothing about it. - The page and the rules file disagreed and now say so. "What is there, and what is not" was given as a
formrubriktell while thematchablevocabulary does not and cannot contain it. Both files now state thatmatchableis the subset a check can match, not the pattern — so the gap is declared rather than latent.
Raised as out of scope, then fixed on Torfinn's instruction the same day. dygdesignalering carried "honest by being clear, not by saying it is honest" and anglicismdrift carried "Kodväxling är inte djup, det är drift" — both antithesis siblings, on the pages that rule kiaster hard, transcribed verbatim in June 2026. Both second sentences cut, not rewritten, per Kiaster's own repair: the substance was already in each first sentence. Four live copies corrected together — both filter pages, the rules file counter_example, and both source entries in TAKS/2-areas/collaboration/writing-style-profile.md, which is a cross-boundary edit made deliberately, because fixing the corpus and leaving the source is mechanism 3 in workspace/status/cookbook.md §22. The plain-english skill packaged from that profile could not be located on disk and may be a fifth copy. The quotations above are kept on purpose.
Done when: Torfinn has read both definitions and either accepts them or replaces the wording, and it is on main.
109 · [CC] branch-hygiene.test.ts measures against local main, and goes false the moment it is stale — DONE 2026-09-09
Owner: CC · Opened: 2026-08-31 · Found by: running the suite
The check fails today with three branches "ahead of main". All three are illusions, and there are two separate defects behind them.
Defect 1 — the trunk reference is local. Local main is at 8e50146, seven commits behind origin/main at 056f1d8, and everything is measured against it. This session's branch is origin/main plus one commit. origin/claude/aleris-vibe-coding-feedback-51e02e is zero ahead of origin/main — it was merged in PR #13 — and looks 4 ahead only against the stale local ref.
Defect 2 — a remote HEAD symref is counted as a branch. The third entry, reported as a branch named origin, is refs/remotes/origin/HEAD, which git branch -a renders as the bare name. git branch --list origin is empty and refs/heads/origin does not resolve. No fix to the trunk reference removes this one.
Why it matters beyond the noise: a check reporting three false findings out of three is on its way to being ignored, and an ignored assertion is indistinguishable from an absent one. Mechanism 5 in workspace/status/cookbook.md §22.
The check is green as of 2026-08-31, because card 110 cleared the way and local main was fast-forwarded. That fixed the symptom and neither defect. Both return the next time nobody works in the main checkout for a few days, which is the normal state of affairs — sessions run in worktrees, so nothing updates that folder.
Defect 2 is latent rather than harmless. With main current, origin/HEAD resolves to a commit that is zero ahead, so it is not reported. It only surfaces once the trunk reference is stale — which means fixing defect 1 alone would hide defect 2 rather than remove it, and the entry would come back the first time someone fixed the trunk reference incorrectly.
Prove it against a stale trunk, not against today. The check has a BRANCH_HYGIENE_TODAY override for testing its expiry logic; a trunk override in the same spirit would let this be tested without waiting for main to rot. Reproducing today's state is a two-line setup: point the trunk at 8e50146 and the three false findings come back.
(Corrected twice on 2026-08-31. First version: two of three findings illusions, third real, and a "stray local branch named origin" to delete — all wrong, from reading git branch -a output as a branch list. Second version: three of three false, two active defects — right about the findings, wrong that both were active. This is the third statement of a four-line finding, and the pattern is the same each time: reporting what a tool printed rather than resolving what it meant.)
Done when: the check resolves the trunk as origin/main when the remote ref exists and falls back to local main otherwise, skips remote HEAD symrefs, is proven against a deliberately stale trunk rather than against a green tree, and it is on main.
110 · [T→CC] The main checkout held uncommitted work that duplicated commits already on origin/main — DONE 2026-08-31
Owner: T · Opened: 2026-08-31 · Found by: attempting the fast-forward card 109 seemed to need
/Users/torfinn/Dev/aleris-brand-os — the main worktree — sits on main at 8e50146, seven commits behind origin/main, and cannot be fast-forwarded, because it carries uncommitted work that collides with the commits it is missing.
Modified there and also changed by the incoming commits: lib/logo-minimum.test.ts. Untracked there and added by the incoming commits: review/SPEC-production-icon-surface.md, scripts/generate-logo-icons.mjs, workspace/evaluations/digital-identity-verification-2026-08-28.md. Also modified and uncommitted: _state.md, _packages/agent-baseline/logo/favicon.ico, plus four untracked icon assets under _packages/agent-baseline/logo/.
The reading: this is the icon and digital-identity workstream, which was rescued off backup/auto-20260828 and merged as PR #14, and the main checkout still holds an uncommitted second copy of the same work. Whether that copy is identical to what landed, newer than it, or a divergent third version is the question, and it decides whether the answer is discard, commit, or reconcile.
git merge --ff-only origin/main was attempted and refused, changing nothing — git protects against exactly this. It will keep refusing until the working copy is resolved, so local main stays stale and card 109's check keeps firing meanwhile.
Not Claude's to resolve unasked: discarding someone's uncommitted working copy is a decision, and three of the files are the digital-identity thread that has open questions of its own. Torfinn approved the backup and the discards on 2026-08-31.
Closed 2026-08-31. Every file compared against origin/main rather than assumed. Three were byte-identical to what PR #14 landed, one was an older copy of a file already on main, one was _state.md carrying the only unique content, and one was the generated favicon.
The favicon was the real hazard and it was not the one the card was opened on. The working copy held the generated 4258-byte ICO against main's provisional 4158-byte file — the one byte-identical to what www.aleris.se serves and the one the verification document measured. A careless git add -A would have shipped an icon set and answered Q2 of the digital-identity verification by accident. Restored to 4158 B; the whole generated set is parked with its reasoning at workspace/drafts/parked-2026-08-31/, inside the gitignored workspace/drafts/, so it cannot be committed by mistake and is one decision away from being usable.
_state.md needed no reconciliation. None of the ten commits touch it, so the fast-forward left it alone; verified byte-identical to its backup afterwards. Its 2026-08-28 Progress entry and three Log entries are still live and still uncommitted, including the correction owed in three files about emotional-modes.md being "the eighth and last Foundation page to flip" when foundation/ holds nine.
Result: local main at ffaa952, zero behind, and the full suite green — 243 tests, 10 files.
Done when: the main checkout's working tree is resolved against what PR #14 landed, local Met.main is fast-forwarded to origin/main, and it is on main.
111 · [T] Q1 — the corpus has no rule for digital identity, and that is what blocks shipping an icon set
Owner: T · Opened: 2026-08-31 · Blocks: Q2 (does the package ship the set) and Q3 (whose files)
This is Q1 of workspace/evaluations/digital-identity-verification-2026-08-28.md, restated with the reason it comes first. It had been reading as a sequencing preference. It is structural.
The corpus does not govern favicons at all. review/SPEC-production-icon-surface.md says it plainly — "This check has no rule to cite" — and gives the reason: every rule in logo/ is written for a mark that includes the wordmark, and the 24px minimum height exists precisely because below it the letters fall under the 14px type floor in foundation/typography.md. A tab icon is 16px. It is the one brand surface with no room for the wordmark at all, and the corpus has never said what the brand looks like there.
Why that blocks Q2 rather than merely preceding it. _packages/README.md rule 1 is that packages are generated from the corpus, never edited. Shipping an icon set to consumers before Foundation has a rule about digital identity would make the package assert something the corpus has not said — the same defect as a package mirror restating a token value, one level up. Five consumer repos would carry an identity decision with no ratified source. So Q1 is not a tidiness step before Q2; Q2 cannot be answered correctly without it.
What is already decided, so this card is narrower than it looks.
- The symbol may stand alone as decoration or brand symbol at any size, and may not substitute for the logotype at any size — Torfinn, 2026-08-27.
- The 16px entry is a deliberately different drawing — Torfinn, 2026-08-28, recorded in
scripts/generate-logo-icons.mjs's own header: the ring's inner boundary stroked at 24 viewBox units, giving two fully-opaque pixels per stroke at 16px while keeping the counter open. Four candidates were rendered and measured before choosing. - The threshold is measured and it is narrow. All three rings resolve at 32px and up (12 alpha-edge crossings across the centre row) and collapse to 8 at 16px, because ring thickness is ~17.5 of 192 units — 1.46px at 16px, 2.9px at 32px. Asserted in both directions in
lib/logo-minimum.test.ts, so if 16px ever resolves the redraw exception stops being justified and fails loudly.
So the open part is not what the icon looks like. It is where the rule lives and what it says: does the ruling on the symbol standing alone go into foundation/logo-identity.md, and does that page acquire a digital-identity section that a package and a check can cite.
A second thing that page is already waiting on. foundation/logo-identity.md is Swedish, status: draft, and the only Foundation page carrying no lang key at all. It is also the ninth file under foundation/ that three records call the eighth — the correction still owed since 2026-08-28. Writing the digital-identity rule may be the occasion to flip the page, which would settle both.
Already done and deliberately not waiting on this card: the Brand OS site now serves the full set (card closed inside the same session — see the changelog entry for 2026-08-31, third entry). That commits nothing about Q2, because the site is not the package. It does mean the mechanism and the 16px drawing are now exercised in a real browser before any consumer carries them.
Done when: foundation/logo-identity.md either carries a digital-identity rule that a package and a check can cite, or records why digital identity belongs to Baseline instead — and it is on main.
112 · [T→CC] Three token values restated where the token should have been named — and one is a ratified exception the constitutional page does not carve out
Owner: T for the ruling, CC for the rest · Opened: 2026-08-31 · Found by: measuring, after app/manifest.ts needed a literal hex and could not have one
constitutional/tokens-are-canonical.md says a document "names the token; they do not repeat its hex", and records that no mechanical check exists: "extending the same pattern to catch a raw value restated in prose is not yet built." Every past breach — cards 8, 21, 22, 24 — was found by review, one instance at a time.
A corpus-wide check is not buildable, and the measurement says so. 222 hex restatements across 17 files, of which the large majority are legitimate: foundation/colour.md (52) is the page whose job is to state the palette, baseline-tokens.json (41) is the generated twin, schemas/design-token.md (33) documents the record's shape. A check that flags all of them is roughly 90% false positives and would be switched off in a week. Same class as the unassertable-invariants finding — the rule is about restating a value where the token should have been named, and that requires knowing what a document is for.
A narrow signature is buildable: a token named and its own hex on the same line. Five instances, three unique once the agent-baseline mirrors are folded in. All three are in files whose job is emphatically not to state values.
1. principles/design-tokens.md:45 — the cleanest breach, and fixable now
Restates #4f866e and #2e8540 beside their token names. It is a direct breach of a specific instruction rather than only of the general rule: baseline/BASELINE.md:45 ends with "This is the rule of record for confirm; other documents reference it rather than restating it." This is the other document, and it restates it.
2. baseline/BASELINE.md:45 — the rule of record breaches itself, and is fixable now
The same sentence that tells other documents not to restate the value restates it: --color-confirm-500 (#4f866e) … --color-goal-achieved (#2e8540). Neither hex carries any argument. The argument is the contrast figure — white on it is 4.23:1, below the floor at rest — and that stays. Dropping the two hexes costs the page nothing.
3. baseline/BASELINE.md:27 — not a breach, a collision between two accepted pages. Torfinn's.
This one is already a ratified exception, added 2026-08-12: "The one documented exception: a page rendered before the token stylesheet can load." A pre-auth surface sits behind the gate it is rendering, cannot reach the hashed stylesheet, and has to inline its colours. The page therefore lists the four values a pre-auth page needs — sand-100, petrol-500, orange-600, gray-100 — "cited from canonical rather than invented for this case", which is exactly the reasoning that makes it defensible. It was added after a real build shipped #d9663d and #e6e1da, two colours that are not Aleris colours at all, "because there was nothing here to check against."
So a document that must state values, and a constitutional rule that says no document may, are both accepted and in force. The constitutional page has no carve-out and does not mention this exception.
And the shapes are not symmetrical, which is the part worth deciding on. app/manifest.ts had the same problem — a surface that cannot use custom properties — and it was solved by reading the token file at build time. A markdown document cannot do that. So the options are genuinely different in kind: write the carve-out into tokens-are-canonical.md naming this exception, or move the four values into something generated that BASELINE.md points at rather than lists.
A fourth thing, found in passing
BASELINE.md:27 specifies a check that does not exist: "Pin the values with a test that extracts every hex a pre-auth page uses and asserts each one is one of these four — a build cannot see this rule fail otherwise." Written 2026-08-12, never built. It is a different check from the one this card proposes and both are small.
What the narrow check would and would not catch
It matches an exact token name and that token's own hex on one line. It misses the semantic-alias shape, and there is a live instance: BASELINE.md:41 writes --surface-page (sand-100, #f2ece4), where the alias resolves through --color-sand-100, so no name-to-hex match fires. Stating the blind spot rather than discovering it later — a check whose coverage is unstated is the failure mechanism 2 in workspace/status/cookbook.md §22 describes.
Done when: Torfinn has ruled on item 3, items 1 and 2 are corrected in canonical and in the agent-baseline mirrors, the narrow check exists with its blind spot named in its own header, and it is on main.
(Cards 113–115 were opened on 2026-08-31 as 106–108, on a branch that did not yet have origin/main's own 106–112. Renumbered on merge: main holds the numbers, a branch does not get to keep them. The board is the list of record and two cards with one number is a defect in it, so this is recorded rather than silently fixed — and it is an argument for taking the next number from origin/main rather than from the working copy.)
113 · [T] The live site has served a 2026-06-16 build for 73 days — redeploy, and find out why it stopped
Files: none in this repo — this card is the deploy · reads README.md § Deploy · the Coolify app on dev.aleris.ai
New 2026-08-28. Measured by fetching every published file from brand.dev.aleris.ai and matching it by hash against the git history, not read from a deploy log. The deploy is frozen at commit 23d4e7b, 2026-06-16. main has moved 201 commits since.
| Served live | Canonical | What it means |
|---|---|---|
aleris-tokens.css 36,804 b, sha256 e2dfab4c… |
50,487 b, sha256 3aa2d866… |
anyone who follows setup.md gets June's values |
BASELINE.md 233 lines |
292 lines | still teaches the confirm-green rule retired 2026-07-31 |
baseline-tokens.json 45,210 b |
49,320 b | stale usage and constraint metadata |
aleris-fonts.css 3,130 b |
2,724 b | the font host every package links |
aleris-tailwind.css 404 |
exists since 2026-08-28 | step 3 of the documented install is broken as written |
/en/* 404, /sv/* 200 |
en is the only locale since 2026-08-12 |
the site serves a locale the app no longer defines |
The reason this needs fixing now is that a builder is told to fetch those URLs. baseline/setup.md line 22 tells them to curl the token file from that host and line 82 to fetch the Tailwind bridge, which 404s. The aleris-design-system skill written 2026-08-28 names five of those URLs as the canonical thing to fetch instead of building from prose; it holds no values on purpose and sends the reader to the source, so a stale source defeats its whole design. llms.txt is generated from the frozen build, so the machine-readable index advertises paths that moved on 2026-08-10.
What is not affected. The two installed vendored-app consumers vendor from _packages/agent-baseline/ rather than from the URL, and brand-provenance.mjs hashes what they carry; sundviktlakemedel's token file was verified byte-identical to canonical on 2026-08-28. The exposure is to anyone following the published setup path, and to agents.
The redeploy is the smaller of the two problems. The larger question is why a Coolify app stopped rebuilding and nothing said so for ten weeks. Card 114 is the assertion that would have caught it; this card is the fix and the diagnosis. [T] because it needs Coolify access, not because it needs a decision.
Done when: brand.dev.aleris.ai serves the current build — verified by re-fetching the six rows above and matching hashes against the working copy, not by the deploy reporting success — the cause of the freeze is recorded in workspace/status/changelog.md, and it is on main.
114 · [CC] Nothing fails when the deployed build and main disagree
Files: a new check, probably lib/deploy-freshness.test.ts or a review/ check · review/README.md if it lands there
New 2026-08-28, opened with card 113 rather than inside it, because the deploy is the incident and this is the missing apparatus.
This repo asserts almost everything else about itself and not this. lib/route-manifest.test.ts fails when what publishes changes; data-products/tokens/tokens.test.ts fails when a ratio moves; lib/consumer-contract.test.ts fails when a promised path stops existing; lib/branch-hygiene.test.ts fails when work sits on an unmerged branch. Every one of those was written after the thing it checks had already gone wrong once. The published surface is the one claim nothing checks, and it was wrong for 73 days without anything reporting it.
Two shapes, and they answer different questions. A build-time check compares the corpus against itself and would not have caught this, because the repo was correct throughout. What is needed is a check that fetches the live host and compares what it serves against the working copy — the same method that found the problem. review/ already drives a real browser against a declared target and already holds the rule that an empty page is a failure, never a pass; a freshness check is the same class of instrument one tier simpler, and it may belong there rather than in lib/.
Three traps to design around.
- It cannot run in an ordinary
npm testwithout making the suite depend on a network and a live host.branch-hygieneis the precedent for a check that reads outside the file tree, and it is worth reading before choosing where this lives. - Not every difference is a failure. A page that is
draftlocally is correctly absent live; the check must compare the published set, whichlib/route-manifest.jsonalready computes, rather than every file. - The check must fail somewhere a human will see it. A test that only fails inside a suite nobody runs after a deploy reproduces the same defect one step further back. Where it reports is part of the design.
Sequence: it can be written before card 113 lands and will fail until the redeploy happens. That is the behaviour we want, and it demonstrates the check works in a way a passing run cannot.
2026-09-02 — the check is written and it fails, which is the half that can be finished without the redeploy. site/deploy-freshness.mjs, run with npm run site:freshness. It sits beside site/link-audit.mjs rather than in review/: that tool's stated job is what a browser paints, and its checks are functions evaluated in a page. This compares bytes over HTTP. The pair reads as one question each — does the build link only where it serves, and does the host serve what the build made.
Written as a failure model rather than as one check, per cookbook entry 21. Eight modes covered, two named as gaps:
| Mode | State |
|---|---|
stale-content served bytes differ from the build's |
checked, byte-exact |
raw-contract a raw endpoint 404s or changed |
checked, byte-exact |
index-stale llms.txt differs from the generated one |
checked, byte-exact |
missing-route a built route 404s on the host |
checked |
empty-route 200 with no text-bearing content |
checked |
fonts-gone a woff2 or the stylesheet 404s |
checked |
fonts-cors /fonts/* stops filtering by Origin |
checked, allowed and denied |
host-down unreachable or erroring |
checked |
extra-route something published the build did not make |
not checked — needs a crawl; nothing enumerates what is served. This is how foundation/en-draft/ became sixteen live URLs |
content-wrong whether the served prose is correct |
not checked — byte-identity is the only mechanical proxy, and it is a proxy |
Byte-exact for some things and not others, deliberately. Raw endpoints, the font files and llms.txt are verbatim copies or generated text, so equality is the right assertion. HTML pages are checked for existing and having content but not for equal bytes — the emitted HTML carries content-hashed asset filenames, so two correct builds of the same source differ, and asserting equality there produces a check somebody deletes.
First run against brand.dev.aleris.ai, 2026-09-02: 42 findings across four modes. 18 stale-content, 21 missing-route, 2 raw-contract, 1 index-stale. Four modes came back clean and that is worth as much: fonts-cors filters correctly in both directions, the font files are all present, no route serves an empty shell, and the host is up. So the one thing every consumer package depends on is not broken.
The self-test runs before every real check and is not optional, following review/. Nine assertions. The first version had two that could never fail — Boolean('*') and a comparison against a literal — so the CORS judgement was extracted into a pure function and the self-test now drives it with real inputs, including a wildcard reaching a denied origin. A tautology in a self-test is worse than no self-test, because it reads as coverage.
WHERE IT RUNS IS STILL NOT DONE, and it is the half that would have caught this. Two wirings are needed and neither exists: a post-deploy step, so a bad deploy is loud when it happens; and a schedule, because nothing failed on 2026-06-16 — the failure was 76 days of nothing happening, and no post-deploy hook can see that. Only a clock can. The script existing is not the same as the script running, which is the trap this card named from the start.
Done when: a check exists that fails when the live host serves content the current main does not, it is proven to fail against the frozen build and pass after the redeploy, where it runs is recorded, and it is on main.
115 · [T] Three of four semantic text roles are never measured, and one of them fails AA everywhere
Files: data-products/tokens/aleris-tokens.css (--text-secondary, --text-tertiary, --text-disabled) · data-products/tokens/tokens.test.ts test 15 and § "text-primary on …" · foundation/colour.md role column
New 2026-08-31, found while building the site — three times in one hour, which is the argument for the card.
The measurement. --text-tertiary resolves to --color-gray-300 (#9e9281). Against the three grounds this corpus actually uses:
| Ground | Ratio | AA text (4.5:1) |
|---|---|---|
--surface-page-instrumental sand-50 |
2.88:1 | fail |
--surface-page sand-100 |
2.60:1 | fail |
--surface-card white |
3.05:1 | fail |
Nothing catches it, and the reason is precise. tokens.test.ts test 15 classifies primitive colours by what foundation/colour.md says they may be used for, and it has gray-300 at level: 'nontext' — which is correct, and passes. The semantic layer above it is checked for --text-primary only; --text-secondary, --text-tertiary and --text-disabled appear nowhere in the suite. So a role whose name begins --text- may point at a primitive the same suite classifies as non-text, and no assertion connects the two.
This is the v0.6.4 defect one layer up. That cut corrected three role cells in foundation/colour.md that documented colours for text below the AA floor, and added test 15 to stop it recurring. Test 15 checks the prose against the primitive. It does not check the semantic alias against either.
The substantive question, and it is why this is [T]. --text-tertiary may not be a defect at all. The token file gives it the same value as --text-disabled and --state-disabled-text, and uses it for --input-placeholder — and disabled controls and placeholder text are genuinely outside WCAG 1.4.3. On that reading the value is right and the name is the problem: --text-tertiary reads as the third level of body text, so that is what it gets used for.
Evidence that the invitation gets accepted: building the new site, it was reached for three times in an hour — a "not written yet" label, a chip, and a card note — each time as caption ink, each time failing. All three are now --text-secondary (6.76–7.94:1). No consumer was surveyed; that survey is part of the card.
The fork. Either the role is for disabled and placeholder text and should be named so — with a real caption colour added if the corpus wants one — or it is a text level and its value is wrong. Both are token decisions.
Whichever way it goes, the check is the same and it is the durable half: every --text-* role must be measured against every page and card surface it can sit on, and a --text-* role resolving to a primitive documented nontext must fail. That closes the class rather than this instance — cookbook entry 21's argument, applied.
Done when: the corpus records what --text-tertiary is for, its value or its name matches that, tokens.test.ts measures every semantic text role rather than one of four, and it is on main.
Amended 2026-09-04 — the durable half is done and the fork is still open. The check the card called "the durable half" landed: tokens.test.ts § "semantic text aliases resolve to a colour that may carry text" joins every --text-* alias to the DOCUMENTED_ROLES classification of the primitive it resolves to, and fails when that classification is nontext. Two exemptions, both structural — --text-inverse (white, only ever on a dark surface, asserted elsewhere) and --text-disabled (WCAG 1.4.3 exempts inactive controls). Verified by re-introducing a defect and confirming it fires.
It found the failure this card did not. The card named --text-secondary, --text-tertiary and --text-disabled as the three unmeasured roles. --text-accent was the fourth, was not listed, and was the one actually failing at every size: it resolved to --color-orange-500, 2.38:1 on white, below the large-text floor as well as the normal-text one. Repointed to --color-petrol-400 on Foundation's precedence — foundation/colour.md documents orange-500 as decorative and print, never as text — and the card's own framing is what made that a correction rather than a decision. Zero consumers, so nothing rendered was wrong.
--text-secondary measures clean (gray-500: 7.94 white, 7.49 sand-50, 6.76 sand-100), so of the four roles the card set out to measure, one was already fine and one is fixed.
What is left is exactly the fork, and it is unchanged: --text-tertiary is now recorded in KNOWN_TEXT_ALIAS_FAILURES, asserted to still fail, with all three ratios (3.05 white, 2.88 sand-50, 2.60 sand-100 — matching this card's table). Fixing the value without removing that entry breaks the suite, which is this file's convention for a breach that has not been ruled on. Still [T]: is the role for disabled and placeholder text and misnamed, or a text level with the wrong value? The consumer survey the card asks for has not been done.
RULED 2026-09-04 (Torfinn): neither — retire the name. The survey ran first and is what moved the answer off both horns of the fork. It found one text use in 849 files (patientguide's print stylesheet) and no ::placeholder rule in any consumer, so the value is right for what it is genuinely used for and the placeholder concern was theoretical. The name is what got reached for. Retired rather than renamed, because --text-disabled and --state-disabled-text already carry gray-300 and a rename would have made a fourth name for one value. No caption tier: the lightest warm grey clearing 4.5:1 on all three grounds sits 1.4 ratio steps from gray-500. Closed — see the Done row.
118 · [T] Error text on the communicative ground — recorded as a known failure for a month, and unowned
Files: data-products/tokens/aleris-tokens.css (--text-error, --input-error-color, --color-error-500) · data-products/tokens/tokens.test.ts KNOWN_INPUT_FAILURES['error-text/sand-100'] and § "semantic text aliases resolve to a colour that may carry text" · foundation/colour.md role row Error
Not a new finding, and the card exists because of that. Card 18 found it on 2026-08-05 — one of eight failures it surfaced and deliberately did not fix — and the suite has asserted it as a known failure ever since, with the note "Not fixed here – card 18 surfaces it." Card 18 closed. Of its eight, three got their own cards (64, 65, 70) and this one did not, so it has been correctly recorded and unowned for a month. Re-encountered 2026-09-04 from the other direction, by the semantic-alias check card 115 asked for, which is how the gap in ownership became visible.
The measurement. --text-error resolves to --color-error-500 (#c14444).
| Ground | Ratio | AA text (4.5:1) |
|---|---|---|
--surface-card white |
5.03:1 | pass |
--surface-page-instrumental sand-50 |
4.74:1 | pass |
--surface-page sand-100 |
4.28:1 | fail |
Why it matters more than the margin suggests. sand-100 is the communicative page ground — patient-facing content, marketing, onboarding — and --input-error-size is --font-size-xs (14px) at --font-weight-regular. So the case that fails is the common one: a field error, at the smallest size in the system, on the surface patients read. It clears 3:1 on all three grounds, so it is large-text-safe everywhere and normal-text-safe only off the communicative ground.
What 2026-09-04 changed, and it is small. The three ratios now travel on the token itself as an @constraint, so baseline-tokens.json stops shipping --text-error to product teams as an unrestricted text colour. A known-failure entry is visible to whoever runs the suite; an annotation is visible to whoever consumes the token. The value was not touched: it is Foundation's, and DOCUMENTED_ROLES already classifies it large, correctly, which is why the suite has been green throughout. Choosing what to do is the decision, and there are at least three shapes: darken error-500 (touches Foundation, and the colour-alone reasoning in § "the status pair is the worst case" cites this hex by name); add a darker error text step alongside the swatch, which is what the audited Claude Design system did — it invented #8f2f2f at 6.52:1 on a tint, and that is § 4.1 of the audit; or rule that a field error always sits on a white card, never on the page ground, and assert that instead.
The trap. Do not repoint --input-error-color and leave --text-error, or the reverse — they are two aliases on one primitive and only one of them is about form fields.
Done when: the corpus records what error text may sit on, --text-error and --input-error-color match that, the annotation states the ruling rather than only the measurement, and it is on main.
DECIDED 2026-09-04 (Torfinn): rule the ground, assert it. Neither alias moved — both stay on error-500, which is Foundation's value — and BASELINE.md § Forms gained the rule. The deciding evidence was that no consumer renders error text on sand-100 at all, so the corpus was being asked what to permit rather than what to repair. Closed — see the Done row.
119 · [CC] The token file moved and the package mirror did not — first value drift since v0.6.3
Files: _packages/agent-baseline/tokens/aleris-tokens.css and baseline-tokens.json · _packages/cut-agent-baseline.mjs · _packages/README.md § version table · consumer brand-provenance.json records
New 2026-09-04. --text-accent changed value in canonical (card 115's amendment above). The package mirror was byte-identical to canonical before that and is now behind it.
This is the fan-out kind that costs the most, and it has not happened since v0.6.3. Every cut from v0.7 to v0.11 was recorded as "No token file touched, so a consumer's vendored-value hash does not move and no revendoring is required — only the pinned version". That sentence is now false for the next cut: a token value moved, so patientguide, sundviktlakemedel and aleris-assistant re-vendor rather than repin.
Not synced in the change that caused it, and that is the rule rather than an oversight. _packages/README.md rule 1: "Packages are generated, never edited. A hand-edited package is drift by definition." And MANIFEST.md on the cut script: "It does not choose a version, write this section, or update the version table — those carry the reasoning and the rule-6 judgement, and neither is mechanical." A repin is [CC]; the version call is not.
A gap this exposes, worth deciding inside the card. Nothing in the repo reports that the mirror is behind canonical. Grepped: no test, no script, no check reads _packages/agent-baseline/tokens/ and compares it. The mirror is a pinned snapshot, so asserting identity would be wrong — it is allowed to lag. What is missing is an assertion that the lag is declared: that when the two differ, _packages/README.md's version table says so. Same shape as branch-hygiene.test.ts failing on a branch ahead of main and undeclared, which is the pattern to copy rather than invent.
A blocking prerequisite, added 2026-09-04 with card 115. --text-tertiary was retired from canonical. patientguide holds a vendored copy that still declares it and uses it once — web/src/app/globals.css:674, inside @media print, on the (url) suffix after an external link. Nothing is broken today, and that is the danger: re-vendoring makes it a dangling var(), which is invalid at computed-value time and therefore inherits rather than erroring. The printed URL suffix would silently go from gray-300 to petrol-500, on patient-facing printed documents, with no build warning. Fix patientguide's line to --text-secondary before re-vendoring, not after. Not done from Brand OS: patientguide is on the no-go list and wants a code-reading human at its own gate.
Done when: the cut is made with a version call recorded in MANIFEST.md, patientguide's print rule is fixed before its copy moves, the three consumer records re-vendored and each repo's own brand-provenance.mjs run clean, an undeclared-lag check exists or its absence is recorded with a reason, and it is on main.
Update 2026-09-07 (Phase 0 revised). Four more items land with this cut and nowhere else, because package files are never edited by hand: (1) eleven shipped files now carry type:/layer: frontmatter for their new tier, so the copies already differ from canonical by those lines; (2) MANIFEST.md's source column names foundation/… for seven moved essays and baseline/setup.md, and its logo/README.md row says "rules from foundation/logo-identity.md" — all now under principles/ or how-to/; (3) the governance copy cites baseline/reference/aleris-grids-tables-dataviz.md, repointed in canonical to principles/grids-tables-dataviz.md; (4) lib/in-repo-references.test.ts pins MANIFEST.md at 5 and the governance copy at 3 unresolved until the cut, which is when both drop back to 2. The map the cut copies through is now _packages/agent-baseline.map.mjs; cut-agent-baseline.mjs --check lists 16 files that would change, nine of them drift since v0.11.
120 · [T] Baseline has layout numbers and no layout behaviour — the column ladder for both surfaces is undecided
Files: workspace/research/layout-and-responsiveness-2026-09-04.md (the brief) · baseline/reference/aleris-grids-tables-dataviz.md § Part 1 · data-products/tokens/aleris-tokens.css § Layout Grid, § Breakpoints · sundviktlakemedel/src/brand/brand.css and src/styles/responsive.test.ts (the only solved instance)
New 2026-09-04, from the Claude Design audit's reflow finding and the research that followed it.
The gap, in one sentence. Baseline states a 12-column grid, two max-widths, four breakpoints and "use container queries" — and nowhere says what any of it does when the screen narrows. Nothing states that columns stack, when, or in what order. The breakpoints are Tailwind's defaults exactly, in px, admitted in the token comment as "not derived from the modular scale", and consumed by nothing in this repo. "Component swap at breakpoints" is a heading whose body is the z-index ladder. F6 does not cover space, so the surface-temperature layout rules have no reasoning parent.
Five builds, five answers, none from Baseline. sundviktlakemedel is the one real responsive story in the portfolio — a one-column measure that grows via clamp() anchored to --breakpoint-sm/md, rem breakpoints for text-zoom, three phone stacking rules, spacing that steps with width, all tested — and Torfinn decided its key call (one growing column, not two) on 2026-09-02. It lives in one product's CSS. Same shape as the row primitive: solved downstream, absent upstream.
What seven other systems agree on (GOV.UK, NHS, USWDS, Carbon, Atlassian, Polaris, Material 3 — sources in the brief): the column count is not constant across widths; mobile is one column and columns are opted into; reading width is a stated measure in characters, not a column span; two container widths for two content kinds is the norm (the part Baseline has right); classify by window not device, with media queries for the page and container queries for components.
The decision that is yours: the column ladder for both surfaces. Communicative: is "grow the one column" at ≥1024 a Baseline rule, or was it a product choice for a one-job screen? Instrumental: what does a 12-column work surface become on a phone — the console had to invent "three things must work" alone. Same session shape as the radius decision (cards 44/45): candidates rendered side by side from the real token file, picked by looking. The Storybook seed (card 99) and this exploration are one piece of work from two ends.
Two prerequisites are [CC] and cost no decision: measure Museo Sans ch at each type size so a measure token can be honest (svl found 10px at 18px; the Claude Design system ships 68ch unmeasured); and a reflow check in review/ at 320 CSS px across every target, which is Baseline's own button rule generalised and would have caught every Claude Design kit.
Prerequisites done 2026-09-04, same day. review/checks/reflow.mjs exists, discriminates in self-test, and ran against the Claude Design kits: 6 of 15 route-viewport pairs fail, every offender a nowrap flex row or a fixed-column grid — the two shapes the 2026-08-12 button entry already names. Museo Sans ch measured from the licensed woff2: exactly 0.5em at both weights (9px at 18px), and real Swedish body averages 0.81 of a ch, so a ch measure overstates by a quarter — 68ch holds 84 characters, not 68. tokens.test.ts now asserts every width media query under site/ and app/ equals a declared breakpoint (generalised from svl's test; mutation-verified). Numbers and tables in the brief § 6a.
A finding against a consumer's record, named and left. sundviktlakemedel/src/brand/brand.css states 1ch = 10px and 64 / 77 characters per line at 640 / 768px; measured, it is 9px and 87 / 105. Its "a cap would bite nothing" conclusion rests on the wrong figures — at 105 characters the column is thirty past the 45–75 guideline. Read-only to Brand OS (constitution rule 5); routes back through the findings path, and it bears directly on the communicative half of this card: 75 characters of 18px Museo Sans is ~548px, so Baseline's "6–8 of 12 columns" (600–800px) is 82–109 characters and was never a readable-measure proxy.
Specimens built 2026-09-04: baseline/conformance/layout/index.html. The decision surface, same shape as ../radius/: three candidate ladders per surface, each rendered as specimen.html in an iframe at exactly 320 / 390 / 640 / 768 / 1024 / 1280 / 1440, from the real token file and the licensed Museo Sans. Every frame reports its own column width, characters per line at 18px body (measured average 7.31px, not ch), columns in use and whether it scrolls sideways. Content is not written for the page — the accepted hero pattern, copy Torfinn authored in the Claude Design kit, and svl's console rows. Not wired into the route manifest. Two defects in the page itself were found on its first look and fixed: the iframe's 1px borders sat inside the declared width, so every frame ran 2px narrow and the 640 frame fell on the wrong side of --breakpoint-sm; and the hidden surface was display:none, so its frames laid out at no width and the table showed a column of zeros that looked like a result.
What the frames report (column px · characters per line at 18px), none of the 42 frames scrolling sideways:
| Surface | Model | 320 | 390 | 640 | 768 | 1024 | 1280 | 1440 |
|---|---|---|---|---|---|---|---|---|
| Communicative | A grow the one column | 288 · 39 | 358 · 49 | 608 · 83 | 640 · 88 | 704 · 96 | 768 · 105 | 768 · 105 |
| Communicative | B capped at a measure | 288 · 39 | 358 · 49 | 548 · 75 | 548 · 75 | 548 · 75 | 548 · 75 | 548 · 75 |
| Communicative | C two columns from wide | 288 · 39 | 358 · 49 | 548 · 75 | 548 · 75 | 527 · 72 · 2 sp | 548 · 75 · 2 sp | 548 · 75 · 2 sp |
| Instrumental | A 12 → 6 → 1 | 1-up | 1-up | 2-up | 2-up | 4-up | 4-up | 4-up |
| Instrumental | B 12 → 1 | 1-up, nav stacked | 1-up, nav stacked | 1-up, nav stacked | 4-up | 4-up | 4-up | 4-up |
| Instrumental | C fluid auto-fit | 1-up | 1-up | 2-up | 3-up | 4-up | 5-up | 5-up |
Three things the numbers say before anyone looks. Model A's column is past the 45–75 guideline from 640px up and reaches 105 at 1280 — the svl ladder, measured against the real face, is a wide measure at every desktop width. Model B holds 75 at every width from 640 and the ground takes the rest; whether that reads as calm or as wasted space is the thing to look at. Instrumental C, with four tiles, lands on five columns at 1280 and 1440 — a row with an empty fifth slot — which is what "the count falls out of arithmetic" looks like in practice, and the argument against it.
DECIDED 2026-09-05 (Torfinn, from the specimens): communicative A, instrumental C. Communicative: one column that grows — svl's clamp, promoted — with no measure cap, chosen knowing it holds 88 characters at 768 and 105 at 1280; recorded on the token as a deliberate departure from the 45–75 guideline so the next reader does not take the width for an oversight. Instrumental: fluid auto-fit on 14rem tiles, no breakpoints, chosen knowing four tiles land on five columns from 1280 up. Landed: --grid-column-communicative and --grid-tile-min-instrumental in aleris-tokens.css with the numbers in their annotations; BASELINE.md § Surface temperature supersedes "6–8 of 12 columns"; the grids reference gains the decision section; the decision surface is marked decided; tokens.test.ts holds the clamp's anchors to --breakpoint-sm/md, renders the ladder at the seven widths and asserts the counts (five at 1280 included), and requires the departure to be stated on the token — mutation-verified. Sent back: the svl measure finding, dropped in sundviktlakemedel/_incoming/.
Handed to TAKS 2026-09-05: ~/TAKS/1-projects/brandguide/_filedrop/brief-card-120-layout-prose-and-patterns-2026-09-05.md carries the three open pieces below with the settled decisions marked do-not-reopen, the evidence pointers, and the return path (a brief back into workspace/incoming/). Logged in that initiative's _state.md.
Still open on this card, all Torfinn's prose or wording: the Part 1 rewrite with a plain first line and an F6-shaped reasoning parent; svl's three phone rules as patterns/ pages (CC can draft, T words); rem breakpoints and width-class names with reasons (recommendation 1 and 4 — the decision above did not settle them).
Done when: the ladder is decided (done); the layout section is rewritten with a first line in plain words and an F6-shaped parent; width classes and rem breakpoints exist with reasons; svl's three phone rules are in patterns/; the reflow check runs (done); the specimens exist (done); the svl measure finding has been sent back (done); and it is on main.
116 · [CC] branch-hygiene's stranding check cannot see superset containment, so a rescued-with-edit blob is flagged forever
Files: lib/branch-hygiene.test.ts § strandedPaths and trunkObjects
New 2026-09-02, found when the check went red on backup/auto-20260830 three days after that branch was made.
The check is better than it first looked, and the gap is narrower than "it compares trees". Its own docblock says the right thing and does it: trunkObjects() walks git rev-list --objects main, which is every blob in trunk's entire history, not its current tree. So an old snapshot holding an old version of a file is correctly silent — that blob was on main once. That design is why only one of the nine differing files was reported.
What it cannot see is containment. review/SPEC-production-icon-surface.md was rescued onto main on 2026-08-31 with a provenance note added in the same commit. So:
| snapshot's blob | 65 lines |
| main's blob | 67 lines — the same 65 plus a 2-line rescue note |
| lines only on the snapshot | 0 |
| snapshot's blob in main's history | never |
Nothing is stranded — main holds a strict superset — but the snapshot's exact blob was never committed, so the check flags it and will keep flagging it until the branch is deleted. Rescue-with-edit is the case that produces this, and it is the normal way a rescue happens: you land the file and say where it came from.
Why it is worth fixing rather than living with. The failure is not a false alarm that costs a glance. It is a red suite that says "content that exists nowhere in main" about content that does exist in main, with instructions to rescue something that needs no rescuing — and the message ends "Do not silence this by deleting the branch unread." A reader who follows that instruction spends the reading, finds nothing, and deletes the branch anyway. Do that twice and the instruction stops being read, which is the one thing this check has going for it.
Two candidate shapes, neither obviously right.
- Containment for text blobs. Before reporting, check whether the snapshot's lines are a subset of some blob on the same path in trunk's history. Cheap for text, meaningless for binaries, and it needs a path-scoped search rather than a global object set.
- Report but do not fail on containment. Keep the object-set answer, add a second tier: stranded fails, superseded-with-additions is printed and passes. That keeps the strong claim strong and stops the weak case going red.
A measured aside worth keeping with this. Running the branch by hand found the reverse of what the check said: five files had lines present only on the snapshot (voice-filter-rules.yaml, ai-tells-and-filters.md, language-quality-sv.md, branch-hygiene.test.ts, package.json) and the check flagged none of them — correctly, because every one of those lines was an older wording main has since replaced on purpose, and each old blob is in main's history. So a line-level diff is the wrong instrument here and the object walk is the right one. Recorded because the obvious fix is the wrong fix.
backup/auto-20260830 was deleted local and remote on 2026-09-02 after every blob on it was checked: the five icon files exist on main byte-identical at different paths, the eight text files' old versions are all in main's history, and the SPEC is a strict subset of main's. Nothing was archived because nothing was unique.
All five snapshots examined on 2026-09-02 were redundant, and that is a measurement about the hook rather than the check. Every blob on backup/auto-20260825, -20260831, -20260901 and -20260902 was already reachable in origin/main's history — zero unreachable each, across roughly 4,300 blobs per branch. -20260830 had exactly one, and it was the contained case above. All five deleted, local and remote; the branch list is now main plus one working branch.
The audit was proved against a known positive before any deletion: run on a working branch seventeen commits ahead it reported 71 unreachable blobs and refused. A tool that says "safe to delete" without having been seen to say the opposite is not evidence.
So the SessionEnd snapshot hook has not once captured content that was not already landed — five for five. That is a question for Torfinn and it is a different question from this card's: the check's blind spot is worth fixing either way, but if the hook never catches anything, the recurring cost is a red suite every time a snapshot ages past SNAPSHOT_GRACE_DAYS and a branch to read and delete by hand. The memory already records that this hook "creates the exact condition branch-hygiene fails on, so that check had never once been green". Five clean audits is the first measurement of whether it earns that.
Done when: the check either sees containment or splits its verdict into two tiers, SNAPSHOT_GRACE_DAYS is reconsidered against how long a snapshot is actually useful, the fix is proven against a fixture that reproduces rescue-with-edit, and it is on main.
117 · [CC] The session-end backup hook snapshotted the main checkout, not the worktree the session was in — DONE 2026-09-02
This answers half of card 116's closing question. That card measured five snapshots in a row holding nothing that was not already landed, and put the hook's value to Torfinn as an open question. One reason is now known rather than guessed: .claude/hooks/backup-push.sh hard-coded REPO="/Users/torfinn/Dev/aleris-brand-os" and changed directory to it before doing anything, so a session running in .claude/worktrees/<id>/ had the main checkout captured and its own work captured not at all. Sessions in worktrees are the normal case here, so a large share of those five snapshots were backups of a tree that was never at risk.
Reproduced before it was fixed. Driving the original script from a linked worktree in a throwaway repo produced a snapshot containing the main checkout's uncommitted file and not the worktree's. It recorded .claude/worktrees/<id> as a gitlink and none of the contents beneath it. The whole of the 2026-09-02 site session — the Astro build, the token-bridge fix, the v0.11 package cut — had never been inside a single snapshot.
Two defects older than this one, found while writing the test. Retention took the date as everything after auto-, so any suffixed branch failed the eight-digit test and would never have been pruned once per-worktree names existed. And the "tree unchanged today" check did exit 0 above the retention block, so pruning was skipped on precisely the ordinary days.
What it does now. Reads cwd from the JSON the hook is handed on stdin, snapshots that working tree, and writes to backup/auto-YYYYMMDD-<slug> per worktree so two sessions ending the same day cannot overwrite each other. A scope guard built on --git-common-dir keeps it acting only on this repo and its worktrees, and is asserted.
Asserted, and the assertion is itself checked. .claude/hooks/backup-push.test.sh — four behaviours, a throwaway repo and worktree under $TMPDIR, nothing touched outside it. Each of the three defects was reintroduced by mutation and the test confirmed to fail on all three, because a test that passes on the broken version proves nothing. The pre-fix script is kept beside it as a .bak.
The hook and its test were untracked until the same day. .gitignore excluded .claude/ wholesale, so the one piece of machinery whose purpose is preventing lost work lived in no repository and no backup. Narrowed to .claude/* plus !.claude/hooks/, which keeps settings.local.json and worktrees/ out.
Still Torfinn's, and unchanged by this card: whether the hook earns its cost at all. What has changed is that the question can now be asked of a hook that backs up the right directory — the five-for-five measurement was taken against a broken one and should not be read as evidence either way.
121 · [T→CC] Controls on a branded ground — five of six variants undefined, and no check can see a gradient
Files: workspace/incoming/brief-sundvikt-grounds-matrix-2026-09-03.md (the brief) · data-products/tokens/aleris-tokens.css § Buttons (--button-primary-inverse-* is the only inverse family) · review/checks/ (three checks, none reads background-image) · ~/Downloads/Aleris Design System (fixed)/components/core/Button.jsx (FILLS_ON_DARK)
New 2026-09-05, from the incoming triage (workspace/evaluations/incoming-triage-2026-09-05.md § 7).
THE GROUND NOW EXISTS, AND MEASURING IT AGAINST THE REAL DESIGN FOUND A LIVE AA FAILURE — 2026-09-13, card 156. --surface-gradient-cold and --surface-gradient-warm landed, the first gradients this token set has ever carried. Then the design system's own painting was served and driven in a browser rather than read, and it settles two things this card was guessing at.
It is not a hypothetical ground: the painting uses the cold gradient eleven times, and its computed value is byte-identical to the token — linear-gradient(135deg, rgb(0,72,81) 0%, rgb(79,134,142) 100%).
And it puts body text directly on it. Measured from the rendered page, not inferred:
| Text the painting places on it | on petrol-500 | on petrol-400 | floor | |
|---|---|---|---|---|
| white 16px w500 body | 10.27:1 | 4.09:1 | 4.5:1 | fails at the far stop |
| petrol-100 14px w500 | 7.73:1 | 3.08:1 | 4.5:1 | fails at the far stop |
| white 18px w700 heading | 10.27:1 | 4.09:1 | 3:1 | passes throughout |
So a white heading that starts legible ends illegible along its own background, and the 14px secondary line is worse. This is the same defect this card was opened on — a live action shipped invisible on five of seven seeded home screens — reappearing as text rather than as a control, and it is now measured rather than suspected. sundviktlakemedel/docs/PLAN-DESIGN-SYSTEM-ADOPTION.md § 9 predicted precisely this before the token existed: "that is a floor, not a preference, and it is the one disagreement where adopting the design system's value is not available."
RULED AND FIXED the same day: retune the far stop so white holds. --surface-gradient-cold now runs petrol-500 → --color-petrol-450, a new primitive at #437d85, where white is 4.66:1 — clearing the floor with headroom on both stops rather than landing on it. The exact 4.5:1 crossing is #467f87 and was deliberately not taken: a value sitting on a floor fails the next time anything nudges it. The new stop is 0.85 of the way along the old interpolation and 12/9/9 in RGB from petrol-400, so the gradient keeps its appearance — the fix is invisible, which is the point. The 450 step is where the existing 100/200/300/400/500/700 numbering puts it.
Still not safe, and stated rather than smoothed over: petrol-100 is 3.51:1 on the new far stop, so the painting's 14px secondary line in that colour still fails on this ground and needs white or a card. Retuning white's floor did not fix a second text colour, and saying it had would be the kind of claim this card exists to catch.
The warm ground stays dark-text-only — white is 2.38:1 at its near stop and 1.60:1 at its far one, nowhere near any floor at either end.
And this card's standing complaint is answered in one narrow place: lib/gradient-contrast.test.ts is a check that can see a gradient. It parses the gradient tokens, resolves each stop through the primitives, and measures the permitted text against every stop rather than the one a reader would think of — because a gradient fails where it is lightest, which is the end nobody checks. Proved against the defect it was written for: putting the far stop back to petrol-400 fires it with #4f868e 4.09:1 (floor 4.5). The warm ground is asserted as a negative, so if anyone ever retunes it far enough for white to pass, the check fails and asks them to grant that permission deliberately instead of inheriting it. What it still cannot do is know which text colours a product actually places on a ground — that is markup, and it remains this card's.
One thing measuring the painting settled for free, and it validates the border ruling. The painting borders with #d7d2cb — gray-100, the warm neutral — while the export's own tokens/colors.css declares --border-default: var(--petrol-200). The export's tokens and the export's paintings disagree with each other, and Torfinn's "neutral (warm grey)" ruling on card 156 matches what he actually designed rather than what the token file claims.
The gap. The button vocabulary is indexed by emphasis and the system has more than one ground. The consumer measured all 24 variant × ground cells: on a petrol ground, outline and ghost sit at 1.00:1 (not there), secondary and complete fill at 1.00:1 against the ground (found, unreadable as a control), and only inverse has tokens. A live action shipped invisible on five of seven seeded home screens.
A position already exists outside Baseline. The Claude Design system's Button defines onDark for all four of its variants: secondary becomes a white outline, outline and ghost go white, all three press to a translucent white. That is three controls on a branded ground, which argues against the brief's one-line rule (inverse only). Torfinn built one and has not ruled on the other.
Yours: ratify or reject the on-dark answer — inverse only, or the transparent variants inverted. Claude's, first: measure white ghost text against the petrol gradient's light stop (petrol-400 #4F868E) before the ruling, so it is a verdict with a number. Claude's, after: the tokens if the answer is "inverted", and a gradient-aware contrast probe in review/ — sample the stops or rasterise and take the worst pixel behind each glyph — because any check walking computed backgroundColor reports the page behind a gradient and passes it. axe-core returns incomplete on the same case.
Related: cards 118 (error text's grounds, same shape one token over), 120 (the other two-axis defect), 123.
Done when: BASELINE.md states which controls may sit on a branded ground, the tokens match the statement, a check can measure text over a gradient, and it is on main.
122 · [T] Third parties on an Aleris surface, and onboarding is not a mode
Files: workspace/incoming/brief-sundvikt-rails-and-allowlist-2026-09-01.md (the brief) · baseline/BASELINE.md § Surface temperature (line listing "onboarding" under communicative) · baseline/governance/aleris-design-governance.md § 3 · workspace/authoring/aleris-patient-product-design-guidelines.md § 4.2 (the "we/you" rule, unpublished)
New 2026-09-05, from the incoming triage § 8.
Corrected framing. The brief read the onboarding conflict as BASELINE.md against the governance file. Both say onboarding is communicative; the governance file says so in as many words. The conflict is inside the governance file, between "an onboarding flow is communicative throughout" and "a patient managing their treatment plan is in instrumental mode", for the case of a treatment onboarding — which is what the consumer has. Since a flow owns its mode, the wrong call propagates to every step.
Three rules confirmed absent — no text in foundation/, baseline/, constitutional/, patterns/ or principles/ mentions BankID, Swish or a mark Aleris does not own; the motion rules cover loading and stop before a hand-off wait the patient controls and may not return from; and the pronoun rule the brief cites is in an unpublished authoring file, so the third-actor gap is inside a document that has not entered the corpus. All three appear the moment a patient touches something that is not Aleris, and the axis paper's principle — a system should state where it does not extend — is the frame.
Yours: strike "onboarding" from the communicative list and say what separates marketing onboarding from treatment onboarding; one governance section for the mandated third-party mark, the hand-off waiting state, and how a sentence places a third actor. Prose throughout.
Related: card 58 (the payment icon the same flow needs), workspace/research/axis-thinking-in-design-systems.md.
Done when: BASELINE.md no longer lists onboarding as a mode, governance carries a third-party section, and both are on main.
123 · [T→CC] Seven findings from the Sund vikt component library still open — three pattern pages, a selected state, a compact button
Files: workspace/incoming/brief-sundvikt-design-system-findings-2026-08-27.md (twelve findings; 5 and 9 shipped in v0.11, verified) · patterns/ (seven files, no row, no chooser, no action cluster) · data-products/tokens/aleris-tokens.css § Buttons, § Tab Navigation · _packages/agent-baseline/logo/README.md · schemas/icon-allowlist-entry.md · baseline/setup.md
New 2026-09-05, from the incoming triage § 6.
Yours (findings 1, 2, 3, 4, 6, 12): pattern pages for the row, the chooser (segmented / tabs / toggle — Baseline has --tab-* tokens and no page) and the floating action cluster, including what may hide behind one and the finding that a menu hiding decline routes makes every decline one step dearer; a checkable gradient rule to replace "sparingly" (the Claude Design card's "Background surfaces only. Max two steps, low contrast, never in charts" is a candidate); and whether buttons get a selected state as a tint plus marker. The consumer's Row.tsx and its grounds stories are the specimens. The fixed Claude Design system ships Switch, Radio, Checkbox and SideNav, no tabs, segmented or row — it did not fill this.
Claude's (7, 8, 10, 11): the --button-min-height-pointer note saying it is a floor with no pointer padding beside it — done 2026-09-05; a setup.md sentence that local additions load in a project layer after the vendored file, never appended to it; an icon rendering contract as utility text beside the allowlist schema, measured from the extracted 512-grid set; and logo/constants.json read by lib/logo-minimum.test.ts so the ratio, minimum and clear-space multiple stop being numbers copied into consumer tests. A compact padding value is yours if 7 is answered that way.
Progress 2026-09-08 — findings 8, 10 and 11 closed; all four of Claude's mechanical items are now landed. Finding 8: the two sentences are on how-to/install-the-tokens.md (the file setup.md became), and the rule is a second file loaded after rather than an append, because appending destroys the byte comparison that is the only way a consumer learns the tokens moved. Finding 11: _packages/agent-baseline/logo/constants.json ships the geometry, the sizing rules and the derivation inputs, each with usage, constraint and provenance; lib/logo-minimum.test.ts reads the type floor and cap-height assumption from it and adds four assertions pinning the file to the rendered SVG and to the README prose, so the JSON, the prose and the drawing are one knot and none can move alone. Five mutations, five failures. The favicon's expiry is deliberately still absent — that is card 111 Q2 and stating one here would answer it by accident. Finding 10: schemas/icon-render-contract.md, draft/prep, with lib/icon-render-contract.test.ts asserting every stated number. One correction to this card's own wording, and it is the substance of finding 10. This card and the triage both described the extracted set as 512-grid. Measured: the viewBox is 512 units tall and its width takes ten distinct values from 64 to 640. A component that hard-codes viewBox="0 0 512 512" therefore distorts 2,406 of the 3,772 icons, and the icons that render correctly are the plurality — which is exactly why two independent handoff authors could each build a contract and neither notice. The contract's operative sentence is read the viewBox, never write one, and it would not have been written from the description this card carried.
Related: cards 98 (eight missing patterns is one missing component layer), 99, 121.
Done when: the three pattern pages exist at proposed or better, findings 6 and 4 have a ruling, the four mechanical items are landed (done 2026-09-08), and it is on main.
138 · [CC] The drop zone is invisible to the path check, and Phase 0 left four stale pointers in briefs queued for execution — DONE 2026-09-08
Files: lib/in-repo-references.ts § RULE_SURFACES · lib/in-repo-references.test.ts · workspace/incoming/ (7 briefs)
New 2026-09-08, found while fixing two stale citations in brief-hard-rule-3-per-section-2026-09-08.md (PR #33). Both of that brief's unresolvable shorthand paths had passed silently, and the reason is structural rather than incidental.
The gap. RULE_SURFACES covers ten content folders plus the package root. It does not cover workspace/. So lib/in-repo-references.test.ts — the check whose whole job is that every multi-segment path a rule surface cites resolves — cannot see workspace/incoming/, which is the folder briefs arrive in specifically to be executed against, by an agent that will follow their paths. The one place a dead pointer is most likely to be acted on is the one place nothing checks.
Do not simply add workspace/. Measured 2026-09-08: the current baseline is 569 references and 84 unresolved. Adding workspace/ wholesale gives 4,414 references and 2,157 unresolved across 151 files — twenty-five times the debt, and the bulk of it is correct. board.md alone contributes 429 and changelog.md 360, plus archive/ and drafts/. Those are records: a changelog entry citing a path that has since moved is accurate about what was true then, and CLAUDE.md § What not to do already says archive files are historical and not edited. Pinning 2,157 references as debt would make the check's own numbers meaningless, which is the failure mode KNOWN exists to avoid.
Per-folder, measured, so the scope is a choice rather than a guess:
| Candidate | References | Unresolved |
|---|---|---|
workspace/incoming |
95 | 47 |
workspace/plans |
228 | 103 |
workspace/briefs |
318 | 181 |
workspace/skills |
3 | 0 |
incoming + plans + skills |
326 | 150 |
Recommendation: workspace/incoming and workspace/skills only. skills is already clean, so it costs nothing and locks in a surface that loads in every session. incoming is where the finding came from and is the drop zone the transit rule already treats as live. plans and briefs are mostly executed or superseded material and should stay out until someone decides they are live surfaces rather than records — that is a scope question, not a defect.
Most of incoming's 47 are not defects and the check needs to be taught that. Three classes, counted by hand:
- Cross-repo, and correct —
Dev/aleris-assistant/BRAND-OS-FINDINGS.md,gcc/AGENTS.md,patientguide/docs/specs/document-surface.md,src/brand/Row.tsx,src/index.css. A consumer finding cites the consumer's own files; that is the point of a consumer finding. These belong inNOT_CITATIONSwith the reason, or need a rule that a path prefixed with a known sibling repo is out of scope. - False tokens the regex invents —
px/12.48(a units fraction),Brand/Button,Patient/Button,Foundations/Surfaces(Storybook paths).FILE_TOKENwants a slash and an extension; these clear it by accident. - Package-relative shorthand —
logo/README.md,logo/constants.json, which resolve inside_packages/agent-baseline/. Arguably fine and arguably worth writing in full.
The four that are real, verified 2026-09-08 — each has a live successor, so an executor following them gets a 404 where a working file exists:
| Cited in a queued brief | Now |
|---|---|
foundation/typography.md (4 uses, print-surface brief) |
principles/typography.md |
foundation/logo-identity.md (digital-identity brief) |
principles/logo-identity.md |
baseline/setup.md (css-weight brief) |
how-to/install-the-tokens.md |
documentation/dependency-reciprocity.md (assistant brief) |
merged into documentation/how-decisions-are-recorded.md 2026-08-25 |
The first three are Phase 0 casualties. Card 132's sweep and scripts/in-repo-references.mjs --list are the instrument that would have caught them, and CLAUDE.md § Documentation maintenance already requires repointing a citation in the same commit as the move — it was followed for every publishing folder and not for the drop zone, because nothing failed.
One more thing to decide, and it is why this is not purely mechanical. foundation/print.md is cited by the print-surface brief as a proposed home that does not exist yet. A brief legitimately names files it is asking someone to create. Whatever shape this fix takes needs an answer for a forward reference — a marker, a convention, or an exclusion — or the check will push briefs toward not naming their own deliverables.
Same shape as cookbook 24, written the same day: a clean report from a check written for one reach is not evidence about anything outside that reach. The check has been green on 84 known-and-pinned references while 47 sat one directory away, unlooked at.
Done when: RULE_SURFACES covers the folders chosen above, the three non-defect classes are handled by a rule rather than by pinning each instance, the four stale pointers are repointed, the forward-reference question has an answer, the count is asserted so a shrinking scope fails, mutation-verified, and it is on main.
124 · [T] The serving rule forbids subsetting by its letter, and the flip condition has no card
Files: workspace/incoming/brief-sundvikt-css-weight-and-the-flip-2026-09-02.md (the brief) · baseline/BASELINE.md § Serving this file ("remove the comments and the blank runs they leave behind, and nothing else") · baseline/setup.md step 1c · workspace/evaluations/sundvikt-component-library-candidacy-2026-08-28.md
New 2026-09-05, from the incoming triage § 5.
§1, corrected. The brief says declaration dropping is "not mentioned either way". The serving rule's "and nothing else" forbids it. A consumer measured what it costs: 199 of 363 vendored tokens unreferenced, 1,080 brotli bytes on each stylesheet, and a console budget headroom of 120 bytes that subsetting would make 1,200. Yours: widen the rule to sanction build-time subsetting with the sha256 governing the vendored source rather than the served artefact, with the warning that a second consumer of the same file (Storybook) can legitimately reference what the app does not — or keep the rule and say the byte cost is accepted.
§2 folds into card 119. The bridge exists and is fetchable by URL per setup.md step 1c; the open half is only whether it becomes a package member at the next cut.
§3 is Claude's: one sentence beside the serving rule for whoever is hunting bytes — the build removes the comments, do not.
§4 is the card the brief asked for. Torfinn, 2026-09-02: the consumer is "both until we decide to flip". The conditions under which it stops being a source and becomes a consumer are unwritten; the brief's starting list is the component set holding its sort twice, Swedish out of the core, both halves of the testing story in one repo, and §1 answered. Yours to word.
§5 is a cookbook line, Claude's: a clean report from a guard written for one drift shape is not evidence the drift did not happen; _state.md records the same shape three times as a query defect.
Done when: the serving rule says which way on subsetting, the flip condition is written somewhere the consumer can point at, and both are on main.
125 · [CC] Drift walk for consumers who copy rather than install — extend consumer-contract.test.ts to declared inherits
Files: lib/consumer-contract.test.ts (asserts the package manifest's canonical source paths still resolve; calls itself "the first buildable" form of this relation) · workspace/status/build-board-html.py package_status() (the git log walk since a generation commit) · workspace/incoming/brief-aleris-assistant-copy-consumers-and-voice-2026-09-04.md § 1 · _packages/README.md rule 7
New 2026-09-05, from the incoming triage § 2.
The gap. aleris-assistant hand-copies the token file and records the seam in prose. The path went stale in four places for two weeks, including the repo's own constitution rule, and nothing here could see it because the drift walk takes a package manifest as its input and the consumer has none. aleris.se is the named next copy consumer.
The build. A declaration per consumer — which canonical paths it copied, and the commit it copied them at — read by the same check that already asserts manifest paths, so a moved or changed path fails the suite here, where the move happens, not there, weeks later. Recommendation over the brief's alternative (declare copy consumers out of scope): the cost is one JSON file and one loop, and the alternative leaves inheritance.md implying a mechanism that does not exist. Portfolio machinery, so it proceeds unless Torfinn says out of scope.
Trap: the consumer's records are read-only from here (constitution rule 5). The declaration lives in this repo and names theirs; nothing writes into aleris-assistant.
Related: cards 81, 82, 88, 119.
Done when: a declared copy-consumer path that moves or changes fails npm test here, mutation-verified, and it is on main.
126 · [T] Voice findings — wanted or not, by what route, at what evidence bar
Files: workspace/incoming/brief-aleris-assistant-copy-consumers-and-voice-2026-09-04.md § 2 · ~/Dev/aleris-assistant/BRAND-OS-FINDINGS.md (both candidate routes written in, 2026-08-25, card 81) · foundation/voice.md
New 2026-09-05, from the incoming triage § 2. Card 81 closed with this question left open on purpose; nothing has moved since.
The gap. Tokens, components and hard rules have had two vendored consumers reporting against them for months. Voice — register, how the brand behaves across a turn — has had none, and the one repo whose brand surface is almost entirely voice has been silent by construction. Its own file names the cases the others cannot meet: the same question asked three ways, a disclosure the assistant must not act on, a register right at turn one and wrong by turn six.
Yours, three things: whether Brand OS wants voice findings at all; the route — direct into workspace/incoming/ like the others, or through TAKS first, given the risk the consumer names itself (voice material arriving where the voice's author also writes it, with no strategic pass); and the evidence bar — one awkward reply is an anecdote, the same awkwardness across an eval run is a finding, and the route should say which it accepts.
Related: cards 81, 82, 78 (moments — a register shifting across a conversation is one).
Done when: the assistant's findings file names one route rather than two, and the bar is written where findings are filed, and it is on main.
127 · [CC] The adapter is the unit that ships wiring, and a consumer's declared checks are unread
Files: _packages/README.md (rules list) · _packages/adapters/vendored-app/wiring/brand-provenance.mjs (header § THE RECORD) · _packages/adapters/vendored-app/instances/sundviktlakemedel.brand-provenance.json § assertions · _packages/adapters/vendored-app/instances/patientguide.brand-provenance.json (has none) · lib/consumer-contract.test.ts
New 2026-09-06, from workspace/decisions/decision-record-shipping-checks-2026-09-06.md.
The gap, in two halves. agent-baseline is content and the adapter is wiring — already true, since brand-provenance.mjs is the one executable check that reaches a consumer — and nothing writes it down, so the question gets re-argued at every cut. And sundviktlakemedel declared its own local checks in an assertions block that is not a documented field: the header lists package, vendored[], overrides[], workarounds[], deviations[], acknowledged_behind, and not this one. The consumer reached for a mechanism that does not exist. Nothing on either side reads it, and patientguide has no block at all.
The build. One paragraph in _packages/README.md stating the adapter rule and the three enforcement routes. assertions documented in brand-provenance.mjs's record format as { file, asserts }. Then a check here that fails when an instance declares neither an assertions block nor a stated reason it has none — same acknowledgement convention as acknowledged_behind, so an empty declaration is a decision rather than a silence.
Trap: the consumer's files are read-only from here (constitution rule 5). The check reads the instance records in this repo; nothing writes into theirs. patientguide's missing block is a finding to send, not a file to fill.
Related: cards 125, 128, 129, 119.
Done when: assertions is in the documented format, an instance with neither a block nor a reason fails npm test here, mutation-verified, and it is on main.
128 · [CC] The out-of-set hex scan is the one assertion that needs the consumer's own source
Files: data-products/tokens/tokens.test.ts § "token conformance: no hex outside the token set" (walks app/ for .css/.ts/.tsx and data-products/ for .css) · _packages/adapters/vendored-app/wiring/ · _packages/consumer-profile.md § Changes
New 2026-09-06, from the shipping-checks decision. The only check the decision contributes downstream.
Why this one and nothing else. Every other assertion's subject is a file the package ships, already byte-verified downstream by brand-provenance.mjs INTEGRITY — a consumer running it re-verifies a verified artefact it is forbidden to edit. This one's subject is the consumer's own source, which Brand OS cannot see and never will. Unshipped, the rule is unenforced everywhere but here.
The build. Extract the scan as dependency-free node, in the adapter's wiring beside brand-provenance.mjs, reading the token set from the vendored token file rather than from a copied list. It joins the gate the consumer already runs, per consumer-profile.md: "this is how a brand or conformance rule becomes enforced rather than asserted." Carry the KNOWN_OUT_OF_SET convention across — a documented exception asserts it is still present, so fixing it without removing the entry also fails.
Trap: dependency-free is the constraint, not a preference. The lib/ checks are vitest and TypeScript; a check that arrives with a runner the consumer did not choose is a check the consumer deletes. sundviktlakemedel has already written its own "no raw hex in the component layer" — reconcile with it rather than shipping a second one beside it.
Related: cards 127, 129.
Done when: the scan runs from the adapter in a repo with no vitest, fails on a planted out-of-set hex, passes on the same repo with it removed, and it is on main.
129 · [CC] Every check is shipped or repo-only, and nothing says which
Files: lib/*.test.ts (8) · data-products/**/*.test.ts (3) · review/checks/*.mjs (3) · _packages/adapters/vendored-app/wiring/brand-provenance.test.ts
New 2026-09-06, from the shipping-checks decision. This is the relook's mechanism, kept — the rest of its proposal was withdrawn.
The gap. Thirteen checks, and nothing declares where each is meant to run. The decision places eleven as repo-only, one as contributed and three as pointed-at, on the property of where the checked thing can be seen from; without a list that is a paragraph in a decision record and drifts the first time someone adds a check.
The build. A declared list — each check with its placement and a one-line reason — asserted by a test that fails when a *.test.ts or review/checks/*.mjs exists with no entry. Same discipline as KNOWN_LANG_MISMATCH and IN_FLIGHT: a dated statement, not a permanent exemption.
Trap: the reason has to be the property, not the placement restated. "Repo-only because it is about this repo" is a label; "its subject is a file the package ships and brand-provenance.mjs already verifies byte-for-byte downstream" is the reason. Detection is by reading a sample, and there is no machine check for it — how-decisions-are-recorded.md §9 obligation 1.
Related: cards 127, 128.
Done when: a new check file with no entry fails npm test, every existing entry carries a property-shaped reason, and it is on main.
130 · [CC] review/ has never been pointed at an Aleris consumer
Files: review/targets/gcc.json, review/targets/claude-design-kits.json (the two that exist) · review/README.md § "Targets are declared here, not in the reviewed repo" and § "Contextual floors are declared, not guessed"
New 2026-09-06, from the shipping-checks decision. The enforcement tier the decision leans on, unused on the two products that vendor the package.
The gap. review/ has reviewed gcc — a repo Torfinn does not own — "without a byte changing in it", and the Claude Design kits. It has never been run against sundviktlakemedel or patientguide, which are the two consumers whose findings drive the package. The rendered tier is where the failures actually live: 37.8×37.8px targets where the static read predicted ~45, a 12px span with no size class of its own, and the 404 page nobody greps for.
The build. A target file each: base URL, route sample, viewports, and the target-size floor declared with its reason per viewport — with no declaration the strict 44 stands, and a patient-facing product should be arguing for 44 rather than inheriting 24 by silence.
Trap: each needs a running instance, and where it runs is not settled — a deployed URL, a local dev server, or a container as gcc used. Settle that before writing the config. And an empty page is a failure, never a pass: a route that redirected to login has not been reviewed and the report must say so on that row.
Related: cards 121 (the gradient probe belongs with these), 104, 105.
Done when: both targets are declared, node review/run.mjs --self-test passes, both report with landing routes recorded, and it is on main.
131 · [T] The corpus quotes a Swedish source it does not hold, cannot version and cannot check
Files: foundation/logo-identity.md (the prohibition list, and eight page references into the 2025 source) · _packages/agent-baseline/logo/README.md § Prohibited · constitutional/language-policy.md § 1 · _markets/sv/ · the ordlistan (2026-04-21, not in this repo)
New 2026-09-06, from flipping logo-identity.md to English. The flip forced a choice that had never been named, and the choice is defensible in the small and unexamined in the large.
What surfaced. The prohibition list is a verbatim quotation from Aleris Brand Guidelines 2025 p. 8. It stays in Swedish in two places now — the package sheet and the Foundation page — on the stated reason "it is a quotation, not a rewrite." That is a reasonable local call and it does not fit any category the corpus has. constitutional/language-policy.md § 1 permits local languages "only for local examples", and a ratified normative rule quoted verbatim is not an example. So either quotation is a third category the policy should name, or two accepted surfaces are carrying a breach with a good reason attached.
The larger version, and it is the reason this is a card rather than an edit. logo-identity.md is substantially a set of pointers into a document the corpus does not hold: eight page references, four ratified constants quoted from it, and the sender rule settled in the ordlistan, which is also not here. Three consequences follow and none is currently handled.
- No version. If the 2025 guidelines are revised, nothing in this repo detects it. Every other dependency the corpus has — tokens, the icon kit, the package — is versioned and asserted. This one is a page number in prose.
- No reach. The package ships the rules and cannot ship the document they were quoted from, so a consumer or an agent meeting "source p. 8" has a citation it cannot follow.
logo/README.mdalready routes around this by telling the reader to ask the owner. - A new content category with no convention. The English rules on that page are marked "Claude's translation of the Swedish source" so the wording can be checked before the page moves to
accepted. Nothing says what that marking means, who clears it, or what happens if the translation and the source diverge later.
Yours, three things. Whether verbatim quotation is a named category in the language policy or an exception it tolerates. What the corpus's relationship to the 2025 source is — an upstream dependency to be versioned and recorded like the others, a historical input the corpus has now superseded in its own words, or something in between per rule. And whether a translated-from-source rule may reach accepted at all before someone who reads both has cleared it.
Note the asymmetry that makes this worth settling now. _markets/sv/ holds Swedish originals of pages the corpus has replaced, which is a clean relationship: English is the source, Swedish is the archived predecessor. The 2025 guidelines are the opposite — Swedish material the corpus has not replaced and still defers to. Same language, opposite direction, no rule distinguishing them.
Related: cards 95 (archived Swedish originals claiming accepted), 111, 74. constitutional/language-policy.md § Open 1 is adjacent but not this — it asks which language a site renders, not what the corpus may quote.
Done when: the language policy says what a verbatim quotation is, logo-identity.md's relationship to the 2025 source is stated on the page rather than implied by page numbers, and whatever clears a translated rule for accepted is written down.
Update 2026-09-07 — the page is accepted and publishes at /identity/logo. Torfinn promoted principles/logo-identity.md with the rest of the tier-1 and identity ratification. What this card asks is unchanged by that: the page still carries the author prompts and the Claude-translated rules marked for checking, and the three questions above (quotation as a category, the corpus's relationship to the 2025 source, what clears a translated rule) are open on an accepted page rather than a draft one — which makes them more pressing, not less.
132 · [T→CC] 94 pointers to nowhere in what publishes and ships, and no check reads an in-repo path
Files: lib/external-references.test.ts (covers the other half) · constitutional/a-rule-is-applicable-where-it-is-read.md · baseline/BASELINE.md (7) · documentation/how-decisions-are-recorded.md (8) · constitutional/voice-is-the-present-expert.md (8) · documentation/aleris-meta-alignment.md (6) · foundation/en-draft/** (33) · _packages/agent-baseline/** (10)
New 2026-09-06, measured rather than suspected. Opened after a session hit three dead pointers by accident in one afternoon.
The measurement. 430 multi-segment in-repo paths are cited in rule surfaces — constitutional/, foundation/, baseline/, principles/, patterns/, schemas/, filters/, documentation/, qualities/ and the package. 94 do not resolve: 73 point at a file that exists somewhere else, 21 at nothing at all. Bare filenames used conversationally were excluded, and so was workspace/, which is working memory rather than a rule surface.
One dissolved directory causes most of it. planning/ no longer exists. Its contents went to workspace/, documentation/ and workspace/archive/, and the pointers did not move: planning/aleris-design-system-open-questions.md, documentation/constitutional-core-test.md, planning/versioning-rules.md, planning/aleris-meta-alignment.md, planning/changelog.md, planning/f4-voice-briefing.md, planning/voice-filters-plan.md. A second cluster is principles/voice.md, principles/colour.md and principles/typography.md, cited from constitutional/ pages and now living at _markets/sv/foundation/ — so three constitutional pages point at Swedish archives.
Ten of the 94 ship. _packages/agent-baseline/ carries dead pointers in BASELINE.md (4), MANIFEST.md (2), governance/aleris-design-governance.md (2), patterns/chat-response-simple.md (1) and foundation/imagery.md (1). A consumer or an agent following one gets nothing.
A finding that fell out: foundation/en-draft/ is not empty. 33 of the 94 sit in en-draft/principles/, en-draft/constitutional/ and en-draft/viewports/. The 2026-08-27 record says "foundation/en-draft/ now holds no markdown at its root" — true, and the subdirectories were never mentioned. Third instance this month of a claim true of something narrower than it appeared to describe. Whether that tree is swept or deleted is a decision, not a fix.
§ references are NOT a problem and should not get a check. Of 16 file § Section references where the file resolves, 2 miss — and both are prose-shaped rather than real section names. Measured before proposing anything, so the sweep does not grow a second instrument it does not need.
The check, and the constraint that shapes it
lib/external-references.test.ts already asserts that a path pointing outside the repo is declared with a kind and a reason. Nothing asserts that a path pointing inside it resolves. That is the whole gap, and it is one function.
Build it the way the hex check was built (lib/token-values-are-canonical.test.ts): pin the count per file, may go down, may never go up, no new file may join. 94 is a debt, not a permission, and a sweep lowers the numbers.
The design constraint, and it is the interesting part. baseline/BASELINE.md and _packages/agent-baseline/BASELINE.md are the same content in two locations. tokens/aleris-tokens.css in it is correct read from the package root and wrong read from baseline/. So a naive resolver reports a real pointer as dead, and a resolver that tries every root reports a dead one as fine. This is constitutional/a-rule-is-applicable-where-it-is-read.md arriving as a mechanical problem: a file that ships in two contexts needs paths valid in both, or a line saying which context it is written for. Settle that before writing the resolver, or the check will be built around a workaround.
Yours, three things. Whether en-draft/'s subdirectories are swept or deleted. What a constitutional/ page should point at when its target is now a Swedish archive in _markets/sv/ — the archive, the English replacement, or nothing. And the dual-context rule above, which decides the resolver.
Related: cards 119 (the package copies are fixed by a cut, not by hand), 131, 88. constitutional/a-rule-is-applicable-where-it-is-read.md is the rule this serves from the inside.
The remedy, sorted 2026-09-06 — and repointing is the wrong fix for most of them
The decision rule already exists. constitutional/a-rule-is-applicable-where-it-is-read.md § Detection: delete the reference and re-read the sentence. Still applicable → attribution, keep it. The reader now has to go somewhere → dependency, absorb the content and then attribute. Written for paths pointing outside the repo; it decides all 94 unchanged. The question was never what path a reference becomes — it is whether the sentence is still true without it.
Why a script cannot do this. Of the 16 references with exactly one candidate target, eleven resolve into workspace/, which does not publish. Repointing them makes the path valid and leaves the reader of a published page unable to follow it — card 86's shape, arrived at by fixing something.
Done 2026-09-06: 12 repointed, only where the target publishes, ships, or is on the named-file allowlist. planning/aleris-meta-alignment.md and planning/versioning-rules.md → documentation/; foundation/en-draft/{voice,colour,typography}.md → the flipped foundation/ pages; governance/4-den-nara-… → its full path from root (×2); reference/aleris-baseline-images.md → baseline/reference/; planning/changelog.md → workspace/status/changelog.md (×2, allowlisted); /icon-picker/… → public/.
Around 8 of the 94 are not defects, and the check must not report them.
baseline/setup.md'ssrc/styles/aleris-tokens.css(×2) is an instruction to create a file, not a reference to one — "write it into the project as…". A resolver that reads it as a citation is wrong about the sentence.baseline/BASELINE.md'stokens/…andlogo/README.mdare dual-context: correct read from the package root, wrong read frombaseline/. Settle the context rule before the resolver, per above.
The rest sort into four routes. Full list with each sentence quoted was produced 2026-09-06 and is reproducible by re-running the scan.
- Attribution — delete the path, keep the sentence (~11). Historical provenance: supersede records naming where a file used to live (
aleris-design-tokens.md,baseline-token-architecture.md, the en-draft accessibility draft), workstream credits (planning/f4-voice-briefing.md,planning/voice-filters-plan.md), and a citation of_state.mdby its old repo name. The sentence carries its meaning without the path. - Declare as external (~4).
patientguide/frame/lib/css.js,sundviktlakemedel/BRAND-OS-FINDINGS.md, and2-areas/collaboration/swedish-language-rules.md— the last being a TAKS path written without its prefix, soexternal-references.test.ts's regex cannot see it and the in-repo resolver cannot either. A reference falling between two checks. Widen that regex as part of this. - Dependency — absorb or move (~5).
constitutional/voice-is-the-present-expert.mdandprinciples/_index.mdboth send the reader toprinciples/voice.md, which does not exist; andconstitutional/voice-is-the-present-expert.mdrests its non-negotiables on a sort indocumentation/constitutional-core-test.md, now inworkspace/research/and unreachable from a published page. These are the consequential ones — two accepted constitutional pages leaning on material a reader cannot get to. - Never written (~4).
documentation/aleris-meta-alignment.mdcitesconstitutional/brand-os-is-canonical.md,patterns/canonical-component-as-peer.md,patterns/diataxis-documentation.mdandcomponents/aleris-svar.md. Either aleris-meta is a separate repo and these are external, or they were planned and never written. Settle which before touching them.
Yours, six calls. Is foundation/voice.md what principles/voice.md should have said (×2)? Does the constitutional-core-test move somewhere reachable, or does the sort get absorbed into the page? Are the aleris-meta pages external or vapour? Does en-draft/ go? And the dual-context rule.
Done when: the dual-context rule is stated, the check exists with its pinned counts, every remaining reference has taken one of the four routes, and it is on main.
Update 2026-09-07 — the check exists; the sweep does not. lib/in-repo-references.test.ts and scripts/in-repo-references.mjs --list, landed with Phase 0 revised step 2. The dual-context constraint is settled in the check's header: a reference resolves in any context the file is actually read in — the repo root, its own folder, and the package root for a file inside _packages/agent-baseline/ or for the canonical copy of a file the package ships (looked up through _packages/agent-baseline.map.mjs). Not "try every root". Pinned at landing: 555 references, 81 unresolved in 21 files — this card counted 94 in 28 with a narrower scan, and five citations left behind by the 2026-09-06 move were repointed before pinning. Counts may fall and never rise; three path-shaped strings are declared as not citations (src/styles/aleris-tokens.css, React/Next.js, decided/considered/suggested). Of the 81: 28 in foundation/en-draft/ (yours — sweep or delete), 13 in the package (card 119), 8 in aleris-meta-alignment.md (vapour or external, yours), the rest sort into the four routes above. Done when clauses one and two are met; three and four remain.
133 · [T] The colour essay — principles/colour.md is cited by a constitutional page and has never existed
Files: constitutional/colour-is-the-aleris-palette.md (promoted 2026-09-06, cites it six times) · foundation/colour.md (316 lines, accepted) · workspace/authoring/colour-authoring-2026-07-28.md · workspace/authoring/colour-supersede-scaffold.md · _markets/sv/foundation/colour.md
New 2026-09-06, from promoting the constitutional colour page out of foundation/en-draft/.
The gap. The constitutional page holds only what makes a colour choice not-Aleris and says so: "the roles in full, the composition logic, and the accessibility detail live in principles/colour.md." That file has never existed. principles/ holds _index.md and design-tokens.md.
Redirected rather than left dangling. All six references now point at foundation/colour.md, which is accepted, publishes, and carries every promise the constitutional page makes — palette tables, per-family roles, Pantone and CMYK, Legacy colours including the teal tier, and the full accessibility section with strong pairs, judgement pairs and figure-ground. So nothing is broken and this card is not urgent.
What the card is actually for. Deciding whether the three-layer shape the constitutional pages were written against — constitutional holds the bright lines, principles/ holds the essay, foundation/ holds identity — is the shape Brand OS wants, or whether foundation/colour.md is the essay and principles/ was a plan that got overtaken. The second is the cheaper answer and the evidence leans that way: foundation/colour.md grew to 316 pages of exactly the material principles/colour.md was supposed to hold, while principles/ stayed at two files for four months.
Yours: does principles/ become the essay layer, or does it stay a two-file layer for cross-element principles (tokens, and whatever else is genuinely not per-element) while foundation/ keeps the per-element essays?
Related: cards 134, 135 (same question, other two elements), 50 (the layer taxonomy), 132.
Done when: the layer question is answered once for all three elements, and each constitutional page points at whatever that answer makes the essay.
Closed 2026-09-07. Both clauses met by PR #27 and the ratification that followed it.
Update 2026-09-07 — answered by execution. Phase 0 revised step 4 moved foundation/colour.md to principles/colour.md, so the essay the constitutional page cites exists at the path it was written against; the redirect note of 2026-09-06 on that page now says so. The 2026-09-05 hex count the token-values check pins moved with the file (48). principles/ is the essay layer because the map Torfinn owns says it is. What remains is not this card's: the essays were not written to principles/' question-led convention (principles/_index.md § File convention says so) — whether they should be is open. Pullable to Done once Torfinn confirms the reading.
134 · [T] The typography essay — same shape, and the smallest of the three
Files: constitutional/typography-is-museo-sans.md (promoted 2026-09-06, cites it five times) · foundation/typography.md (136 lines, accepted) · _markets/sv/foundation/typography.md
New 2026-09-06. Same defect as card 133 and the least material: foundation/typography.md is 136 lines and already carries the scale, the two working weights, line spacing, and the hierarchy reasoning — everything the constitutional page sends a reader for. References redirected there 2026-09-06.
The one thing specific to this element. The constitutional page's Open item 1 lists what the 2026-05-31 sort demoted to principles/typography.md: never-italics-for-emphasis, rem-not-px, keep-it-simple, the 8pt step, and the two-weight mapping. Those five rules were routed to a file that was never written. Three of them are in foundation/typography.md today (the 8pt step, the weight mapping, the scale as relative sizes) and two are not — never-italics and rem-not-px. So this card carries a real remainder rather than only an architecture question: two demoted rules have no home at all.
Related: cards 133, 135, 93 (the en-dash convention, same class of craft rule with no home).
Done when: the layer question from card 133 is answered, and never-italics and rem-not-px are either placed or dropped on the record.
Update 2026-09-07 — the layer question is answered as in card 133; foundation/typography.md is principles/typography.md. The real remainder stays: never-italics-for-emphasis and rem-not-px still have no home. Also worth knowing before placing them: the constitutional page's Supersedes: line and every citation now read principles/typography.md.
135 · [T] The voice essay — two documents, 256 lines and 145, that were never merged
Files: foundation/en-draft/principles/voice.md (256 lines) · foundation/voice.md (145 lines, accepted) · foundation/en-draft/constitutional/voice-is-the-present-expert.md (96 lines) vs the live constitutional/ one (115) · workspace/authoring/voice-examples.md
New 2026-09-06. This one is not the same as 133 and 134 — the essay exists. It is the last thing in foundation/en-draft/, and it is a merge rather than a move.
What differs, measured 2026-09-06. The en-draft page frames each of the five principles as a question — "Is the patient the one acting, or the one things happen to?", "What does the reader need in the first three sentences?", "Whose purpose does this sentence serve?" — with the principle as the answer. The live page states them flat. Five sections exist only in en-draft: From patient information to patient guide, Across audiences, Across markets (culture and language), Anti-pattern (global), and Origin/Open/Related. Three exist only live: What the Present Expert is not, Quick reference, For AI systems. And principle 5 differs in substance — en-draft has "legal and contractual content earns its place only when it serves the patient", live has "the organisation's need for protection and the patient's need for information are not mixed."
Why the question-form matters beyond style. documentation/site-map.md § "The core as a reasoning system" is the corpus's own argument for it: "questions generalise where answers don't." The en-draft page is that argument applied, and it is the only page in the corpus written that way. Whether the merged page keeps it is the substantive call here, not which sections survive.
Also to settle: the en-draft constitutional voice page is a 96-line variant of the 115-line live one and nothing records which is newer. Compare before either is edited.
Blocks: deleting foundation/en-draft/. It holds these two files and nothing else.
Related: cards 133, 134, 55 (the "For AI systems" idiom rule, which exists only in the live page), 131.
Done when: one voice essay exists, the constitutional variant question is settled, and foundation/en-draft/ is empty and gone.
136 · [T] depends_on identifiers still say foundation/colour after the essays moved — is an identifier a path or a name? — DONE 2026-09-10
Files: lib/content.ts (DEPENDENCY_ALIASES) · lib/baseline-content.ts (FOUNDATION_LINKS) · every file declaring depends_on: — the seven shipped patterns, the Baseline reference and governance pages, the three Swedish voice guides, principles/iconography.md, principles/imagery.md, principles/logo-identity.md, the constitutional pages, filters/* · documentation/site-map.md § "The dependency model"
New 2026-09-07, from Phase 0 revised steps 3 and 4. The essays moved from foundation/ to principles/; the identifiers other files declare for them did not. foundation/constant-contextual appears in 24 files, foundation/colour in 19, foundation/typography in 20, foundation/emotional-modes in 12 — shipped patterns, Baseline pages the migration must not touch, _markets/sv/ archives and package copies among them.
What was done instead of rewriting them. The renderer resolves the old names through one alias table in lib/content.ts, used by both link directions, and FOUNDATION_LINKS keeps its foundation/… keys. Nothing a reader sees changed. But the corpus now carries identifiers that are false as paths and true only as names, and the dependency model (site-map.md) describes them as declarations of "the principles/constitutional it rests on" without saying which kind of thing an identifier is.
Yours. Is a depends_on value a path (then every identifier is rewritten to principles/… in one sweep, the shipped copies at the next cut, and the alias table goes) or a name (then the values are stable handles, the alias table is the registry, and how-decisions-are-recorded.md says so)? The second is cheaper and survives the next move; the first is what the values look like. Note the in-repo path check deliberately does not read frontmatter identifiers — if they are paths, it should.
Related: cards 132, 119, 50 (the BASELINE.md retirement will move more of them), 133/134.
Done when: the dependency model names what an identifier is, the files agree with it, and either the alias table or the foundation/… values are gone — and it is on main.
140 · [T] Hard rule 3 is ruled per section and stated three different ways across nine places — ratify the wording, and define what a section is
Files: workspace/incoming/brief-hard-rule-3-per-section-2026-09-08.md (the inventory and the drafted wording) · principles/colour.md § the button rule (Foundation, accepted v1) · baseline/BASELINE.md hard rule 3 · schemas/design-token.md § Buttons · how-to/install-the-tokens.md · patterns/chat-bubble.md · patterns/hero.md · documentation/site-spec-v2.md · workspace/skills/aleris-design-system/SKILL.md · _markets/sv/foundation/colour.md
New 2026-09-09, opened because the ruling had no card. Torfinn ruled per section on 2026-09-08 and the brief recording it sat in the drop zone with nothing on the board pointing at it — which is the one place a decision cannot be tracked from.
What the corpus says now. Not one statement in nine places: three different scope words across the two tiers that have a declared precedence order. Foundation says per surface; Baseline says per screen in four places; the site spec says per page; and the Swedish Foundation page has said per sektion all along. BASELINE.md opens by saying Foundation takes precedence when the two conflict, so the corpus currently resolves to the word the ruling replaced.
The wording is drafted and needs no writing — changed words only, the rest carried verbatim per the consolidation rule, for seven of the nine surfaces. Under CLAUDE.md § The content boundary a drafted rule sentence becomes his by ratification rather than by being written, so these are drafts until he says the words are his, on the pattern used for the two sentences ratified 2026-09-07.
What actually blocks it, and why this is not clerical. Section has no boundary anywhere in the corpus, so per section is undecidable at a glance and unenforceable by a check. Card 23's adjacency invariant — never a filled orange beside a filled petrol — is the candidate definition, and the brief says so itself. Ratifying the wording first would put a decided-but-uncheckable rule into a Foundation page that is accepted v1, and then discover the term is still undefined. Card 23 is the root and the order is load-bearing.
Why it is worth doing before the other sittings. One of the nine copies is workspace/skills/aleris-design-system/SKILL.md, which loads in every session and states the retired rule, so every build gets the old wording until this lands. Four consumers hold vendored copies, two of them in Swedish. And principles/colour.md is accepted v1, so changing it is a Foundation version question (documentation/versioning-rules.md), not an edit.
Also worth knowing before he rules: the Swedish Foundation page is stale in the other direction — it still states "Ingen dark mode", retired on 2026-08-11 with a written reason. So principles/ and _markets/sv/ disagree in both directions at once and nothing compares them. That is the larger finding in the brief and it is card 57's neighbour, not this card's.
Related: cards 23 (the definition, and the root), 119 (the cut that carries the six package copies), 121 and 123 (the two rulings that ride along), 57 (the Swedish page's opposite staleness).
Done when: section has a definition, the nine surfaces state one scope word, principles/colour.md's version question is answered, and the skill and the package copies are current.
141 · [CC] An accepted constitutional page states a count that is wrong by fourteen
Files: constitutional/language-policy.md § Detection of violation · foundation/en-draft/
New 2026-09-09, found by the board PO pass while checking card 34.
The page says "Sixteen en-draft/ pages currently carry the English source correctly; the three Baseline voice guides are the one known gap." foundation/en-draft/ holds two markdown files today (measured 2026-09-09) — the two voice files card 135 is about. The fourteen others moved into their tiers or were deleted as the English flip completed and Phase 0 ran.
Why it is not just a stale number. The sentence is the page's own detection of violation, on a page that is accepted and therefore in force, and it publishes. A rule whose detection describes a directory that no longer exists in that shape cannot be run by someone who did not write it — which is the admission test the constitution sets for a rule existing at all. So this is a defect in an in-force rule, not a typo in prose.
It is also the second instance of one shape, and the first is on this same board: card 34 was filed as work in progress while the work had been finished the day it opened. Both are a record describing a world that moved, on a surface nobody re-reads. The check that would catch this class does not exist — lib/in-repo-references.test.ts asserts that a cited path resolves, not that a stated count is current.
Not corrected here. accepted pages are treated as immutable except corrections (CLAUDE.md § Status badges), and this one is a correction — but the sentence also carries the claim that the three voice guides are "the one known gap", which is a scope statement about the rule rather than an arithmetic slip. Claude can recount; whether the surrounding claim still holds is his.
Done when: the count is current or the sentence is reworded to state a condition rather than a census, and it is on main.
137 · [T→CC] Rename the package folders to the tiers — a breaking cut, taken once, at Foundation v1
Files: _packages/agent-baseline.map.mjs (the left-hand side of every entry) · _packages/agent-baseline/{foundation,reference,governance,voice,patterns}/ · _packages/agent-baseline/MANIFEST.md and README.md · _packages/adapters/vendored-app/instances/patientguide.brand-provenance.json and sundviktlakemedel.brand-provenance.json · _packages/adapters/vibe-coding-seed/ · lib/consumer-contract.test.ts · lib/in-repo-references.ts (the package root in the dual-context rule) · baseline/BASELINE.md § the file table
New 2026-09-07, opened on Torfinn's instruction after the Phase 0 revised moves. Since that day nine of the package's thirty files come from principles/ and only seven from baseline/, while the package still says foundation/colour.md, reference/aleris-baseline-animation.md and governance/aleris-anti-patterns.md. That gap is the generated-view ruling (2026-09-06) working as intended: the left side of the map is the consumer contract and did not move. This card is the one deliberate moment it does.
Why it is a breaking cut and not a side effect. Four consumers record package paths and compare bytes against them — patientguide and sundviktlakemedel through brand-provenance.json, aleris-assistant by direct reference (it broke for fifteen days the last time a path moved, 2026-08-10), and aleris-vibe-coding through Richard's mirror on Bitbucket, still pinned at v0.6.5. Renaming a folder breaks all four at once, so it happens once, announced, with every consumer re-pinned in the same pass — not as a consequence of whatever cut happens to come next. The v0.12 catch-up cut (card 119) keeps the old names on purpose.
Why Foundation v1. _packages/README.md rule 3: until Foundation v1 ships, packages are stamped by date; after it, they pin to a version. A consumer re-pinning to a version-stamped package for the first time is already taking a deliberate step, so that is the cheapest moment to hand them new paths as well. Two breaking changes for the price of one migration.
What the cut carries, so nothing is renamed twice. The package folders become the tiers: principles/ for the essays and the reference pages, patterns/ for the library and anti-patterns, and whatever the governance split (⚑ item 5) and the BASELINE.md retirement (card 50) have produced by then. The aleris- file prefixes the corpus dropped go at the same time. Card 136's outcome — identifiers as paths or names — decides whether the depends_on values inside the shipped files change in the same cut, which is why it is a wait-on rather than a follow-up.
What must exist before the cut. A written migration note per consumer (old path → new path, generated from the two versions of the map rather than typed); brand-provenance.mjs in the vendored-app adapter able to read both shapes for one version, or a stated cut-over date; and lib/consumer-contract.test.ts fed the new left-hand side. lib/in-repo-references.ts's dual-context rule needs no change — it reads the package root, not the folder names.
Yours (T). Whether the rename happens at Foundation v1 or waits for a later version; whether it coincides with retiring BASELINE.md (card 50), which would change the package's entry point in the same breaking cut; and the folder names themselves, which should be the tier names unless the package wants a consumer-facing vocabulary of its own.
Related: cards 119 (the catch-up cut that keeps the old names), 136, 50, 125 (consumers who copy rather than install), 14 and 34 (Richard's mirror).
Done when: the map's left-hand side names the tiers, all four consumers are re-pinned to the version that carries it and their provenance checks pass, the migration note is in the manifest, and it is on main.
142 · [CC] The depends_on sweep card 136 authorised — 67 files, and DEPENDENCY_ALIASES retires with them
Files: lib/content.ts (DEPENDENCY_ALIASES, 7 entries) · lib/baseline-content.ts (FOUNDATION_LINKS) · 67 files declaring a stale foundation/… identifier · documentation/site-map.md § Dependency model (the ruling)
New 2026-09-10, from card 136's ruling: a depends_on identifier is a path. The Foundation essays moved to principles/ on 2026-09-07 and the identifiers other files declare for them did not; lib/content.ts has aliased the old names ever since so the links kept resolving. Under the ruling the aliases are a migration device, not a layer, so they come out.
The scope, measured 2026-09-10 rather than estimated. 67 files carry at least one stale identifier: 17 live corpus, 16 _packages/ mirror, 3 _markets/sv/ archive, 31 workspace/, 0 in foundation/en-draft/. By identifier: constant-contextual 45, emotional-modes 29, colour 20, typography 12, imagery 11, iconography 3, logo-identity 0.
Three traps, and the scope is not uniform. The _packages/ mirror is a generated view (ruled 2026-09-06) — sweeping it by hand puts the corpus and the generator out of step, so those 16 change by regenerating, at the next cut, not in this pass. The 3 _markets/sv/ files are archives now carrying status: superseded (card 95); an archive that has been edited is not an archive, so they stay stale on purpose and get declared rather than swept. And of the 31 in workspace/, dated records — decision records, briefs, changelog entries — state what was true when written and are not citations to keep live.
So the real sweep is the 17 live corpus files, and the other 50 are a declaration of why they were left. Getting that split wrong in either direction is the failure this card is most likely to produce.
The check that has to come with it, or the ruling is a preference: a stale depends_on should fail rather than silently alias. lib/in-repo-references.test.ts is the nearest existing shape and the natural home.
Related: cards 136 (the ruling), 137 (the package rename, which must not collide with this), 132 (94 pointers to nowhere — same defect class, different field).
Done when: the 17 live corpus files declare real paths, the 50 deliberately-left files are declared with the reason, DEPENDENCY_ALIASES is empty or gone, a planted stale identifier fails a check, and it is on main.
143 · [CC] Card 124's three Claude halves — the Tailwind sentence, the comment-density rule, and the cookbook entry
Files: how-to/install-the-tokens.md · baseline/BASELINE.md § Serving this file · workspace/status/cookbook.md · reads workspace/evaluations/card-124-sundvikt-answers-reviewed-2026-09-10.md
New 2026-09-10, from reviewing Sund vikt's answers. Card 124 assigned three of its five sections to Claude and they have no card of their own. None depends on Torfinn's §1 or §4 ruling.
§2 · one sentence in how-to/install-the-tokens.md. The Tailwind v4 bridge is approved, documented in a BASELINE.md reference row, and does not exist as a package member. That silence produced two dead dependencies in a real consumer: tailwindcss and @tailwindcss/vite at ^4.3.3 are installed in sundviktlakemedel and the plugin runs on every build, with no @import "tailwindcss", no @theme block and not one utility class in the markup — verified 2026-09-10. The install page should say a consumer should not install Tailwind until the bridge ships. An installed-but-unused build plugin on a patient-facing product is a supply-chain surface, so the sentence is worth more than tidiness.
§3 · the positive rule beside the serving rule. BASELINE.md § Serving this file argues weight and build safety, both defensive. The missing claim is positive: comment density in a design-system stylesheet is an asset, because the build removes it and the reasoning is what makes a rule survive its author. The worked proof is in the corpus already — the Museo Sans tabular-figures finding reached every consumer's instruction file because a comment recorded a measurement beside the code it governed. The risk is specific: a consumer hunting bytes, opening a file that is 67% prose, has an obvious first move that is also the worst one, and it nearly happened in sundviktlakemedel.
§5 · one cookbook entry, covering three instances rather than one. A sweep built to catch "the list of eight" drift reported clean for a week while the drift was present, because its patterns needed an article the surviving phrase did not have. That is the same defect as _state.md's three same-day instances of a claim true of a narrower thing than it appeared to describe, and as board card 83's A/B/C absence claim, which searched three descriptive names while Baseline referred to the model by label. General form: a clean result is a fact about the query, never about the world. The detection is the cheap one and it is what makes this more than a slogan — when a check reports clean, plant the thing it is supposed to catch and confirm it fails. That is already the standing rule for new checks; what this adds is that it applies to existing checks reporting clean, which nobody does.
Two more instances landed 2026-09-10, so the entry covers five rather than three, and both are worth naming because neither is a check — which widens the class. The branch-hygiene test went from failing to passing between two runs of the same commit, not because the stranded branch was dealt with but because the second run was in a clone whose remote had changed under it: the check reads its own origin, and origin moved from GitHub to Forge. And a grep across app lib components site/src reported the tree clean of a deleted import while hooks/useAuth.ts held one, because hooks/ is in none of the four paths — the build found it, the grep could not. So the general form should not say check: a clean result from any query — a grep, a test, a scan — is a fact about that query's reach. Forge's own FORGE-MIGRATION.md scan is a sixth instance from outside this repo, reporting zero Supabase call sites because it does not read supabase/.
Trap: §2 and §3 are both one sentence in a publishing file, which is the size at which a card gets skipped and the sentence never written. They are on one card so the three land together.
Related: cards 124 (the two halves that are Torfinn's), 119 (whether the bridge becomes a package member), 99 (the bridge is phase 0c of the starter-kit plan).
Done when: all three are written, the cookbook entry names its three instances and its detection, and it is on main.
144 · [CC] Generate a vendored-app adapter for gcc, now that pursuing it is ruled
Files: _packages/adapters/vendored-app/PROFILE-gcc.md (filled 2026-08-28, with citations) · _packages/adapters/vendored-app/ · nothing in ~/Dev/gcc until the gate clears
New 2026-09-10, from card 105's ruling: pursue. The profile exists and nothing in that repo has been touched.
Blocked on card 104, and not by choice of sequencing. Gröna korset's five-state safety scale is either a ratified clinical exception or a standing breach. The adapter's checks cannot be generated without knowing which, because the answer decides whether they exempt those colours or flag them — and an adapter may not decide it: adapter rule 2 says an adapter carries no brand content, so this is the one question the mechanism is forbidden from answering for itself.
Done when: card 104 is ruled, the adapter is generated and installed, its first run's findings are recorded, and it is on main.
145 · [CC] Conform Brand OS to Forge — scoped to Astro, and the generated scan is wrong in two places — DONE 2026-09-10 (bar the console Deploy)
Files: FORGE.md, FORGE-MIGRATION.md (Forge-generated, repo root) · Dockerfile · site/ and astro.config.mjs (what ships) · supabase/migrations/ (4 files, only if the icon-picker survives)
New 2026-09-10, rescoped the same day by Torfinn's ruling: Astro wins, drop the Next work. The card opened asking which app Forge should conform. That question is answered, and answering it removed most of the card.
What the move was. Forge forked aleris/brand from GitHub at 2a1110d, Mohan added FORGE.md and FORGE-MIGRATION.md, and the day's ruling pass merged across at a91c7fd. Trunk is code.aleris.ai/aleris/brand; GitHub is the github remote and is history.
What the Astro ruling deletes from the checklist
Measured 2026-09-10 against site/dist, not assumed. The Astro build already emits all 26 content routes (54 HTML files), llms.txt, foundation/raw/* and baseline/raw/*, and the 3,776 icon assets — so the machine-readable surface the README advertises is covered. site/src references Supabase, Resend and notify-comment nowhere.
A static site needs none of what FORGE-MIGRATION.md spends four of its five steps on. Struck: step 2 (apply a schema through the broker), step 3 (the pg + withClaims seam, Authentik OIDC, the RPC and table-read ports), step 4 (re-key authz on forge). No database, no claim bridge, no forced RLS, no login — because nothing static reads a row.
What is left, and it is step 1 plus a container
- Bind
0.0.0.0on$PORT, serve a real/health. The currentDockerfileruns a full Next.js Node server and its own header says why — dynamic routes, the cookie-based Supabase server client, filesystem reads, the native@resvg/resvg-jsendpoint. Three of those four reasons go with the Next app. The Dockerfile is rewritten, not adjusted: a static file server oversite/dist, which is also what makes the deploy cheap enough that card 113's freeze cannot repeat quietly. - Build-time env, and there is almost none left. The Font Awesome token and every
NEXT_PUBLIC_*were build args on Coolify. The Astro build reads exactly one variable,SITE_URL, defaulting tobrand.dev.aleris.aiinastro.config.mjs; set it in the Forge console when prod cuts over. The token is optional since card 147. - Deploy moves to the Forge console. Push, then press Deploy.
The two errors in the generated scan, measured rather than suspected
Both survive the rescope, because a regenerated scan will repeat them.
- It reports
Supabase: YES — 0 call site(s) across 0 file(s)and prints (none detected) under all four Replace-Supabase sub-steps. There are seven files, four importing directly, both packages inpackage.json. Under the Astro ruling this is no longer work to do — but the scan being wrong is still worth fixing, because the next app Forge onboards inherits the same blind spot. - It reports an own schema at
db/ · migrations/ · schema.sql.db/holds one JSON seed,migrations/does not exist, and the four real migrations are insupabase/migrations/. Both errors point atsupabase/, which the scan does not appear to read.
A trap if the icon-picker does keep a database. workspace/archive/rescued-from-backup-branches-2026-08-03/supabase/migrations/ holds two migrations belonging to other products — crossborder and vårdförsäkring. Brand OS owns the brand schema only. A glob wide enough to catch the archive applies another product's schema to brand_dev.
What does not come with the static site
Two things, and neither is a consequence anyone chose — they fall out of the substrate. Card 146 carries the first; the second is scoped here.
- The annotation and comments feature — card 146. Torfinn's, because it is a feature disappearing.
- The Qlik icon-picker (
/tools/icon-picker/qlik+ three API routes). This is the backend island the corpus has named since the Phase C framing, and it genuinely needs Postgres and the native renderer. It is the one thing that would use a forge schema, a scoped role andwithClaims. Not in this card's scope: conform the static site first, then decide whether the island is a second Forge app, a serverless function, or retired./tools/icon-pickeritself (the plain picker) reads no database and its assets already ship in the Astro build.
Two loose ends from the move. GitHub holds six branches Forge does not — one fully merged and deletable, five backup/auto-*, two of which differ from trunk. Nothing lost, but stranded. And the pre-push harness did not travel: a fresh clone of the Forge repo has no hooks, so the gate that refused a push on 2026-09-09 does not exist there. Dev/_tools/guards/wiring.json is where it is version-controlled, and that is portfolio-level rather than this repo's.
Related: cards 146 (comments), 113 (the deploy that had frozen; the Forge console is now the path), 99 (its phase 4 said sequence hosting after Phase C — Phase C is now decided).
Done when: the Dockerfile serves site/dist statically on $PORT with a real /health, a Forge deploy goes live and serves the current corpus, FORGE-MIGRATION.md is regenerated or its two errors corrected in place with a note, and it is on main.
146 · [T] Astro wins, and the annotation feature does not come with it — DONE 2026-09-10
Files: components/comments/ (4 files) · components/auth/ (3) · lib/auth.ts, lib/supabase.ts, lib/supabase-server.ts · app/auth/callback/route.ts, app/api/notify-comment/route.ts · app/[locale]/layout.tsx and app/baseline/layout.tsx (both wrap every page in CommentWrapper) · supabase/migrations/ (the annotations table) · supabase/import-annotations-after-first-login.sql
New 2026-09-10, opened because a ruling had a consequence nobody named. Astro wins was a ruling about the renderer. It is also, silently, a ruling to switch off in-page commenting, and that deserves to be decided rather than discovered.
What exists today. Every content page in the Next app is wrapped in CommentWrapper. A reader selects text, a popover offers a comment, the panel writes to a Supabase annotations table over a realtime channel, and /api/notify-comment emails Torfinn and Sanna through Resend. There is a login flow behind it — signIn, signOut, getProfile, createProfile — and an import-annotations-after-first-login.sql script, which is the shape of something that has been used rather than merely built.
What the Astro site has instead: nothing. site/src contains no reference to Supabase, comments or Resend. So the moment site/dist is what brand.dev.aleris.ai serves, commenting stops, and any annotation already in that table becomes unreachable rather than deleted.
How load-bearing it actually is, stated honestly and not resolved. Two facts point opposite ways. It is wired into every page, which is the shape of a core feature. But the open threads have carried "Resend sender-domain verification" as unfinished for weeks, so the notification half may never have worked in production — and a comment feature nobody is emailed about is a comment feature nobody answers. Neither of those is a measurement of use. The one measurement that would settle it is the row count in the annotations table, and that is in a database this session did not read.
Three routes, and the middle one is the cheap one.
- Retire it. Delete the components, the auth flow and the two API routes with the Next app. Export the existing annotations to
workspace/archive/first so the record survives the feature. - Replace it with something static-compatible. A third-party layer, or a comment link that opens a form. Loses in-page selection anchoring, keeps the loop.
- Rebuild it against Forge. Authentik OIDC instead of Supabase auth, a forge schema with forced RLS, the claim bridge. This is real work and it is the only route that puts the whole
FORGE-MIGRATION.mdchecklist back on the table — which is the argument for deciding it now rather than after the static cut.
Yours, and the order matters. If the answer is 3, card 145's Dockerfile is not a static file server and the scope reopens. If it is 1 or 2, 145 stands as scoped and this card is an archive-then-delete.
Before deciding, one number is worth having: how many annotations exist, and when the most recent one was written. That is a query against the Supabase project, not a file in this repo.
Related: cards 145 (which assumes routes 1 or 2), 113, 99.
Done when: the route is chosen, any existing annotations are exported or deliberately abandoned with that said, and it is on main.
147 · [T→CC] The Font Awesome token returns E401, and it is the first thing a Forge build runs — DONE 2026-09-10
Files: .npmrc (gitignored, repo-local, present in the main checkout) · ~/.npmrc · Dockerfile stage 1 (FONTAWESOME_NPM_TOKEN build arg) · package.json (@awesome.me/kit-6f31575c9d)
New 2026-09-10, found while trying to build the Forge image for card 145. Torfinn's belief and the machine disagree, so this is the conflict named rather than a side chosen.
What he said: the token has been globally available since earlier this week — which matches the record, FA_PACKAGE_TOKEN exported in his shell profile on 2026-09-08.
What is measured on this machine today, 2026-09-10. npm view @awesome.me/kit-6f31575c9d version returns code E401 using the repo-local .npmrc in the main checkout. Not 404, not a missing scope mapping — the registry is reached, the scope resolves, and the credential is rejected. FA_PACKAGE_TOKEN is unset in this session's shell, and neither ~/.zshrc (4 lines) nor ~/.zprofile (2 lines) mentions it, so whatever exports it is not in either file. ~/.npmrc carries a separate token for the @fortawesome scope, which the record already flags as dead.
Why this is urgent rather than housekeeping. Dockerfile stage 1 runs npm install before npm run site:build, and package.json carries the private kit. If the Forge console's build variable holds the same value that is failing here, the deploy fails at the first step — before Astro runs, with an E401 that reads like a platform problem and is a credential problem. Card 145's remaining action is that Deploy, so this is directly in front of it.
What is not the problem, ruled out by measuring. The @awesome.me scope mapping is present in the repo-local .npmrc; a worktree without that file gets a 404 instead, which is a different error and was the first thing checked. The two-stage Dockerfile and site/serve.mjs are verified independently — the runtime stage was built and run — so nothing about card 145's own work depends on this.
Why Claude stopped here. Passing the token to docker build --build-arg means reading a credential out of a file and putting it on a command line, which Claude does not do. The full image build is therefore unverified locally, and that is stated on card 145 rather than left to look like an oversight.
Yours, and it is small. Confirm whether the value in the Forge console's env panel is current, and re-issue the kit token if it is not. npm view @awesome.me/kit-6f31575c9d version answering with a version rather than E401 is the whole test.
Related: cards 145 (the deploy this blocks), 58 (the icon allowlist — re-running scripts/build-icon-metadata.py after a token refresh rewrites the committed SVGs, so do that with the card, not as a side effect).
Done when: the token resolves the kit from this machine and from the Forge builder, and it is on main.
148 · [T] The Forge deploy is refused by a domain conflict, and the override on offer would break two live things
Files: none in this repo — this card is the cutover · reads Dockerfile, site/serve.mjs, lib/route-manifest.json · touches the Coolify app that holds brand.dev.aleris.ai and the Forge console
New 2026-09-10, from Torfinn's first Forge deploy attempt. Nothing was built and nothing broke — the deploy plane refused before starting:
coolify POST /applications/private-deploy-key -> 409: Domain conflicts detected.
Use force_domain_override=true to proceed.
The diagnosis is not a build problem. The old Coolify application still owns brand.dev.aleris.ai, and Forge asked Coolify to register a second application on the same domain. Coolify refused. Verified by fetching the live host: it serves _next/static/chunks/… and redirects / to /sv/about-the-brand/brand-in-brief — the Next.js app, frozen at the 2026-06-16 build card 113 has tracked for 86 days.
The error offers force_domain_override=true, and that is the thing not to click yet. Two things are live on that domain today and neither survives a static Astro site taking it over.
Correction 2026-09-11, and it is a correction to this session's own wording. The ruling below lifted the reason to wait; it did not establish how the domain moves, and the index row briefly said "one action in the console" as though it had. Nobody has seen an override control. The evidence is the parameter name in Coolify's 409 above, and FORGE.md § deploy step 4 says the deploy plane's dashboard is operator-only and a different address from the Forge console — Coolify is that plane. So two routes, and the second is preferred: pass the override (mechanism unknown, and it takes a domain off a running app as a side effect), or release brand.dev.aleris.ai from the old Coolify app first and then deploy the brand Forge app normally, which removes the conflict rather than forcing past it and is reversible — put the domain back and you are where you started. Torfinn asked "how do i press the override?" and the honest answer was that Claude did not know and had written as if it did. Next action is one cheap measurement: press Deploy on the brand app and read the refusal, which already failed safely once — nothing built, nothing broke. If no control is offered it becomes an operator ask, and it should travel with ASK-IKON-001 in one message.
CLOSED 2026-09-11. Torfinn deployed and it is up, verified independently rather than taken on report. scripts/verify-deploy.mjs against https://brand.dev.aleris.ai: 26/26 routes byte-identical to the local build, 35 passed, 0 failed, 0 content differences. /health returns ok + pages: /app/site/dist + index: 8751 bytes — our container, not the Forge placeholder, which returns a bare ok. / is 200 with no redirect, /identity/colour 200 text/html, /llms.txt 200 text/plain. The two rulings held: /sv/about-the-brand/brand-in-brief 404s, and both picker paths 404. Card 113 closes with this, and step 5 — deleting the Next app — is the remainder and is Claude's.
Which of the two routes was taken is not recorded here, because it did not need to be asked: it worked. The distinction below is kept anyway, since it was measured and is the kind of thing the next domain move will need.
One finding from the verification, card 153: the plain /tools/icon-picker is dark too, not only the Qlik one — the "nobody is using it" confirmation was about the Qlik tool — and the site still serves 3,776 icon files that zero built pages reference.
Stopped is not released, measured 2026-09-11. Torfinn stopped the old Coolify server the same day: both applications in Aleris Labs / brand guidelines / production read Status Exited — Brand Guidelines and icon-picker-qlik. The domain is still theirs. Coolify's own resource list still shows brand.dev.aleris.ai against the first and brand.dev.aleris.ai/tools/icon-pi… against the second, and both URLs now return 503 rather than failing to connect — a proxy route that exists and points at a dead backend, which is the signature of a claimed host with nothing behind it. Coolify's refusal said Domain conflicts detected, which is its domain registry and not container state, so stopping the containers probably does not clear the 409. The release step is clearing the Domains field on each of those two applications, in the same console where they were just stopped.
And the cheap test comes first, because it is free: press Deploy on the brand Forge app now. Still 409 means ownership is the blocker and the two Domains fields need clearing; a successful deploy means Coolify's conflict check only counted running applications. That answers in one click what is otherwise a guess.
One consequence of stopping without releasing: brand.dev.aleris.ai is now visibly down rather than stale — a 503 instead of a three-month-old site. Still inside the accepted gap, since nobody is using it, but the gap is now conspicuous rather than invisible, which is a reason to finish rather than park.
Measured 2026-09-11, so the state is not yesterday's: brand.dev.aleris.ai still 307s to /sv/about-the-brand/brand-in-brief, the 2026-06-16 Next app. forgebrand.dev.aleris.ai/health returns ok + pages: /app/site/dist + index: 8751 bytes — our container, not the placeholder, which returns a bare ok.
RULED 2026-09-10, and it lifts the hold: nobody is using it. Torfinn, the same day: "the tool might be live, but I got confirmation today that nobody is actively using it atm. It's thought to be in dev" — and "we can kill it and then work on setting up the new instance and invite users to that when we're done".
That is the evidence option 1 below was missing, supplied by asking rather than by measuring. The objection was never that the picker mattered; it was that answering 200 is not evidence that nobody uses it, and nothing in this repo could tell the difference. A confirmation from the people who would be using it can. So the domain override is safe, step 4 no longer waits on card 149, and the picker is deliberately dark between the cut and ikon going live — an accepted gap, not an oversight, on the ground that there is no one to interrupt.
What it does not unblock: ikon itself. Its schema still has no documented route to a post-onboarding database (forge/ASK-IKON-001.md), and inviting users still needs the OIDC credentials. Those were always Torfinn's and they stay Torfinn's — the change is that the brand site no longer waits behind them.
1 · The Qlik icon-picker is live and answers 200. /tools/icon-picker/qlik measured 2026-09-10. It is the backend island — Supabase plus the native @resvg/resvg-js renderer — and card 145 put it explicitly out of scope, on the reasoning that it would come back as its own app if it came back at all. Nobody decided to switch it off, and a domain override would do exactly that, silently, as a side effect of a deploy. It is the last thing in this repo still using lib/supabase-server.ts.
2 · Every published URL changes shape. Three shapes are in play and no two agree:
| Content URL | |
|---|---|
| Live today (2026-06-16 build) | /sv/about-the-brand/brand-in-brief |
| The Next app as it stands in the repo | /en/about-the-brand/brand-in-brief |
| Astro, what would deploy | /about-the-brand/brand-in-brief |
Measured: /sv/about-the-brand/brand-in-brief returns 200 on the live host and /about-the-brand/brand-in-brief returns 404. The Astro build emits no /sv/ tree at all. So every link anyone holds — a bookmark, a Slack message, a citation in another repo — breaks at the cut. The /foundation/raw/* and /llms.txt endpoints are unaffected; they are already prefix-free on both.
This was never decided, and it is not the same question Phase 0 answered. "Every URL unchanged" was the property the corpus moves preserved (card 145, Phase 0 revised). It said nothing about Next → Astro, because that was a different cut. The locale prefix disappearing is a consequence of the language exit finally reaching the renderer, which is right — but it is a redirect job, not a no-op.
Three routes, and the middle one is what a careful cutover looks like.
- Override now. One click, the site is current within minutes, the icon-picker dies and every
/sv/link 404s. Defensible only if nobody uses either — and the icon-picker answering 200 is not evidence that nobody uses it. ← this is the route, ruled 2026-09-10. Both halves now have their evidence: the/sv/URLs were never operational, and nobody is using the picker. - Deploy to a temporary domain first —
brand-forge.dev.aleris.aior similar. The Forge container has never run anywhere but this laptop; this is the first real environment it meets. Verify it there, decide the two questions below deliberately, then move the domain. Recommended: it converts a discovery into a decision, and costs one extra console step. - Retire the old Coolify app first, then deploy clean. Same end state as 1 with the conflict resolved rather than forced, but it leaves the domain dark in between and still needs the two questions answered.
The two questions, BOTH RULED 2026-09-10.
- Does the icon-picker survive, and where? → Its own Forge app. Torfinn sets up the repo and domain; card 149 is the move.
It gates the domain cut.It does not, as of 2026-09-10: nobody is using the live one, so the cut can take the domain and the new instance can be finished at its own pace, with users invited once it serves. - Do the
/sv/URLs redirect or die? → Die. "It was never operational with that feature." No redirect map, nosvtree, no legacy handling insite/serve.mjs. That reason is the evidence that was missing when the question was raised — the objection was that links would break, and the answer is that there are none to break. Recorded rather than left implicit, because it will be re-litigated by whoever meets the first 404.
And route 2 was taken: forgebrand.dev.aleris.ai is provisioned. It is not yet serving our container — measured 2026-09-10, it returns a Forge placeholder (<title>hello</title>, /health a bare ok, /llms.txt as text/html). So step 1 of the sequence is still to press Deploy there. The tell that it worked is /health returning ok followed by pages: and index: N bytes; the placeholder answers 200 with a bare ok, which is precisely the kind of green light that hides a deploy that never ran.
The full sequence is now workspace/plans/forge-cutover-sequence-2026-09-10.md — five steps, with 2 and 3 running in parallel and 4 waiting on both.
Related: cards 113 (the frozen deploy this finally addresses), 145 (the container, ready and verified), 105/144 (gcc is the one initiative already through a Forge cutover).
Update 2026-09-10 — staging is deployed and verified, and the verifier is kept. forgebrand.dev.aleris.ai runs the container: /health answers with pages: /app/site/dist and index: 8751 bytes, the same numbers the local image reported, which is how it is told apart from Forge's placeholder.
scripts/verify-deploy.mjs is the check, written for this and kept for the cutover because it asks the question card 113 needed asking for 86 days. It compares bytes, not status codes — a deploy that built the wrong commit answers 200 on every route — so every published page is fetched and compared against the local site/dist, with canonical tags and host names normalised out so a staging run is readable rather than solid red. Result: 26/26 routes byte-identical, 35 checks passed, 0 failures, including the raw-markdown content types, llms.txt, immutable hashed assets, revalidating HTML, a woff2, an icon SVG, and an unknown route returning 404 — that last one because if a catch-all returned 200 every other check would be meaningless.
Proved in both directions. Pointed at the stale brand.dev.aleris.ai it fails on all 26 routes plus the health probe, which is exactly the failure it exists to catch and the most concrete demonstration of the /sv/ shape difference: every current URL 404s on the live host today. And planting a 21-byte change in one local page surfaced it as a content difference on that route alone, so the comparison arm is doing work rather than agreeing with whatever it finds.
Update 2026-09-10 — the 1:1 constraint, and the mirror that works around it. The plan's step 1 said "point the Forge app at aleris/brand and press Deploy". There is no such field. Forge matches an app's domain to a repository of the same name, so forgebrand.dev.aleris.ai can only build from a repository called forgebrand. Torfinn found this by trying; the plan had asserted a capability nobody had checked.
aleris/forgebrand is now a mirror of this repository, force-pushed from main. It was a pure Forge scaffold — two commits, Initial commit and Mohan's FORGE.md, with the generic auth.mjs/server.mjs/db/ starter — so there was nothing to lose, which is the condition gcc's FORGE-PUSH-RUNBOOK sets before a force-push.
Two files differ on purpose and scripts/push-staging.sh restores both. Its own FORGE.md, because Forge generates one per app naming that app's schema, OIDC client and domain — the runbook's rule is that the console-generated brief is the app's identity card. And a README.md banner saying not to commit there, because a commit in a mirror is destroyed at the next refresh with no conflict to notice it by.
The script refuses rather than clobbering, and getting that right took two corrections worth recording. The first guard compared the tree after git reset --hard — tautologically the preserved files, because the reset had already erased what the check was for. A check that cannot fail, caught by planting a rogue commit and watching it pass; the eighth instance of card 143's defect. Moved before the reset, where it compares origin/main against brand/main and names every file the refresh would destroy. Proved by planting a commit, pushing it, and watching the script exit 1. And the refusal message itself carried backticks inside double quotes, so printing it executed git reset --hard — harmless by luck, and not a thing to leave in a script whose job is not destroying work.
Related: the mirror is temporary and goes with the cutover.
Done when: the Astro build is serving on a Forge-deployed container, the icon-picker's fate is decided and acted on, the /sv/ redirect question is answered, and it is on main.
149 · [T→CC] Extract the icon-picker to its own Forge app, and Brand OS becomes Supabase-free
Files: app/tools/icon-picker/** (22 files, 2,105 lines) · lib/supabase-server.ts · public/icon-picker/svgs/ (3,772 files, 16 MB) · data-products/iconography/allowlist.json (78 entries) · supabase/migrations/ (3 files) · db/seeds/icon_assignments.json · _packages/adapters/vendored-app/
New 2026-09-10, from Torfinn's ruling: "Let's make icon-picker its own forge app — i can set up the domain and we'll move it there." Step 3 of workspace/plans/forge-cutover-sequence-2026-09-10.md, and it gates step 4 — the picker answers 200 on brand.dev.aleris.ai today, so the domain cut kills it unless this lands first.
It extracts cleanly, measured rather than hoped. External dependencies only — react, next, jszip, @supabase/ssr, @resvg/resvg-js, node builtins. Nothing imports the corpus: no lib/content, no lib/baseline-*, no lib/foundation-*. lib/supabase-server.ts is one file with no internal imports and comes along whole.
Two disk reads are the entire coupling, and they are different problems.
public/icon-picker/svgs/regular/*.svg, read at render time so@resvg/resvg-jscan rasterise. Needed on disk — a URL will not do, which rules out the obvious "just point at the brand site".data-products/iconography/allowlist.json— a corpus data-product, and the file card 58's review will change. This is the one that matters.
So the picker becomes the fourth consumer of Brand OS and vendors both, through _packages/adapters/vendored-app/, which exists for exactly this: a provenance record with a sha256, a freshness check against the source's current version, and an expiry check that fails when a workaround's own condition stops being true. Two repos holding one allowlist with nothing comparing them is a failure this project has now found in several shapes; the adapter is the answer it already built. Rejected: fetching the allowlist at build or run time — it trades a checkable copy for a runtime dependency between two apps that fails silently when one is mid-deploy.
The consequence worth having. With the picker gone, lib/supabase-server.ts and both @supabase/* packages leave this repo. Brand OS becomes entirely Supabase-free — seven files on 2026-09-09, four after the annotation retirement, zero after this. The boundary the corpus describes in prose becomes the boundary in the dependency list.
Split of work. Torfinn: create the repo on code.aleris.ai (the name is a decision, not a default) and set up its domain. Claude: the move, the adapter wiring, the three migrations and the seed, a Dockerfile on the two-stage pattern from card 145, and a verification that the render endpoint still rasterises a real icon.
Trap: the three migrations target the brand schema, and workspace/archive/rescued-from-backup-branches-2026-08-03/supabase/migrations/ holds two belonging to other products. A glob wide enough to catch the archive moves another product's schema into the new app.
Related: cards 148 (the cutover this gates), 58 (whose review changes the vendored allowlist, and which is itself blocked on the dead Font Awesome token), 145, 105/144.
Update 2026-09-10 — extracted, ported and pushed. code.aleris.ai/aleris/ikon at 56169d6.
The move was the easy half and the measurement held: 22 files, ~2,105 lines, nothing importing the corpus. The port off Supabase was the real work. The whole data layer was one table and four operations — list, upsert on the convergence key, delete by id — now lib/db.ts over raw pg with forge's claim bridge, followed from FORGE.md rather than adapted. Auth reuses forge's own lib/oidc.mjs; only the wrapper is rewritten, because the scaffold wrapped it for a plain node:http server and this app is Next.
RLS is now real, and that is the substantive change. The Supabase original carried four policies, every one using (true), under a comment admitting it: "internal tool behind a known URL with one writer. Anon read+write+update+delete acceptable for v1." RLS switched on and then opted out of — exactly what forced RLS exists to stop. db/001_icon_assignments.sql gates on the request claim, ENABLE + FORCE, single-tenant on controller: these rows are org-wide configuration, so an owner-per-row model would hide colleagues' work from each other for no reason (FORGE.md § What is controller?).
Three things vendored with a sha256 each in brand/provenance.json: the 78-entry allowlist, the canonical token file (the CSS module references 71 of its variables — that dependency was not in the card's measurement and turned up on the first build), and the 3,772 SVGs. Plus the two Museo Sans faces.
Verified by building the image and running it with no database, which is the state that matters: /health returns 503 naming what is missing while reporting icons: 3772; /qlik renders the catalogue and degrades to no assignments exactly as it did before; /login returns 503 naming the five variables it needs rather than a 500. Runs as node, 456 MB, no node_modules in the runtime stage. The app's own render tests pass, 7 of them.
One bug found by running rather than reading: the fonts were mode 0600, copied from a working copy where the licensed files are restricted, so the container's node user got a 500 on them while an SVG from the same tree served fine. Local-only — git records 100644 and a Forge clone gets 0644 — and fixed anyway so both paths agree.
What is left, and none of it is Claude's: apply the schema through the broker, supply FORGE_OIDC_CLIENT_ID and FORGE_OIDC_CLIENT_SECRET plus SESSION_SECRET, set the domain, deploy. aleris/brand keeps the picker source until step 5 — the cutover plan deletes the Next app last, deliberately, so there is something to compare against, and the picker is still serving users on the old host until the domain moves.
A naming note, recorded not argued. ikon is Swedish and CONSTITUTION.md rule 7 keeps internal names English. Forge ties a repository name to a domain 1:1, so this name is also the address people type, and a domain has an audience — which is the same rule pointing the other way. Torfinn's call, made when creating the repo; it is in that repo's README so the next person meets the reasoning rather than the apparent inconsistency.
Done when: the picker serves from its own Forge app on its own domain, renders an icon through @resvg/resvg-js, carries a provenance record for the allowlist and the icon set, grep -rl supabase lib app components in this repo returns nothing, and it is on main.
150 · [CC] An unreproduced intermittent in the suite — recorded rather than declared fixed
Files: lib/branch-hygiene.test.ts (the leading suspect, and only a suspect) · lib/static-server.test.ts (ruled out — its fixed ports were a real flake and are fixed)
New 2026-09-10. Two observations, no reproduction, and this card exists because "it passes now" is not a diagnosis.
What was seen. Test Files 1 failed | 22 passed twice, roughly an hour apart. Both times the run came immediately after heavy work on the machine — the first right after a merge with a large diff, the second right after a large file write plus a site build, with Docker builds and npm install runs happening through the same period. Neither run's failing file name was captured, which is the gap that makes this a card rather than a fix: the failure was seen through a grep that showed the summary line and not the name.
One real flake was found and fixed in the hunt, and it is probably not this one. lib/static-server.test.ts bound ports 4187 and 4188; two runs in quick succession could collide on a socket not yet released. Fixed by binding PORT=0 and reading the assigned port from the server's own startup line — which in turn surfaced a genuine defect in site/serve.mjs, logging the requested port rather than the bound one, so it printed 0. Both fixed. But that flake needed two overlapping runs, and neither observed failure had one.
The leading suspect, stated as a suspicion. lib/branch-hygiene.test.ts is the slowest file at 4,147 ms against a 30-second ceiling on an idle machine, and its cost is a git object walk that scales with refs and repository size. Card 109 already records it timing out once at 6.0 s against vitest's 5 s default "while its own verdict was correct and printed — a red light saying the wrong thing", and today's session created and destroyed six worktrees and many branches. A 7× margin makes this unlikely rather than impossible, and unlikely is where it has to be left: it is a hypothesis, not a finding.
Sixteen consecutive clean runs since (six, then ten in a loop capturing failures by name). The trunk is green.
CAPTURED 2026-09-13, with its name, and the leading suspect was right about the file and wrong about the cause.
It is lib/branch-hygiene.test.ts, and it is a timeout rather than a failing assertion. Observed at 33,943 ms against its 30-second ceiling, reported as "has no undeclared branch ahead of main" failing — the name of whichever test the clock ran out on, not a test with anything wrong with it. Card 109 already described this shape once: "a red light saying the wrong thing". Anyone reading that failure would go and look at branch declarations, which are fine.
It is load-dependent, which is why it never reproduced. Three consecutive measurements of the same file, same tree, minutes apart: 25.1 s alone, 54.1 s, then green in a full run. Nothing changed between them but what else the machine was doing. That variance is the whole phenomenon, and it is why sixteen clean runs proved nothing.
The cost is fixture construction, not the object walk. This card and card 109 both guessed a git walk scaling with refs and repository size. Measured: 31 refs, which is trivial. What the file actually does is build roughly twenty temporary git repositories, each about 1.1–1.4 s of subprocess work, and run them while vitest has 25 other files in flight.
One hypothesis tested and disproved, recorded because it looked convincing. git count-objects -v showed 9,112 loose objects against 172 in-pack — an unpacked repository, which genuinely does slow every git call, and exactly the kind of finding that ends an investigation. git gc packed all 8,544 and the very next run took 54 seconds, worse than before. So the loose objects were real and were not this. Repacking was worth doing on its own account; it was not the fix.
What this means for the fix, and it is not the ceiling. The ceiling has been raised once already, 5 s → 30 s under card 109, and it is being outgrown again while the file's work grows. Raising it a third time chases the cost rather than meeting it. The two real options are reducing the fixture count — most of those twenty repositories differ in one commit and could share a base — or taking this file out of the parallel pool so it is not competing for the machine while being timed on wall-clock. Not done here: it is its own change, the trunk is green, and this card existed to get the diagnosis rather than the repair.
What would settle it, and is the actual work of this card: capture the failing file name when it next happens. A run that reports only a summary line cannot be diagnosed afterwards, and both of today's were lost that way. Either the suite's invocation should always preserve full output, or the harness should keep the last failing run — the cheap version is not piping vitest through grep when the result matters.
A process defect found alongside it, and worth more than the flake. Both failures were pushed anyway, because the commands were chained — npx vitest run | grep … && git push and later … ; git push. The filter's exit status gated the push, not the test's. The trunk was briefly red twice as a result. Chaining anything after a test run hides the thing the run was for; the push has to read the test's own status or be a separate step after reading the output.
Done when: a failing run is captured with its file name, or the suite has gone a month without one and this card is closed as unreproduced with that stated.
151 · [CC] The extracted picker had a provenance record and no reader
Files: _packages/adapters/vendored-app/wiring/brand-provenance.mjs · wiring/out-of-set-hex.mjs · instances/ikon.brand-provenance.json · lib/provenance-wiring.test.ts · lib/provenance-record.ts · lib/check-placement.ts · adapter MANIFEST.md / README.md / INSTALL.md · in aleris/ikon: the four installed files, package.json, CLAUDE.md, README.md, and brand/provenance.json deleted
CLOSED 2026-09-10. Opened and closed the same session, from a question about what comes next after card 149.
What was actually wrong. Card 149 said the picker "becomes the fourth consumer of Brand OS and vendors both, through _packages/adapters/vendored-app/, which exists for exactly this: a provenance record with a sha256, a freshness check against the source's current version, and an expiry check". The record shipped. None of the three checks did. ikon held brand/provenance.json in an ad-hoc shape, no brand-provenance.mjs, no test, no script, and Brand OS had no instance file — so the source repo did not know it had a fourth consumer. ikon/CLAUDE.md stated the invariant in as many words — "every file in them has a sha256 in brand/provenance.json" — with nothing asserting it. Two repos holding one allowlist and nothing comparing them, which is the exact failure the card cited the adapter as the answer to. Measured, not inferred: both files were still byte-identical to canonical, so nothing had drifted yet.
The adapter did not fit, and that is the substantive half. ikon is the first consumer that vendors from the corpus rather than from a cut package. The allowlist is a data-product no cut has ever carried; the icon set is 3,772 SVGs read from disk at render time. The record's only source root was package.source, and a 3,776-entry file list is not a record anyone reads. So adapter v0.3: a top-level corpus { repo, source, commit } with from_root: "corpus" per entry, and vendored_tree[] — a count plus one digest over every file's hash keyed by its path under the tree root, sorted. Root-relative because the point is comparing two trees at two paths in two repositories; sorted explicitly because readdirSync order is filesystem-dependent, and an unsorted digest would differ between a laptop and a Linux build container with every byte identical.
Two silent passes closed, and they had both been there since v0.1.
- An unresolvable
fromwas a silentcontinue. The corpus moves a file, the record keeps naming the old path, the byte comparison quietly stops happening, and the suite stays green. Same defect class as a line-number citation into a governing document or a staledepends_on— and it was sitting inside the apparatus built to catch exactly that. Now a failure that says what it looked for, where, and that nothing is comparing the file against anything. - The entrypoint guard never fired under a symlinked path. Both scripts compared
import.meta.urlto`file://${process.argv[1]}`; node symlink-resolves the first and leaves the second as typed. Under/tmpon macOS, or any symlinked checkout,main()did not run — no output, exit 0. The whole check disabled, reporting success. Found by accident while building the fixture for the first one, which is the only reason it was found at all.
Both are pinned as regressions rather than described: lib/provenance-wiring.test.ts writes the pre-fix checker out of main with git show and asserts it passes on the same record, and prints nothing through the symlink. A fix without a test that fails before it is a claim.
Five failure modes proved in ikon before the green run was believed, each fired and each restored: one byte changed in one of 3,776 SVGs (reported as bytes, count still right), a file added (reported as a count), the token file hand-edited, the allowlist repointed at a corpus path that does not exist, the corpus made unreachable (named as a skip, not a pass). Plus the hex scan's anti-rot half — replacing the recorded colour with a token while leaving its entry fails.
A third defect, found by installing rather than reading. hex_scan.include: ["app"] — the obvious guess — crashed with a raw TypeError from node:path. Entries are { dir, exts }. The shape is now verified before use and the message prints the default. ikon's record omits the field entirely, because the default already covers app/.
One finding for Torfinn, card 152: the hex scan's first run in ikon flagged #003942, a header colour sitting exactly where a petrol-600 would be — a step the scale decided against on 2026-08-05.
And one observation not acted on. baseline/fonts/ and public/fonts/ in this repo hold the same four Museo Sans faces, byte-identical, with nothing comparing them. Not this card's work, and the same shape as everything above.
Verified: Brand OS 440 → 447 tests green in 24 files; ikon 7 → 9 tests in 2 files plus a clean hex scan, wired into its npm test.
152 · [T] A live app uses the petrol step the scale decided against
Files: data-products/tokens/aleris-tokens.css (the petrol ramp, --color-petrol-500 / -700) · in aleris/ikon: app/qlik/qlik.module.css:10, and its known_out_of_set entry in brand-provenance.json
New 2026-09-10, found by the out-of-set hex scan's first run in ikon (card 151).
It was already evaluated once and ruled out, which the first version of this card did not know. data-products/tokens/tokens.test.ts carried this value as a documented exception with the note: "this is the same dark petrol board card 16 evaluated and ruled out as a secondary hover". So the question is not new — the value was considered for the ramp, rejected for that use, and then used anyway in a product. That changes what is being asked: not should we add a step, but does a rejected-for-hover value earn a place as a surface. Found 2026-09-11 while clearing the entry, which was pointing into a file the cutover deleted.
The measurement. --header-dark: #003942 in the Qlik picker's stylesheet. It sits between --color-petrol-500 (#004851) and --color-petrol-700 (#003238) — which is where a petrol-600 would be. The scale deliberately does not have one: petrol-700's own token comment records that card 19 added only the press step, "hover no longer needs a colour step (card 16, the hover/focus merge), so only the press step was needed, not the petrol-600 the design return also proposed".
It was known in the app and had never reached the corpus. The declaration already carried the comment "promote candidate → Brand OS petrol scale" before any check looked at it, which makes this a reporting gap rather than a discovery: someone saw it, wrote it down where only that repo could read it, and there was no route from there to here. The scan is now that route.
Not fixed, and the reason is not caution. Substituting either neighbour changes what the header looks like, which is a design decision. It is recorded in hex_scan.known_out_of_set with this card named, so the check is green while the finding is visible, and the anti-rot half fires if someone swaps the value without removing the entry.
The ruling, and it is a token decision: does --color-petrol-600: #003942 enter the ramp, or does the app move to petrol-500 or petrol-700 and accept the change? A third option worth naming: the value is close enough to petrol-500 that the difference may not survive a look at the two side by side, in which case this is a one-line fix in ikon and no token at all.
Done when: the ramp has the step and ikon uses it, or ikon uses a neighbour — and either way the known_out_of_set entry is gone, which the check enforces.
153 · [T] The plain picker went dark too, and the site still ships the icons it no longer uses
Files: astro.config.mjs (publicDir) · public/icon-picker/ (3,776 files, 16 MB) · in aleris/ikon: app/picker/ and app/qlik/
New 2026-09-11, found by verifying the cutover deploy rather than by reading anything. Two halves, and only the first needs a ruling.
1 · /tools/icon-picker is a 404, not just /tools/icon-picker/qlik. Measured on brand.dev.aleris.ai right after the deploy. Card 145's scoping said "/tools/icon-picker itself (the plain picker) reads no database and its assets already ship in the Astro build" — a true sentence that reads as it survives the cutover, and it does not. Card 149 moved app/tools/icon-picker/**, all 22 files, which is both surfaces. The "nobody is actively using it" confirmation was about the Qlik tool, which is the one card 148 had measured answering 200; nobody asked about the plain picker, and it is now equally dark. It may genuinely not matter — that is Torfinn's to say, and the cutover plan already carries the open question "whether the picker is one app or eventually two surfaces", which this turns from speculative into live.
2 · The site serves 3,776 icon files that no page on it references. Measured: /icon-picker/svgs/regular/house.svg returns 200 and 700 bytes, /icon-picker/metadata/categories-sv.json returns 200, and grep for src=/href="/icon-picker/…" across every built HTML page returns zero. The five files that mention icon-picker mention it in prose — the board, the changelog, BASELINE.md and one constitutional page. So 16 MB ships in every image and deploy for nothing, and it is a second live public copy of the icon set now that ikon holds the vendored one. Card 149 explicitly rejected "fetching the allowlist at build or run time" because it trades a checkable copy for a silent runtime dependency; leaving these URLs live is an open invitation to exactly that.
The repo must keep the files — public/icon-picker/ is the canonical source ikon vendors from, brand-provenance.json hashes the tree against it, and lib/build-has-no-private-registry.test.ts asserts the set is committed rather than a stub (checked: it asserts the repo, not the build output, so excluding them from the build breaks no test). The question is only whether the deployed site should serve them, which is one line in astro.config.mjs.
Depends on half 1: if the plain picker comes back on brand.aleris.ai, the assets stay. If it lives only at ikon, they go.
A third thing, found 2026-09-11 when the Next app was deleted: a check lost its subject and nothing picked it up. data-products/tokens/tokens.test.ts carried app/tools/icon-picker/qlik/qlik.module.css as a known sub-14px-floor file — 23 literal declarations at 11px, 12px and 13px. That file is now in aleris/ikon, and nothing checks the font floor there: the contributed out-of-set-hex.mjs that travelled with the picker reads colours only. So an accessibility finding that had a standing check in this repo has neither a check nor a home. Recorded here rather than dropped; closing it is either a font-floor scan added to the contributed set, or a decision that a dense instrumental tool is exempt — which is the call card 30 asked for and never got.
Done when: the plain picker's fate is ruled, the build either serves the icon set for a reason or stops serving it, and the font floor has a checker or a ruled exemption.
154 · [T] Two Baseline gaps, found because the products that papered over them are being taken apart
Files: data-products/tokens/aleris-tokens.css · data-products/tokens/tokens.test.ts § KNOWN_OUT_OF_SET · site/src/styles/site.css
New 2026-09-11. Both halves are the same shape: a product met a situation Baseline does not cover, invented a value, and the invention was recorded as a known exception rather than as a gap in the system. Deleting the Next app is what made that visible, because the exceptions expired with the file while the gaps did not.
1 · No status tint scale. app/globals.css declared --color-status-success-20, --color-status-warning-20 and --color-status-suggested-20 — #dceee5, #f5ede0, #e5eff0. The exception entry said it plainly and has since 2026-08: "Baseline has no status tint scale; the app invented one. Gap in Baseline, not app error." The app is now gone and the gap is unchanged; what has gone is the only evidence anyone was looking at it. A tinted background behind a status message is not an exotic requirement, and the next product to need one will invent its own three values.
2 · No dark-mode surface scale. Found the same day by repointing the token scan from app/ to site/src — a reach that would otherwise have gone quiet. site/src/styles/site.css declares --surface-page: #002b31 and --border-default: #14606a in dark mode: a page ground darker than petrol-700, and a border between petrol-400 and petrol-700. This one has more weight than a missing tint, because board card 1293 established that respecting a user's dark-mode setting is a legal requirement, the same class of obligation as the WCAG floors rather than a brand preference. The system requires the mode and supplies no tokens for it, so the site publishing the design system is the first product to have improvised around it.
Neither is rewritten to an existing token, deliberately. Substituting petrol-700 for #002b31 changes what the page looks like, and that is a design decision rather than a lint fix. Both are recorded in KNOWN_OUT_OF_SET, whose anti-rot half asserts each value is still present — so the moment someone fixes one without removing its entry, the suite says so.
The ruling is whether these become scales. A status tint ramp and a dark-mode surface set are both additions to aleris-tokens.css, which makes them Torfinn's and a package version. The cheaper alternative worth naming: rule that neither is a gap, and that a product needing a tint or a dark ground picks from the existing ramps — in which case these five values get replaced and the entries removed.
Done when: either the tokens exist and both products use them, or the ramps are ruled sufficient and the five improvised values are gone. Either way KNOWN_OUT_OF_SET empties, which the check enforces.
155 · [T] The site is noindex until release, behind one flag
Files: site/release-state.mjs (the switch) · site/src/layouts/Base.astro · site/src/pages/robots.txt.ts · site/src/pages/build-state.json.ts · site/serve.mjs · lib/noindex.test.ts
New 2026-09-11, Torfinn: "noindex the site until we are ready for release". Done the same session; this card exists to be closed at release, not to be worked.
The switch is INDEXABLE in site/release-state.mjs. Setting it true is the whole change: the meta tag disappears, robots.txt starts advertising the sitemap, the header stops being sent. Verified by doing it — flipped, rebuilt, checked all four surfaces, flipped back. The suite is green in both states and asserts the state the flag implies rather than hard-coding noindex, so it will not go quiet after release.
It is not Disallow: /, and that is the one thing worth reading here. The obvious way to hide a site is a robots.txt Disallow, and it is the most common way a site stays in an index while its owner believes it is hidden: Disallow asks a crawler not to fetch the page, and a crawler that never fetches the page never reads the noindex on it. A URL discovered anywhere else — a link, someone else's sitemap, a mail scanner — stays listed as a bare URL with no description, and cannot be cleared until crawling is allowed again. So the site now invites crawling and answers every request with noindex. Allowing the crawl is what makes the refusal readable.
Four surfaces, because a meta tag only exists in HTML. This site deliberately serves raw Markdown at /baseline/raw/* and /foundation/raw/* and a machine index at /llms.txt — machine readers are a first-class audience here — and none of those has a <head>. X-Robots-Tag from site/serve.mjs covers them, measured on /, /llms.txt and /baseline/raw/BASELINE.md.
One design point found by a check rather than by thinking. The server first read the flag with import { INDEXABLE } from './release-state.mjs', and lib/static-server.test.ts failed instantly: serve.mjs imports nothing but node builtins, because the runtime image holds only site/dist and that file, so a non-builtin import breaks the container at startup, in production. The decision now travels as data — the build stamps build-state.json and the server reads it. Better than the import for a second reason nobody had in mind: the server reports what was built rather than what the source currently says, and those differ the moment someone edits the flag without rebuilding.
It fails closed. A missing, unreadable or malformed stamp means not indexable, proved by deleting the file and watching the header persist. The asymmetry is the argument: hiding a released site is visible the day someone searches for it; leaking an unreleased one into an index is not visible at all, and is the expensive half to undo.
One thing that went wrong and left a comment behind. The stamp was first written as _index-state.json, and Astro excludes _-prefixed files in src/pages from routing — so it emitted nothing, the server fell back to its default, and the output was correct for the wrong reason with no symptom. Caught by looking for the file. Renamed, and the trap is recorded in that endpoint's header.
VERIFIED LIVE 2026-09-13. Deployed and measured on brand.dev.aleris.ai: robots.txt carries the pre-release body with Allow: / and no Sitemap: line, /build-state.json reads {"indexable": false}, the meta tag is in the page head, and X-Robots-Tag: noindex, nofollow comes back on /, /llms.txt, /baseline/raw/BASELINE.md, /favicon.ico and /sitemap.xml — HTML and non-HTML alike, which is the half a meta tag cannot do.
Done when: Torfinn says the site is ready, INDEXABLE flips to true, and a deploy is verified showing no noindex meta, no X-Robots-Tag, and robots.txt naming the sitemap again.
156 · [T] Which token vocabulary is canon, and the borders disagree
Files: data-products/tokens/aleris-tokens.css (364 tokens) · ~/Downloads/Aleris Design System (fixed)/tokens/*.css (135 tokens, 9 files) · sundviktlakemedel/_state.md § Blocked by · cards 64, 121
New 2026-09-13. This card exists because the decision blocking another product's UI rebuild was not on this board at all.
The situation. sundviktlakemedel has frozen v1 and is rebuilding its UI as v2. Its _state.md names the gate three times — "the token vocabulary gating v2", "Which token vocabulary is canon is the gate on Phase 2, and it is Torfinn's" — and gives the reason a port cannot start without it: an undefined custom property fails silently, so a half-reconciled vocabulary produces a UI that looks broken in ways nothing reports. Nothing in this repo carried that question. The board is the list of record for what is open and who holds it; this was open, held by Torfinn, and visible only in the blocked party's own notes. It could not have surfaced in a decision sitting.
The premise it has been waiting on is understated in three ways, measured here 2026-09-13. That state file records "73 design-system tokens against Brand OS's 365, 21 shared names, values agreeing — so it is a naming reconciliation, not an absorption of new concepts". Recomputed from both files:
| Recorded | Measured | |
|---|---|---|
| Export tokens | 73 | 135 |
| Brand OS tokens | 365 | 364 |
| Shared names | 21 | 40 |
| Shared values agreeing | all | 36 of 40 |
The 73 was an undercount and this session reproduced it before catching it — the export is minified, several declarations to a line, and a line-anchored scan sees only the first on each. It returned exactly 73, matching the recorded figure, which is the most persuasive possible way to be wrong. Worth recording as its own small lesson: an independent recomputation that agrees with the number it is checking has not necessarily checked it.
The four that disagree, and two are real. --ease-out and --ease-in-out differ only in whitespace and leading zeros — the same curves. The other two are not:
| Token | Export | Brand OS |
|---|---|---|
--border-default |
#abc7c9 (petrol-200) |
#d7d2cb (gray-100) |
--border-strong |
#004851 (petrol-500) |
#9e9281 (warm grey) |
This is one decision, not two: are Aleris borders brand-coloured or neutral? The export's whole border family is petrol-tinted and shifted one step — --border-subtle is gray-100, --border-default is petrol-200, --border-decor petrol-300, --border-strong petrol-500. Brand OS's borders are warm neutrals. A port that assumed the values agree would silently restyle every border in the application, which is precisely the failure the state file warns about, arriving through the half nobody was watching.
And it sets a trap for card 64. That card asks for --border-subtle as a lighter row separator than gray-100, for a 250-row worklist whose separators and panel edges are forced to one value. The export already has a token by that name and it is gray-100 itself — the same name for a different thing. Adopting the export wholesale would fill card 64's name while leaving its requirement unmet, and nothing would report that, because a token that exists resolves.
Most of the rest genuinely is naming, as the state file believed. 95 export names are absent here; 22 of them are this repo's own primitives under a shorter prefix — --petrol-500 against --color-petrol-500. Of the concept families in the remainder, Brand OS already carries spacing (25 tokens), focus (9), leading (12), weight (21), radius (19), row heights (3), touch targets (2) and the max-width/grid family (5) under its own names.
Two families are genuine gaps, and both are already open as card 121. The export has --gradient-petrol and --gradient-orange; Brand OS has no gradient token at all. It has an --action-* family with hover rings; Brand OS has none. Card 121 is "Controls on a branded ground — five of six variants undefined, and no check can see a gradient", opened from a consumer measuring a live action that shipped invisible on five of seven seeded home screens. So the export is not only a rival naming — it carries the vocabulary card 121 says is missing. Whether it is the right vocabulary is a separate question from whether it fills the hole.
RULED 2026-09-13, all three, and the reconciliation is built and self-checking.
1 · Borders are neutral (warm grey). Canonical stands: --border-default is gray-100 #d7d2cb, --border-strong is #9e9281. The export's petrol-tinted family does not enter at any name — which also clears the trap above, because --border-subtle stays free for what card 64 actually asked for, a separator lighter than gray-100.
2 · Canonical is this repo's scheme, on Torfinn's criterion — coherent and well structured, following good defaults rather than a bespoke invention. The good default is already written down here and did not need inventing: schemas/design-token.md § Layer 2 documents primitives → semantic → component and says "reference semantic tokens in code… never reference primitives directly — typing a hex code is the signal something is wrong." The export publishes bare primitives — --petrol-500, --sand-100, --gray-300 — as its public vocabulary, which invites exactly the direct-primitive reference that document forbids, and drops the category prefix that tells a primitive from a semantic token apart.
3 · The export's vocabulary enters unless it collides — and measured, almost all of it collides. Five tokens enter. --gradient-petrol and --gradient-orange (Brand OS has no gradient token at all, which is card 121's gap), --status-info (canonical has error, warning, confirm, background — no informational state), --measure-text (a 68ch line-length measure, nothing canonical expresses one), and --surface-row-hover.
The collision worth reading first, because it would have failed silently. --text-* carries two different types. Canonically it is a colour family — --text-primary, --text-secondary, --text-inverse. In the export, --text-xs … --text-2xl are font sizes, and canonical font sizes are --font-size-xs … --font-size-2xl, which map one-to-one across all seven. Adopting the export's --text-* alongside ours would make color: var(--text-base) resolve to 1.125rem; a declaration with an invalid value is dropped at computed-value time with no error, so the symptom is text silently inheriting its parent's colour.
A fifth disagreement the shared-name comparison could not see. --duration-shimmer is 1400ms in the export; canonical --skeleton-duration resolves through --duration-moderate to 350ms. The names differ, so a name-keyed comparison was blind to it — a four-fold difference in how fast a loading skeleton pulses. Canonical holds under ruling 3; recorded rather than decided, because 350ms is fast for a shimmer and it belongs with whoever next touches motion.
One thing the two vocabularies agree on completely: the spacing scale. The export's --space-1…9 and canonical's --spacing-3xs…3xl are the same nine values — 4/8/12/16/24/36/48/72/96px — under numeric versus t-shirt names. Nothing is reinterpreted.
The deliverable is data-products/tokens/export-reconciliation.json, every one of the 135 export names mapped or listed as entering, with lib/export-reconciliation.test.ts asserting three properties so it cannot rot into a stale table: every target is a token canonical actually declares, every export name is accounted for exactly once, and nothing listed as entering is already taken. All three proved able to fail — a dangling target, a dropped name and a false enters claim each fire. The coverage half skips and says so when the export is not on the machine, since it lives in ~/Downloads.
NAMED AND LANDED 2026-09-13. Torfinn took the proposals for the gradients and the informational status, and deferred the measure. Three tokens added, one deferred, and one candidate withdrawn on a measurement.
| Export | Canonical | |
|---|---|---|
--gradient-petrol |
--surface-gradient-cold |
added |
--gradient-orange |
--surface-gradient-warm |
added |
--status-info |
--status-info + new --color-info-500 |
added |
--measure-text |
— | deferred |
--surface-row-hover |
--table-row-hover-bg |
already existed |
The gradients are named for the axis, not the pigment. This token file already carries a cold/warm/neutral surface axis — --surface-strong-cold is petrol-500, --surface-strong-warm is orange-500 — and each gradient starts on its own strong value and lightens one step. So they join a family a reader can predict rather than opening a --gradient-* one. --gradient-petrol names the pigment, which is primitive-layer thinking; the semantic layer names a role, and the role is a branded ground. This does not answer card 121 — which controls may sit on one, and how a check measures text over a gradient, are still that card's. It supplies the ground the card was missing.
--status-info needed a primitive, and not the export's. The export's value was #004851, which is petrol-500, which is --text-primary — an informational state rendered in body-text colour does not read as a state. It takes #007bc7, the blue already in this system as --color-goal-no-data, as a separate declaration rather than an alias: that set is constrained "Dashboard only… never patient-facing", and tying a general status to it would give one value two unrelated roles with nothing comparing them. Same hex, two declarations, on the precedent warning-500/sand-500 already set. Measured before adding, not after: 4.51:1 on white, 4.26:1 on sand-50, 3.84:1 on sand-100 — so it clears AA for text on white and on neither sand ground, the same shape as confirm-500, and it is constrained the same way under card 118's rule.
--measure-text is deferred, and that is recorded as a decision rather than a silence. Torfinn: "I cannot say what is right for this without more data about how it impacts our layouts and accessibility, so let's keep what we have until data shows us that we need to change it." The reconciliation file carries it in a deferred block with the condition that reopens it — a product measuring a readability or layout problem it can attribute to line length. Its test counts deferred toward coverage, so a token that is neither mapped, entering nor deferred is one nobody has considered.
--surface-row-hover was withdrawn before it landed. The concept exists at --table-row-hover-bg, in the component layer, because a row hover is table-scoped; the first pass checked --surface-* and not --table-*. The two also disagree on value — canonical is --surface-subtle-warm, an orange tint, against the export's sand-50. The check did not catch this, and the reason is worth keeping: it asserts nothing entering shares a name with canonical, and this collided by concept under a different name. Concept collision is a judgement, not mechanically checkable. The name check is the floor, not the rule.
Also landed: the shimmer. --skeleton-duration was var(--duration-moderate), 350ms, against the export's 1400ms. Torfinn took 1400ms. The alias was the defect rather than the number — the four --duration-* steps are transition levels, annotated as such, and a shimmer is an ambient loop that happens to be measured in the same unit. At 350ms it swept four times inside the same section's own "< 2s: shimmer only" window, which reads as flicker rather than as waiting. It now carries its own literal and a constraint against re-aliasing that scale.
CUT AS v0.13, 2026-09-13. Three mirrored files changed — the token file, its generated JSON, and BASELINE.md — and the cut closed all three lag declarations, which is what a declaration promises. --check reports 30/30 byte-identical to canonical afterwards.
A whole version, and not close: five new token declarations. Rule 6 sends new content to a whole version and a correction to a point release. (Claude-drafted call, one edit in two files to revise.)
RE-VENDOR rather than repin, and gentler than v0.12 in the way that matters: nothing was retired. A retirement turns a consumer's var() into a dangling reference, which is invalid at computed-value time and therefore inherits rather than erroring — no build warning, a colour silently replaced by the parent's. v0.13 adds five declarations and changes one value, so a consumer adopting none of the new tokens sees exactly one difference, the shimmer. v0.12's mandatory pre-grep does not apply to this cut — though it still applies to sundviktlakemedel, which is pinned at v0.11 and takes v0.12's retirement on the way past.
_packages/adapters/vendored-app/REVENDOR-v0.13.md is the hand-off, per consumer, prepared to the gate and left there because all three are on the no-go list. It names the check that matters: a re-vendor is verified not by a green suite but by the provenance record's hashes moving in the same commit as the bytes — a copy without a record update fails, and a record update without the copy fails.
What is left is the re-vendor itself, and it is Torfinn's. sundviktlakemedel is the consumer this cut exists for: its _state.md has named "the token vocabulary gating v2" as a blocker three times, and this is the first cut carrying the vocabulary that answers it.
Superseded — the questions as originally asked:
- Are borders petrol-tinted or neutral? One visual ruling, two token values, every surface in every product. Nothing else here can be settled while it is open, because it decides what
--border-subtleis allowed to mean. - Which naming scheme is canon —
--petrol-500or--color-petrol-500? Mechanical either way, and it decides the direction of a rename that has to happen once, in one place, before v2 starts porting. - Does the export's gradient and
--action-*vocabulary enter Baseline, adapted, or is card 121 answered from scratch? A cut and a version judgement, whichever way.
Not recommended from here. Question 1 is a brand decision about what Aleris looks like, and the corpus has no position to read it off — foundation/colour.md does not rule on borders. Questions 2 and 3 are Claude's to execute the moment 1 is settled.
Done when: the border question is ruled and both tokens carry the ruled values, the naming scheme is chosen and a reconciliation is written that sundviktlakemedel can port against, and this card's answer is recorded somewhere that repo can point at rather than in a state file only it reads.