Phase 1a.2 + 1a.3: delete --color-primary and retire the #0066d6 anchor rule - #518
Conversation
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Correction to the body: the sequencing rationale is recorded in Same applies to the |
Screenshot diffs detected
|
Alias hub (--jt-primary, --jt-primary-color) repointed to --color-ruby, so every var(--jt-primary, ...) consumer follows without touching each site. Companion blues in the same rule groups go with it: --jt-primary-dark -> --ruby-700, --jt-primary-hover / #007af4 button hovers -> --color-ruby-hover, #006ddc button edges -> --ruby-700, the rgba(26,140,255,.5) focus ring -> the ruby equivalent. Leaving those blue would give a ruby button a blue hover and a blue edge. Roles: buttons/onsale badge/nav-toggle = accent surface -> --color-ruby (white on #cc342d is 5.1:1, AA both ways); nav hover + current-item = accent text -> --color-ruby; focus outlines -> --color-ruby. Historical comments that record what an undefined var(--color-primary) used to compute to are left verbatim - they are history, not live references. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
c-button, c-hero, c-hero-sections, c-social-share, content-block, testimonials. All accent roles -> --color-ruby: primary/tertiary buttons, hero CTA, typewriter caret, hero badge + stat numbers, breadcrumb hover, eyebrow text, the --color-primary content-block modifier, social focus ring. Companion blues in the same rule groups moved with them - #0066cc hovers -> --color-ruby-hover (bg) / --ruby-700 (text on light, where #e04a42 would miss AA at 3.9:1), rgba(26,140,255,a) -> the ruby triple, and the rgba(17,82,147,a) gradient partner -> the --ruby-700 triple. Alpha kept as alpha where it is load-bearing (hero overlays, video buttons over media, the hero badge tint); --ruby-100 used only where the tint sits on an opaque page background (tertiary button hover). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ten above-the-fold bundles. Every site is an accent role: FL buttons and the generic button reset (accent surface, #fff on #cc342d = 5.1:1), section eyebrows and mission/culture headlines, service card more-links and their icons, the current-page nav ancestor, case-category chips. #006ddc button edges -> --ruby-700 (same darker-edge relationship the blue pair had); the rgba(26,140,255,.1) category-chip tint sits on an opaque card so it takes --ruby-100 rather than an alpha. No blue-family literal left anywhere under critical/. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ten page bundles, 48 sites, all accent roles: section eyebrows, service-card more-links and their icons, grid pagination + load-more pills (text, hover fill and pill border), carousel dots, case-category chips, career share-button hovers. The rgba(26,140,255,.1) !important chip tint sits on opaque cards -> --ruby-100 (the !important stays; it belongs to 1a.3's cleanup, not this step). services.css #007af4 more-link hovers are text on light, so --ruby-700 (#9e2620) rather than --color-ruby-hover, which would land at 3.9:1. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
404, 586, component-bundle, footer, homepage-sections, homepage, legacy-theme-skin, navigation, pagination, single-post, technologies, use-cases-dynamic. Menu hover/current-item and their carets, generic a:hover, FL buttons, footer 'special' badge, card accent bars, tab pills, pagination links. One site did NOT go ruby: .home-report-row-key--next. That block is a three-state status legend (shipped green / blocked --color-ruby / next blue). Mapping next -> ruby would have made it identical to blocked and destroyed the distinction, so it takes --ink-500 as the neutral 'upcoming' marker. This is the case the brief's 'resolve by role, never find-and-replace' rule exists for. Also swept the last blue companions: #007af4 button hovers -> --color-ruby-hover (surface) / --ruby-700 (text on light), #006ddc edges -> --ruby-700, rgba(26,14[01],255,a) tints -> --ruby-100 or the ruby triple. Two navigation.css comments describing live rules were reworded so they no longer claim the elements are blue. No blue-family literal remains in themes/beaver/assets/css. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The definition is gone from foundations/css-variables.css, and home.html's
one inline style="color: #1a8cff" (the Clutch link in the hero) becomes
var(--color-ruby) - link/accent text.
Rendered-output verification on a clean _dest rebuild, not a source grep:
built css with a live var(--color-primary): 0
(17 bundles still carry the string, all inside historical comments that
record what the token used to compute to before it was ever defined -
PostCSS keeps comments in the dev build, prod strips them)
built css or html with #1a8cff in a rule or inline style: 0
What still contains #1a8cff after the build is image assets only: the
inline header logo (logo-dark.svg / logo-white.svg) and 23 icon SVGs. Those
are files, not consumers of the token - see the handoff note.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ly untouched Per coordinator decision (option 1). These icons are UI decoration in an accent role - role-equivalent to the CSS accents moved in batches 1-5 - so #1a8cff -> #cc342d. 41 under themes/beaver/assets/img/icons/theme/ + 6 under content/services/*/submenu-*.svg. One (hover-arrow-icon-hover.svg) carried the value uppercase as #1A8CFF and would have survived a case-sensitive sweep. HARDCODED ON PURPOSE, AND A KNOWN LIMITATION: SVG files cannot read CSS custom properties, so these are literal hex values, not tokens. They are invisible to any future token work - a later palette change has to sweep these files again. Recorded here so nobody assumes --color-ruby covers them. DELIBERATELY NOT CHANGED - themes/beaver/assets/img/icons/logo-dark.svg, logo-white.svg, logo-small.svg. #1a8cff is the brand mark's only colour, so --color-primary was extracted FROM the logo rather than invented. A company's mark is identity, not a design token: a blue mark alongside a ruby UI accent is a coherent pattern (the mark identifies, the accent directs). Recolouring it is Paul's brand call and is out of scope for the design-system phases entirely - not deferred to 1a.4. These three files were left, not missed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
desktop/{homepage,services}/_cta-contact_us and _footer. Recorded with
bin/record-baselines (kept 4, restored 83), and every PNG opened and looked
at before committing, not just captured.
What moved, and why it is an improvement rather than a swap: the CTA card's
40px accent bar and the footer 'we're hiring' badge were blue while the
'Contact Us' button sitting between them was ALREADY ruby. The before state
was internally inconsistent; they now agree. White on #cc342d is 5.1:1.
macOS leg only - the Linux leg goes through the CI dispatch, since local ARM
Docker records plant false drift.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
--link / --link-hover / --link-visited / --link-underline, defined as the values already shipping (#0066d6, #0a4f9a, and the rgba underline), and the existing link rules in style.css and navigation.css repointed at them. Same zero-delta trick as 1a.1: nothing renders differently, which turns the colour decision into a one-line value change carrying its own screenshot review, instead of a colour change tangled up in the specificity surgery that follows. Either half can then be reverted without the other. Recorded in the token comment, because it reframes the whole step: the #0066d6 anchor rule is NOT just a late-cascade specificity monster. Sprint #2 introduced that value because --color-primary #1a8cff measured 3.37:1 and failed WCAG AA; #0066d6 is 4.78:1. We are replacing an accessibility fix whose delivery mechanism was bad, not deleting a mistake - so the replacement has to clear AA as well. One real change rides along, per the agreed design: the body-link hover underline goes to full --color-ruby rather than opaque blue. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Root cause, not the symptom: eight critical/ bundles each carry a copy of
the Bootstrap reboot's `a { color: #007bff }`, and they load AFTER
navigation.css. The site-wide link rule could only beat them by climbing to
a :not() chain 8 class-levels high - which also made it unbeatable by
ordinary page CSS, which is why three page files fight it with scoped
!important.
So the chain was never the disease. Fixed where all the bare `a` rules meet:
those eight reboot rules now resolve var(--link) too, so every bare `a`
agrees, and the site-wide rule drops to plain element specificity (0,0,1).
Anything with a class now beats it naturally - no !important needed.
Kept `text-decoration: none` and `background-color: transparent` in the
reboot blocks; only the colour declaration was live-but-wrong. Deleting the
whole rule would have underlined every link on those eight pages.
EVIDENCE: bin/qtest --changed - 34 runs, 0 failures, 53 screenshots compared,
no failures. A specificity rewrite of the site-wide link rule that moves zero
pixels is the proof the replacement is equivalent; the colour change lands
separately, in step 4, where its diff is legible.
Left a note against reintroducing a :not() chain - if a link needs another
colour, it gets a class.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Body links become ink text with a ruby underline, not ruby text. Ruby is the ACTION colour; a post body full of ruby links stops a reader telling a link from a button. The underline — not the colour — is what satisfies WCAG 1.4.1 (distinguishable by more than colour), which frees the text colour to stay calm and keeps ruby meaning "act here". Contrast, measured rather than assumed: --ink-900 #14110f on white is 18.7:1 and --ruby-700 #9e2620 hover is 8.1:1. Both clear AA comfortably, so this IMPROVES on the 4.78:1 the blue delivered rather than regressing the accessibility fix the anchor rule was originally introduced to make. !IMPORTANT BURN-DOWN — 14 deleted, 0 survivors: pages/blog-list.css 5 -> 0 (filters, aria-current pill, card tags) single-post.css 5 -> 0 (blog CTA button + note, post tags) pages/vibe-code-rescue.css 4 -> 0 (header links on the obsidian band, CTA) pages/blog-single.css 0 -> 0 (none were ever anchor-related) Every deletion is one whose comment cited the anchor rule. The three stale comments explaining why the !important was needed are rewritten rather than left to mislead. NOT TOUCHED, deliberately: the ~36 other !important declarations in those files. 20.02 counted 55 "workarounds waiting on this" by counting every !important in the four files; the real anchor-attributable set is 14. The rest are @media print rules and fights with style.css's legacy heading margins. blog-single.css is the instructive case - its comment says "same class of fight as the #0066d6 anchor rule - both die in Phase 1a", and same class, different CAUSE is exactly how a plausible number becomes a wrong one. Those heading fights die when style.css's heading rules die; that is not 1a.3. Verified by computed style, not source: a body link resolves to color rgb(20,17,15), text-decoration underline, decoration-color rgb(204,52,45), thickness 2px. bin/qtest --changed: 34 runs, 0 failures, 53 screenshots compared, no failures. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ro delta)
Found by computed style, not source grep, per the transparent-background trap
Lane C hit: on a service page every .fl-row/.fl-row-content computes to
rgba(0,0,0,0), and the element actually painting white is
div#fl-main-content.fl-page-content — hardcoded #fff, not a token. Zero
visual delta today because --surface IS #ffffff, which is exactly why it
would have sat undetected until a surface token moved and left half the page
white.
Same duplicated-reboot shape as the a { color: #007bff } fixed two commits
ago: .fl-page-content { background-color: #fff } is copied into TEN bundles
(legacy-theme-skin, theme-main, and eight critical/). All ten now resolve
var(--surface).
PROVEN BY COMPUTED STYLE, not by reading the CSS: with the page loaded,
setting --surface to rgb(1,2,3) at :root changes the painted background of
.fl-page-content to rgb(1,2,3) and removing it restores white. The painter
follows the token now; before this commit it could not.
bin/qtest --changed: 34 runs, 0 failures, 53 screenshots compared, no
failures - as expected for a change that is zero-delta by construction.
DELIBERATELY NOT FIXED, and escalated instead: `body { background-color:
#fff }` is duplicated across the SAME ten bundles. That is the second painter
- blog posts have no .fl-page-content at all, so their ground comes from
body. Two reasons to stop rather than sweep it:
1. It takes the latent total to 20 declarations, which is the number the
coordinator set as the stop-and-report threshold.
2. theme-main.css line 15 carries an explicit KEEP LITERAL warning on the
adjacent body declaration: "reboot-generic selector - var() form invites
minifier merge-hoist / dedup unmask". Converting body declarations to
var() has a known, documented hazard here. That deserves its own step
with its own verification, not a ride-along.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…olour
Reviewer finding B4, confirmed by computed style on the rendered page before
and after.
THE DEFECT I SHIPPED IN step 4: --link is a LIGHT-surface decision, but the
rule applying it is element-scope `a`, which reaches every anchor on every
surface. The retired :not() chain had identical reach - but #0066d6 was a
mid-tone that survived BOTH grounds (3.87:1 on obsidian). --ink-900 does not.
The chain's specificity was the defect; its COLOUR was quietly doing
load-bearing work on the dark bands.
Measured, on the dark grounds:
services/vibe-code-rescue "4.8 out of 5 on Clutch" 1.03:1 (was 3.87:1)
homepage technologies band "audit and stabilize" 1.12:1
Both are bare <a> with no class and no rule anywhere colouring them. Both had
become invisible text.
FIX: the site-wide rule is now `a { color: inherit }`. An unclassed link takes
the colour of the text around it, so it is legible on every surface by
construction and no dark section needs a bespoke override. Pinning any single
colour there - blue or ink - is the actual bug, and the eight Bootstrap-reboot
copies now inherit too so they agree.
Link-ness then has to come from the underline rather than the colour, which is
what WCAG 1.4.1 wants regardless. Wired up for the two prose sites that had no
class of their own: .vcr-why p a (--color-on-dark + ruby underline, now
14.93:1) and .technologies-component .description a (inherits, ruby underline,
legible on both the light and .white variants).
Also fixed from the same review: the css-variables note quoted --ruby-700 on
white as 8.1:1 where it computes to 7.6:1 (still AA), and two style.css
comments still advertised #0066d6 above rules that no longer use it.
bin/qtest --changed: 34 runs, 0 failures, 53 screenshots compared, no failures.
STILL OPEN, deliberately not fixed here - see the handback:
B5 in its general form (a bare anchor in light-page body copy now inherits the
body colour exactly, so nothing but an underline distinguishes it, and the
reboot's `a { text-decoration: none }` suppresses that). The reviewer's fix is
to move the underline onto the site-wide rule and drop `text-decoration: none`
from the eight reboot copies - that underlines every currently-unclassed
anchor site-wide, which is a real visual change needing its own screenshot
review, not a ride-along at the end of this branch.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
All ratios below measured in the browser on the rendered page, not computed
from source - the visual suite cannot see changes this small (see the note at
the end).
B2 homepage TECHNOLOGIES eyebrow, 4.10 -> 5.23:1. --color-ruby #cc342d is a
LIGHT-surface value; on the near-black band it misses AA. Added
`.technologies-component.white .subheading { color: var(--color-ruby-hover) }`
- ruby needs a lighter tone on dark and a darker one on light. Same role,
two values.
B3 use-cases action button, 4.02 -> 5.13:1. The resting surface was painted
with --color-ruby-hover; a hover value used at rest is the bug. Now
--color-ruby, white on it 5.13:1.
N1 case-category chips, 4.40 -> 6.55:1, six sites. Ruby text on a ruby tint
was never going to clear AA; --ruby-700 is the token that exists for exactly
this (already used correctly at .c-button--tertiary:hover).
N3 seven #428AF7 hover borders -> --color-ruby. They sat in the same rule
groups as pills recoloured in 1a.2, so they were flashing blue on hover.
N5 dead .c-content-block--color-primary deleted - no template referenced it.
B5, sized before fixing rather than after. The reviewer's first proposal was
to move the underline onto the site-wide rule and drop `text-decoration: none`
from the 8 reboot copies; on measuring, the genuinely-uncovered population is
ONE link per page. Underlining every unclassed anchor site-wide to fix that is
the wrong trade. Instead `.fl-rich-text p a` - the FL-builder prose container,
which nav, footer and buttons are not inside - takes the underline. Measured
blast radius on /use-cases/ and /services/: 1 anchor each, exactly the one
that needed it; zero indistinguishable prose links left.
A FOURTH hardcoded blue, which every CSS-side grep in this phase missed:
partials/services/related-content.html styles links, a CTA button, a
border-left and tag chips with INLINE `style="color:#0066cc"` etc. Inline
styles beat every cascade change, so those were still blue on service pages
after all of 1a.2 and 1a.3. Now var(--link)/--color-ruby/--ruby-100/--ruby-700.
Lesson worth keeping: a recolour has to grep templates for inline style
attributes, not only stylesheets.
GATE HONESTY: qtest passing is NOT evidence for any of this.
test/application_system_test_case.rb runs section screenshots at
tolerance: 0.02, and a one-word eyebrow or an inline link is far under 2% of a
section image - so a stale baseline still passes. Every number above therefore
comes from a computed-style contrast measurement in the browser. The suite
being blind to colour-only changes in small text is itself a finding; it is in
the handback.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…moved Recorded with bin/record-baselines, macOS leg only. A representative sample opened and looked at, covering each surface class rather than spot-checking one: clients/_technologies (light band), homepage/_technologies (dark band), blog/special/codeblocks (blog CTA), plus mobile. WHY 94 AND NOT THE 4 ACCEPTED EARLIER - this is the important part. The section screenshots assert at tolerance: 0.02 (test/application_system_test_case.rb), and a recoloured eyebrow or an inline link is far below 2% of a section image. So for two whole phases the suite reported 0 failures while baselines still showed the OLD blue. 'qtest green' was never evidence that any of this rendered correctly; it only ever proved nothing large moved. The re-record is what surfaced the true extent. Every contrast claim in the preceding commits comes from computed-style measurement in the browser instead, and the tolerance blindness is written up for the coordinator - a colour-only phase needs a gate the tolerance cannot swallow. Linux leg deliberately not attempted: local ARM Docker records plant false drift, so it goes through the CI dispatch after push. CI failing on missing Linux baselines until then is expected. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Same 4.40:1 class as the six case-category chips, missed on the first pass because the reviewer's finding described them in prose rather than listing them as sites - so I fixed the enumerated six and left these: 586.css:527 careers file-upload button --ruby-100 bg + --color-ruby text components/c-hero-sections.css:491 hero badge ruby .1 tint + --color-ruby Both -> --ruby-700, the token that exists for ruby-on-light. Same one-token fix, same 6.55:1 result as the chips. Verified structurally rather than by re-measuring each: no `color: var(--color-ruby)` remains within three lines of a --ruby-100 or rgba(204,52,45,.1) background anywhere in themes/beaver/assets/css. That is the whole pattern, not a sample of it. The hero badge also drops its `var(--jt-primary, ...)` indirection. --jt-primary resolves to --color-ruby, so routing a contrast-critical value through an alias meant the AA-safe choice could be silently undone by a change to the alias. Contrast-critical colours name their token directly. bin/rake test:critical: 34 runs, 0 failures, 53 screenshots, no failures. NOT DONE HERE, queued for 1a.4: the ruby ramp has no light end. Ruby-on-dark is currently solved three different ways - a hardcoded #ff8a7a in vibe-code-rescue.css:95 and :366, --color-ruby-hover borrowed at rest for the homepage eyebrow (B2, this branch), and nothing at all elsewhere. One --ruby-300 alongside --ruby-700 collapses all three and is a smaller diff than the present state. Coupling the eyebrow to a hover value is structurally the same shape as the B3 bug fixed one commit earlier; it measures 5.23:1 so it is not a blocker, but it should not survive 1a.4. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
a5c42ef to
ad87d10
Compare
The pass queued on #516 could not run while the session sat on #518, where this bundle state did not exist. #516 has merged, so it runs now. **Two corrections to workflows/site-redesign-rollout.md, written four passes ago and both inherited from the plan doc without independent verification:** * The engagement figure. The concept repeated "25.2% scroll / 26.3s vs a 32.9-40.3% site average". Measured: one 3-day Clarity window of five, and the lowest. The windows swing 2.9x (29.89/51.13/75.11/50.91/25.56) and session-weighted across 743 bot-filtered sessions the blog sits at 44.31% / 34.97s - at or above the average it was said to trail. The low window also straddles the 08-20 deploy, so the clean pre-ship baseline is 08-06->08-17: 451 sessions, 56.4% / 40.1s. Blog-first still holds on a better fact - GSC puts the blog at 77% of the site's entire Google traffic. * The course coupling. The concept repeated 20.01's "2.2 couples the course page". True of the FILE, false of the SELECTORS: course/single.html:55 renders class="single-content" with no .post-article, and all 15 styled rules in pages/blog-single.css are .post-article-prefixed. DECOUPLED - 2.3 need not follow 2.2. The genuinely shared file is single-post.css, which also drives bin/generate-template-pdfs. Both failures share a cause, now named as a rule in that concept: **check phase status against GIT, not the plan table.** Phases 2.1 and 2.2 had already shipped (#487 and #494, both 2026-08-20) while the plan still listed them pending, and a status answer was given from the table. A plan records what was decided; only the tree records what shipped. **Added:** * build/test-gates.md - a skip_area mask blinds a gate STRUCTURALLY where tolerance blinds it statistically. All four blog-index screenshots mask .post-feature, which IS the feature slot, so the index content area has never been visually gated at any tolerance, and two phases shipped through that hole. Also: local gates are the merge authority while CI is unreliable, with the resulting Linux-red debt stated rather than hidden; and quote the `[snap_diff] N screenshots compared` count, since a suite that compared nothing also prints "0 failures". * workflows/analytics-access.md - /blog/ fires no scroll_depth at all (page/analytics.html:72 gates on .IsPage, false for list pages), so GA4 cannot see the blog index and Clarity is the only instrument that can. Plus the 3-day-window trap: session-weight across every window, and check whether a window straddles a deploy. * workflows/review-swarm.md - non-colliding agents can still collide with an unmerged branch, and never switch branches under a running agent (it silently changes files it is mid-read of and nothing errors). okf validate --strict: conformant, no warnings on any edited concept. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ped deprecation The Codex round fixed the 2608 specs. One finding was a durable CODE fact left in a project doc, where nobody doing template work would find it. * architecture/hugo-site.md — **a permalink rewrite does NOT change .Section or .Kind.** The taxonomy is `tag = "tags"`; only [permalinks.term] rewrites the URL. A page served at /blog/tags/rails/ still has .Section == "tags", so every `eq .Section "blog"` condition MISSES tag pages while reading as though it covers them — the URL says blog, the page object does not. The proposed analytics gate was written exactly this way and would have shipped instrumentation that skipped the pages it named. * architecture/blog-list-page.md — the same drift in a second form. That concept already records index and tag templates drifting apart in MARKUP, fixed with shared partials. Unifying markup did not unify PREDICATES: a .Section guard added anywhere still covers one and skips the other. Also records the inline !important H1 styles still at list.html:51,70. * design/site-palette.md — two corrections. --color-primary no longer "dies in Phase 1a.2"; it is GONE as of #518, and the seven surviving matches in the CSS are comments recording what each rule replaced — a loose grep reads them as survival. And the --rr-* alias deprecation was missing from the concept entirely: it now names the three live consumers and the rule that matters, **verify by grep at deletion time, never against a written inventory.** That inventory was wrong twice in one review, and single-post.css belongs to the COURSE bundle, so deleting the aliases early breaks blog and course together. okf validate --strict: conformant, no warnings on any edited concept. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…519) * Phase 2 specs + pre-ship baseline: both blog phases already shipped Three artifacts from a parallel spec/measurement pass. The headline is a premise inversion that changes what Phase 2 work remains. **Phases 2.1 and 2.2 already shipped on master**, both on 2026-08-20: #487 (17:35, blog index/tags/posts restyle) and #494 (20:16, whole-blog rebuild). Verified with `git merge-base --is-ancestor e1fa540 origin/master`. The plan's Phase 2 table still lists them as pending rows; 20.03 and 20.04 are therefore specs-of-record plus residual punch-lists, not to-do lists. **The engagement number that justified blog-first does not survive recomputation.** The plan cites 25.2% scroll / 26.3s against a 32.9-40.3% site average. That is ONE 3-day Clarity window of five, and the lowest; the windows swing 2.9x (29.89 / 51.13 / 75.11 / 50.91 / 25.56%). Session-weighted over all 743 bot-filtered sessions the blog sits at 44.31% scroll / 34.97s - at or above the average it was said to trail. That window also straddles the 08-20 deploy, so the clean pre-ship baseline is 08-06 -> 08-17: 451 sessions, 56.4% / 40.1s. What does hold up strategically: GSC shows the blog at 105 clicks / 28d, **77% of the entire site's Google traffic**. **The course coupling was pointed at the wrong file.** 20.01's "2.2 note" is true of the file and false of the selectors: `course/single.html:55` renders `class="single-content"` with no `.post-article`, and all 15 styled rules in `pages/blog-single.css` are `.post-article`-prefixed. Only two selectors reach course. DECOUPLED - 2.3 need not follow 2.2. The genuinely shared file is `single-post.css`. Two blindnesses found and verified, both of which explain why nobody noticed the phases had shipped: * `/blog/` fires no `scroll_depth` at all - `page/analytics.html:72` gates on `.IsPage`, false for list pages. GA4 cannot see the blog index. * All four blog/index screenshots mask `.blog-post` AND `.post-feature` (`desktop_site_test.rb:34,42`, `mobile_site_test.rb:25,33`). `.post-feature` IS the feature slot. The visual gate has never covered the index's content area, at any tolerance. Gaps are recorded as gaps, not estimated: per-post scroll depth is unobtainable (Clarity per-page 0-2% contradicts its own aggregate 25.56% for the identical window, ~3x), no pre-ship GA4 scroll_depth exists (it shipped WITH the rebuild), and no conversion metric exists for the window. Open decision for Paul: the cover shipped article-bleed at 900px, not the plan's full-bleed. Recommendation is to keep 900px - a 100vw break-out risks horizontal body scroll across 624 post dirs and invalidates the `sizes="...864px"` on the LCP image. Docs only - no code, templates, or CSS touched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * OKF: run the deferred pass, and correct two of my own claims The pass queued on #516 could not run while the session sat on #518, where this bundle state did not exist. #516 has merged, so it runs now. **Two corrections to workflows/site-redesign-rollout.md, written four passes ago and both inherited from the plan doc without independent verification:** * The engagement figure. The concept repeated "25.2% scroll / 26.3s vs a 32.9-40.3% site average". Measured: one 3-day Clarity window of five, and the lowest. The windows swing 2.9x (29.89/51.13/75.11/50.91/25.56) and session-weighted across 743 bot-filtered sessions the blog sits at 44.31% / 34.97s - at or above the average it was said to trail. The low window also straddles the 08-20 deploy, so the clean pre-ship baseline is 08-06->08-17: 451 sessions, 56.4% / 40.1s. Blog-first still holds on a better fact - GSC puts the blog at 77% of the site's entire Google traffic. * The course coupling. The concept repeated 20.01's "2.2 couples the course page". True of the FILE, false of the SELECTORS: course/single.html:55 renders class="single-content" with no .post-article, and all 15 styled rules in pages/blog-single.css are .post-article-prefixed. DECOUPLED - 2.3 need not follow 2.2. The genuinely shared file is single-post.css, which also drives bin/generate-template-pdfs. Both failures share a cause, now named as a rule in that concept: **check phase status against GIT, not the plan table.** Phases 2.1 and 2.2 had already shipped (#487 and #494, both 2026-08-20) while the plan still listed them pending, and a status answer was given from the table. A plan records what was decided; only the tree records what shipped. **Added:** * build/test-gates.md - a skip_area mask blinds a gate STRUCTURALLY where tolerance blinds it statistically. All four blog-index screenshots mask .post-feature, which IS the feature slot, so the index content area has never been visually gated at any tolerance, and two phases shipped through that hole. Also: local gates are the merge authority while CI is unreliable, with the resulting Linux-red debt stated rather than hidden; and quote the `[snap_diff] N screenshots compared` count, since a suite that compared nothing also prints "0 failures". * workflows/analytics-access.md - /blog/ fires no scroll_depth at all (page/analytics.html:72 gates on .IsPage, false for list pages), so GA4 cannot see the blog index and Clarity is the only instrument that can. Plus the 3-day-window trap: session-weight across every window, and check whether a window straddles a deploy. * workflows/review-swarm.md - non-colliding agents can still collide with an unmerged branch, and never switch branches under a running agent (it silently changes files it is mid-read of and nothing errors). okf validate --strict: conformant, no warnings on any edited concept. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Fix all 8 Codex findings; one had already reached the OKF bundle Every finding verified against the tree before acting. All eight valid. **P1 - the one that got published.** `.okf/workflows/analytics-access.md` claimed Clarity's per-page numbers "contradict its own aggregate for an identical window and page-set" (~9% vs 25.56%, ~3x). Not the same page-set: ~9% was session-weighted over the TOP TEN pages, 25.56% covers EVERY /blog/ page. The omitted long tail can account for the entire gap. No disagreement was demonstrated and per-post analysis was never ruled out - it needs the full page rows. Corrected in the concept AND in 40.01, kept as a worked near-miss because the shape recurs: the API returns a top-N subset by default and the aggregate on request, so comparing them is the most available mistake to make. The section's own rule is "state the denominator". **P1 - alias inventory would have broken live CSS.** 20.03 omitted blog-list.css:78 and named vibe-code-rescue.css, which has ZERO var(--rr-*) references. Verified inventory now in the spec: blog-list.css (9 lines), single-post.css:434-491 (6), blog-single.css (3). single-post.css carries CTA and tag colour/background declarations reaching the course bundle, so deleting the aliases in 1a.4 on the old list would have broken blog AND course. **P1 - headline baseline was contaminated.** Deploy time now confirmed: #487 at 17:35 and #494 at 20:16 on 2026-08-20, both inside the 08-18->20 window. The 34.97s/743-session headline is superseded by the clean 08-06->08-17 window (451 sessions, 56.4% / 40.1s), with the 12-vs-28-day length mismatch recorded as a follow-on rather than papered over. **P1 - Linux baselines.** Codex is right that CLAUDE.md:148 requires both legs before a PR. That is knowingly overridden (Paul 2026-08-21, CI unreliable). The override and its cost - master's Linux job red until one batched dispatch - are now stated in the spec, with an explicit instruction to do the dispatch before merge if CI is healthy when the phase runs. **P2 fixes:** the analytics gate cannot use `eq .Section "blog"` for tag pages (hugo.toml:37-41 rewrites the term PERMALINK; the taxonomy is `tag = "tags"`, so .Section is `tags`) - needs an explicit term/taxonomy predicate; inline !important H1 styles remain at themes/beaver/layouts/list.html:51,70 and a stylesheet rule cannot override them; the three !importants in blog-single.css must NOT be probed for removal - 20.02:69-81 records that they fight legacy heading-margin rules, not the retired anchor rule, and removing them restores a title-alignment regression; R4 marked done and linked to 40.01. Gates: okf validate --strict conformant, no warnings on edited concepts; bin/hugo-build clean. Docs + bundle only, no code or CSS touched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * OKF: lift the Hugo permalink trap out of the spec, and correct a shipped deprecation The Codex round fixed the 2608 specs. One finding was a durable CODE fact left in a project doc, where nobody doing template work would find it. * architecture/hugo-site.md — **a permalink rewrite does NOT change .Section or .Kind.** The taxonomy is `tag = "tags"`; only [permalinks.term] rewrites the URL. A page served at /blog/tags/rails/ still has .Section == "tags", so every `eq .Section "blog"` condition MISSES tag pages while reading as though it covers them — the URL says blog, the page object does not. The proposed analytics gate was written exactly this way and would have shipped instrumentation that skipped the pages it named. * architecture/blog-list-page.md — the same drift in a second form. That concept already records index and tag templates drifting apart in MARKUP, fixed with shared partials. Unifying markup did not unify PREDICATES: a .Section guard added anywhere still covers one and skips the other. Also records the inline !important H1 styles still at list.html:51,70. * design/site-palette.md — two corrections. --color-primary no longer "dies in Phase 1a.2"; it is GONE as of #518, and the seven surviving matches in the CSS are comments recording what each rule replaced — a loose grep reads them as survival. And the --rr-* alias deprecation was missing from the concept entirely: it now names the three live consumers and the rule that matters, **verify by grep at deletion time, never against a written inventory.** That inventory was wrong twice in one review, and single-post.css belongs to the COURSE bundle, so deleting the aliases early breaks blog and course together. okf validate --strict: conformant, no warnings on any edited concept. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Close the cover deviation: keep 900px article-bleed Decided under Paul's standing "do not wait for me" grant rather than parked. 20.01's Phase 2 table says "full-bleed"; the implementation shipped article-bleed at 900px. Keeping 900px: * A 100vw break-out inside a centred column is the classic source of horizontal body scroll, which CLAUDE.md forbids outright - across 624 post directories at once. * The sizes attribute (single.html:99, :114) is written for an 864px render box. Full-bleed makes every one of those wrong, so this is not a CSS-only change but CSS plus a srcset/sizes revision on the LCP image. * The stated purpose - one confident visual in the fold, wider than the prose - is already met at 900px against a 680px measure. Reversible in one max-width plus a sizes revision, which is why it did not need a person. The TABLE wording should be corrected to "article-bleed", not the code chased to match the table. bin/hugo-build clean. Docs only. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Fix all 7 second-round Codex findings **P1 - R2 still directed an executor to probe the !important removal.** I fixed the S4a discussion last round and missed the residual that repeats it. Deleted, with the reason inline: those declarations fight LEGACY HEADING-MARGIN rules, not the retired anchor rule (20.02:69-81), and removing them restores the title-alignment regression. R2 is now comment hygiene only. **Superseded engagement figure was still the stated rationale in BOTH specs.** 25.2% / 26.3s / 219 is one 3-day window of five, the lowest, straddling the 08-20 deploy; clean pre-ship is 56.4% / 40.1s over 451 sessions, and GSC is 105 blog clicks, not 145. "Visitors leave in the first quarter" is unsupported and is now retracted in both specs. What survives is better: those 105 clicks are 77% of the site's entire Google traffic. **Contaminated window was still named as the active comparator.** The headline was struck last round but the surrounding prose still quoted 743 sessions / 44.31%. Replaced with a table that makes the clean 451-session figures primary and secondary and marks the 743 numbers as audit-only. **The disproven population claim survived in a second place.** Gap 1 still said per-page and aggregate cover "the same window and page-set" and concluded protocol step 3 cannot run. Corrected: top-ten vs all-pages, so step 3 remains EXECUTABLE and the open task is retrieving all rows. Second time this round a fix landed in one location and missed its duplicate; swept for every corrected claim before committing this time. **The 4.2x bot-gap multiplier is withdrawn.** GA4 Organic Search includes Bing and DDG; the GSC figure is Google only. Not equivalent populations, so the multiplier is overstated. analytics-access.md:122-125 prescribes the correct comparison and it was not run. What stands without it: Direct is 8,598 sessions, 91% of the total, which is not plausible human direct navigation. **The "site-redesign-rollout.md does not exist" claims are closed in all three places.** It exists and governs both specs; it read as absent only because the specs were drafted from a worktree on an unmerged branch predating it. Recorded generalisably: a missing-file conclusion from inside a worktree is a branch question first. **"blog-list.css is their last consumer" corrected** - it is one of three, and that summary contradicted the verified inventory later in the same file. Also fixed the line list (nine lines, not the six claimed, and 203 not 204). bin/hugo-build clean. Docs only. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Fix all 9 third-round Codex findings; sweep the corrected metrics everywhere No P1s this round. Three findings were defects in the OKF bundle itself. **My own concept contradicted itself.** site-redesign-rollout.md quoted ~145 GSC clicks at line 65 and the corrected 105 at line 79, and said "live phase status lives in the plan doc" a few lines after establishing that the plan went stale and status must come from git. Both fixed: neither document is a status source, and the figure is now 105 throughout. **The mask warning was overstated.** The masks hide the listing ROWS and the FEATURE SLOT, not "the entire content area" - lead, filters, CTA and pagination stay covered - and the post template has 24 dedicated baselines, so Phase 2.2 was never unguarded. Narrowed to what is true: Phase 2.1's rows and feature slot went unseen. **The GA4 scroll claim was too absolute.** Only the CUSTOM 25/50/75/90 milestones are lost to the .IsPage gate; if enhanced measurement is on, the built-in `scroll` (90%) still fires. Not verified either way here, so the concept now says so rather than asserting GA4 sees nothing - discarding a usable signal because a doc overstated a gap is its own error. **Merge time is not deploy time.** The baseline treated #487/#494 merge timestamps as proof the window was contaminated. GitHub Pages publishes on a separate run that can lag or fail. Downgraded to CONTAMINATED-PENDING- CONFIRMATION with the restore condition stated. **Arithmetic:** 12 days to 28 needs 16 more, not 12 - "four more 3-day pulls" reaches 24. Corrected in both places it appeared. **Tag pages do not share all of 2.1.** They have no feature slot (it lives behind a first-page guard in blog/list.html), so verifying them against the full scope list returns a false negative. **Scope recount:** seven edits across four files, not six across three - 3.7 was added in review and the summary never caught up, which would let an executor skip the taxonomy cleanup. **Baseline churn was describing already-shipped work.** Marked historical; the residual work in 4b requires ZERO visual delta, and a moved baseline there is a regression to investigate, not one to accept. **Swept the corrected metrics through the canonical summaries** - the project README and 20.01 itself both still presented 25.2%/26.3s as current. A cold session reads those first. okf validate --strict conformant; bin/hugo-build clean. Docs + bundle only. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…esign surface **new-page.md §4 instructed new pages to use `--color-primary` (#1a8cff)** — the token Phase 1a.2 DELETED in #518. `var(--color-primary)` now resolves to nothing and CSS drops the declaration silently, so the paved path was producing invisible breakage on the newest pages. Found while fixing the contradiction review flagged; it is the more serious half. **And §4's visual language pointed at `.stitch/design.md`** while the gate added lower in the same file says that file is the dark COVER system and using it on a page yields the forbidden recolour. One document, two contradictory sources. §4 now names `.okf/design/site-palette.md` (LIGHT by default, three deliberate dark surfaces) and sends in-body SVG to the house spec. **There are four design sources, not three.** In-post SVG, Mermaid diagrams and LinkedIn exhibits are governed by `.okf/design/house-visual-spec.md` (hand-drawn style, 2-2.5px strokes, semantic colour — green = money ONLY, labels INSIDE shapes), with social assets also under `linkedin-posts/README.md`. Omitting that row would have sent a diagram review through the page palette and missed its typography and mobile rules entirely. Added to the concept table and the router. Pattern across this branch, now three for three: every time a routing summary compressed a source, it dropped the qualifier that made the source correct — the exceptions, the scope, or the surface it applies to. okf validate --strict conformant; bin/hugo-build clean. Docs + bundle only. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…superseded **P1: I mandated a gate with no way to run it.** All three stitch skills GENERATE — `stitch-design` makes screens, `stitch-design-taste` makes a DESIGN.md, `stitch-loop` iterates — and the MCP surface is create/edit/apply with no critique verb. "Route design review through stitch" was therefore an instruction an agent could only improvise, or satisfy by generating a screen nobody asked for. Contract now explicit: render at 1280x800 and 390x844 first (stitch reviews an image, not a URL), NAME the governing source and paste its rules in (stitch assumes whatever was last loaded), ask for a DELTA against those rules rather than an opinion, and verify each item against the render. If a step cannot be performed, report the gate DID NOT RUN rather than substituting a generic design opinion — that substitution is the failure this route exists to prevent. **Root DESIGN.md contradicted the resolved palette, and would have been followed.** It declares dark JetVelocity "normative for new brand/conversion surfaces", calls light chrome "legacy/incumbent", and specifies `#1a8cff` primary buttons. ADR-0003 resolved chrome to LIGHT on 2026-08-20, and #518 DELETED that token. An agent building a new conversion page today would have gone dark with a token that resolves to nothing. Added a superseded-in-part banner naming all three reversals and corrected the two-layers section; full regeneration is a separate job and is called out as outstanding rather than half-done here. **Precedence for course visuals.** An SVG inside a course lesson matched two rows. Use BOTH, course first: the course file declares itself the single source of truth for course in-post visuals, the house spec carries stroke, semantic colour and label rules the course file does not repeat. Taking either alone drops half the governing rules. okf validate --strict conformant; bin/hugo-build clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…compression pattern **site-palette.md is the LIGHT authority and never mentioned root DESIGN.md, which says the opposite.** DESIGN.md frames dark JetVelocity as normative for new conversion surfaces, calls light chrome "legacy/incumbent", and specifies a token deleted in #518. The banner added there yesterday helps someone who opens that file; it does nothing for someone who opens the authority and reasonably assumes it is uncontested. site-palette now names the sibling, states which wins for page design, and records that regeneration is outstanding. The general form is worth the line it costs: an authority that does not name what contradicts it leaves the reader to discover the conflict by shipping the wrong thing. **Routing text drops the qualifier that made the source correct.** Adding ONE routing rule took five review rounds and every finding had that shape: * "site pages are LIGHT" — lost three surfaces that are dark BY DESIGN * "detail in review-swarm" — lost that the loop's canon/taste steps are COURSE-scoped * "there are three sources" — lost the in-post SVG/Mermaid/social fourth * "route review through stitch" — lost that stitch has no review verb at all Routing text is the most-read and least-reviewed prose in a repo, and the compression that makes it readable is what makes it wrong. Carry the qualifier into the routing line — "LIGHT by default, three documented exceptions" is barely longer than "LIGHT" and is the difference between a correct review and a recolour of a deliberate dark page. And check the destination is EXECUTABLE before making a route mandatory: a gate an agent cannot run gets improvised, which is worse than no gate because it reports as done. Recorded alongside: a rule in this bundle is not ON THE PATH. AGENTS.md routes sessions through docs/workflows/flow-router.md, so a concept nobody is routed to is read only by sessions already looking for it. okf validate --strict conformant, no warnings on either edited concept; bin/hugo-build clean. Bundle only. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* OKF: route design review through /stitch-design Paul, 2026-08-21: "for design review use /stitch-design to provide feedback and use it for critical stuff to consult when it's not clear." Recorded in workflows/review-swarm.md next to the taste pass, and surfaced from design/index.md. Stitch reviews a change against the DESIGN SYSTEM rather than against taste - which is what a generic critic gives you, and why generic critics keep proposing recolours the anchor text already ruled out. It is also the consult for a critical call that is genuinely unclear: input BEFORE deciding, not instead of deciding. Decide-don't-wait still holds; stitch informs a call, it never owns one and is never grounds to park a decision. Two boundaries recorded because both are easy to get wrong: * It COMPLEMENTS the rendered gates rather than replacing them. Baselines and the scroll gate check what SHIPPED; stitch checks what was INTENDED. A change can match its baseline exactly and still be wrong against the system, and a stitch-approved design can still ship broken. * Its output is scoped like any critic's - a punch-list of surgical fixes, not a licence to redesign a working page. Deliberately NOT recorded: that a parallel PR currently owns the Linux baseline re-record (Paul, same message). The accepted-debt POLICY already lives in build/test-gates.md; who holds the work this hour is a state snapshot that would rot within days. okf validate --strict conformant, no warnings on either edited file; bin/hugo-build clean. Bundle only. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Fix both Codex findings: name the design source, and wire stitch into the mandatory route **The rule could have caused the exact failure it prevents.** "Route design review through /stitch-design" did not say WHICH design system stitch should load, and there are three: | Surface | Source | | |---|---|---| | Blog cover images | `.stitch/design.md` | "The Obsidian Engine" — DARK, 2400x1260 | | Site chrome and pages | `.okf/design/site-palette.md` | LIGHT (ADR-0003), confirmed by Paul 2026-08-21 | | Course pages | `.stitch/course-taste-design.md` | taste-scoring anchor | `.stitch/design.md` is the COVER project, not the site. Point stitch at it while reviewing a light page and it judges light chrome against dark cover tokens and recommends the recolour ADR-0003 explicitly rules out — the precise failure this route exists to prevent. The surface table is now the first thing the rule says. **An OKF sentence an agent never reaches is worthless.** AGENTS.md sends every session through `docs/workflows/flow-router.md`; that router routed HTML/CSS to css-consolidation only, `new-page.md`'s Evaluate step named Impeccable alone, and review-swarm step 2 still spawned a generic DESIGN critic. An agent following the canonical flow would finish without ever seeing the rule. Wired into all three, each carrying the surface warning so the routing cannot be followed into the wrong system. Generalisable, and worth the note: recording a rule in the bundle is not the same as putting it ON THE PATH. The bundle is what a session reads when it goes looking; the router is what it reads when it does not. okf validate --strict conformant; bin/hugo-build clean. Docs + bundle only. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Refresh all three OKF surfaces, and fix a sentence that rendered as a list **The follow-up commit edited a concept without refreshing its surfaces.** It added the surface-to-source table and the routing wiring, but left review-swarm.md's timestamp at the pre-edit value, left workflows/index.md describing only "the two-critic pattern and its failure modes", and left the log entry claiming only two files changed when five did (three bundle files plus flow-router.md and new-page.md). Both halves matter and for different reasons: a stale timestamp loses conflict ordering during concurrent maintenance, and a stale index means a cold session browsing the bundle cannot discover the rule from its owning listing. All three refreshed; the timestamp is a measured `date -u` value. **A sentence rendered as a broken list.** A leading-space hyphen mid-sentence opens an unordered-list item, splitting the clause and orphaning the text after it. Joined into one paragraph. The log entry now also records the two things review caught in the first draft - that it named no design source among three, and that it lived in the bundle but not on the mandatory path - because both are the kind of mistake that repeats. okf validate --strict conformant; bin/hugo-build clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Light is the DEFAULT not a monopoly, and the swarm loop is course-scoped Two findings, both about the routing I added flattening a nuance the source concepts already carried. **"Site pages are LIGHT" would have had a critic flag intentional dark design.** site-palette.md says it plainly: "Light is the default, not a monopoly" — three surfaces keep dark BY DESIGN and anything else going dark is the defect. Blog cover art; ONE dark proof band per page; and `/services/vibe-code-rescue/`, a dark variant of the same tokens rather than a second design system. A reviewer following my flat summary would have recommended recolouring a deliberate campaign landing page. Now stated as default-plus-named-exceptions in the router, the concept's surface table, and the new-page evaluate row. **The route sent every surface into a course-scoped playbook.** review-swarm's step 2 diffs content against the COURSE canon and its taste pass scores against `.stitch/course-taste-design.md`. Since AGENTS.md makes the router mandatory, site and blog reviews would have collected findings from rules that do not govern those pages. The loop now declares its scope up front, and the router says to take the stitch step and the render gate from it while skipping the course-canon steps off-surface. Both are the same failure: summarising a source into a routing line drops the qualifier, and the summary is what gets read. When a route points at a concept, the exceptions travel with it or the route is wrong. okf validate --strict conformant; bin/hugo-build clean. Docs + bundle only. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * The paved path told authors to use a deleted token; plus the fourth design surface **new-page.md §4 instructed new pages to use `--color-primary` (#1a8cff)** — the token Phase 1a.2 DELETED in #518. `var(--color-primary)` now resolves to nothing and CSS drops the declaration silently, so the paved path was producing invisible breakage on the newest pages. Found while fixing the contradiction review flagged; it is the more serious half. **And §4's visual language pointed at `.stitch/design.md`** while the gate added lower in the same file says that file is the dark COVER system and using it on a page yields the forbidden recolour. One document, two contradictory sources. §4 now names `.okf/design/site-palette.md` (LIGHT by default, three deliberate dark surfaces) and sends in-body SVG to the house spec. **There are four design sources, not three.** In-post SVG, Mermaid diagrams and LinkedIn exhibits are governed by `.okf/design/house-visual-spec.md` (hand-drawn style, 2-2.5px strokes, semantic colour — green = money ONLY, labels INSIDE shapes), with social assets also under `linkedin-posts/README.md`. Omitting that row would have sent a diagram review through the page palette and missed its typography and mobile rules entirely. Added to the concept table and the router. Pattern across this branch, now three for three: every time a routing summary compressed a source, it dropped the qualifier that made the source correct — the exceptions, the scope, or the surface it applies to. okf validate --strict conformant; bin/hugo-build clean. Docs + bundle only. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Give the stitch route an executable contract; flag DESIGN.md as part-superseded **P1: I mandated a gate with no way to run it.** All three stitch skills GENERATE — `stitch-design` makes screens, `stitch-design-taste` makes a DESIGN.md, `stitch-loop` iterates — and the MCP surface is create/edit/apply with no critique verb. "Route design review through stitch" was therefore an instruction an agent could only improvise, or satisfy by generating a screen nobody asked for. Contract now explicit: render at 1280x800 and 390x844 first (stitch reviews an image, not a URL), NAME the governing source and paste its rules in (stitch assumes whatever was last loaded), ask for a DELTA against those rules rather than an opinion, and verify each item against the render. If a step cannot be performed, report the gate DID NOT RUN rather than substituting a generic design opinion — that substitution is the failure this route exists to prevent. **Root DESIGN.md contradicted the resolved palette, and would have been followed.** It declares dark JetVelocity "normative for new brand/conversion surfaces", calls light chrome "legacy/incumbent", and specifies `#1a8cff` primary buttons. ADR-0003 resolved chrome to LIGHT on 2026-08-20, and #518 DELETED that token. An agent building a new conversion page today would have gone dark with a token that resolves to nothing. Added a superseded-in-part banner naming all three reversals and corrected the two-layers section; full regeneration is a separate job and is called out as outstanding rather than half-done here. **Precedence for course visuals.** An SVG inside a course lesson matched two rows. Use BOTH, course first: the course file declares itself the single source of truth for course in-post visuals, the house spec carries stroke, semantic colour and label rules the course file does not repeat. Taking either alone drops half the governing rules. okf validate --strict conformant; bin/hugo-build clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * An authority that does not name what contradicts it; and the routing-compression pattern **site-palette.md is the LIGHT authority and never mentioned root DESIGN.md, which says the opposite.** DESIGN.md frames dark JetVelocity as normative for new conversion surfaces, calls light chrome "legacy/incumbent", and specifies a token deleted in #518. The banner added there yesterday helps someone who opens that file; it does nothing for someone who opens the authority and reasonably assumes it is uncontested. site-palette now names the sibling, states which wins for page design, and records that regeneration is outstanding. The general form is worth the line it costs: an authority that does not name what contradicts it leaves the reader to discover the conflict by shipping the wrong thing. **Routing text drops the qualifier that made the source correct.** Adding ONE routing rule took five review rounds and every finding had that shape: * "site pages are LIGHT" — lost three surfaces that are dark BY DESIGN * "detail in review-swarm" — lost that the loop's canon/taste steps are COURSE-scoped * "there are three sources" — lost the in-post SVG/Mermaid/social fourth * "route review through stitch" — lost that stitch has no review verb at all Routing text is the most-read and least-reviewed prose in a repo, and the compression that makes it readable is what makes it wrong. Carry the qualifier into the routing line — "LIGHT by default, three documented exceptions" is barely longer than "LIGHT" and is the difference between a correct review and a recolour of a deliberate dark page. And check the destination is EXECUTABLE before making a route mandatory: a gate an agent cannot run gets improvised, which is worse than no gate because it reports as done. Recorded alongside: a rule in this bundle is not ON THE PATH. AGENTS.md routes sessions through docs/workflows/flow-router.md, so a concept nobody is routed to is read only by sessions already looking for it. okf validate --strict conformant, no warnings on either edited concept; bin/hugo-build clean. Bundle only. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Add a fallback for the stitch gate without overriding Paul's instruction Review's last finding argues /stitch-design cannot perform the delta review and the route should use /impeccable critique instead. The technical half is correct - all three stitch skills generate, and the MCP surface has no critique verb. But Paul's 2026-08-21 instruction named stitch specifically, and he has not seen this objection, so silently rerouting his gate to a different tool would be substituting my judgment for a decision he made. Resolved by honouring the instruction and making the gap visible instead: * The route stays /stitch-design with the explicit render/anchor/delta contract. * If stitch returns a generated screen rather than a delta, pair it with /impeccable critique against the SAME named source and SAY which produced the findings. A tooling gap gets reported, never silently rerouted. * The concept carries an explicit open question for Paul: stitch has no critique verb, so if a delta review is the intent, either a wrapper is needed or impeccable should carry this gate with stitch reserved for generation. This keeps the gate runnable today without pretending the tooling fits, and puts the decision where it belongs rather than burying it in a routing line. okf validate --strict conformant; bin/hugo-build clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Design review is /impeccable critique; stitch stays for generation Paul reversed his own instruction the same day: "i was wrong, you should use `/impeccable critique`." Review had established the gap — all three stitch skills GENERATE (`stitch-design` screens, `stitch-design-taste` a DESIGN.md, `stitch-loop` iterations) and the MCP surface is create/edit/apply with no critique verb, so a review routed there returns a generated screen or an improvisation. Switched at all four sites: workflows/review-swarm.md, design/index.md, and the two repo docs on the mandatory path (flow-router.md, new-page.md). The retraction is recorded at each site rather than silently overwritten — a reader who remembers the first instruction learns why it changed instead of wondering which is current. **What survived the tool change is the part that mattered: the ANCHOR.** Four design sources, and the wrong one inverts the verdict no matter which critic runs. The surface-to-source table, the course-visual precedence rule (use both, course first), and the render / name-the-anchor / ask-for-a-delta contract are all tool-independent and unchanged. The tool turned out to be the least durable thing in the rule — worth remembering next time a routing rule is written around a command name. Private memory corrected rather than accumulated, so the retracted instruction cannot resurface from there. okf validate --strict conformant; bin/hugo-build clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * "Tracked separately" was not true; give the DESIGN.md regeneration a real home The banner I added to DESIGN.md earlier today said the full regeneration "is tracked separately". Nothing tracked it. The 2608 project had no outstanding section at all, so the only record of the work was a sentence inside the very file that cannot be trusted — which is to say, no record. Now an **Outstanding** section in docs/projects/2608-site-design-system/README.md with the scope written out: reconcile the two-layers section with ADR-0003, strip every --color-primary reference including the primary-button spec, restate .stitch/design.md as cover-scoped rather than "the site design system", and regenerate .impeccable/design.json if it encodes the same palette — verified that file exists, and /impeccable critique now carries the design-review gate, so it would score pages against stale values. DESIGN.md's banner and site-palette both point at it. Worth keeping as a shape: **"tracked separately" is a claim like any other.** It is the most natural thing to write when deferring work and among the least likely to be checked, because it reads as process rather than as fact. If no tracker can be named, the work is not deferred — it is dropped. okf validate --strict conformant; bin/hugo-build clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Phase 1a.2 and 1a.3 of the Rescue Room rollout
(
docs/projects/2608-site-design-system/20-29-strategy/20.02-phase-1a-plan.md).Style-only recolour — layout does not move.
16 commits, 194 files. Rebased onto current master, zero conflicts.
What lands
--color-primary(#1a8cff) deleted — swept in 6 reviewable batches(theme-main → shared components → critical/ → pages/ → root-level bundles →
the definition itself), so a bad batch reverts alone.
untouched —
--color-primaryis the logo's colour, and that is the reasonto stop calling it "primary", not a reason to keep it.
#0066d6anchor rule retired at its cause (1a.3), in 4steps: introduce link-role tokens at current values (zero delta) → retire the
rule → land the link colour → delete the workarounds it forced.
--surface(zero delta).Gates — macOS local (CI is unreliable right now, per Paul 2026-08-21)
bin/hugo-buildclean. The53 screenshots comparedline matters: it is thetell that the run actually compared rather than silently recording.
Linux baselines are NOT re-recorded and this PR does not claim they are.
Local ARM Docker emulation plants false drift (violated 2026-08-20; CI went red
on exactly the 7 fixtures the rule predicts), and
bin/record-baselines --linuxrefuses to record locally by design. That leg needs one CI dispatch
(
gh workflow run test.yml --ref <branch> -f screenshots=true -f update-baselines=true)once CI is healthy. Until then master's Linux screenshot job stays red — known,
accepted debt, and the first thing to clear when CI recovers.
Baselines in this PR
94 macOS baselines re-recorded across 1a.2/1a.3 — not the ~3 a reader might
expect from "a recolour".
Review
Pre-commit reviewers caught a genuinely broken 1.03:1 contrast pair that
self-review had cleared twice, plus the last two ruby-on-ruby-tint survivors
(fixed in
66a898d1). No blocking findings outstanding.Sequencing note
This lands BEFORE the Phase 2 blog rebuild, inverting the written blog-first
order — because 1a.2/1a.3 are already built and they edit the same
pages/blog-list.css/pages/blog-single.cssthat 2.1/2.2 rewrite. Startingthe blog on top of an unmerged 194-file recolour is exactly the merge pain the
1a/1b split exists to avoid. Rationale recorded in
.okf/workflows/site-redesign-rollout.md.🤖 Generated with Claude Code