61 ready now6 blocked2 superseded89 done2 with no row
12 card numbers do not line up
A card's status lives in a table near the top of the file and its content lives further down. These do not line up. Named rather than corrected — the board is the list of record, and an error in it is usually the finding.
Listed with nothing to read: 31, 44, 45, 46, 47, 48, 49, 50, 139 — a row in a table, with no card body.
Written up with no status: 117, 159 — a card body, in no table.
In two sections at once: 145 is listed under Ready now and Done.
Where things stand
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.
logo-minimumfails intermittently, and one of those failures refused a pushDone when a failing run's output is captured and says which of the two it is; the fix matches; and the check is run enough times under load to show the fix took — an intermittent failure cannot be closed by one green run, which is the trap this card exists to avoid falling into.
Detail
Files:
lib/logo-minimum.test.ts§ "is very nearly square, which is what a favicon slot needs" ·bin/hooks/pre-push·data-products/tokens/constants.json§geometry.symbol_aspectNew 2026-09-20. Three failures observed in one day's work on this repo, none reproducible on demand.
What was seen.
When What happened What it cost Session open A full run reported 5 files and 8 tests failing; an immediate re-run reported 1, which was a real failure in token-values-are-canonicalFour minutes deciding which failures were real Mid-session The pre-push gate refused a push — "the gate failed at: npm test" — and npm testrun by hand seconds later passed 492/492A false refusal. The push went through unchanged on a retry After the change-note sweep logo-minimum§ "is very nearly square" failed in a full run and passed in isolation, with no logo file modifiedFive minutes ruling out a real cause Committing this card The gate refused the push seconds after a green 495/495 by hand; the retry passed 495/495 and pushed A second false refusal It does not reproduce on demand, and it keeps happening anyway. Eight deliberate attempts after the third instance — five isolated runs of the file, three full-suite runs — all green. Then the fourth instance arrived unprompted, while committing this card. Four in one day, two of them false push refusals. That rules out a stale working copy and points at contention: the gate runs the suite in a hook, and this repo is known to have more than one session open at a time.
The fourth instance was not captured either, and that is now a process problem rather than bad luck. The push output was piped to
tail -2, so the failure detail scrolled past before anyone could read it. When the gate next refuses, runnpm test 2>&1 | tee /tmp/gate.logbefore retrying — the retry destroys the only evidence, and two chances have now been lost that way.What the assertion does, and why it is a plausible place for this to land. The test rasterises
aleris-logo.svgwith@resvg/resvg-js— native code — measures the symbol's bounding box, and asserts the aspect withtoBeCloseTo(symbol_aspect, 2). Two decimal places is ±0.005 on a measured value of 0.966, so a single pixel of difference at the render size is enough to break it. A measurement asserted that tightly against a rasteriser is the shape of thing that wobbles under contention, and this repo is known to run more than one session at a time.What is not known, and it is the thing that would settle it. Nobody captured whether the failure was a wrong number or a timeout. Those have opposite fixes — a wrong number means the tolerance is too tight for the renderer, a timeout means the test needs longer under load — and the failure output was not kept. Until one is captured, any fix is a guess.
Why this is worth a card rather than a shrug. An intermittent check is a check that can go dark: the next person to see it red has been taught by this history to re-run rather than investigate. Worse, one of the three instances was a false refusal by the pre-push gate, and a gate that refuses for no reason is how
--no-verifybecomes a habit. That is the cost, and it is not paid by whoever sees the failure.Done when: a failing run's output is captured and says which of the two it is; the fix matches; and the check is run enough times under load to show the fix took — an intermittent failure cannot be closed by one green run, which is the trap this card exists to avoid falling into.
The session-end backup hook snapshotted the main checkout, not the worktree the session was in — DONE 2026-09-02
Detail
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.shhard-codedREPO="/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 didexit 0above the retention block, so pruning was skipped on precisely the ordinary days.What it does now. Reads
cwdfrom the JSON the hook is handed on stdin, snapshots that working tree, and writes tobackup/auto-YYYYMMDD-<slug>per worktree so two sessions ending the same day cannot overwrite each other. A scope guard built on--git-common-dirkeeps 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.
.gitignoreexcluded.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 keepssettings.local.jsonandworktrees/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.
Get the icon picker serving
Done when
ikonserves on Forge with assignments visible to a signed-in user, the tokens match the current package, and it is onmain.Waits on Torfinn: pull
FORGE.mdagain in the console forikonandbrand, then DeployDetail
Files: the
ikonrepo —app/qlik/page.tsx,brand-provenance.json,FORGE.md, branchextract-icon-picker· Brand OS_state.md(the milestone)New 2026-09-28.
ikonhas been dark since the extraction on 2026-09-10, blocked onASK-IKON-001. Forge answered it on 2026-09-28 (ikon/forge/ANSWER-IKON-001.md, andANSWER-BRAND-001here, which closes both): schema apply runs at every deploy since 2026-09-11, sodb/001_icon_assignments.sqlis applied by pressing Deploy. Both repos'FORGE.mdcopies predate that and still say "at onboarding".Found while landing card 167's picker half, and in front of a deploy:
/qlikis prerendered at build, andgetSessionreturns null whenSESSION_SECRETis absent at build time, so the deployed page would never show assignments. It needs to render per request. This was true onmainbefore card 167 touched it.- The tokens are pinned at
agent-baselinev0.12 while the package is at v0.22; the fix is a re-vendor that sits unmerged onextract-icon-picker(three commits, eleven days). The provenance check carries anacknowledged_behindentry, review date 2026-10-10, so it is not red meanwhile. - Fixed on the way: three paths still pointed at
/tools/icon-picker/…from before the extraction, so/redirected to a 404 and saving and both downloads returned 404.
Torfinn's: pull
FORGE.mdagain from the console forikonandbrand(Forge's own recommendation), and press Deploy onikononce the two code items are on itsmain. Claude Code's: the per-request render, the re-vendor, and removing theacknowledged_behindentry.Done when:
ikonserves on Forge with assignments visible to a signed-in user, the tokens match the current package, and it is onmain.An expressive typeface for internal material
Done when Torfinn has decided whether an expressive face is wanted; if it is, it is chosen and written into
constitutional/typography-is-museo-sans.mdwith its scope; if it is not, the floor decision says so; and it is onmain.Detail
Files:
constitutional/typography-is-museo-sans.md§ 2 ·workspace/plans/internal-ai-material-discovery-2026-09-18.md·workspace/decisions/internal-material-floor-2026-09-18.mdNew 2026-09-26, from ruling 2 of sitting 2. The discovery found that five of five colleagues making internal material with AI bought a typeface, because Museo Sans at 500 and 700 cannot carry a celebration ("thank you for a wonderful year"). The floor decision of 2026-09-18 left script faces free internally; the binding rule allows only Museo Sans or Arial. Torfinn ruled that the binding rule stands: the passed answer says Museo Sans at a large size, bold 700, or light 300 from 40px. Whether Aleris wants an expressive face at all, and for what, is a design question in its own right, and the floor decision's wording now disagrees with the binding rule until it is answered.
Done when: Torfinn has decided whether an expressive face is wanted; if it is, it is chosen and written into
constitutional/typography-is-museo-sans.mdwith its scope; if it is not, the floor decision says so; and it is onmain.The website switch-over, and what stands between the build and it
Done when Torfinn has flipped
NEW_SITE, the route-manifest diff is accepted, the link audit and review checks pass on the switched site, and the old pages are gone.Waits on Torfinn: Deploy on the
brandForge appDetail
Files:
workspace/plans/website-content-and-switch-over-2026-09-25.md(the plan: sitting 0's eight rulings, eight sittings, the build items) ·site/release-state.mjs(NEW_SITE) ·workspace/briefs/prd-brand-os-website-2026-09-24.md·lib/route-manifest.jsonNew 2026-09-25. Phases 1–4 of the iteration-1 build are on
mainbehindNEW_SITE: the layout and components, the content checks, the nine concept pages, the photo hero, the Create routes, answers, Export and the machine index (changelog, twenty-eighth entry). Phase 5, feedback, is card 165. The switch-over —/becomes the new front page, the old rendered pages retire, the route manifest changes as a reviewed diff — is Torfinn's call, and these stand in front of it:Sittings 0 and 1 held 2026-09-25. The plan's rulings are in the PRD as § 10: the switch happens at a threshold (Story, Colour, Typography, Voice, Imagery, Digital products, Logo; routes presentations, patient information, web pages, digital products, working with AI), not when everything is passed. Colour, its five answers, the hero translation and the alt text are passed; the front-page photo is the 2500px original. Dark mode and the About pages no longer stand in front of the switch.
Concepts done 2026-09-28. Sittings 1b to 5 passed every concept page — the seven the switch needs and Icons and Reader state — with 26 answers published; card 131 is closed.
Torfinn's:
- Routes done 2026-09-28: sittings 6 and 7 passed six route guides, all five the switch needs; print and signage stays
none. - Content done 2026-09-28: sitting 8 passed the front page, the Create page and the gap register. What remains before the flip is Claude Code's B5 and B6; then the flip, the manifest diff and Deploy are Torfinn's. Export descriptions and gap-notice guidance lines are utility text and do not come to him.
- B5 built 2026-09-28 and B6 rehearsed 2026-09-29. The machinery is on branch
claude/new-site-walkthrough-51083b; the flip itself is the tip ofworktree-agent-a47991a96093332d0(229d1e5), both pushed. The flipped image, built on Linux in Docker, matched the local build on all 48 routes and passed the post-deploy check 57/0 once that check stopped reading a retired page; suite 817 passed in switched state, link audit clean at 4,076 paths (first recorded as 4,199, from a build folder holding an earlier build's leftovers; the clean rebuild gives 4,076), both redirects and every survivor answer, noindex on every page. The route guides rendered their sections unstyled (headings at body size, lists as plain lines), and are styled 2026-09-29: each route file is parsed into the concept pages' numbered rules, do-and-don't cards and checklist (lib/route-page.ts), guarded bylib/route-page.test.ts, which three deliberate breaks of the parser each turn red. That closes the "first route file rendered end to end" item below. Suite 856 unswitched, 855 switched. - Flip accepted 2026-09-29 (Torfinn), and on
main.NEW_SITEistrueand the route-manifest diff is accepted. What remains for Done: Deploy on thebrandForge app (Torfinn's), then the link audit and the post-deploy check against the live host.
Claude Code's, before or at the switch:
- The plan's build items B1–B7: route and answer checks before the first route is drafted, the front and Export copy files wired in, the review report per sitting, the switch as one flip with the one redirect, and the rehearsal.
- Add the new pages to the sitemap (left out while the site is pre-release).
- Token the two motion distances BASELINE states without tokens (the card's 2px rise, the arrow's 4px shift).
- Visuals for the do-and-don't cards; medium tabs on "How to apply it"; a first route file rendered end to end.
- Check the scroll spy and the copy buttons in a foreground browser tab — the preview pane runs as a background tab and delivers neither scroll events nor clipboard access.
Done when: Torfinn has flipped
NEW_SITE, the route-manifest diff is accepted, the link audit and review checks pass on the switched site, and the old pages are gone.- Routes done 2026-09-28: sittings 6 and 7 passed six route guides, all five the switch needs; print and signage stays
Six pattern files are written in Swedish, and the corpus source language is English
Done when all seven pattern files have an English source, the known-mismatch list holds no pattern, and Torfinn has passed the translations.
Detail
Files:
patterns/button-label.md·chat-bubble.md·chat-response-simple.md·confirmation.md·empty-state.md·error-message.md·lib/route-manifest.test.ts§KNOWN_BASELINE_LANG_MISMATCHNew 2026-09-24.
constitutional/language-policy.md§ 1 makes English the source language, with local languages only for local examples. Torfinn noticed on 2026-09-24 that the hero pattern had been amended in Swedish ("All corpus source material should be in English, so the translation runs the other way");patterns/hero.mdwas translated to an English source the same day, keeping its Swedish examples. The other six are Swedish throughout, and the suite already lists all of them as known language mismatches.The work: translate each to an English source, keeping Swedish example copy as local examples with an English gloss, the way
hero.mdnow does; remove each from the known-mismatch list as it lands; cut the package, since all six ship inagent-baseline. The translations are Claude's and need Torfinn's pass.Done when: all seven pattern files have an English source, the known-mismatch list holds no pattern, and Torfinn has passed the translations.
A method for keeping URLs once the site has users
Done when there is a written method, and a check, for what happens to a URL that leaves the manifest once the site is released: a redirect table the server reads, with a test that every removed route either redirects or is declared gone.
Detail
Files:
site/serve.mjs·lib/route-manifest.json·workspace/briefs/prd-brand-os-website-2026-09-24.md§ 2.4New 2026-09-24, prep work. Torfinn ruled that the website redesign may change today's content URLs without redirects, because the site is pre-release ("the urls that we change right now doesn't need to survive unless the software/site itself depends on them internally"), and that keeping URLs should be solved as a method for when the site is live with users.
What exists. The site is static files served by
site/serve.mjs, which answers GET and HEAD only and has no redirect table.lib/route-manifest.jsonalready records every published route, and a change to it is a reviewed diff, so it is the natural input: a route that leaves the manifest could be required to name where it went. An earlier ruling let the/sv/URLs die with no redirect map (lib/sitemap-and-robots.test.ts).Done when: there is a written method, and a check, for what happens to a URL that leaves the manifest once the site is released: a redirect table the server reads, with a test that every removed route either redirects or is declared gone.
Search for the Brand OS website
Done when Torfinn has decided the scope (titles and headings, or full text) and it is built into the site with a keyboard-reachable control, working without a client framework.
Waits on Torfinn: scope
Detail
Files:
workspace/briefs/prd-brand-os-website-2026-09-24.md§ 2.5New 2026-09-24. The PRD's first draft put a search button in the header, searching page titles, section headings and answer questions. Torfinn moved it to the backlog the same day ("Let's put that on the backlog for now"), so iteration 1 ships without it and the header carries no search control. The site has no search today — no index, no library.
Done when: Torfinn has decided the scope (titles and headings, or full text) and it is built into the site with a keyboard-reachable control, working without a client framework.
Generate the Aleris Design System from Brand OS, so it stops being a second author
Done when the artifact's README and tokens are generated from Brand OS, the drift check runs in the cut, and the four items above read as Brand OS states them.
Waits on Torfinn's pass of the README's hand-written part
Detail
Files:
workspace/briefs/brief-design-system-artifact-generated-2026-09-24.md· the organisation's default Design System artifact (https://claude.ai/code/artifact/e7069248-84f6-4dcf-ba2b-e60cbc3032d4)New 2026-09-24. The rule changes from the website design (changelog, twenty-fifth entry) landed in Brand OS, and the design system that Claude sessions build with still states the old versions. Everything outside the repo that the rule-changes brief listed as "correct there after this lands" is this card:
- the old ground rule ("never white"), the sticky limit and the side-accent rule, now ruled differently or more fully in
BASELINE.md; - the button bevel, which now lives in the tokens (
--button-*-shadow,--button-press-shadow); tokens.jsonsetting all four motion durations to 0ms against the canonical 100, 200, 350 and 500ms — its reduced-motion values carried over as the defaults;- its README's motion note, "Only transform and opacity animate", which hard rule 7 no longer says.
The brief fixes these by generating the design system's README and tokens from Brand OS, with a drift check wired into the package cut, rather than hand-correcting them once.
Blocked on Torfinn for one part only: the README's hand-written part (brief § 3.1, step 4.2) needs his pass before the first publish. Until then the brief publishes the generated parts only, so the rest can run in its own session now. Publishing to the artifact is a Cowork step (brief § 4).
Done when: the artifact's README and tokens are generated from Brand OS, the drift check runs in the cut, and the four items above read as Brand OS states them.
- the old ground rule ("never white"), the sticky limit and the side-accent rule, now ruled differently or more fully in
Tables need their own design guidelines
Done when tables have their own guidelines, including how rows are separated, placed where § Tables points, and Torfinn has passed them.
Detail
Files:
baseline/BASELINE.md§ Tables ·principles/grids-tables-dataviz.mdNew 2026-09-24, from Torfinn's ruling on the website design, iteration 1 (
workspace/decisions/website-design-iteration-1-2026-09-24.md§ 4b item 4): "Tables need to have their own design guidelines. The no-dividers rule applies to lists across Aleris design."What exists. § Tables in
BASELINE.mdholds six bullets on density, headers, alignment and actions. It says nothing on row separation, which is now the open question: the no-dividers rule landed for lists (§ Lists) and states that tables are out of its scope, so a table's rows are neither required nor forbidden to carry rules.Not written in the change that opened this card, on the brief's instruction (
workspace/briefs/brief-website-iteration-1-rule-changes-2026-09-24.md§ D.3).Done when: tables have their own guidelines, including how rows are separated, placed where § Tables points, and Torfinn has passed them.
Colleagues are making Aleris material with AI, and five artefacts say what is actually wrong
Done when the instruction is replaced in ChatGPT and the floor survives its own falsification test — that the four read as permission rather than prohibition — and strand 3 has reported against the new text. Blocked on Torfinn for: his pass on the Swedish, the intranet location for the header files, and names for strands 1 and 2. (Two items dropped from this line on 2026-09-22 because the card's own body records them as closed: the footer became a header, and all three
logo-identity.mdcollisions closed on 2026-09-18.)Detail
Files:
workspace/plans/internal-ai-material-discovery-2026-09-18.md(the discovery) ·workspace/briefs/den-nara-experten-instruktion-2026-09-22.md(the replacement instruction) ·workspace/sources/den-nara-experten-core-instruction-2026-09-22.md(what it replaces) ·workspace/tools/palette-distance.py(the harness) ·principles/imagery.md·principles/logo-identity.md· card 157 (the page that started this)New 2026-09-18. Card 157 produced a page of instructions; Torfinn brought five real artefacts made by colleagues and asked to explore the problem before building more. The five changed the diagnosis.
The frame he set, because it rules out whole branches. Enablement rather than compliance. Group communications owns the brand and "ownership in the Aleris context cannot really be enforced since it's like herding cats" — so the success measure is adoption, not conformance, and anything slower than a blank prompt will not be used. Creative room internally is worth something on purpose; the expressive face is his own separate design challenge and is out of this scope.
What the five measurably show. Five of five use a script face the corpus has no position on whatsoever — the most consistent signal in the sample is not a breach of anything written. Three of five carry a self-assembled unit lockup ("Aleris ASIH Haninge", "Aleris | Uppsala Närakut") against a settled ratified rule and its named misuse list; the 2025 merger lockups were removed as a resolved edge case, so there is nothing sanctioned to point people at, which is likely why units are building their own. Three of five carry generated imagery implying a real Aleris place, patient or outcome, against an accepted rule. Two of five invent colours that are close to the twelve
--color-chart-*tokens already in the system, scoped to charts where no poster-maker would look.Three failures wearing one coat, and only the first is fixable with words. Rules that exist and never reached anyone; needs the system has no answer for (the typeface, print layout); and tool limits no instruction can cross — an image model does not honour a hex value, which if true makes a paste block of values theatre for anyone generating the artefact as an image. That last one is assumed and is what strand 3 exists to settle.
CORRECTED, and the correction matters. A first reading of the marks claimed none had been hallucinated. Wrong: the crop of artefact 3 had missed its logo. There are two production routes in the sample and they fail differently — four artefacts composed in a layout tool, where the logo is right because a person pasted it and what drifts is judgement, and one generated end to end, where the mark, the letterforms and the diacritics are all painted approximations ("hālsa", a macron for the umlaut). Words reach the first route and cannot reach the second. The diacritic is the cheap detector for telling them apart.
THE ROOT CAUSE IS A ROUTING BUG IN AN ASSISTANT THAT ALREADY EXISTS. Colleagues use Den nära experten, the custom GPT Torfinn built on Brand Core 2025. Its activation logic reads "Visuellt material (PDF, PPT, bild) → aktiverar Modul 1 + 2" — the visual branch fires on uploaded material. Someone typing "gör en affisch" is text input, gets modules 1 and 3, and Modul 2 never loads. The design module is unreachable at the moment something is being designed. That explains the artefacts exactly: the Swedish is good because the text modules fired and are well written; the visuals drift because the module governing them was never in the room. The system works as designed and the gap is a missing branch.
It also explains the colour measurement. Modul 2 gives colour as adjectives — sand = bas, petrol = huvudfärg, orange = accent — with no values anywhere in the file. A model asked to produce from adjectives invents the hexes, which is the 5-to-12 ΔE near-miss band across all five. Now a hypothesis with a cheap test rather than a curiosity.
Two structural problems behind it.
core_instruction.mdwas not in the exported folder — the file that always loads first lives in the GPT's instructions box, so the most load-bearing file is the only one nobody can review or version. And the assistant is built on Brand Core 2025 rather than Brand OS: two sources of truth, one a year-old snapshot in Word and PDF, with nothing that notices when the other moves. That is the stale-copy problem card 157 amended the constitution for on the same day.RULED 2026-09-18, Torfinn — the floor is four, recorded in
workspace/decisions/internal-material-floor-2026-09-18.mdwith the test that selects it and what would falsify it: the logo and fixed text are placed never drawn; no generated image claiming a real place, patient or outcome; the sender is Aleris; sentence case. Everything else explicitly free, stated first. Also ruled: the twelve--color-chart-*tokens are released as an internal warm palette — they are almost exactly the range people were inventing.The intervention moved, and the first version of the plan was wrong about it. Not a page on the site: three edits to the assistant — a production branch in the routing, a production half for Modul 2 carrying values instead of adjectives, and a rule about what a model may not draw. Already adopted, sits at the moment of failure, updatable by Torfinn alone, which is the only thing in view that meets the adoption test.
A finding left alone rather than fixed.
site/src/lib/site-nav.tssays the four About pages are "drafted and held atproposed". They are drafted nowhere — not in the corpus, not inworkspace/drafts/. The nav renders them as planned links on that basis.RULED the same day — the supply rule 3 owed: a header. A sand band across the head of the page, logo top left, unit name top right, on A4 and 16:9, upright or sideways. It answers the need the homemade lockups were meeting — the unit gets to say who is speaking — while the mark stands alone and unattached, so "the sender is always Aleris" holds in full. The band height is not a design choice: the safe zone is 50% of the logo's height on all sides, so a band that is the safe zone is exactly twice the logo height. The unit name shares the wordmark's baseline, at 0.7445 of the logo's height from its top — measured from the logo file, cross-checked against the ratified 0.4933 cap ratio to 0.4%, and then verified by canvas measurement rather than by eye (1px of difference in 157). Its size is stated against the wordmark's cap height so it holds at any scale. Set in Arial, ruled 2026-09-18 — "most users won't have Museo Sans installed and none of them using Figma" — which applies the 2025 rule that Arial is the face for PowerPoint and for material made outside the communication department, rather than making an exception.
And a finding that decides how the header ships. An SVG with live text loses the unit name silently in macOS Quick Look: band and logo draw, the
<text>element is dropped, no error. Browsers render it correctly, so it looks right wherever it is checked and wrong wherever it is used. With nobody in Figma and most without Museo Sans, the asset cannot assume a design tool or a font — the candidates are a PowerPoint template with an editable text box, PNG strips per unit, or SVGs with the name outlined. Generator and check live inworkspace/tools/header/, so whatever ships is regenerated rather than redrawn.Three collisions with
principles/logo-identity.mdwere raised and all three closed the same day, each by leaning on the existing rule rather than writing a new one. It was first ruled as a footer, which put the mark bottom right against his ratified "placement is always in the top left corner" — moved to a header, and the exception disappears instead of being recorded. A first draft said the symbol is never used as "ornament", which read as narrowing his standing permission for the symbol as a brand element — Torfinn: keep the rules, drop the term, "better to just be clear about what is and is not allowed in plain text". Andsafe zonekeeps its existing meaning and stays attached to the logo; the band is a header. The design never breached the misuse list, only the word did. No change tologo-identity.mdis needed by any of this, which is the outcome worth noticing.The colour figure from this card's first version is withdrawn. v1 of the harness measured photographs. Rebuilt, checked against synthetic artefacts at exact and at invented values, it says something narrower and more useful: grounds are close in all five (1.8–5.8 ΔE), petrol is a consistent near-miss where it appears (5.9, 6.3), and the accent is absent from all five — no flat orange anywhere, at any distance. Told "orange som accent" with no value, people did not pick a wrong orange; they went to script faces and hearts instead.
RULED 2026-09-18 on delivery: the intranet, with the assistant as signpost, and one template with an editable unit name rather than one per unit — "managing this file whenever units come or go is futile." That put the baseline back into a PowerPoint text box, which is positioned by its box and not its baseline, so it is calibrated rather than derived:
workspace/tools/header/calibrate.pyrenders the file, measures the name's baseline against the wordmark's, and the constant in the generator is that measurement. It reports zero difference; the first guess at Arial's ascent was 0.4mm out. The calibration is against this machine's renderer, and Windows is an assumption until someone opens it there.And the showcase and the experiment are the same job (Torfinn): the accepted versions of the observed artefacts double as strand 3's condition B, with the five artefacts already serving as condition A at no cost. One condition decides whether it is a test at all — the accepted version must be made through the assistant the way a colleague would, not by hand in a design tool, or it only proves a good poster is possible. And a named unit's work should not be shown as the "wrong" half without asking them first; strand 2 is contacting them anyway, and an invitation to co-author the pair turns a slight into a credit.
Strand 3 is written and ready to run —
workspace/plans/strand-3-protocol-2026-09-18.md. Twelve generations, about an hour, both prompt conditions written, scoring sheet included, instrument proved. It settles which project this is: if values move the output the fix is three edits to an assistant Torfinn owns; if they do not move it on the image route, written guidance cannot reach those artefacts at all and the effort belongs in templates.THE THREE EDITS BECAME ONE REPLACEMENT, because the module layer was never a mechanism. 2026-09-22, drafted in
workspace/briefs/den-nara-experten-instruktion-2026-09-22.md. A custom GPT retrieves its knowledge files by semantic similarity rather than loading them on command, and.mdis among the least reliable formats — frequently routed to Code Interpreter instead of the retrieval pipeline. So "Modul 2 aktiveras" never guaranteed anything, and the routing bug this card found had a second cause underneath it: edit 1 fixed when the module is called for and could not fix whether it arrives. Only the Instructions box loads every time. The floor, the nine values, the image rules and both output contracts now live there, measured at 7,892 characters against the box's 8,000 limit after four compression passes — the first draft came in at 8,837 and could not have been pasted at all.The file nobody could review is now in the repo. Torfinn pasted the Instructions box on 2026-09-22 and it is captured verbatim at
workspace/sources/den-nara-experten-core-instruction-2026-09-22.md, closing this card's second structural problem and open item 4 of the discovery. It is 1,518 characters, and it confirms the diagnosis in its own words: "Beroende på vad användaren laddar upp eller frågar om: … Visuellt material (PDF, PPT, bild) → aktiverar Modul 1 + 2". The replacement is 5.2 times longer than what it supersedes, which is the riskstrand-3-protocol-2026-09-18.mdalready names — a long instruction crowding out the brief — and the reason to run strand 3 as an acceptance test rather than assume a more complete text is a better one.A third gap, found while writing and absent from the discovery: nothing governed the image prompt. Image generation is on. When the assistant generates a picture it writes a prompt for a second model, and no brand rule travels to that model — only the words typed into the prompt. That is the exact moment the mark gets hallucinated, and it was ungoverned in the current instruction, in all four modules and in the 2026-09-19 edits. The replacement rules it directly: no text at all in generated images, which collapses hard rule 1 from a fourth prohibition into a consequence of one design decision and is the only reliable defence against the diacritic failure; the top fifth reserved as a positive property rather than by negation, because a prompt that names the logo can summon one; prompts written in English; and a look-at-the-output check, which is the only closed loop in the design. Note for the falsification test: the durable argument against drawing the logo is not capability. Image models render text well in 2026, so "it cannot do text" expires and takes the rule with it. What survives is that the logo is a file rather than a description, and that a mark which looks right is harder to catch than one that looks wrong.
The review half was rewritten too, not only the production half. Modul 2's criteria pass the artefacts this card exists to prevent — a hallucinated mark with generous clear space passes "logotyp och friyta", and a well-lit generated patient satisfies "visa äkta människor och naturligt ljus". That phrase is gone; as worded it reads as an instruction to produce the failure. The floor is now checked before the expression, and the friyta criterion carries an explicit note that it cannot vouch for the mark.
AN EXTERNAL DEADLINE ARRIVED, and it is not ours to move. OpenAI announced on 2026-09-11 that custom GPTs retire. For Enterprise workspaces, new GPT creation ends in late October and custom GPTs stop running on 2026-12-11. Migration turns a GPT's instructions into a skill inside a plugin; knowledge files carry over, custom actions and the selected model do not. Three consequences, all of which the draft already takes: what is in the Instructions box is what survives, so the guardrails belong there on migration grounds as well as on retrieval grounds; nothing may be built on actions or URL fetching, which closes the discovery's option of pointing the assistant at the raw token endpoint and settles the values as a dated copy with provenance; and the draft opens with a trigger paragraph that is harmless preamble today and becomes the plugin skill's description field in December. Eleven weeks. One rewrite now, then a copy.
Strand 3 is updated to v0.2 and its kit is built, 2026-09-22. Three conditions rather than two — today's Modul 2 text, the short values block, and the new instruction as it will run — because a bad result under 7,892 characters could be the values or the length, and only the middle term separates them. Eighteen generations, about ninety minutes.
workspace/plans/strand-3-run-kit-2026-09-22.mdhas the paste blocks, the naming, the sheet and the commands. Claude cannot run it: it needs image generation in ChatGPT and a layout tool. Under condition C, route 1 should produce no finished poster at all, and one appearing anyway is the run's most important single result, because it would mean the instruction never reached the image step.Re-proving the harness found it cannot see a white page. It responds correctly to distance — 0.0 ΔE at exact token values, 1.8 weighted at plausible invented ones, 6.5 with two roles banded off. But a poster with a pure white ground and otherwise perfect brand colours scores 1.8 weighted and is banded on, because sand-50 is
#faf8f6and sits 2.6 ΔE from white, and the page-ground role scores against whichever sand is closest. The most-cited AI tell in the corpus reads as correct. Moved to the by-eye sheet, which had not carried it. Also found: the five original artefacts are stored nowhere — they were images in a conversation, so §2's baseline figures cannot be reproduced and the showcase has no "before". Same defect class as the core instruction. Worth saving intoworkspace/sources/before strand 2 contacts anyone.Done when: the instruction is replaced in ChatGPT and the floor survives its own falsification test — that the four read as permission rather than prohibition — and strand 3 has reported against the new text. Blocked on Torfinn for: his pass on the Swedish, the intranet location for the header files, and names for strands 1 and 2. (Two items dropped from this line on 2026-09-22 because the card's own body records them as closed: the footer became a header, and all three
logo-identity.mdcollisions closed on 2026-09-18.)
The page for colleagues making things with AI, and the folder it cannot publish from
Done when that wording has Torfinn's pass. The page itself is done — verified rendered at 1,243 words with its paste block intact, and it needs a deploy to reach the live host.
Waits on —
Detail
Files:
how-to/using-aleris-in-ai-tools.md(new,draft, labelawaiting-pass) ·lib/content.ts§CONTENT_DIRS·site/src/lib/site-nav.ts§SECTIONS·workspace/plans/website-v3-plan.mdNew 2026-09-18, Torfinn: "it's very important that we get something out into the hands of colleagues who produce stuff with AI that is in the uncanny valley territory of branding". Offered the choice between releasing the site as it stands and a narrow page for that audience, he chose the narrow page.
What the page is. One page for a colleague who is not building software and does not want to read this site: a paste block for an AI tool, the nine colours with their values and their prohibitions, the type rules, a checklist of what gives an AI away, and an honest list of what the page does not give them. Every value is quoted from
data-products/tokens/aleris-tokens.cssand all nine of the URLs it cites were fetched and returned 200 on 2026-09-18.It carries one thing the corpus had nowhere obvious to put. Three characters an AI tool reaches for constantly —
→,←,✓, plus≤and≥— are absent from both licensed Museo Sans weights, so they fall back silently to another face mid-sentence. That was measured 2026-09-05 and lives inBASELINE.md§ Typography, which this audience will never open.Why it does not publish, and what the ruling is.
how-to/is not inCONTENT_DIRS, and the comment above that constant says so deliberately: "widening the publishing rule is Torfinn's decision, not a move's side effect". So the page sits in the tier the map assigns it and has no route. Its one sibling reaches a route only becauseMOVED_PATHSremembers where it used to live; a new file has no such history, and inventing one would be a fake legacy path. The three real options are: addhow-totoCONTENT_DIRS(a second widening of the 2026-08-31 rule, with alib/route-manifest.jsondiff as the designed gate), put the file inbaseline/(publishes today, wrong tier, and files it under a section label meaning "for builders"), or leave it unpublished and circulate it as text.The wiring change is inert until the words are passed. Every file in
how-to/isdraftor carries nostatus, and the gate needsstatus: acceptedplus a servedlang. Adding the folder toCONTENT_DIRStoday would therefore publish nothing at all — which makes the two decisions separable, and the publishing one safe to take first.Nav placement is the second half and it is also his. A route under
how-to/matches no section prefix, sosectionForRoutewould fall through to Foundation. The page is for everyone rather than for builders or for readers of the brand, which is a shelf the six current sections do not have.The page broke a constitutional rule, and the suite said so.
constitutional/tokens-are-canonical.md§ 1 — no document outside the token file restates a token's value — isacceptedand in force, andlib/token-values-are-canonical.test.tsfailed on this file. That was the check working: the page holds nine values because its reader is pasting into a tool that cannot fetch a stylesheet, which is the one case the rule was not written against. It could not be quieted by adding the file to that check'sKNOWNregister, whose own words are "no new file may join".RULED 2026-09-18, Torfinn: "it's a checked view of the source". § 1 now carries one exemption — a page written for a reader who cannot fetch the token file may carry values provided every value on it is asserted against the token file by a check that fails when a token moves. The exemption is the check, not the intent, and a page claiming it without one is an ordinary restatement. The rule page is at v0.2; the amendment is Claude-drafted from his ruling and the wording still awaits his pass.
The exemption is machine-conditioned rather than declared.
lib/pasted-pages.tsis the single list both checks read:lib/pasted-values-are-live.test.tsasserts every hex on each listed page is a value the token file declares today, andlib/token-values-are-canonical.test.tsrefuses the exemption to any file not on it — and fails if the live-values check is ever deleted, which is the way an exemption quietly becomes a hole.Why the exempt page is now better covered than a
KNOWNone. The original check counts only hexes that still match a declared value, so on the day a token moves, every document printing the old value has its count go down and the suite reads greener. The rule's own failure mode looks like an improvement to the check written for it. The new check is the inverse and fails on that same commit; it was proved against a deliberately dead value before being trusted. Suite green at 492 tests.The page's role changed on 2026-09-18, same day, by card 158's diagnosis. It is no longer the thing colleagues are sent to — that is the assistant they already use. It is the human-readable source the assistant's production half is written from, which is a better fit for a page that has no URL yet anyway.
One line rewritten on Torfinn's criticism. "Weights 500 and 700 only. Never 400. Never 300 below 40px." — three statements accreted around three separate incidents, reading as enforcement history rather than as instruction, and "strangely saying only 500 and 700 and then going on to say never 400 or 300". Now one: two weights, 500 for body and 700 for headings, with the 400 and 300 cases explained in the prose instead of stacked in the block. The general defect is worth keeping: rules written to catch a known breach do not convert into instruction without being rewritten, and this page lifted several of them unrewritten.
RULED 2026-09-20 — both open questions, and neither is open any more. "Yes, how-to should publish", and "let's create a new nav option for how-tos so that we have a place to put new how-tos as we keep expanding the material."
how-tojoinedCONTENT_DIRS— the second widening of the 2026-08-31 rule, and the decision the comment above that constant had been reserving for him in those words — and aHow-tosection sits in the header between the design system and the rules, carriedpending. It published nothing on the day it landed, verified by readinglib/route-manifest.jsonrather than by trusting the test's silence: every file in the folder isdraftor carries no status, so the wiring and the words stayed separable. Recorded inworkspace/decisions/how-to-publishes-2026-09-20.md.And
CLAUDE.md's publishing sentence was wrong about a different folder. It omittedprinciples/, which joinedCONTENT_DIRSon 2026-09-07, while the folder list forty lines below said it publishes. The two disagreed for thirteen days and were only wrong together to someone holding both. Corrected with thehow-toline.PASSED 2026-09-20 and the page is accepted.
status: accepted,label: shareable, publishing at/how-to/using-aleris-in-ai-tools. The route set went 27 → 28 and the manifest is regenerated. The gate earned its keep on the way through: the auto-route stripsaleris-from file names, so this would have published at/how-to/using-in-ai-tools— "using in AI tools" — which is the second instance of the trap that caughtcolour-is-the-aleris-paletteon 2026-09-07.ROUTE_OVERRIDESnow carries both and says that two makes it a pattern.Rewritten before it was accepted, on two of Torfinn's corrections. It still aimed at interfaces — radii, a 14px floor, 44px touch targets, buttons throughout — and those moved to the software section where they are true. Its typography led with a licence note about websites when the fact that matters is that almost nobody has Museo Sans, so Arial is what reaches the page; the 2025 guidelines already make Arial the face for PowerPoint and for anything made outside the communication department, so this applies a rule rather than conceding one. Six places came out in the language pass, including a line claiming the page's own plainness and, with it, a factual error: "three characters" followed by five.
Still open on this card: the amendment's wording in
constitutional/tokens-are-canonical.md, which publishes and is Claude-drafted.Done when: that wording has Torfinn's pass. The page itself is done — verified rendered at 1,243 words with its paste block intact, and it needs a deploy to reach the live host.
Which token vocabulary is canon, and the borders disagree
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
sundviktlakemedelcan port against, and this card's answer is recorded somewhere that repo can point at rather than in a state file only it reads.Waits on Torfinn: the re-vendor, each consumer being on the no-go list
Detail
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, 121New 2026-09-13. This card exists because the decision blocking another product's UI rebuild was not on this board at all.
The situation.
sundviktlakemedelhas frozen v1 and is rebuilding its UI as v2. Its_state.mdnames 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-outand--ease-in-outdiffer 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-subtleis gray-100,--border-defaultis petrol-200,--border-decorpetrol-300,--border-strongpetrol-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-subtleas 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-500against--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-petroland--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-defaultis gray-100#d7d2cb,--border-strongis#9e9281. The export's petrol-tinted family does not enter at any name — which also clears the trap above, because--border-subtlestays 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-petroland--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-2xlare 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 makecolor: var(--text-base)resolve to1.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-shimmeris 1400ms in the export; canonical--skeleton-durationresolves through--duration-moderateto 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…9and canonical's--spacing-3xs…3xlare 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, withlib/export-reconciliation.test.tsasserting 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-coldadded --gradient-orange--surface-gradient-warmadded --status-info--status-info+ new--color-info-500added --measure-text— deferred --surface-row-hover--table-row-hover-bgalready existed The gradients are named for the axis, not the pigment. This token file already carries a cold/warm/neutral surface axis —
--surface-strong-coldis petrol-500,--surface-strong-warmis 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-petrolnames 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-infoneeded 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 precedentwarning-500/sand-500already 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 asconfirm-500, and it is constrained the same way under card 118's rule.--measure-textis 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 adeferredblock 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-hoverwas 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-durationwasvar(--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.--checkreports 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 tosundviktlakemedel, which is pinned at v0.11 and takes v0.12's retirement on the way past._packages/adapters/vendored-app/REVENDOR-v0.13.mdis 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.
sundviktlakemedelis the consumer this cut exists for: its_state.mdhas 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.mddoes 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
sundviktlakemedelcan port against, and this card's answer is recorded somewhere that repo can point at rather than in a state file only it reads.
- 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
The site is noindex until release, behind one flag
Done when Torfinn says the site is ready,
INDEXABLEflips totrue, and a deploy is verified showing nonoindexmeta, noX-Robots-Tag, androbots.txtnaming the sitemap again.Waits on Torfinn saying it is ready
Detail
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.tsNew 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
INDEXABLEinsite/release-state.mjs. Setting ittrueis the whole change: the meta tag disappears,robots.txtstarts 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-codingnoindex, 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.txtDisallow, and it is the most common way a site stays in an index while its owner believes it is hidden:Disallowasks a crawler not to fetch the page, and a crawler that never fetches the page never reads thenoindexon 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 withnoindex. 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-Tagfromsite/serve.mjscovers them, measured on/,/llms.txtand/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', andlib/static-server.test.tsfailed instantly:serve.mjsimports nothing but node builtins, because the runtime image holds onlysite/distand that file, so a non-builtin import breaks the container at startup, in production. The decision now travels as data — the build stampsbuild-state.jsonand 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 insrc/pagesfrom 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.txtcarries the pre-release body withAllow: /and noSitemap:line,/build-state.jsonreads{"indexable": false}, the meta tag is in the page head, andX-Robots-Tag: noindex, nofollowcomes back on/,/llms.txt,/baseline/raw/BASELINE.md,/favicon.icoand/sitemap.xml— HTML and non-HTML alike, which is the half a meta tag cannot do.Done when: Torfinn says the site is ready,
INDEXABLEflips totrue, and a deploy is verified showing nonoindexmeta, noX-Robots-Tag, androbots.txtnaming the sitemap again.
Two Baseline gaps, found because the products that papered over them are being taken apart
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_SETempties, which the check enforces.Detail
Files:
data-products/tokens/aleris-tokens.css·data-products/tokens/tokens.test.ts§KNOWN_OUT_OF_SET·site/src/styles/site.cssNew 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.cssdeclared--color-status-success-20,--color-status-warning-20and--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/tosite/src— a reach that would otherwise have gone quiet.site/src/styles/site.cssdeclares--surface-page: #002b31and--border-default: #14606ain 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
#002b31changes what the page looks like, and that is a design decision rather than a lint fix. Both are recorded inKNOWN_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_SETempties, which the check enforces.
The plain picker went dark too, and the site still ships the icons it no longer uses
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.
Detail
Files:
astro.config.mjs(publicDir) ·public/icon-picker/(3,776 files, 16 MB) · inaleris/ikon:app/picker/andapp/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-pickeris a 404, not just/tools/icon-picker/qlik. Measured onbrand.dev.aleris.airight after the deploy. Card 145's scoping said "/tools/icon-pickeritself (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 movedapp/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.svgreturns 200 and 700 bytes,/icon-picker/metadata/categories-sv.jsonreturns 200, andgrepforsrc=/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.mdand 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 thatikonholds 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 sourceikonvendors from,brand-provenance.jsonhashes the tree against it, andlib/build-has-no-private-registry.test.tsasserts 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 inastro.config.mjs.Depends on half 1: if the plain picker comes back on
brand.aleris.ai, the assets stay. If it lives only atikon, 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.tscarriedapp/tools/icon-picker/qlik/qlik.module.cssas a known sub-14px-floor file — 23 literal declarations at 11px, 12px and 13px. That file is now inaleris/ikon, and nothing checks the font floor there: the contributedout-of-set-hex.mjsthat 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.
A live app uses the petrol step the scale decided against
Done when the ramp has the step and
ikonuses it, orikonuses a neighbour — and either way theknown_out_of_setentry is gone, which the check enforces.Detail
Files:
data-products/tokens/aleris-tokens.css(the petrol ramp,--color-petrol-500/-700) · inaleris/ikon:app/qlik/qlik.module.css:10, and itsknown_out_of_setentry inbrand-provenance.jsonNew 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.tscarried 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: #003942in the Qlik picker's stylesheet. It sits between--color-petrol-500(#004851) and--color-petrol-700(#003238) — which is where apetrol-600would 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_setwith 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: #003942enter 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 inikonand no token at all.Done when: the ramp has the step and
ikonuses it, orikonuses a neighbour — and either way theknown_out_of_setentry is gone, which the check enforces.
An unreproduced intermittent in the suite — recorded rather than declared fixed
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.
Detail
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 passedtwice, 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 andnpm installruns 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 agrepthat 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.tsbound ports 4187 and 4188; two runs in quick succession could collide on a socket not yet released. Fixed by bindingPORT=0and reading the assigned port from the server's own startup line — which in turn surfaced a genuine defect insite/serve.mjs, logging the requested port rather than the bound one, so it printed0. 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.tsis 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 -vshowed 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 gcpacked 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
vitestthroughgrepwhen 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 pushand 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.
Extract the icon-picker to its own Forge app, and Brand OS becomes Supabase-free
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 componentsin this repo returns nothing, and it is onmain.Waits on Torfinn: the schema through the broker, then the OIDC credentials and the domain
Detail
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 onbrand.dev.aleris.aitoday, 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: nolib/content, nolib/baseline-*, nolib/foundation-*.lib/supabase-server.tsis 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.tsand 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
brandschema, andworkspace/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/ikonat56169d6.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.tsover rawpgwith forge's claim bridge, followed fromFORGE.mdrather than adapted. Auth reuses forge's ownlib/oidc.mjs; only the wrapper is rewritten, because the scaffold wrapped it for a plainnode:httpserver 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.sqlgates on the request claim,ENABLE+FORCE, single-tenant oncontroller: 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 iscontroller?).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:
/healthreturns 503 naming what is missing while reportingicons: 3772;/qlikrenders the catalogue and degrades to no assignments exactly as it did before;/loginreturns 503 naming the five variables it needs rather than a 500. Runs asnode, 456 MB, nonode_modulesin 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'snodeuser got a 500 on them while an SVG from the same tree served fine. Local-only — git records100644and a Forge clone gets0644— 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_IDandFORGE_OIDC_CLIENT_SECRETplusSESSION_SECRET, set the domain, deploy.aleris/brandkeeps 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.
ikonis Swedish andCONSTITUTION.mdrule 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 componentsin this repo returns nothing, and it is onmain.
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)
Done when the
Dockerfileservessite/diststatically on$PORTwith a real/health, a Forge deploy goes live and serves the current corpus,FORGE-MIGRATION.mdis regenerated or its two errors corrected in place with a note, and it is onmain.Waits on 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/URLDetail
Files:
FORGE.md,FORGE-MIGRATION.md(Forge-generated, repo root) ·Dockerfile·site/andastro.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/brandfrom GitHub at2a1110d, Mohan addedFORGE.mdandFORGE-MIGRATION.md, and the day's ruling pass merged across ata91c7fd. Trunk iscode.aleris.ai/aleris/brand; GitHub is thegithubremote 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/*andbaseline/raw/*, and the 3,776 icon assets — so the machine-readable surface the README advertises is covered.site/srcreferences Supabase, Resend andnotify-commentnowhere.A static site needs none of what
FORGE-MIGRATION.mdspends four of its five steps on. Struck: step 2 (apply a schema through the broker), step 3 (thepg+withClaimsseam, 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 thebrandschema only. A glob wide enough to catch the archive applies another product's schema tobrand_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.jsonis 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
Dockerfileservessite/diststatically on$PORTwith a real/health, a Forge deploy goes live and serves the current corpus,FORGE-MIGRATION.mdis regenerated or its two errors corrected in place with a note, and it is onmain.
- Bind
Card 124's three Claude halves — the Tailwind sentence, the comment-density rule, and the cookbook entry
Done when all three are written, the cookbook entry names its three instances and its detection, and it is on
main.Detail
Files:
how-to/install-the-tokens.md·baseline/BASELINE.md§ Serving this file ·workspace/status/cookbook.md· readsworkspace/evaluations/card-124-sundvikt-answers-reviewed-2026-09-10.mdNew 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 aBASELINE.mdreference row, and does not exist as a package member. That silence produced two dead dependencies in a real consumer:tailwindcssand@tailwindcss/viteat ^4.3.3 are installed insundviktlakemedeland the plugin runs on every build, with no@import "tailwindcss", no@themeblock 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 insundviktlakemedel.§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, andoriginmoved from GitHub to Forge. And a grep acrossapp lib components site/srcreported the tree clean of a deleted import whilehooks/useAuth.tsheld one, becausehooks/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 ownFORGE-MIGRATION.mdscan is a sixth instance from outside this repo, reporting zero Supabase call sites because it does not readsupabase/.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.
The
depends_onsweep card 136 authorised — 67 files, andDEPENDENCY_ALIASESretires with themDone when the 17 live corpus files declare real paths, the 50 deliberately-left files are declared with the reason,
DEPENDENCY_ALIASESis empty or gone, a planted stale identifier fails a check, and it is onmain.Detail
Files:
lib/content.ts(DEPENDENCY_ALIASES, 7 entries) ·lib/baseline-content.ts(FOUNDATION_LINKS) · 67 files declaring a stalefoundation/…identifier ·documentation/site-map.md§ Dependency model (the ruling)New 2026-09-10, from card 136's ruling: a
depends_onidentifier is a path. The Foundation essays moved toprinciples/on 2026-09-07 and the identifiers other files declare for them did not;lib/content.tshas 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, 31workspace/, 0 infoundation/en-draft/. By identifier:constant-contextual45,emotional-modes29,colour20,typography12,imagery11,iconography3,logo-identity0.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 carryingstatus: 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 inworkspace/, 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_onshould fail rather than silently alias.lib/in-repo-references.test.tsis 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_ALIASESis empty or gone, a planted stale identifier fails a check, and it is onmain.
An
acceptedconstitutional page states a count that is wrong by fourteenDone when the count is current or the sentence is reworded to state a condition rather than a census, and it is on
main.Detail
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
acceptedand 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.tsasserts that a cited path resolves, not that a stated count is current.Not corrected here.
acceptedpages 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.
Hard rule 3 is ruled per section and stated three different ways across nine places — ratify the wording, and define what a section is
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.Waits on Card 23 — the adjacency invariant is the candidate definition, and per-section is unenforceable without one
Detail
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,acceptedv1) ·baseline/BASELINE.mdhard 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.mdNew 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.mdopens 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
acceptedv1, 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. Andprinciples/colour.mdisacceptedv1, 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.
The voice essay — two documents, 256 lines and 145, that were never merged
Done when one voice essay exists, the constitutional variant question is settled, and
foundation/en-draft/is empty and gone.Detail
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 liveconstitutional/one (115) ·workspace/authoring/voice-examples.mdNew 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.Found 2026-09-26 while drafting the website's Voice page, for the merge to settle. (1) Direct or inviting:
foundation/voice.mdgives "You need to fast" as the good, direct example, whilebaseline/voice/3-den-nara-experten-digital-rost-v2.mdsays interfaces invite and rejects "Du måste…". (2) Who the voice is for: the foundation page names patients, employees and partners; the constitutional rule adds next of kin, public servants and investors; the Swedish guides address the patient only. (3) Staff tools: no published source covers the voice of staff tools; the website's answer on it was dropped as a gap. The test wording and the fasting example in § For AI systems went to Torfinn in the same sitting.The typography essay — same shape, and the smallest of the three
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.
Detail
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.mdNew 2026-09-06. Same defect as card 133 and the least material:
foundation/typography.mdis 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 infoundation/typography.mdtoday (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.mdisprinciples/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'sSupersedes:line and every citation now readprinciples/typography.md.94 pointers to nowhere in what publishes and ships, and no check reads an in-repo path
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.Detail
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 wasworkspace/, which is working memory rather than a rule surface.One dissolved directory causes most of it.
planning/no longer exists. Its contents went toworkspace/,documentation/andworkspace/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 isprinciples/voice.md,principles/colour.mdandprinciples/typography.md, cited fromconstitutional/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 inBASELINE.md(4),MANIFEST.md(2),governance/aleris-design-governance.md(2),patterns/chat-response-simple.md(1) andfoundation/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 inen-draft/principles/,en-draft/constitutional/anden-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 § Sectionreferences 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.tsalready 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.mdand_packages/agent-baseline/BASELINE.mdare the same content in two locations.tokens/aleris-tokens.cssin it is correct read from the package root and wrong read frombaseline/. So a naive resolver reports a real pointer as dead, and a resolver that tries every root reports a dead one as fine. This isconstitutional/a-rule-is-applicable-where-it-is-read.mdarriving 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 aconstitutional/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.mdis 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.mdandplanning/versioning-rules.md→documentation/;foundation/en-draft/{voice,colour,typography}.md→ the flippedfoundation/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.mdwhatprinciples/voice.mdshould 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? Doesen-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.tsandscripts/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 infoundation/en-draft/(yours — sweep or delete), 13 in the package (card 119), 8 inaleris-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.review/has never been pointed at an Aleris consumerDone when both targets are declared,
node review/run.mjs --self-testpasses, both report with landing routes recorded, and it is onmain.Waits on 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
gccused. 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 answeredDetail
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 reviewedgcc— a repo Torfinn does not own — "without a byte changing in it", and the Claude Design kits. It has never been run againstsundviktlakemedelorpatientguide, 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
gccused. 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-testpasses, both report with landing routes recorded, and it is onmain.Voice findings — wanted or not, by what route, at what evidence bar
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.Detail
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.mdNew 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.The serving rule forbids subsetting by its letter, and the flip condition has no card
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.Detail
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.mdstep 1c ·workspace/evaluations/sundvikt-component-library-candidacy-2026-08-28.mdNew 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.mdstep 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.mdrecords 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.
Seven findings from the Sund vikt component library still open — three pattern pages, a selected state, a compact button
Done when the three pattern pages exist at
proposedor better, findings 6 and 4 have a ruling,the four mechanical items are landed(done 2026-09-08), and it is onmain.Detail
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.mdNew 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'sRow.tsxand 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-pointernote saying it is a floor with no pointer padding beside it — done 2026-09-05; asetup.mdsentence 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; andlogo/constants.jsonread bylib/logo-minimum.test.tsso 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 filesetup.mdbecame), 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.jsonships the geometry, the sizing rules and the derivation inputs, each with usage, constraint and provenance;lib/logo-minimum.test.tsreads 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, withlib/icon-render-contract.test.tsasserting 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-codesviewBox="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
proposedor better, findings 6 and 4 have a ruling,the four mechanical items are landed(done 2026-09-08), and it is onmain.
Third parties on an Aleris surface, and onboarding is not a mode
Done when
BASELINE.mdno longer lists onboarding as a mode, governance carries a third-party section, and both are onmain.Detail
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.mdagainst 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/orprinciples/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.mdno longer lists onboarding as a mode, governance carries a third-party section, and both are onmain.
Controls on a branded ground — five of six variants undefined, and no check can see a gradient
Done when
BASELINE.mdstates which controls may sit on a branded ground, the tokens match the statement, a check can measure text over a gradient, and it is onmain.Waits on Claude's measurement first: white ghost text against the petrol gradient's light stop, so the ruling carries a number rather than a preference
Detail
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 readsbackground-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-coldand--surface-gradient-warmlanded, 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-coldnow 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#467f87and 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. The450step 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.tsis 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 owntokens/colors.cssdeclares--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,
outlineandghostsit at 1.00:1 (not there),secondaryandcompletefill at 1.00:1 against the ground (found, unreadable as a control), and onlyinversehas tokens. A live action shipped invisible on five of seven seeded home screens.A position already exists outside Baseline. The Claude Design system's
ButtondefinesonDarkfor 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 inreview/— sample the stops or rasterise and take the worst pixel behind each glyph — because any check walking computedbackgroundColorreports 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.mdstates which controls may sit on a branded ground, the tokens match the statement, a check can measure text over a gradient, and it is onmain.
Baseline has layout numbers and no layout behaviour — the column ladder for both surfaces is undecided
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 inpatterns/;the reflow check runs(done);the specimens exist(done);the svl measure finding has been sent back(done); and it is onmain.Detail
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.cssandsrc/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.
sundviktlakemedelis the one real responsive story in the portfolio — a one-column measure that grows viaclamp()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 Sanschat each type size so a measure token can be honest (svl found 10px at 18px; the Claude Design system ships68chunmeasured); and a reflow check inreview/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.mjsexists, discriminates in self-test, and ran against the Claude Design kits: 6 of 15 route-viewport pairs fail, every offender anowrapflex row or a fixed-column grid — the two shapes the 2026-08-12 button entry already names. Museo Sanschmeasured from the licensed woff2: exactly 0.5em at both weights (9px at 18px), and real Swedish body averages 0.81 of ach, so achmeasure overstates by a quarter —68chholds 84 characters, not 68.tokens.test.tsnow asserts every width media query undersite/andapp/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.cssstates1ch = 10pxand 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 asspecimen.htmlin 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, notch), 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 wasdisplay: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-fiton 14rem tiles, no breakpoints, chosen knowing four tiles land on five columns from 1280 up. Landed:--grid-column-communicativeand--grid-tile-min-instrumentalinaleris-tokens.csswith 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.tsholds 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 insundviktlakemedel/_incoming/.Handed to TAKS 2026-09-05:
~/TAKS/1-projects/brandguide/_filedrop/brief-card-120-layout-prose-and-patterns-2026-09-05.mdcarries the three open pieces below with the settled decisions marked do-not-reopen, the evidence pointers, and the return path (a brief back intoworkspace/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 inpatterns/;the reflow check runs(done);the specimens exist(done);the svl measure finding has been sent back(done); and it is onmain.
The token file moved and the package mirror did not — first value drift since v0.6.3
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 ownbrand-provenance.mjsrun clean, an undeclared-lag check exists or its absence is recorded with a reason, and it is onmain.Waits on 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 bylib/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.mdcarries the per-consumer instructions, the grep that has to run before any copy, and the failure mode (a danglingvar()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 danglingvar()on patient-facing printed documents. It will not — that file declares the token itself and isverbatim: falsein 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-mutedalias is declared once and used nowhere. Also honest about a gap in the hand-off:sundviktlakemedelhas not been surveyed for the retired token. Stated in the note rather than glossed. Andaleris-vibe-codingis 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.Detail
Files:
_packages/agent-baseline/tokens/aleris-tokens.cssandbaseline-tokens.json·_packages/cut-agent-baseline.mjs·_packages/README.md§ version table · consumerbrand-provenance.jsonrecordsNew 2026-09-04.
--text-accentchanged 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,sundviktlakemedelandaleris-assistantre-vendor rather than repin.Not synced in the change that caused it, and that is the rule rather than an oversight.
_packages/README.mdrule 1: "Packages are generated, never edited. A hand-edited package is drift by definition." AndMANIFEST.mdon 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 asbranch-hygiene.test.tsfailing on a branch ahead ofmainand undeclared, which is the pattern to copy rather than invent.A blocking prerequisite, added 2026-09-04 with card 115.
--text-tertiarywas retired from canonical.patientguideholds 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 danglingvar(), 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-secondarybefore re-vendoring, not after. Not done from Brand OS:patientguideis 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 ownbrand-provenance.mjsrun clean, an undeclared-lag check exists or its absence is recorded with a reason, and it is onmain.
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 namesfoundation/…for seven moved essays andbaseline/setup.md, and itslogo/README.mdrow says "rules fromfoundation/logo-identity.md" — all now underprinciples/orhow-to/; (3) the governance copy citesbaseline/reference/aleris-grids-tables-dataviz.md, repointed in canonical toprinciples/grids-tables-dataviz.md; (4)lib/in-repo-references.test.tspinsMANIFEST.mdat 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 --checklists 16 files that would change, nine of them drift since v0.11.Nothing fails when the deployed build and
maindisagreeDone when a check exists that fails when the live host serves content the current
maindoes not, it is proven to fail against the frozen build and pass after the redeploy, where it runs is recorded, and it is onmain.Detail
Files: a new check, probably
lib/deploy-freshness.test.tsor areview/check ·review/README.mdif it lands thereNew 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.tsfails when what publishes changes;data-products/tokens/tokens.test.tsfails when a ratio moves;lib/consumer-contract.test.tsfails when a promised path stops existing;lib/branch-hygiene.test.tsfails 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 inlib/.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 withnpm run site:freshness. It sits besidesite/link-audit.mjsrather than inreview/: 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-contentserved bytes differ from the build'schecked, byte-exact raw-contracta raw endpoint 404s or changedchecked, byte-exact index-stalellms.txtdiffers from the generated onechecked, byte-exact missing-routea built route 404s on the hostchecked empty-route200 with no text-bearing contentchecked fonts-gonea woff2 or the stylesheet 404schecked fonts-cors/fonts/*stops filtering by Originchecked, allowed and denied host-downunreachable or erroringchecked extra-routesomething published the build did not makenot checked — needs a crawl; nothing enumerates what is served. This is how foundation/en-draft/became sixteen live URLscontent-wrongwhether the served prose is correctnot 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.txtare 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. 18stale-content, 21missing-route, 2raw-contract, 1index-stale. Four modes came back clean and that is worth as much:fonts-corsfilters 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
maindoes not, it is proven to fail against the frozen build and pass after the redeploy, where it runs is recorded, and it is onmain.
- It cannot run in an ordinary
Three token values restated where the token should have been named — and one is a ratified exception the constitutional page does not carve out
Done when Torfinn has ruled on item 3, items 1 and 2 are corrected in canonical and in the
agent-baselinemirrors, the narrow check exists with its blind spot named in its own header, and it is onmain.Detail
Owner: T for the ruling, CC for the rest · Opened: 2026-08-31 · Found by: measuring, after
app/manifest.tsneeded a literal hex and could not have oneconstitutional/tokens-are-canonical.mdsays 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-baselinemirrors 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 nowRestates
#4f866eand#2e8540beside their token names. It is a direct breach of a specific instruction rather than only of the general rule:baseline/BASELINE.md:45ends 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 nowThe 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
#d9663dand#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.tshad 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 intotokens-are-canonical.mdnaming this exception, or move the four values into something generated thatBASELINE.mdpoints at rather than lists.A fourth thing, found in passing
BASELINE.md:27specifies 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:41writes--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 inworkspace/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-baselinemirrors, the narrow check exists with its blind spot named in its own header, and it is onmain.
(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:mainholds 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 fromorigin/mainrather than from the working copy.)Q1 — the corpus has no rule for digital identity, and that is what blocks shipping an icon set
Done when
foundation/logo-identity.mdeither 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 onmain.Detail
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.mdsays it plainly — "This check has no rule to cite" — and gives the reason: every rule inlogo/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 infoundation/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.mdrule 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.mdis Swedish,status: draft, and the only Foundation page carrying nolangkey at all. It is also the ninth file underfoundation/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.mdeither 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 onmain.
The two coined heading patterns — wording passed, acceptance open
Done when Torfinn has read both definitions and either accepts them or replaces the wording, and it is on
main.Detail
Owner: T · Opened: 2026-08-31 · Wording pass done 2026-08-31 at Torfinn's instruction
formrubrik(form instead of content,hard) andetikettrubrik(label heading,soft) sit infilters/ai-tells-and-filters.mdand 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.
dygdesignaleringcarried "honest by being clear, not by saying it is honest" andanglicismdriftcarried "Kodväxling är inte djup, det är drift" — both antithesis siblings, on the pages that rule kiasterhard, 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 filecounter_example, and both source entries inTAKS/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 inworkspace/status/cookbook.md§22. Theplain-englishskill 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.- 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.
Does WCAG 2.4.6 enter the accessibility constitutional node as a fourth non-negotiable
Done when the node either carries 2.4.6 or records why it does not, and it is on
main.Detail
Owner: T · Opened: 2026-08-31
baseline/BASELINE.mdcommits to WCAG 2.1 AA minimum andconstitutional/accessibility-is-foundational.mdnames 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.106 title-case headings ship to consumers against a decided sentence-case rule
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.Detail
Owner: T · Opened: 2026-08-31 · Found by:
lib/heading-tells.test.tson its first runSentence 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.jsonand 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_NOUNSinlib/heading-tells.tsalready 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.Gröna korset's five-state safety scale — ratified clinical exception, or standing breach
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.Detail
Files: none yet · reads
_packages/adapters/vendored-app/PROFILE-gcc.md§7 andreview/reports/gcc-ux-review.htmlNew 2026-08-28, from profiling
gccas 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.
OQ-12 — framework-agnostic component specs — open since 2026-03-21
Done when OQ-12 either closes against card 99's outcome or is restated to say what is still open, its
last_verifiedmoves, and it is onmain.Detail
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.mdon 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 inreview/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_verifiedmoves, and it is onmain.The 2026-05-07 no-framework component decision was reversed in practice with no record
Done when either the synthesis document records that the component-framework decision was superseded and by what, or
aleris-assistantrecords why it diverges, and it is onmain.Detail
Files:
documentation/architecture-v2-synthesis.md§ Resolved 2026-05-07 ·workspace/status/changelog.md§ 2026-05-07New 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-assistantships React 19 + react-dom + Vite + Tailwind 4 +@storybook/react-vite+@testing-library/react. No.mdin 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.mdit 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-assistantrecords why it diverges, and it is onmain.
Two token consumers are provably stale or wrong
Done when both consumers either carry a provenance record or are recorded as deliberately unmanaged with a reason, the dangling path in
aleris-svar-mainis corrected, and it is onmain.Detail
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 adapterNew 2026-08-28, found while surveying the portfolio for card 99. Independent of every other card here.
aleris-units-explorercarriessrc/tokens/aleris-tokens.cssat 33,409 bytes against canonical's 50,487 — an older, smaller version with a different hash, imported fromsrc/index.css. It has no provenance record, so nothing in that repo can tell it is behind.aleris-svar-mainvendors 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 todata-products/tokens/on 2026-08-10. Its siblingaleris-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-mainis corrected, and it is onmain.
The starter-kit Storybook — scope, substrate and the four mechanisms
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.mdnames the fourth delivery surface, and it is onmain.Detail
Files: none yet · reads
workspace/evaluations/sundvikt-component-library-candidacy-2026-08-28.md· touchesdocumentation/site-map.md§ Delivery surfaces once decidedNew 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— "leavesrc/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.htmlalready has. A fourth delivery surface, alongside the threedocumentation/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 throughbrand-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, threedata-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 ofBASELINE.md's component rules anywhere.Substrate: HTML and CSS, not React. It is the only choice that reaches every Aleris app —
aleris-apt,aleris-eventandbryning-appare vanilla HTML,aleris-presentationis 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:35is 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 ofarchitecture-v2-synthesis.mdnames. 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.mdnames the fourth delivery surface, and it is onmain.
Card 62 re-read — eight missing patterns is one missing component layer
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.Detail
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.htmlandbaseline/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.
"Chrome" is a retired word with 54 uses in what publishes and ships
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
RETIREDrow asserts it — withwholeWord: true, since "chrome" appears inside "Chromebook" and CSS identifiers.Detail
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.htmland 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 asedcannot close this.A second thing this card has to settle before it can be asserted.
lib/retired-terms.test.ts's third assertion requires everyRETIREDrow to name arecordpath that exists in this repo. The chrome retirement is recorded in~/TAKS/2-areas/collaboration/and inDev/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
RETIREDrow asserts it — withwholeWord: true, since "chrome" appears inside "Chromebook" and CSS identifiers.
Print has no home in Brand OS — scope what may be lifted from
patientguide's document surfaceDone 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/.Detail
Files:
workspace/incoming/brief-print-surface-scope-2026-08-14.md(the brief) ·principles/design-tokens.md· would land inprinciples/+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/anddata-products/, in.md,.cssand.html: zero occurrences of@page. Zero page dimensions in mm. One@media print— four lines inbaseline/index.htmlhiding 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. Meanwhileprinciples/design-tokens.mdnames 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/.
Prose blocks D and E — the colour-alone rule, deferred by route 3
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.Detail
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 isacceptedbut does not publish, becauseconstitutional/is not inCONTENT_DIRS. Porting the rule would put a dead link on an accepted, published page. So the rule stays as it stands untilconstitutional/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'sCONTENT_DIRSquestion — 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
#C14444and success green#4F866Esit 76.87 apart in normal vision and 14.55 apart under protanopia — computed twice independently, and the check lives atscripts/delta-e-check.pywith 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.
Decision 1 withdrawn — what F6 gains instead, and it is not a list
Done when F6's position is stated as decided rather than repeatedly re-proposed, and
dependency-reciprocity.mdhas a decided status and home, and it is onmain.Detail
Files:
documentation/dependency-reciprocity.md(the 2026-08-24 section, now onmain) ·foundation/constant-contextual.md(F6, unchanged and to stay unchanged) ·workspace/decisions/decision-record-context-factors-and-emotional-modes-2026-08-25.md§ 1New 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.mdisstatus: proposedand sits indocumentation/, 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 statusis 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 thanmain— 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 PROPOSEDin F6's en-draft, still card 54Enumerate 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 thatcontext-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 statesThe 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: planningis 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 adraftpage, 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.mdhas a decided status and home, and it is onmain.
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.mdanddependency-reciprocity.mdwere 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 loggives the direction: written 2026-08-24 independency-reciprocity.md, restated 2026-08-25 incontext-architecture.mdunder 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 isacceptedand 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 toaccepted. That is the one thing left on this card.Also produced.
workspace/status/observations.md— adoab.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 isTAKS/doab.md2026-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.
- The original 2026-07-28 half proposes a dependency graph. Genuinely still
Moments have no home in the layer taxonomy
Done when there is a decided home for moment-level material, or a recorded decision that patterns absorb it, and it is on
main.Detail
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.
Record the one service property that survives A/B/C's retirement
Done when the property is recorded somewhere a page can depend on, or explicitly discarded with a reason, and it is on
main.Detail
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 undecidedNew 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.
The categorical chart order puts near-identical hues next to each other
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.Detail
Files:
data-products/tokens/aleris-tokens.css(--chart-1…--chart-12) · whichever reference file carries dataviz guidanceNew 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..5in 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.patientguidemaps 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/-balready serve; if that is what they are for, then--chart-1..12is 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.
A non-colour status treatment for a genuine third state
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 onmain.Detail
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 fromborder-style: dashedplus 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
StatusChipthree-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 onmain.
--border-subtle— a lighter row separator thangray-100Done when a
--border-subtlevalue is chosen and lands inaleris-tokens.css,BASELINE.md§ Tables documents when to use it over--border-default, and it is onmain.Detail
Files:
data-products/tokens/aleris-tokens.css(a new colour + border token pair) ·baseline/BASELINE.md§ TablesNew 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-defaultfor 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 betweengray-100and 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-subtlevalue is chosen and lands inaleris-tokens.css,BASELINE.md§ Tables documents when to use it over--border-default, and it is onmain.
Six tool-surface pattern files — content-boundary conflict, not yet resolved
Done when Torfinn picks an option above, or restates the boundary doesn't apply here and why — not before then.
Waits on Card 99's three rulings — the seven only exist as components if the kit does
Detail
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 asButton 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'sdocs/environments.md:131makes 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.gccbuilds 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'sGAPS.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.
Design the positive rule replacing "No dark mode"
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 onmain.Detail
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 onmain.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.
Rewrite the "For AI systems" idiom rule in
voice.mdfor an English sourceDone when the bullet states a real English-language failure mode Torfinn has written, and it is on
main.Detail
Files:
foundation/voice.md(after promotion) ·viewports/voice-for-ai-systems.md(the generated adapter this section feeds, perprinciples/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.mdis 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.
Physical domain ownership, and the three specifics to confirm with Sanna
Done when the decision record carries a role in
owner:, the three specifics each have a yes or no, anddocumentation/site-map.md's open decision 3 records physical as settled.Detail
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 B3New 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 pastproposed.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, anddocumentation/site-map.md's open decision 3 records physical as settled.
Author English Group-level originals for the three Baseline voice guides
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.Detail
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 hereNew 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 hardcodedlang="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.mdprecedent: 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.
The language and voice-pass inventory – what B2 needs
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.
Detail
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 stancePullable 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
acceptedFoundation pages, voice-passed and frozen. The English set is fiveacceptedplus oneproposed, and_translation-notes.mddescribes 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 markedacceptedby inheritance.Note the trap:
foundation/imagery.mdis English sitting in the Swedish tree atidentity/imagery, so a count of Swedishacceptedpages 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.mdrecords 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.
The pairing rule – adopt the flow ladder, or not
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.Detail
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, § ButtonsNew 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.- It amends hard rule 3.
Feedback on the website: "Did this answer your question?"
Done when a reader can answer yes or no on a concept or answer page, the row lands in the Forge database under forced row-level security, and the brand team can export it as CSV.
Waits on Forge:
forge/ASK-BRAND-001.mdandforge/ASK-BRAND-002.mdDetail
Files:
workspace/briefs/prd-brand-os-website-2026-09-24.md§ 4.15 and § 6.3 ·site/serve.mjs·db/New 2026-09-25. Every concept and answer page is meant to ask whether it answered the reader's question (decision record § 2). Torfinn ruled that answers go to a table in a Forge database (2026-09-24), and on 2026-09-25 that the feature waits until the database question is resolved ("the yes/no feedback feature can be added in later when the DB issue is resolved"). Nothing of it is built; the pages carry no feedback box.
Half answered 2026-09-28.
forge/ANSWER-BRAND-001.md: a migration after onboarding is adb/0002_*.sqlfile, pushed, then Deploy pressed in the console — forge applies it as the app's own migrator, and has since 2026-09-11 (SEC-20); ourFORGE.mdis stale on this.ASK-BRAND-002is still open (how a public endpoint writes a row when nobody is signed in, and whichcontrollera group-wide site uses): forge calls it a real design decision. So the table can be written now and the endpoint cannot be finished until 002 is answered.Blocked on Forge, not on this repo (as first written). Forge's scoped role cannot create tables, and
FORGE.mddocuments a schema reaching the database only "at onboarding". A feedback table arrives after onboarding. That is the questionikon/forge/ASK-IKON-001.mdhas asked since 2026-09-10, including "how a second migration reaches the schema later", and it is still open. The one answer unblocks both apps. Asked of Mohan on 2026-09-25, on Torfinn's instruction:forge/ASK-BRAND-001.md(how a migration reaches the database after onboarding) andforge/ASK-BRAND-002.md(how a public endpoint writes a row when nobody is signed in, and whichcontrollera group-wide site uses). Answers arrive asforge/ANSWER-BRAND-00N.md.Ready to build once answered: a
db/migration for one table in thebrandschema (timestamp, page path, yes or no, optional comment; no names, email or IP), with row-level security forced; a small feedback module besidesite/serve.mjs, which stays free of packages, carrying the one dependency (pg) and the claims pattern fromFORGE.md; the Dockerfile carrying only that dependency;lib/static-server.test.tschanged on purpose so only the feedback route accepts a POST; rate limiting and a 2 000-character cap; a test against a throwaway Postgres. The box itself (PRD § 4.15) goes on concept and answer pages, and stays hidden with JavaScript off.Done when: a reader can answer yes or no on a concept or answer page, the row lands in the Forge database under forced row-level security, and the brand team can export it as CSV.
Generate a
vendored-appadapter forgcc, now that pursuing it is ruledDone when card 104 is ruled, the adapter is generated and installed, its first run's findings are recorded, and it is on
main.Waits on 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)
Detail
Files:
_packages/adapters/vendored-app/PROFILE-gcc.md(filled 2026-08-28, with citations) ·_packages/adapters/vendored-app/· nothing in~/Dev/gccuntil the gate clearsNew 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.
Rename the package folders to the tiers — a breaking cut, taken once, at Foundation v1
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.Waits on 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 collideDetail
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.mdandREADME.md·_packages/adapters/vendored-app/instances/patientguide.brand-provenance.jsonandsundviktlakemedel.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 tableNew 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 frombaseline/, while the package still saysfoundation/colour.md,reference/aleris-baseline-animation.mdandgovernance/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 —
patientguideandsundviktlakemedelthroughbrand-provenance.json,aleris-assistantby direct reference (it broke for fifteen days the last time a path moved, 2026-08-10), andaleris-vibe-codingthrough 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.mdrule 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 theBASELINE.mdretirement (card 50) have produced by then. Thealeris-file prefixes the corpus dropped go at the same time. Card 136's outcome — identifiers as paths or names — decides whether thedepends_onvalues 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.mjsin the vendored-app adapter able to read both shapes for one version, or a stated cut-over date; andlib/consumer-contract.test.tsfed 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.
review/gains a rendered-component targetDone when a target exists,
--self-testpasses, the run is clean or its findings are declared with reasons,review/README.mdrecords the in-repo-target exception, and it is onmain.Waits on 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
Detail
Files:
review/targets/(a new target) ·review/README.md· possiblyreview/checks/New 2026-08-28. Depends on card 99 producing something to point at.
review/README.mdopens by saying what it is for: "data-products/tokens/tokens.test.tsasserts 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 inreview/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-testruns before every real review; a component gallery does not change that.Contextual floors need declaring.
target-sizehas 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-testpasses, the run is clean or its findings are declared with reasons,review/README.mdrecords the in-repo-target exception, and it is onmain.
Build the physical source layer, and split wayfinding out of it
Done when
physical/holds an interior page and a separate wayfinding page, both declaringdepends_onagainst 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.Waits on Cards 36, 37 – colour/ownership decisions the layer would have to state (38 closed 2026-08-06)
Detail
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.mdNew 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 adigifysiskt lagersection.interior.mdand 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.mdalready 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/*.mdwithstatus: accepteduntilCONTENT_DIRSis 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 declaringdepends_onagainst 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.
The palette boundary –
sage,varmvit/ljusgrå, and who owns compositionDone when
sagehas 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.Waits on Card 36 – whether
sageis Sanna's real choice or a drafting artefact;varmvit/ljusgråand 70/20/10 already closedDetail
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 itNew 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.mdsays "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.mdisstatus: acceptedand 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. Ifsagegoes 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
sageon a wall and be right to think they were following the guide.Done when:
sagehas 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 toSand 50(#FAF8F6) andSlate(#D7D2CB), both already named and roled in F1's Neutrala toner table. A pointer note added there rather than restating the mapping — same disciplinetokens-are-canonical.mdjust formalised. Decision record §3 updated to match.sagealone 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.mdis not a translation of the Swedish page — its own frontmatter saystranslation_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'sSand 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.
Review the icon allowlist — too conservative
Done when the allowlist reflects a deliberately reviewed scope, not last review's default, and it is on
main.Waits on 2026-09-26 · replaced by Torfinn's ruling that there is no allowlist at all: any Font Awesome Pro icon may be used except the floor's motifs. Retiring the machinery is card 167
Detail
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.
sundviktlakemedelfound (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, anddata-products/iconography/allowlist.test.tsnow 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 ownnoterecords (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 inschemas/icon-allowlist-entry.mdwhere 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-iconto refuse anything off the list until then, so the narrowness now costs every icon insertion. The file references above predate Phase 0:foundation/iconography.mdisprinciples/iconography.md.
AA pass two – add the steps, turn the test green
Done when
npm testpasses with no allowlisted failures and the reference doc agrees with the token file.Waits on 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.
Detail
Files:
baseline/tokens/aleris-tokens.css·baseline/tokens/tokens.test.ts·baseline/reference/aleris-design-tokens.mdDeliberately 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-bgoff petrol-300 to the new darker step. Add the confirm interactive fill below confirm-500 and repoint--button-confirm-bgand--button-confirm-hover-bgto it, leaving--status-confirmon confirm-500 so non-button uses are untouched. Remove the "Derived value – validate when a confirm scale is added to F1" comment ataleris-tokens.css:473along with its Anchor justification.Then remove the known-failure markers from
tokens.test.ts– the suite goes green. Updatebaseline/reference/aleris-design-tokens.mdto 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 testpasses with no allowlisted failures and the reference doc agrees with the token file.
89 closed
Retire the icon allowlist machinery
Detail
Files:
data-products/iconography/allowlist.json·data-products/iconography/allowlist.test.ts·schemas/icon-allowlist-entry.md·baseline/iconography/*.html·lib/icon-render-contract.test.ts·site/src/components/v3/Icon.astro·_packages/adapters/vendored-app/(theikonprovenance record and wiring) ·documentation/brand-os-map.md,aleris-meta-alignment.md,architecture-source-of-truth-and-assets.md· theikonrepoNew 2026-09-26. Torfinn ruled in sitting 2 of the website plan: "we should allow icons instead of trying to control it. Keeping an allowlist up to date is not something we can afford unless we have an agentic service doing it." The floor stays — weapons, logos and trademarks, religious symbols, anything aggressive or alarmist — and so do the rules about how an icon is used (one style per product with solid status markers, a label on an icon that means something, palette colour, the petrol gradient's grounds). Signage keeps following external wayfinding standards.
Already done, the same day:
principles/iconography.md§ Governance per context states the ruling and says the allowlist is no longer a requirement;BASELINE.mdand.font-awesome.mddrop their allowlist instructions; the website's Icons page and its answer say icons are chosen freely;agent-baselinev0.20 carries it to consumers.What still enforces or describes the list, and is this card: the allowlist data and its test; the schema, which also holds the duotone steps
principles/iconography.mdcites; the curation and visualisation pages underbaseline/iconography/; the site'sIconcomponent and the render-contract test, which may read names from the list; theikonpicker, extracted to its own repo and built to choose from the list, and its provenance record here; three documentation pages that describe the list as a data product; and any consumer that vendored the list as a rail (sundviktlakemedel's rails brief inworkspace/incoming/). The 157__FILL_IN__labels card 58 carried become moot.Torfinn's, before the machinery goes: what the icon picker becomes without a list — a search of the whole kit with the floor filtered out, or nothing. That decides whether the
ikonmilestone in_state.mdstill stands. A suggestion, not a ruling: the floor is short and names motifs, so it can become a small blocklist of Font Awesome names that a check enforces, which costs nothing to keep current in the way a list of permitted icons does.Ruled 2026-09-26 (Torfinn), both as Claude recommended. (1) The picker stays, as the assignment tool it always was — which icon means which function in Qlik, recorded so every app uses the same one. The allowlist leaves it and the floor is filtered out of its catalogue. So the
ikonmilestone stands, still blocked on the database question with Forge. (2) The floor becomes a blocklist of Font Awesome names with a check, kept once in Brand OS and enforced on the site, in the package and in the picker.Found while deciding: the picker already searches the whole kit (3,772 icons) and used the allowlist only as a filter and a "Rekommenderade" default. Its catalogue filters nothing, so it offers weapons and religious symbols today — 48 icons in Font Awesome's religion category and about 30 named weapons. The Brand OS site's
Iconcomponent refuses to build with any name off the allowlist.Brand OS half landed 2026-09-26. Torfinn passed the blocklist the same day (93 names; radiation, circle-radiation and biohazard moved in from the borderline set; thirteen reviewed names allowed) and confirmed that duotone icons stand. The list, its check, the site's
Iconcomponent and the archived allowlist are onmain;agent-baselinev0.22 ships the list. What remains, all Claude Code's: in theikonrepo, stop vendoringallowlist.jsonand drop the "Rekommenderade" default and the admin category filter; vendorblocklist.jsonwith a provenance hash; filter its names out ofCatalogPickersearch, browse, alias resolution and deep links; test that a planted blocked name never appears. Then theikonprovenance record under_packages/adapters/vendored-app/instances/follows.Done when: nothing in the repo or the package requires an icon to be on a list, the blocklist is passed and enforced here, in the package and in the picker, and it is on
main.The extracted picker had a provenance record and no reader
Detail
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· adapterMANIFEST.md/README.md/INSTALL.md· inaleris/ikon: the four installed files,package.json,CLAUDE.md,README.md, andbrand/provenance.jsondeletedCLOSED 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.ikonheldbrand/provenance.jsonin an ad-hoc shape, nobrand-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.mdstated the invariant in as many words — "every file in them has asha256inbrand/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.
ikonis 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 waspackage.source, and a 3,776-entry file list is not a record anyone reads. So adapter v0.3: a top-levelcorpus { repo, source, commit }withfrom_root: "corpus"per entry, andvendored_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 becausereaddirSyncorder 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.tswrites the pre-fix checker out ofmainwithgit showand 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
ikonbefore 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 rawTypeErrorfromnode: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 coversapp/.One finding for Torfinn, card 152: the hex scan's first run in
ikonflagged#003942, a header colour sitting exactly where apetrol-600would be — a step the scale decided against on 2026-08-05.And one observation not acted on.
baseline/fonts/andpublic/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;
ikon7 → 9 tests in 2 files plus a clean hex scan, wired into itsnpm test.
- An unresolvable
The Forge deploy is refused by a domain conflict, and the override on offer would break two live things
Detail
Files: none in this repo — this card is the cutover · reads
Dockerfile,site/serve.mjs,lib/route-manifest.json· touches the Coolify app that holdsbrand.dev.aleris.aiand the Forge consoleNew 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 releasebrand.dev.aleris.aifrom the old Coolify app first and then deploy thebrandForge 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 thebrandapp 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 withASK-IKON-001in one message.CLOSED 2026-09-11. Torfinn deployed and it is up, verified independently rather than taken on report.
scripts/verify-deploy.mjsagainsthttps://brand.dev.aleris.ai: 26/26 routes byte-identical to the local build, 35 passed, 0 failed, 0 content differences./healthreturnsok+pages: /app/site/dist+index: 8751 bytes— our container, not the Forge placeholder, which returns a bareok./is 200 with no redirect,/identity/colour200text/html,/llms.txt200text/plain. The two rulings held:/sv/about-the-brand/brand-in-brief404s, 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-pickeris 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 / productionread Status Exited —Brand Guidelinesandicon-picker-qlik. The domain is still theirs. Coolify's own resource list still showsbrand.dev.aleris.aiagainst the first andbrand.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
brandForge 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.aiis 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.aistill 307s to/sv/about-the-brand/brand-in-brief, the 2026-06-16 Next app.forgebrand.dev.aleris.ai/healthreturnsok+pages: /app/site/dist+index: 8751 bytes— our container, not the placeholder, which returns a bareok.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
ikongoing live — an accepted gap, not an oversight, on the ground that there is no one to interrupt.What it does not unblock:
ikonitself. 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/qlikmeasured 2026-09-10. It is the backend island — Supabase plus the native@resvg/resvg-jsrenderer — 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 usinglib/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-briefThe Next app as it stands in the repo /en/about-the-brand/brand-in-briefAstro, what would deploy /about-the-brand/brand-in-briefMeasured:
/sv/about-the-brand/brand-in-briefreturns 200 on the live host and/about-the-brand/brand-in-briefreturns 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.txtendpoints 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.aiis provisioned. It is not yet serving our container — measured 2026-09-10, it returns a Forge placeholder (<title>hello</title>,/healtha bareok,/llms.txtastext/html). So step 1 of the sequence is still to press Deploy there. The tell that it worked is/healthreturningokfollowed bypages:andindex: N bytes; the placeholder answers 200 with a bareok, 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 (
gccis 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.airuns the container:/healthanswers withpages: /app/site/distandindex: 8751 bytes, the same numbers the local image reported, which is how it is told apart from Forge's placeholder.scripts/verify-deploy.mjsis 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 localsite/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.aiit 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/brandand press Deploy". There is no such field. Forge matches an app's domain to a repository of the same name, soforgebrand.dev.aleris.aican only build from a repository calledforgebrand. Torfinn found this by trying; the plan had asserted a capability nobody had checked.aleris/forgebrandis now a mirror of this repository, force-pushed frommain. It was a pure Forge scaffold — two commits,Initial commitand Mohan'sFORGE.md, with the genericauth.mjs/server.mjs/db/starter — so there was nothing to lose, which is the condition gcc'sFORGE-PUSH-RUNBOOKsets before a force-push.Two files differ on purpose and
scripts/push-staging.shrestores both. Its ownFORGE.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 aREADME.mdbanner 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 comparesorigin/mainagainstbrand/mainand 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 executedgit 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 onmain.
- Override now. One click, the site is current within minutes, the icon-picker dies and every
The Font Awesome token returns E401, and it is the first thing a Forge build runs — DONE 2026-09-10
Detail
Files:
.npmrc(gitignored, repo-local, present in the main checkout) ·~/.npmrc·Dockerfilestage 1 (FONTAWESOME_NPM_TOKENbuild 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_TOKENexported in his shell profile on 2026-09-08.What is measured on this machine today, 2026-09-10.
npm view @awesome.me/kit-6f31575c9d versionreturnscode E401using the repo-local.npmrcin the main checkout. Not 404, not a missing scope mapping — the registry is reached, the scope resolves, and the credential is rejected.FA_PACKAGE_TOKENis 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.~/.npmrccarries a separate token for the@fortawesomescope, which the record already flags as dead.Why this is urgent rather than housekeeping.
Dockerfilestage 1 runsnpm installbeforenpm run site:build, andpackage.jsoncarries 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.mescope 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-stageDockerfileandsite/serve.mjsare 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-argmeans 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 versionanswering 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.pyafter 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.
Astro wins, and the annotation feature does not come with it — DONE 2026-09-10
Detail
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.tsxandapp/baseline/layout.tsx(both wrap every page inCommentWrapper) ·supabase/migrations/(theannotationstable) ·supabase/import-annotations-after-first-login.sqlNew 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 Supabaseannotationstable over a realtime channel, and/api/notify-commentemails Torfinn and Sanna through Resend. There is a login flow behind it —signIn,signOut,getProfile,createProfile— and animport-annotations-after-first-login.sqlscript, which is the shape of something that has been used rather than merely built.What the Astro site has instead: nothing.
site/srccontains no reference to Supabase, comments or Resend. So the momentsite/distis whatbrand.dev.aleris.aiserves, 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
annotationstable, 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.
- Retire it. Delete the components, the auth flow and the two API routes with the Next app. Export the existing annotations to
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
Detail
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_SURFACEScovers ten content folders plus the package root. It does not coverworkspace/. Solib/in-repo-references.test.ts— the check whose whole job is that every multi-segment path a rule surface cites resolves — cannot seeworkspace/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. Addingworkspace/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.mdalone contributes 429 andchangelog.md360, plusarchive/anddrafts/. 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 modeKNOWNexists to avoid.Per-folder, measured, so the scope is a choice rather than a guess:
Candidate References Unresolved workspace/incoming95 47 workspace/plans228 103 workspace/briefs318 181 workspace/skills3 0 incoming+plans+skills326 150 Recommendation:
workspace/incomingandworkspace/skillsonly.skillsis already clean, so it costs nothing and locks in a surface that loads in every session.incomingis where the finding came from and is the drop zone the transit rule already treats as live.plansandbriefsare 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.mdfoundation/logo-identity.md(digital-identity brief)principles/logo-identity.mdbaseline/setup.md(css-weight brief)how-to/install-the-tokens.mddocumentation/dependency-reciprocity.md(assistant brief)merged into documentation/how-decisions-are-recorded.md2026-08-25The first three are Phase 0 casualties. Card 132's sweep and
scripts/in-repo-references.mjs --listare 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.mdis 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_SURFACEScovers 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 onmain.
- Cross-repo, and correct —
depends_onidentifiers still sayfoundation/colourafter the essays moved — is an identifier a path or a name? — DONE 2026-09-10Detail
Files:
lib/content.ts(DEPENDENCY_ALIASES) ·lib/baseline-content.ts(FOUNDATION_LINKS) · every file declaringdepends_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/toprinciples/; the identifiers other files declare for them did not.foundation/constant-contextualappears in 24 files,foundation/colourin 19,foundation/typographyin 20,foundation/emotional-modesin 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, andFOUNDATION_LINKSkeeps itsfoundation/…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_onvalue a path (then every identifier is rewritten toprinciples/…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, andhow-decisions-are-recorded.mdsays 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.mdretirement 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 onmain.The colour essay —
principles/colour.mdis cited by a constitutional page and has never existedDetail
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.mdNew 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.mdanddesign-tokens.md.Redirected rather than left dangling. All six references now point at
foundation/colour.md, which isaccepted, 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 whetherfoundation/colour.mdis the essay andprinciples/was a plan that got overtaken. The second is the cheaper answer and the evidence leans that way:foundation/colour.mdgrew to 316 pages of exactly the materialprinciples/colour.mdwas supposed to hold, whileprinciples/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) whilefoundation/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.mdtoprinciples/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 toprinciples/' question-led convention (principles/_index.md§ File convention says so) — whether they should be is open. Pullable to Done once Torfinn confirms the reading.The corpus quotes a Swedish source it does not hold, cannot version and cannot check
Detail
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.mdto 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.mdis 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
acceptedat 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 foracceptedis written down.Update 2026-09-07 — the page is
acceptedand publishes at/identity/logo. Torfinn promotedprinciples/logo-identity.mdwith 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.Ruled 2026-09-25 (Torfinn), all three as Claude recommended. (1) English is the rule. A quotation from a local-language source is carried as English rule text, with the original kept beneath it as source wording, so no new category enters the language policy and the policy stands as written. (2) The 2025 guidelines are a historical input, not an upstream dependency: the corpus states the rules in its own words, and a revised edition would arrive through intake. No version check. (3) Ratification clears a translated rule, the same as any rule sentence Claude drafts; no bilingual clearance step.
Two corrections to this card, found while ruling. Its title says the corpus does not hold the source:
workspace/sources/Aleris Brand Guidelines 2025.pdfhas been in the repo since before 2026-08-05, and the ordlistan beside it. The update above says the page isaccepted: the strip moved it back todrafton 2026-09-22 and removed the author prompts, leaving one page reference into the source rather than eight. A second copy of the guidelines in Torfinn's Downloads differs in bytes; which edition is later is not recorded, and under ruling 2 it no longer needs to be.Applied:
principles/logo-identity.md§ Prohibited now lists the eight rules in English with the Swedish beneath as source wording;workspace/sources/README.mdrecords the guidelines as historical input. Still open, and now all that stands between the page andaccepted: Torfinn's pass of the eight English rule sentences, the thirteen Why and Test lines Claude drafted in the strip, and the strip's two restructures (digital identity folded into the rules, sender architecture down to one rule). Then_packages/agent-baseline/logo/README.md§ Prohibited, which is authored in the package and still Swedish, follows in the next cut. That pass is sitting 1b of the website plan.Done when (revised): the page is
acceptedwith English rules and its provenance stated on it, and the package sheet matches.Every check is shipped or repo-only, and nothing says which
Detail
Files:
lib/*.test.ts(8) ·data-products/**/*.test.ts(3) ·review/checks/*.mjs(3) ·_packages/adapters/vendored-app/wiring/brand-provenance.test.tsNew 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.tsorreview/checks/*.mjsexists with no entry. Same discipline asKNOWN_LANG_MISMATCHandIN_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.mjsalready 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 onmain.
The out-of-set hex scan is the one assertion that needs the consumer's own source
Detail
Files:
data-products/tokens/tokens.test.ts§ "token conformance: no hex outside the token set" (walksapp/for.css/.ts/.tsxanddata-products/for.css) ·_packages/adapters/vendored-app/wiring/·_packages/consumer-profile.md§ ChangesNew 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.mjsINTEGRITY — 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, perconsumer-profile.md: "this is how a brand or conformance rule becomes enforced rather than asserted." Carry theKNOWN_OUT_OF_SETconvention 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.sundviktlakemedelhas 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.
The adapter is the unit that ships wiring, and a consumer's declared checks are unread
Detail
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.tsNew 2026-09-06, from
workspace/decisions/decision-record-shipping-checks-2026-09-06.md.The gap, in two halves.
agent-baselineis content and the adapter is wiring — already true, sincebrand-provenance.mjsis the one executable check that reaches a consumer — and nothing writes it down, so the question gets re-argued at every cut. Andsundviktlakemedeldeclared its own local checks in anassertionsblock that is not a documented field: the header listspackage,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, andpatientguidehas no block at all.The build. One paragraph in
_packages/README.mdstating the adapter rule and the three enforcement routes.assertionsdocumented inbrand-provenance.mjs's record format as{ file, asserts }. Then a check here that fails when an instance declares neither anassertionsblock nor a stated reason it has none — same acknowledgement convention asacknowledged_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:
assertionsis in the documented format, an instance with neither a block nor a reason failsnpm testhere, mutation-verified, and it is onmain.
Drift walk for consumers who copy rather than install — extend
consumer-contract.test.tsto declared inheritsDetail
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.pypackage_status()(thegit logwalk since a generation commit) ·workspace/incoming/brief-aleris-assistant-copy-consumers-and-voice-2026-09-04.md§ 1 ·_packages/README.mdrule 7New 2026-09-05, from the incoming triage § 2.
The gap.
aleris-assistanthand-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.seis 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.mdimplying 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 testhere, mutation-verified, and it is onmain.
Error text on the communicative ground — recorded as a known failure for a month, and unowned
Detail
Files:
data-products/tokens/aleris-tokens.css(--text-error,--input-error-color,--color-error-500) ·data-products/tokens/tokens.test.tsKNOWN_INPUT_FAILURES['error-text/sand-100']and § "semantic text aliases resolve to a colour that may carry text" ·foundation/colour.mdrole row ErrorNot 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-errorresolves to--color-error-500(#c14444).Ground Ratio AA text (4.5:1) --surface-cardwhite5.03:1 pass --surface-page-instrumentalsand-504.74:1 pass --surface-pagesand-1004.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-sizeis--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, sobaseline-tokens.jsonstops shipping--text-errorto 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, andDOCUMENTED_ROLESalready classifies itlarge, 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#8f2f2fat 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-colorand 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-errorand--input-error-colormatch that, the annotation states the ruling rather than only the measurement, and it is onmain.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.
branch-hygiene's stranding check cannot see superset containment, so a rescued-with-edit blob is flagged foreverDetail
Files:
lib/branch-hygiene.test.ts§strandedPathsandtrunkObjectsNew 2026-09-02, found when the check went red on
backup/auto-20260830three 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()walksgit 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.mdwas 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-20260830was 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,-20260901and-20260902was already reachable inorigin/main's history — zero unreachable each, across roughly 4,300 blobs per branch.-20260830had exactly one, and it was the contained case above. All five deleted, local and remote; the branch list is nowmainplus 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
SessionEndsnapshot 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 pastSNAPSHOT_GRACE_DAYSand a branch to read and delete by hand. The memory already records that this hook "creates the exact conditionbranch-hygienefails 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_DAYSis reconsidered against how long a snapshot is actually useful, the fix is proven against a fixture that reproduces rescue-with-edit, and it is onmain.Three of four semantic text roles are never measured, and one of them fails AA everywhere
Detail
Files:
data-products/tokens/aleris-tokens.css(--text-secondary,--text-tertiary,--text-disabled) ·data-products/tokens/tokens.test.tstest 15 and § "text-primary on …" ·foundation/colour.mdrole columnNew 2026-08-31, found while building the site — three times in one hour, which is the argument for the card.
The measurement.
--text-tertiaryresolves to--color-gray-300(#9e9281). Against the three grounds this corpus actually uses:Ground Ratio AA text (4.5:1) --surface-page-instrumentalsand-502.88:1 fail --surface-pagesand-1002.60:1 fail --surface-cardwhite3.05:1 fail Nothing catches it, and the reason is precise.
tokens.test.tstest 15 classifies primitive colours by whatfoundation/colour.mdsays they may be used for, and it hasgray-300atlevel: 'nontext'— which is correct, and passes. The semantic layer above it is checked for--text-primaryonly;--text-secondary,--text-tertiaryand--text-disabledappear 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.mdthat 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-tertiarymay not be a defect at all. The token file gives it the same value as--text-disabledand--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-tertiaryreads 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 documentednontextmust fail. That closes the class rather than this instance — cookbook entry 21's argument, applied.Done when: the corpus records what
--text-tertiaryis for, its value or its name matches that,tokens.test.tsmeasures every semantic text role rather than one of four, and it is onmain.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 theDOCUMENTED_ROLESclassification of the primitive it resolves to, and fails when that classification isnontext. 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-tertiaryand--text-disabledas the three unmeasured roles.--text-accentwas 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-400on Foundation's precedence —foundation/colour.mddocuments 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-secondarymeasures 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-tertiaryis now recorded inKNOWN_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
::placeholderrule 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-disabledand--state-disabled-textalready 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.
The live site has served a 2026-06-16 build for 73 days — redeploy, and find out why it stopped
Detail
Files: none in this repo — this card is the deploy · reads
README.md§ Deploy · the Coolify app ondev.aleris.aiNew 2026-08-28. Measured by fetching every published file from
brand.dev.aleris.aiand matching it by hash against the git history, not read from a deploy log. The deploy is frozen at commit23d4e7b, 2026-06-16.mainhas moved 201 commits since.Served live Canonical What it means aleris-tokens.css36,804 b, sha256e2dfab4c…50,487 b, sha256 3aa2d866…anyone who follows setup.mdgets June's valuesBASELINE.md233 lines292 lines still teaches the confirm-green rule retired 2026-07-31 baseline-tokens.json45,210 b49,320 b stale usageandconstraintmetadataaleris-fonts.css3,130 b2,724 b the font host every package links aleris-tailwind.css404exists since 2026-08-28 step 3 of the documented install is broken as written /en/*404,/sv/*200enis the only locale since 2026-08-12the 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.mdline 22 tells them tocurlthe token file from that host and line 82 to fetch the Tailwind bridge, which 404s. Thealeris-design-systemskill 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.txtis 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-appconsumers vendor from_packages/agent-baseline/rather than from the URL, andbrand-provenance.mjshashes 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.aiserves 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 inworkspace/status/changelog.md, and it is onmain.
The main checkout held uncommitted work that duplicated commits already on
origin/main— DONE 2026-08-31Detail
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 onmainat8e50146, seven commits behindorigin/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-20260828and 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/mainwas attempted and refused, changing nothing — git protects against exactly this. It will keep refusing until the working copy is resolved, so localmainstays 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/mainrather than assumed. Three were byte-identical to what PR #14 landed, one was an older copy of a file already onmain, one was_state.mdcarrying 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 whatwww.aleris.seserves and the one the verification document measured. A carelessgit add -Awould 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 atworkspace/drafts/parked-2026-08-31/, inside the gitignoredworkspace/drafts/, so it cannot be committed by mistake and is one decision away from being usable._state.mdneeded 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 aboutemotional-modes.mdbeing "the eighth and last Foundation page to flip" whenfoundation/holds nine.Result: local
mainatffaa952, 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, localMet.mainis fast-forwarded toorigin/main, and it is onmain.
branch-hygiene.test.tsmeasures against localmain, and goes false the moment it is stale — DONE 2026-09-09Detail
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
mainis at8e50146, seven commits behindorigin/mainat056f1d8, and everything is measured against it. This session's branch isorigin/mainplus one commit.origin/claude/aleris-vibe-coding-feedback-51e02eis zero ahead oforigin/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, isrefs/remotes/origin/HEAD, whichgit branch -arenders as the bare name.git branch --list originis empty andrefs/heads/origindoes 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
mainwas 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
maincurrent,origin/HEADresolves 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_TODAYoverride for testing its expiry logic; a trunk override in the same spirit would let this be tested without waiting formainto rot. Reproducing today's state is a two-line setup: point the trunk at8e50146and 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 readinggit branch -aoutput 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/mainwhen the remote ref exists and falls back to localmainotherwise, skips remote HEAD symrefs, is proven against a deliberately stale trunk rather than against a green tree, and it is onmain.
gccis profiled as a consumer candidate — decide whether to pursue it — DONE 2026-09-10Detail
Files:
_packages/adapters/vendored-app/PROFILE-gcc.md·_packages/adapters/vendored-app/PROFILE.md·_packages/adapters/README.mdNew 2026-08-28. The profile is filled, with citations, per rule 1 of
_packages/adapters/README.md. Nothing is installed and nothing in~/Dev/gccwas touched. This card is the decision the profile exists to inform.Structurally it is a
vendored-appand nothing argues for a new type — Vite build compiling CSS, forge deploy on analeris.aiorigin, 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.mdholds the design guidance but contradicts its own repo (it still describes a Supabase backend and points at directories that do not exist), whileCLAUDE.mdis 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/gccat 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.
A Tailwind v4
@themebridge for the token file — DONE 2026-08-28Detail
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 bridgesNew 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-ositself among them —app/globals.csscarries a 122-line hardcoded@theme inlineblock 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 inlinegenerates utilities that reference a custom property rather than inlining its value. So one canonical file can feed both worlds, and a project getsbg-petrol-500from the same 363 declarationstokens.test.tsalready 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 fromaleris-tokens.cssrather than hand-writing it is the way to make that true rather than intended, and it is the same shape asgenerate-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.htmllinks no token file and carries twelve hardcoded hex including#4F866Eand#4F868E. Not a typo — both are real Aleris tokens,--color-confirm-500and--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 inbaseline/setup.mdas an optional step, has a test asserting it declares no value of its own, and it is onmain. All but the last true as of 2026-08-28 — see the Done table for what the build actually found. 235 tests,npm run buildclean, both routes prerendered and verified byte-identical to source. Carried forward, not silently absorbed:baseline/BASELINE.md,baseline/setup.mdandschemas/design-token.mdall changed today, so_packages/agent-baselineis behind on three files and a cut has not been asked for.
imagery.mdwaits on a reader-state model for professional readers that now exists — DISSOLVED 2026-08-27Detail
Files:
foundation/imagery.md:84·foundation/emotional-modes.md§ Three families of reader stateNew 2026-08-27, found by querying wider than card 94's five references rather than by looking for it.
The sentence.
imagery.md:84reads: "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.mdnow publishes dispositional states for professional readers – time-pressure, cognitive load, accountability, scrutiny – as one of three families under reader state. Soimagery.mdis 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.mdhas 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.mdshould 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.
Seven archived Swedish originals claim
acceptedwhile their replacements do too — DONE 2026-09-10Detail
Files:
_markets/sv/foundation/(7 files, 6 of themaccepted) ·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
_marketstoAUDIT_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 intoORPHANED_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 underfoundation/. So two files claim to be in force for the same content in two languages. The corpus hassupersededfor 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, andacceptedis 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
acceptedis correct – andORPHANED_ACCEPTED's comment stops posing the question.
worryis still the mode name in two accepted pages after the rename — DONE 2026-08-27Detail
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.mdrenamed its first mode worry → uncertainty (Torfinn: worry presumes distress most arriving patients do not feel). Two otheraccepted, 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:74table 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:36Reader state row – "Worry, need for control" Two of these must NOT change, and getting that backwards is the real risk.
constant-contextual.md:38andemotional-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:78is 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:78has Torfinn's wording, the package is re-cut, and it is onmain.
The en-dash convention has never held – 9,079 em dashes against 1,405 en — DONE 2026-09-10
Detail
Files: measured across the whole corpus.
workspace/status/changelog.mdholds 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.mdhas 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 anacceptedFoundation 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.mdshowed 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.mdwas 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.
- The convention is real – then it needs one corpus-wide pass and a check, because 9,079 instances is not something attention closes.
agent-baselinev0.8 is cut and three consumer records still pin v0.7.1 — DONE 2026-08-27Detail
Files:
_packages/adapters/vendored-app/MANIFEST.md·_packages/adapters/vendored-app/instances/patientguide.brand-provenance.json·.../sundviktlakemedel.brand-provenance.json· the two consumer repos themselvesNew 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.mdrecords 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-appv0.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.mdto track this. Rule 7 of that file is explicit that drift is computed inworkspace/status/board.html's Package status tab, fromgit 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.
A rule is applicable where it is read — a seventh constitutional rule, with a check
Detail
Files:
constitutional/a-rule-is-applicable-where-it-is-read.md(new,proposed) ·lib/external-references.test.ts(new) ·constitutional/_index.mdNew 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
presentationsverktygpath 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.mdreachedacceptedciting 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.tscannot 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 asKNOWN_LANG_MISMATCHandIN_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~/TAKSpath infoundation/voice.mdfails; 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
attributionpasses, 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 toaccepted. 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 onmain.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.
A moved file breaks a consumer silently — the staleness mechanism, fourth statement and first build
Detail
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-assistantinstall (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/todata-products/tokens/on 2026-08-10.aleris-assistantpointed at the old path in four places —README.md, the source comment insrc/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.
patientguideandsundviktlakemedelpoint atagent-baseline's internal paths, recorded in theirbrand-provenance.jsonfromfields.tokens/aleris-tokens.csshas held across v0.4 → v0.7.1 including straight through the move. The package is the indirection layer and it worked.aleris-assistantwas 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 asroute-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.mdtwo 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.mdrule 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.)
The backup hook and the branch-hygiene check cancel each other out — DONE 2026-08-25
Detail
Files:
.claude/hooks/backup-push.sh(theSessionEndhook) ·.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.shruns atSessionEnd, snapshots the entire working tree — committed HEAD plus uncommitted changes plus untracked files — intobackup/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.tsfails when any branch is ahead ofmain. 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
mainwere 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-20260825was recreated, and the suite went red again. Its snapshot is byte-identical tomain; 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-hygieneflag oncolour-swedish-port" — the check had become something to note and step past.colour-swedish-portthen 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 skipbackup/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-20260825held the pre-review originals of the 2026-08-25 decision record and brief, whichmainhad never carried, now atworkspace/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
maindoes not — which is a different check from "is a branch ahead", and the one actually wanted. A snapshot whose tree matchesmainis 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 frommain, 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
maindoes not — or it is recorded as deliberately red with what a reader should do about it, and it is onmain.
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 matchingbackup/auto-YYYYMMDD— the hook's own naming, so deliberate snapshots likebackup/brand-os-full-*are untouched — leave the ahead-of-mainrule and enter a new check that asks a different question: does this snapshot hold content that exists nowhere inmain?SNAPSHOT_GRACE_DAYS = 2keeps 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, becausemainmoves on and their trees necessarily diverge. That would have changed nothing. The check compares content: it walks every object reachable frommain(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 farmainhas travelled.Proven against five cases before being trusted, not just run once. Using the file's own
BRANCH_HYGIENE_REPOandBRANCH_HYGIENE_TODAYinjection points against a throwaway repo:Case Expected Result Ordinary branch ahead of mainfail 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 mainpass 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:
ageDaysis 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-20260825sitting 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.
Rescue three records from
colour-swedish-portbefore the branch goes — DONE 2026-08-25Detail
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.mdcard 42 · branchcolour-swedish-port, commits45bdca9,0a1c779,e4a28ca,dbbc985New 2026-08-25, from the branch sweep after three
backup/auto-*snapshots were deleted.colour-swedish-portis the last branch ahead ofmain— 4 commits, 2026-08-11, and it is the only thing keepinglib/branch-hygiene.test.tsred.The branch cannot be merged, and it does not need to be. It ports the Swedish Foundation colour page while
foundation/colour.mdwas still Swedish. Ten days later0ae89ffflipped 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 ownIN_FLIGHTdeclaration 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'sfoundation/colour.mdagainst today's_markets/sv/foundation/colour.mdgives 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 thatagent-baselinev0.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
maincurrently asserts the opposite of (1).workspace/plans/plan-swedish-colour-port-session.md:98says 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 onmainrepeat 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 deletedbackup/auto-*snapshots andworkspace/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, andcolour-swedish-portis deleted — at which pointbranch-hygienegoes 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'sfoundation/colour.mdagainst_markets/sv/foundation/colour.mdgave three lines, all of them the branch holding older text — thepetrol-400,petrol-300and confirmation-green roles thatagent-baselinev0.6.4 corrected for being below the AA floor. Merging or replaying would have been a regression, not a rescue.colour-swedish-portdeleted, local and origin.lib/branch-hygiene.test.tsis green — the first time since it was written on 2026-08-06.
- The ΔE recomputation, with its method. The English page asserts error red
Instructional imagery is not a category the corpus names — DONE 2026-08-27
Detail
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 insundviktlakemedelandpatientguideNew 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.mdgoverns 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 isbaseline/reference/aleris-baseline-images.md:154, which says graphic content can take lower compression.Why it is live rather than theoretical.
sundviktlakemedelhas injection technique.patientguidehas 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.mdforbids 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: doesimagery.mdgovern 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.
Two A/B/C references survive the retirement, one in an accepted page — DONE 2026-08-27
Detail
Files:
baseline/governance/4-den-nara-experten-digital-behavior-v1.md:122(status: accepted) ·baseline/iconography/curation-session-b.html:535New 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
langand 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/orprinciples/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.
Nothing carries a finding from a repo to Brand OS — DONE 2026-08-25
Detail
Files:
_packages/adapters/vendored-app/GAPS.md§§3–4 (where this is already named) ·~/Dev/_commands/and~/.claude/commands/(the/wrapdefinition)New 2026-08-25, from the learning model (card 79). Named as a known blocker in
GAPS.mdand 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-baselinev0.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
/wraphalf 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
/wrapwas behind its own specification.GAPS.md§4 says "/wrapalready 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'sfile-manifest.mdfinding, and as the three-definitions-of-/wrapproblem 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.mdproposed. A question asked every session becomes a question nobody hears — that is exactly howCLAUDE.mdcould ask for a frozen manifest to be updated for three months. Step 5c compares each consumer'sLast 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,sundviktlakemedelandaleris-assistantare sweepable.aleris-vibe-codingis 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. ✔aleris-assistanthas no findings file and nobrand-provenance.json— DONE 2026-08-25Detail
Files:
aleris-assistant/(itsLEARNINGS.mdis 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.mdhas 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-assistantcarries a findings file and a provenance record, and it is onmain.
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 vendoredagent-baselineandaleris-tokens.cssreturned 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 ownCLAUDE.md. It does vendor tokens, hand-copied intosrc/index.cssunder@theme, governed by its constitution rule 7 and recorded indocs/architecture/inheritance.md. Corrected before committing.No
brand-provenance.jsonwas created, deliberately. It pins a package version and carries a file hash; neither applies to a hand-copy.inheritance.mdalready 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.mdand the source comment insrc/index.cssboth record the vendored tokens as coming frombaseline/tokens/aleris-tokens.css. That path has not existed since 2026-08-10, when Phase B step 3 moved tokens todata-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.mdroutes strategic material to TAKS and technical patterns toCOOKBOOK.md; voice findings fit neither. Both candidate routes are written into the file with their risks, for Torfinn.Done when:
aleris-assistantcarries a findings file and a provenance record, and it is onmain. ✔ — with the provenance record deliberately not created, and the reason recorded.A fourth finding kind, and the field that makes a finding's reach computable — DONE 2026-08-25
Detail
Files:
_packages/adapters/vendored-app/(the findings template and consumer profile) ·patientguide/BRAND-OS-FINDINGS.mdandsundviktlakemedel/BRAND-OS-FINDINGS.md(the two live instances) ·documentation/dependency-reciprocity.md§ CaptureNew 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.mdnow 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 —sundviktlakemedel2026-08-12,patientguidenever, 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. ✔Communicative/instrumental is used in three Foundation pages and defined in none — DONE 2026-08-25
Detail
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.mdsets two page backgrounds by it — sand-100 for communicative contexts, sand-50 for instrumental.iconography.mdrules out number icons "specifically to instrumental surfaces".imagery.mdinherits 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.mdsets two page backgrounds by it (rows 75–76 and the cream note at 180), andiconography.mdrules 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 declaringdepends_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 propertiesthe two Foundation pages either stop depending on a branch's vocabulary, or the corpus records that they may — and it is onmain.
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.mdsaid "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 textCLAUDE.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.mdPage background (communicative contexts)Page background — pages someone readsfoundation/colour.mdPage background (instrumental contexts)Page background — surfaces someone works infoundation/colour.mdcream note, both terms same examples, terms dropped foundation/iconography.md"applies specifically to instrumental surfaces" clause deleted _markets/sv/foundation/colour.mdSidbakgrund (kommunikativa sammanhang)Sidbakgrund — sidor som någon läser_markets/sv/foundation/colour.mdSidbakgrund (instrumentella sammanhang)+ cream noteproperty, terms dropped Table cells and the cream note are Claude-drafted under the utility-text calibration; the
iconography.mdchange is a deletion. Verified: the grep found 6 atHEADand 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.mdandschemas/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:56andprinciples/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.
- That
emotional-modes.mdpromises a detailed digital model that nothing published carries — DONE 2026-08-25Detail
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 isNew 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.mdsays "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. Seebaseline/patterns."Nothing underCorrected 2026-08-25 — false as written, see the close below. The model lives only inbaseline/,patterns/,principles/orfoundation/mentions any of the three modes.workspace/— chieflyauthoring/ALERIS-DESIGN-WORKING.md, the working reference still being absorbed, plus five files inarchive/,sources/baseline-web-view-research.md,research/brand-os-inventory.mdand 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.mdputs 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.mdeither resolves to something a reader can open, or is narrowed to what the corpus actually holds — and it is onmain.
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.mdis 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.mddeliberately not touched (_packages/README.mdrules 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.
Cut
agent-baselinev0.7 — the logo, with its rules ratifiedDetail
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.jsonNew 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.mdshowed how right. That page is adraftskeleton. 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.mdrather 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-resourceson 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.mdrule 6 — it adds a package member. All 30 content files verified byte-for-byte against canonical, 30 identical.Found while cutting:
sundviktlakemedelrenders 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
mainand inaleris-vibe-codingon 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.
Both emotional modes are relative to a pending event; a habitually revisited surface has neither — DONE 2026-08-27
Detail
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.mdnames 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 inarchive/,sources/baseline-web-view-research.md,research/brand-os-inventory.mdand 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 underfoundation/,baseline/,patterns/andprinciples/carry no trace of any of the three.baseline/carry the labels —governance/4-den-nara-experten-digital-behavior-v1.md:122andiconography/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.mdpromises 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.
imagery.mdsplits on audience; a product splits on situation, and a surface can be both — DONE 2026-08-27Detail
Files:
foundation/imagery.md(the mode table and § "Imagery in professional and internal surfaces") · possiblyfoundation/constant-contextual.mdandfoundation/iconography.md, which carry the parallel boundaryNew 2026-08-21, from
workspace/archive/finding-patient-facing-instrumental-surface-2026-08-20.md(triaged there 2026-08-24, fromworkspace/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.mddivides 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 reachesconstant-contextual.mdand 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.mdstates which axis governs and gives the patient-facing instrumental surface a rule, or records why it does not need one — and it is onmain.
Rework the seed adapter to Richard's structure, and ship the wiring test he asked for
Detail
Files:
_packages/adapters/vibe-coding-seed/(v0.2 — two stanza files deleted,wiring/aleris-standard-patch.mdrewritten as the whole wiring,wiring/verify-aleris-setup.mjsnew, 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:119says the refresh updatesdocs/and not the project's ownCLAUDE.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 asymmetryadapters/vendored-app/GAPS.mditem 4 names one layer out. Andadapters/README.mdgained 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 onadd-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:
maincarries nodocs/agent-baseline/at all, because PR #1 has never been merged. So the documented refresh, run in a project today, clonesmainand 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.
Correct the font licence claim, cut v0.6.5, and close the PDF-embedding question
Detail
Files:
baseline/setup.md§ Step 1b ·_packages/README.mdrule 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 tableMuseo 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.mdsaid 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-codingstill carries it at v0.6.2. No content file changed: all 28 verified byte-for-byte, 28 identical, 0 differing. Only the package's ownREADME.mdandMANIFEST.mdmoved.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.
The
vendored-appadapter, and the four expired workarounds it foundDetail
Files:
_packages/adapters/vendored-app/(MANIFEST, PROFILE, INSTALL, GAPS,wiring/with six artefacts,instances/with two filled records) ·_packages/adapters/README.mdNew 2026-08-19, immediately after card 67, on Torfinn's request: write the adapter for
patientguideandsundviktlakemedel. 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.tsstops a screen bypassing the component layer, and both suites cite Brand OS's owntokens.test.tsas 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; thefoundation/colour.mdread-from-canonical override, made unnecessary by v0.6.1; andvar(--target-min, 44px), standing in for the token v0.6.3 added. Inpatientguide: theCLAUDE.mdnote 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.aiandsundviktlakemedel.aleris.aiare 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.mdrule 4 speaks only to linking. That needs the licence read, not reasoned about (GAPS.mditem 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 inworkspace/briefs/records it, whilesundviktlakemedel's findings file produced two filed briefs and four releases. Shipping the file closes the writing half; the carrying half is unowned (GAPS.mditem 4).Done when: the adapter is on
mainand installed in both repos, with the four expired entries cleared. True as of 2026-08-19 — installed at84de52b(sundviktlakemedel) and7fc2ed6(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-minnow falls back tovar(--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-radiusnow resolves to--radius-l(16px) per card 45, whilescreen.css:17aliases--radius-cardto--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.csshand-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.78remis 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'sCLAUDE.mdactually 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-smis 0.89rem, so raisingxsalone 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'sBRAND-OS-FINDINGS.md, and the mechanism that would catch this class isGAPS.mditem 6.
Split packaging into content core and adapter, and write the first adapter
Detail
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.mdNew 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 indocs/with prose pointers, an absolute path reference plus a vendored token file, and a token file copied intosrc/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 refreshdocs/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. AndBASELINE.mdis 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 atCLAUDE.md:5for 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.mdis 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
mainandadapters/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.
Cut
agent-baselinev0.6.3: the three mechanical closes from the second build feedbackDetail
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.mdNew 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 flaggedagent-baselinev0.6.2 as 3 files behindmainthe 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.mdrule 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-baselineis at v0.6.3, the package status tab shows it in sync withmainagain, and it is onmain. True as of 2026-08-13.
Fix a build-breaking comment bug in
aleris-tokens.css, cut v0.6.2Detail
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.mdNew 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
*/insidechart-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 withcssMinify: falserather 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-1through--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-baselineis at v0.6.2, and it is onmain. True as of 2026-08-12.
BASELINE.mdcorrections from build feedback: confirm-button contradiction, stale 14px entry, target-size relaxation, density reframeDetail
Files:
baseline/BASELINE.mdNew 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'sROOT_FONT_SIZE_PXis 16 (confirmed nothing overrides it), and--font-size-xsis0.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-unfixedqlik.module.csssub-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.mdand onmain. True as of 2026-08-12.
Fix
imagery.mdregression and its mis-filed market-package copyDetail
Files:
foundation/imagery.md·_markets/sv/foundation/imagery.md(removed) ·_markets/sv/MANIFEST.mdNew 2026-08-12, from consumer feedback:
workspace/briefs/findings-from-sundviktlakemedel-build-2026-08-12.md§1/§2. The 2026-08-12 language sweep treatedfoundation/imagery.mdas 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.mdmislabelled as "the Swedish original."Root cause, traced via
git log --follow: commitc46bc2f(2026-06-17) promotedfoundation/imagery.mdto English canonical and tombstonedfoundation/en-draft/imagery.mdas superseded. Commit62b939d, 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.mdrestored 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.mdcorrected to list five archived pages, not six, with a note explaining the removal.Done when:
foundation/imagery.mdmatches 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.
Package status tab — computed drift, not hand-written notes
Detail
Files:
workspace/status/build-board-html.py(thePACKAGESconfig,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
mainand 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:PACKAGESdeclares which directories a package mirrors (seven rules foragent-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 ownMANIFEST.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-baselinev0.6 (generated commit1681658) correctly showsfoundation/colour.mdas 1 of 28 files behind, citing commit8169bdf, "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.mdrule 1). A file matching nodir_maprule 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 onmain. True as of 2026-08-12.
Regenerate
agent-baselineto v0.6 and ship it to RichardDetail
Files:
_packages/agent-baseline/*(regenerated, 28 files + newfoundation/iconography.md) ·_packages/agent-baseline/MANIFEST.md,README.md·_packages/README.md· (inaleris-vibe-coding, external repo)docs/agent-baseline/*, PR #1New 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-osagain.CLAUDE.md,AGENTS.md, anddocs/aleris-standard.md's "Look and feel" section all point agents atdocs/agent-baseline/BASELINE.mdand 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 indocs/aleris-standard.mddoes not refreshdocs/agent-baseline/at all, andBASELINE.mdis 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,imagerynow 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.mdadded — every prior version's manifest excluded it as "draft, ships once authored"; it'sacceptednow with Torfinn's own closing paragraph, so the exclusion reason no longer holds.constant-contextual.md/emotional-modes.mdstay 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-osbranch 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/inaleris-vibe-codingmatchesagent-baselinev0.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.
constant-contextual.md— confirm the contextual-axes framework before English can flip — RESOLVED 2026-08-25Detail
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.mdalready 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 thatprinciples/voice.mdstill 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 nowdocumentation/how-decisions-are-recorded.md, merged withdependency-reciprocity.mdlater the same day — card 79. The rule that generalises this card's outcome isconstitutional/a-thinking-tool-stays-a-thinking-tool.md.) It supersedeslanguage-architecture-hypothesis.mdand 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.mddoes.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 testheading restored, Example 3's audience framing dropped, and the danglingpropagates_to: foundation/expression-calibrationremoved. 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.mdhad kept Expression calibration as one of four core thinking tools whileCLAUDE.md's phase list recorded it demoted; corrected 2026-08-25, and its naming question is moot. The digital instance inBASELINE.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 tofoundation/constant-contextual.mdwithlang: en,status: accepted,last_verified: 2026-08-27, the scaffolding banner stripped and the translation fields removed.foundation/en-draft/constant-contextual.mddeleted — the draft is consumed. The route path is unchanged, so all 46 files that referencefoundation/constant-contextualkeep resolving; that is why this flip was cheap.Two gates were crossed on purpose rather than worked around.
lib/route-manifest.jsonnow recordsabout-the-brand/constant-and-contextualaslang: en, hand-edited, which is the deliberate acceptance card 27 built the gate for. And the flip failed the suite first:KNOWN_LANG_MISMATCHstill 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.mdis still the Swedish page andMANIFEST.mdstill lists this row as Swedish. Not patched —_packages/README.mdrule 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 intofoundation/, and it is onmain.
emotional-modes.md— resolve the reader-state rename before English can flip — DONE 2026-08-27Detail
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
Opensection 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.mdExample 1's table label and quick-test question 2 now read reader state, matching whaten-draft/emotional-modes.mdalready 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) intofoundation/, and it is onmain.
Ship
agent-baselinev0.5 toaleris-vibe-codingDetail
Files: (in
aleris-vibe-coding, not this repo)docs/agent-baseline/*· PR #1New 2026-08-10, the hand-off card 49/51 both left open:
agent-baselinehad 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-authas 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 branchadd-brand-osin 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. Replaceddocs/agent-baseline/wholesale with the current_packages/agent-baseline/(30 files: 27 manifest + README/MANIFEST/FEEDBACK), which correctly droppedreference/aleris-design-tokens.md(retired from the package in v0.5) along with the rest. Pushed toadd-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/inaleris-vibe-codingmatchesagent-baselinev0.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.
Retire
klinfys-baselineandbhsv-baseline.zip, makeagent-baselinethe single standard packageDetail
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-baselineis 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) andbhsv-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.mdsince 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 currentagent-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 (renamedagent-baseline-pre-manifest-2026-06-17.zipso 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 onlyagent-baselineand itsaleris-vibe-codingdownstream mirror, with a standing line declaringagent-baselinethe 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.mdshows one current standard package, the retired packages are archived with a recorded reason, and it is onmain.
Regenerate
agent-baselineto v0.4Detail
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.mdNew 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
mainuntil now.constitutional/isn't inagent-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 buildclean.MANIFEST.mdbumped to v0.4, "what changed since v0.3" rewritten, provenance table's patterns row corrected topatterns/(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 toorigin/main– pushing is Torfinn's call, flagged separately.
- The 14px accessibility floor. Card 18 found
The colour supersede's English page has no publish path under route B — DONE 2026-09-10
Detail
Files:
foundation/en-draft/colour.md·foundation/colour.md·workspace/decisions/language-exit-decision-brief.md·workspace/status/changelog.mdNew 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.mdis not a translation of the Swedish page — its own frontmatter saystranslation_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 tomain2026-08-03). The English page's own banner, dated 2026-07-30, says explicitly: "the Swedishfoundation/colour.mdis 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-portbranch, 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 isaccepted, butconstitutional/is not inCONTENT_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 untilconstitutional/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.
Close the four-branch tail
Detail
Files:
lib/branch-hygiene.test.ts ← IN_FLIGHT declares two of these·workspace/status/board.mdNew 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
mainand pushed, nothing unmerged. The tail is four old branches, and the ages are the finding:Branch Ahead of mainLast commit Verdict origin/claude/thirsty-diffie-0cf5070 2026-05-06 Fully merged. Delete — nothing is owed. basepath-test1 2026-04-07 A reverse-proxy experiment ( basePath,robots.txt,sitemap.ts,next.config.ts). Four months old. Decide or delete.teal-legacy4 2026-06-08 Superseded in substance. Teal-as-legacy reached mainthrough the colour supersede;foundation/colour.mdcarries the full Legacy-färger section. Expected outcome is deletion, not a merge.feat/font-hosting30 2026-06-16 The real one. Museo Sans hosting did land ( baseline/fonts/*.woff2andpublic/fonts/are both onmain). 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
mainby 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.lockfiles in.gitthat also could not be removed from there. The bridge has no network either, sogit push origin --deleteis 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-testDone when: the two safe branches are gone locally and on
origin;feat/font-hostingandteal-legacyeach have a recorded outcome (landed, or abandoned with a reason); theirIN_FLIGHTentries inlib/branch-hygiene.test.tsare deleted — and the suite is green, which it cannot be while an entry outlives its branch. And it is onmain.CLOSED 2026-08-07. All four gone, both locally and on
origin(confirmed bygit fetch --prune;git branch -ashows onlymain).feat/font-hosting's one unique file,foundation/en-draft/imagery.md, was retrieved before deletion.teal-legacyconfirmed superseded in substance, as expected.IN_FLIGHTinlib/branch-hygiene.test.tsis 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).
CONTENT_DIRSstill listsphysicalagainst the ratified publishing rule — DONE 2026-08-27Detail
Files:
lib/content.ts·lib/route-manifest.test.tsNew 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 thatfoundation/andbaseline/publish; everything else does not.physical/does not currently exist, which is the only reason this has been harmless — and_state.mdalready records the observation from the backup-branch cleanup that restoring the rescuedphysical/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.mdwithstatus: 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.tsbuilds its manifest by walkingCONTENT_DIRS(:240), and the gate is armed in both directions — it asserts that everyacceptedfile under those dirs has a route, and that no route exists without a file. Removingphysicalchanges 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_DIRSmatches the ratified rule in both files, the route manifest is regenerated in the same commit, and the gate is verified still armed — a deliberately-addedacceptedfile under a non-publishing folder must not produce a route, and must not be silently ignored either.
Teal's position in Norwegian physical environments
Detail
Files:
foundation/colour.md ← Legacy-färger, and the Norway row·workspace/decisions/decision-record-physical-interior-2026-08-06.md ← §4New 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.mdlists 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.
Triage the domain owner's interior guide; record what it decides
Detail
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 ← sameNew and CLOSED 2026-08-06.
Interiör och fysisk miljö.docxarrived 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.mdand judged it against_TEMPLATE.md— no frontmatter, nodepends_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 isproposed, notaccepted, 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 touchfoundation/colour.md, which she does not own.The palette boundary was resolved from an existing rule, not negotiated.
foundation/constant-contextual.mdalready 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.sageis 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 sourcesgit mv'd intoworkspace/sources/, and the floors extracted toworkspace/sources/physical-accessibility-floors-2026-08-06.mdwith 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) andIngen dark modeis 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_onagainst, and the accessibility floors are citable from one page. All three true. Not done and deliberately not attempted: buildingphysical/— see card 40 for why that waits.
Write the three constitutional rule statements
Detail
Files:
constitutional/group-scope.md·constitutional/language-policy.md·constitutional/tokens-are-canonical.mdNot 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_onagainst a real rule rather than a stub. Card 26 (accessibility) and the earliervoice-is-the-present-expert.mdshow 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 namesAuthor: Torfinnin 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
Relatedfield 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 Torfinnmarkers left — andtokens-are-canonical.mdspecifically gives Phase B step 6 (theBASELINE.mdsplit) 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 toworkspace/authoring/, unverified, awaiting Torfinn's voice pass.
Phase B, sequencing step 4: reconcile
patterns/withbaseline/patterns/Detail
Files:
patterns/ ← 7 files, moved in·lib/baseline-content.ts·app/baseline/raw/[...slug]/route.ts·lib/route-manifest.test.tsPart 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/viagit mv. Routes are unchanged — still/baseline/patterns/*— via a newresolveBaselineSource()inlib/baseline-content.ts: checksbaseline/<path>first, falls back to the item's own path from the repo root once a category's files leavebaseline/. Wired intogetBaselinePageData, the/baseline/raw/[...slug]route, and the route-manifest test's own baseline manifest builder (which hadbaseline/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-sideSidebarInteractive.tsx, andfs/pathcan't ship in a client bundle. Moved the resolver tolib/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.mdserves 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 buildclean.
Create
workspace/, move the administrative material, take the eighteen routes outDetail
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 missingWaits 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/. Absorbsplanning/,baseline/planning/,baseline/source/,_sources/,_incoming/,_drafts/,_tooling/and the root working documents.CLAUDE.mdand_state.mdstay at root.documentation/is a new root-level folder, not a workspace subfolder (Torfinn, 2026-08-04). Documentation is a consumable –CLAUDE.mdalready defines its job as letting an outsider understand what the site is and how it works. This resolves thearchitecture/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/). Sosite-map.md, the three architecture notes,aleris-meta-alignment.md,versioning-rules.md,dependency-reciprocity.md,site-spec-v2.mdandslash-commands.mdgo todocumentation/;phase-0,brand-os-refactor-planandmigration-checklist-llm-firstgo toworkspace/plans/.values-layer.mdandconstitutional-core-test.mdneed 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 renamedfilters/, not deleted – removal was considered and rejected because it holds twoacceptedfiles, three of the four declaredepends_on: how-to/ai-tells-and-filters, anddata-products/voice-filter-rules.yamlnames two of them in a machine-readablegenerated_from:field under the tokens discipline, so deletion would orphan a data product. The rule from card 29 also forbids it:workspace/never publishes andacceptedmeans 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: threedepends_on, fourtype:, onelayer:, four yaml references. Do not name the folder to accommodate the unwrittencommunication-genres.md– that is thetutorials/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: insideworkspace/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 readsplanning/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 toworkspace/archive/if their logic is confirmed absorbed into the suite;baseline/tokens/tokens.test.ts:15says 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). Fourdraftpages 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(nostatus– content or working file?) andtools/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/renamedfilters/, twelve references fixed._sources/,_incoming/,_tooling/,_drafts/,_archive/,baseline/planning/,baseline/source/, and every classifiedplanning/file absorbed. Two flagged root working documents (ALERIS-DESIGN-WORKING.md,aleris-patient-product-design-guidelines.md) moved toworkspace/authoring/, their disposition still Torfinn's to decide. Two open calls answered by Torfinn before executing:voice-for-ai-systemstreated as a working file (moved),tools/icon-pickerconfirmed public on purpose (untouched). Commitsff2a8c0,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-testroutes went as planned. The status-filtering fix (card 29's rule, enforced inlib/content.tsfor the first time) removed the four draft pages the card named — and seven moreen-draft/files nobody had cross-checked against their own frontmatter: four with nostatusfield at all, threeproposedordraft.identity/colour-endisappeared with them, since its source (colour.md) isproposed. The known-language-mismatch list narrowed from seventeen entries to five — the ones still genuinelyacceptedEnglish content underlang="sv".Two file pairs need Torfinn's read, not resolved here.
_archive/'s copies ofaleris-design-system-open-questions.md(282 lines vs 117) andaleris-product-design-skill-workplan.md(missing a frontmatter block the other copy has) collided with thebaseline/planning/copies on the same filename. Both_archive/variants landed inworkspace/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 intoworkspace/'s kind-typed subfolders is a judgment call, not a verified fact — done from filename and content signals, not confirmed with Torfinn. Correctable bygit mvlater; flagged here so a wrong placement doesn't read as decided.Reference sweep:
CLAUDE.md,_state.md,workspace/status/board.md, andworkspace/status/file-manifest.mdupdated.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.mdis 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 buildclean, verified live: accepted pages return 200, the newly-excluded draft page 404s.
Write the publishing rule and the transit rule into CLAUDE.md
Detail
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.tsWaits 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/andbaseline/publish. Everything else does not. And a file inside a publishing folder publishes only if it also carriesstatus: acceptedand declares alangmatching 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. Soworkspace/takes three guards: absent fromCONTENT_DIRS, absent from the Baseline nav, and listed inSKIP_DIRSso 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 plusstatus: acceptedpluslang. The move is agit mvin 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.mdnames_inbox/andarc - 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. Theworkspace/-specific guards (absent fromCONTENT_DIRS, absent from Baseline nav, listed inSKIP_DIRS) are card 30's to add whenworkspace/is created, not written here ahead of the folder existing.
Commit the route manifest gate, and extend it to Baseline
Detail
Files:
lib/route-manifest.test.ts·lib/route-manifest.json·vitest.config.ts· branchfeat/route-manifest-gatePullable now, and mostly already done. The gate was built and run on 2026-08-04 –
KNOWN_LANG_MISMATCHis populated from its own first run,ORPHANED_ACCEPTEDcame back genuinely empty, andvitest.config.tsnow includeslib/**. It is sitting uncommitted in the working tree on a branch at the same commit asmain. 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 testrather 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 alangkey), and running it found the whole seven-file pattern library (hero,button-label,confirmation,empty-state,error-message,chat-bubble,chat-response-simple– then atbaseline/patterns/, moved intopatterns/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 underapp/baseline/layout.tsx's hardcodedlang="en"), seventeen in foundation. 174 tests passing, all green undernpm test.
Reconcile the two accessibility homes
Detail
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 pathWaits 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 tofoundation/*,baseline/*,communication/*,physical/*, carrying three bright-lines – no meaning by colour alone, no text below 14px, contrast meets AA. The stub inconstitutional/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 whileen-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 § Accessibilitycites 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.mdwas 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 § Accessibilityrepointed to the new path, along with seven other live references acrossfoundation/en-draft/andbaseline/. Commit0d08aa8(+2 follow-up commits – the firstgit add -Ahit a bad pathspec and silently committed the pre-rewrite content instead of erroring loudly).
The language exit – choose where English lives — DONE 2026-09-10
Detail
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 beenproposedfor nine weeks), it has no home (language-policy.mdis 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
langattribute, in both directions: seventeen of twenty-six locale routes serve English underlang="sv", and three of thirty-four Baseline pages serve Swedish underlang="en". WCAG 3.1.1 is Level A. Also live: asupersededpage, an internal working note whose glossary proposes a term retired on 2026-06-16, and two Swedishdraftpages.Three routes, costed in the brief. A ratify English-canonical and build the
enlocale; B English becomes unpublished source and the site serves Swedish only; C govern the mixture withlangandpublish: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, andidentity/colour-enretired 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
langis 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 chosendone – remaining: the rule is written intoconstitutional/language-policy.md(card 29 carries the zone half intoCLAUDE.md; the language half belongs here), and phase-0 is accepted or re-scoped so nothing else migrates under an unratified decision.
Carry the confirm retirement to its remaining locations
Detail
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 annotationSplit 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.mdhard rule 5 inverted and made the rule of record – it had read "--color-confirm-500is 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.cssstill carried@usage Interactive confirm actionson--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 andbaseline-tokens.jsonregenerated. 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:73carries the same claim inside a March 2026 JSON illustration, against the value#27ae60that 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.The token parser is fooled by a comment
Detail
Files:
baseline/tokens/tokens.test.ts ← the declaration regex, ~line 65Pullable 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: valuewith 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-activeis 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.The boundary test measures rest fills only
Detail
Files:
baseline/tokens/tokens.test.ts ← Test 2, SURFACE_RULES and the loop under it·baseline/buttons/index.html ← the same gap, renderedPullable 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-*-bgonly. 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_RULEShas 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-borderis 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#7ba492is 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.htmlapplied 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-bgdoes 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.
Fix the fitness check's per-variant state blind spot
Detail
Files:
baseline/tokens/tokens.test.ts ← the variant-discovery regex, ~line 88Pullable 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-bgthat yields a variant namedprimary-active, which has no-texttoken and is then skipped silently byif (!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-bghas to be recognised as a state, alongsidebg,textandhover-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 –bgwill always eat the tail ofactive-bgotherwise. 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-innerbeatingfocus-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.Rebuild the button state change from the design return
Detail
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.htmlRetitled 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 aredisabled={!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 testpasses with no allowlisted failures,baseline/buttons/index.htmlshows 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 buildclean, noapp/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 aredisabled={!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.Extend the test surface beyond buttons, as a conformance surface
Detail
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 shippingA 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 § Accessibilityare wider than that: 44px minimum touch target,focus-visiblewith a petrol ring,prefers-reduced-motion: reducedisabling all animation,aria-invalid+aria-describedbyon 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.sepublishes atillgänglighetsredogörelsethat 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 § Accessibilityhas 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 asbaseline/buttons/index.html) plustokens.test.tsTests 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 buildclean.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 § Accessibilityclaims are structural claims about components that don't exist inapp/yet (colour-alone meaning for status messages,aria-invalid/aria-describedbyon 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.
Button state set – adopt the brief, or amend it
Detail
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 ruleUpdated 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 andprefers-reduced-motionintact), 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.mdare still unamended in the return's own edits. Hard rule 5 at:37still asserts confirm-500 is interactive, in the same file whose § Buttons retires it. And § Accessibility still reads "focus-visiblewith 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-activerather 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-visiblewith 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,textandhover-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-activeis 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-visiblewith 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 inBASELINE.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 buildclean. 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."Secondary hover – and whether hover is a fill at all
Detail
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.mdis the assessment; the specimens are inworkspace/archive/button-state-return-2026-08-01/reference/Button States Brief.dc.html(moved fromworkspace/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-bgtoken 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 hover5.4 22.5 4.9 #003A42– rejected on this card6.3 21.7 5.7 petrol-700 #003238– the brief's active10.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 rolesSplit 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.
#003A42measures Δ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 #D9E1E2fill, petrol text, 7.73:1; ΔE 63.8 from restStrongest 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-bgtoken and--state-hoverare 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.- 1b, 1e or 1d touch
Update the records — DONE 2026-08-27
Detail
Files:
workspace/status/changelog.md·_state.md·workspace/status/board.md ← next decision goes hereStruck 2026-08-25 — the manifest is archived (workspace/status/file-manifest.mdfor 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 perdocumentation/versioning-rules.md._state.mdat the initiative root. Do not create a second changelog:workspace/archive/baseline-changelog.mdwas merged in and superseded on 2026-07-28 after dying two days into its life. Mark resolved briefs inworkspace/incoming/.Then propose the next decision on this board: Phase 0 sign-off, plus whether scoping
documentation/dependency-reciprocity.mdmoves ahead of it, since this session's rework came from exactly the gap that document names.Done when:
manifest,changelog and_state.mdreflect reality, and the next decision is on the board.Ship agent-baseline v0.4 to Richard
Detail
Files:
workspace/incoming/2026-06-20-brand-os-into-vibe-coding-repo-brief.mdRuns
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 retiredFixed/Anchorvocabulary 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
filedorarchivedis 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.gitinto~/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, wireCLAUDE.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'stiffotofu@…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 literallyx-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.§5 Regenerate all three packages and verify the mirrors
Detail
Files:
_packages/agent-baseline/·_packages/klinfys-baseline/·_packages/README.md ← rule 6·_packages/agent-baseline/MANIFEST.md ← retired vocabularyThis is the gate for sharing Brand OS again, not the supersede.
Regenerate
_packages/agent-baseline/and_packages/klinfys-baseline/, and record a regeneration path forbhsv-baseline.zip, which predates the manifest rule. Then verify rather than assume: the agent-baseline mirrors ofgovernance:201,BASELINE.md:122andtokens.css:473are verbatim copies, and_packages/README.mdrule 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 thedecided/fixed/anchorvocabulary retired on 2026-06-16.Surface, do not resolve: whether the shipped governance doc keeps its ~18
Fixed/Anchorrule 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/, withklinfys-baseline/andbhsv-baseline.zipexplicitly not regenerated – see below, this is not the full "all three" the done-when asked for.agent-baseline/→ v0.3. All 27 files in itsMANIFEST.md's provenance table re-copied verbatim from canonical and verified file-by-file withdiff– 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 infoundation/colour.md(248, 124, 86instead of245, 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.mdbumped to v0.3, dated 2026-08-05, contents note rewritten.The vocabulary question, surfaced rather than resolved, as asked.
governance/aleris-design-governance.mdcarries 32 mentions of the retiredFixed/Anchor/Hypothesisvocabulary in the shipped copy, unchanged since v0.2. Recorded inMANIFEST.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/andbhsv-baseline.zip– not regenerated, and the reason is itself the finding.agent-baseline/could be regenerated mechanically because itsMANIFEST.mdnames 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 assumingagent-baseline/'s list applies. This is Torfinn's call, not made here.§6 Wire the loader so the English page publishes
Detail
Files:
lib/content.ts ← CONTENT_DIRS line 20, ROUTE_OVERRIDES lines 9–17Two edits, not one.
lib/content.ts:20isCONTENT_DIRS = ['foundation', 'physical']–en-draft/is not scanned. And there is no route mapping for the en-draft colour page inROUTE_OVERRIDES(lib/content.ts:9–17). The English page needs the scan directory and a path mapping before it can publish. CheckSKIP_DIRSandSKIP_FILESfor interactions.Done when: the English colour page renders at its intended route in a local build.
§4c Land the contrast fitness check, failing on secondary and confirm
Detail
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 39The 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 fromworkspace/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 thevar()chains to hexes, assert. This fails today on secondary and confirm – that is correct. Land it with those two as documented known failures (test.failsor 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/andbaseline/appears in the token set. Point it at a defined token-set source – the--color-*declarations inbaseline/tokens/aleris-tokens.css– or it will flagapp/globals.css's own valid custom properties as violations.Either build this or correct the claim at
colour-is-the-aleris-palette.md:39that the check already exists. Do not leave a page asserting a control that is not wired.Done when:
npm testruns, Test 1 fails only on secondary and confirm as documented, Tests 2 and 3 pass, and no page claims an unwired control.§4b Fix the four pre-existing errors
Detail
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 missingNone 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.
- The brand-orange sRGB triplet.
§4 Token and rule ripple, with the retirement reversal applied
Detail
Files:
baseline/tokens/aleris-tokens.css·baseline/BASELINE.md·baseline/governance/aleris-design-governance.md·baseline/reference/aleris-design-tokens.mdRead §1's reversal note first – it overrides every "retired" instruction in §4.
Add
--color-orange-600: #d14811and--color-orange-700: #b23c0e; confirm orange-100. Keep--color-orange-400; add--color-orange-200: #fbd1c0. Add petrol-200#abc7c9and petrol-400#4f868efor scope B.Rewrite
@usageand@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@usageleaves 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-bgoff 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-baselinemirrors. Use the ADR vocabulary –supersededwith a pointer to the replacement, recorded where the Anchor sat. No new retirement procedure is needed:Fixed/Anchorwas retired as a vocabulary on 2026-06-16. Replacement wording is authoring §1C, verbatim.Also:
BASELINE.mdsupersedes "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.
§3 De-duplicate the colour-alone rule across twelve locations
Detail
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.cssThe 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:149becomes the constitutional seed ·foundation/colour.md:239Snabbtest → 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:43is 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.
§1–§2 English colour page and the constitutional rule
Detail
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: proposedwhile in review,depends_ongains the constitutional accessibility node, version bumped toward a Foundation major perdocumentation/versioning-rules.md.§2. Confirm
foundation/en-draft/constitutional/_accessibility-shared-draft.md(nowconstitutional/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. Setnormative: true, carry the rule text verbatim.colour.mdreferences it viadepends_onplus 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.
§0 Safety – snapshot, branch, freeze
Detail
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.mduntil the last step – Englishen-draft/is the edit surface.Git run from the Cowork sandbox leaves stale
.git/*.lockfiles; clear withrm -f .git/*.lock.Done when: tag exists, recovery confirmed, branch created.
Patch the handoff (Patches 1–3)
Detail
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 targetMechanical edits to
workspace/briefs/handoff-colour-supersede-claude-code.mdperworkspace/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 atfoundation/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.
Confirm – retire the variant, or recolour it
Detail
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 decisionDECIDED 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
#4B7F694.63:1, hover#406D5A5.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:195and:197,reference/aleris-design-tokens.md:141and:148,patterns/button-label.md, plus hard rule 5 inBASELINE.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.mdand carried intoworkspace/briefs/handoff-button-states-claude-design.mdas settled context, so the design exploration factors in that secondary carries two jobs.Role wording for the reinstated orange-400 and orange-200
Detail
Files:
workspace/authoring/colour-supersede-scaffold.md ← ramp table, two SLOT markersTwo
[SLOT – Torfinn]markers in the ramp table inworkspace/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.
Rewrite 2G – the print-absence note
Detail
Files:
workspace/authoring/colour-authoring-2026-07-28.md ← edit here·foundation/en-draft/colour.md ← lands here·foundation/colour.md ← Swedish twinBlock 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).