Skip to content

Phase 1a.4: one eyebrow style (4 variants → 1) - #528

Merged
pftg merged 5 commits into
masterfrom
phase-1a4-eyebrow
Aug 21, 2026
Merged

pftg merged 5 commits into
masterfrom
phase-1a4-eyebrow

Conversation

@pftg

@pftg pftg commented Aug 21, 2026

Copy link
Copy Markdown
Member

Second 1a.4 item. Four eyebrow variants converge on the blog's canonical form —
the one the plan records as already done.

Canonical

pages/blog-list.css .blog-eyebrow — 12px / 700 / 0.14em / uppercase /
--color-ruby

Variant Was
services-critical.css --color-ruby, weight 600 — no size, tracking or transform
careers-critical.css same near-miss
components/content-block.css same near-miss
pages/friday-report.css 13px, weight 600, 0.18em, --ruby-700

The green suite proves nothing for three of these

Only the services page is in the screenshot set. For careers, content-block and
friday-report, [snap_diff] 53 screenshots compared, no failures means "not
captured"
, not "unchanged" — exactly the trap the phase brief flags for
eyebrows.

Verified by computed style in the rendered production build instead:

fontSize 12px · fontWeight 700 · letterSpacing 1.68px (= 0.14em × 12)
textTransform uppercase · color rgb(204,52,45)

For services, which is captured, I read the rendered _overview.png — the
eyebrow sits small, ruby and tracked above the H1.

Contrast, measured

This commit lightens one thing: friday-report moves --ruby-700 → --color-ruby.
Against that page's actual background — rgb(253,245,244), a warm tint, not
white — the ratio is 4.77:1. Passes AA for normal text (4.5:1), but the
margin is thin: the same pair on pure white is ~5.1:1, and any darkening of that
tint pushes it under. Recorded so the next person to touch that surface knows
the eyebrow is the binding constraint.

Gates

  • bin/test — commit 1: 3 baselines moved (services), reviewed and accepted.
    Commit 2: 53 screenshots compared, no failures, no baseline moved.
  • bin/hugo-build clean
  • macOS only; Linux baselines belong to the parallel PR

1a.4 remaining

Three button roles (not started). Footer onto surface-ink stays blocked —
see #526; it needs dividers and adjacent dark sections migrated together across
7+ bundles.

🤖 Generated with Claude Code

pftg and others added 2 commits August 21, 2026 04:14
"ONE eyebrow style" - first bundle-group. The canonical form is the blog's,
which shipped in Phase 2 and the plan records as already done
(pages/blog-list.css `.blog-eyebrow`): 12px / 700 / 0.14em tracking / uppercase
/ --color-ruby.

services-critical.css carried a near-miss: --color-ruby and weight 600, no
size, tracking or transform. Now matches, keeping `text-align: center` which is
layout rather than type.

The markup already says "SERVICES" in caps, so `text-transform` is a no-op here
and the visible delta is size, weight and tracking only - worth knowing before
reading the diff as larger than it is.

Verified by looking, not by a green suite: the section screenshot at 0.02
tolerance would not reliably catch a small type change, so I read the rendered
`services/_overview.png`. The eyebrow renders small, ruby, tracked and legible
above the H1.

Gate: bin/test - [snap_diff] 53 screenshots compared, 3 failures, all the
intended eyebrow change (services/_overview, services/_services,
mobile/services), reviewed and accepted. bin/hugo-build clean. macOS only.

Three variants remain for the following commits: careers-critical.css,
components/content-block.css, and pages/friday-report.css (which also deviates
on colour, using --ruby-700).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
careers-critical.css, components/content-block.css and pages/friday-report.css
now match pages/blog-list.css `.blog-eyebrow`: 12px / 700 / 0.14em / uppercase /
--color-ruby. Four variants become one.

friday-report was the largest deviation - 13px, weight 600, 0.18em tracking, and
--ruby-700 rather than --color-ruby.

**The suite is green and that proves nothing here.** None of these three pages
is in the screenshot set, so `[snap_diff] 53 screenshots compared, no failures`
means "not captured", not "unchanged" - exactly the trap the phase brief warns
about for eyebrows. Verified by computed style in the rendered production build
instead:

  fontSize 12px · fontWeight 700 · letterSpacing 1.68px (= 0.14em x 12) ·
  textTransform uppercase · color rgb(204,52,45)

**Contrast measured, because this commit darkens nothing and lightens one
thing.** Moving friday-report from --ruby-700 to --color-ruby reduces contrast.
Against that page's actual background - rgb(253,245,244), a warm tint rather
than white - the ratio is **4.77:1**. That passes AA for normal text (4.5:1),
but the margin is thin: on pure white the same pair is ~5.1:1, and any future
darkening of that tint would push it under. Recorded so the next person to
touch that surface knows the eyebrow is the constraint.

Gate: bin/test - [snap_diff] 53 screenshots compared, no failures, no baseline
moved. bin/hugo-build clean. macOS only.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 21, 2026 •

Copy link
Copy Markdown
Contributor

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 6b04156d-e1b6-40c4-bbd8-6ef37d31a799


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Aug 21, 2026 •

Copy link
Copy Markdown
Contributor

Review caught it and the cascade confirms it: `pages/careers.css:1215`
re-declares the identical selector
(`.fl-builder-content .careers-why-us-eyebrow .fl-rich-text`) with
`font-weight: 600`, and pages/ loads AFTER critical/ at equal specificity. My
edit to careers-critical.css could never take effect.

Reverted rather than left in. A change that looks like work and does nothing is
worse than no change: it reads as "careers is done" to the next person, and the
green suite would not contradict them because careers is not in the screenshot
set at all.

Scope correction that follows from the same finding: **"one eyebrow style" is 41
FL eyebrow rules across 8 files**, not the four I found by grepping for the
obvious names - homepage (11), single-service (7), about-us (6),
single-use-cases (5), services-critical (4), careers (3), careers-critical (3),
use-cases (2). Aligning 41 copies is the wrong shape anyway; the intent is ONE
definition. That needs a centralised rule plus a load-order decision so it wins
against the per-page declarations, which is a design call rather than a sweep.

What remains in this branch is verified and effective: services-critical
(rendered and read), components/content-block, and pages/friday-report (computed
style confirmed, contrast measured at 4.77:1).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@pftg

pftg commented Aug 21, 2026

Copy link
Copy Markdown
Member Author

Attempted the full sweep review asked for (all 41 FL eyebrow rules) and reverted it — it introduces a WCAG AA failure. Measured in the rendered production build:

Eyebrow Contrast Threshold
home-services-eyebrow 4.10 4.5 ❌ ruby on black
home-clients-eyebrow 4.10 4.5 ❌ ruby on black
home-why-us-eyebrow 5.13 4.5 ✅ on white

The canonical eyebrow (--color-ruby, 12px) was designed against the blog’s light surfaces. Shrinking to 12px makes these unambiguously "normal text", so the 4.5:1 threshold applies and ruby-on-black fails at 4.10.

"One eyebrow style" cannot be one style. Eyebrows sit on both light and dark surfaces, so the canonical form needs a dark-surface variant with a lighter ruby — a design decision, not a mechanical sweep. technologies.css:10 already hints at this ("on the .white variant this eyebrow sits on the near-black band").

This PR keeps only the three conversions verified safe on light surfaces: services-critical, components/content-block, pages/friday-report.

Branch is green: [snap_diff] 53 screenshots compared, no failures.

pftg and others added 2 commits August 21, 2026 04:37
Measured in the rendered production build, and the second consequence is the
one worth having:

| Token | on #000 | on --surface-ink #14110f |
|---|---|---|
| --color-ruby #cc342d | 4.10 FAIL | 3.67 FAIL |
| --color-ruby-hover #e04a42 | 5.23 pass | 4.68 pass (thin) |
| --ruby-700 #9e2620 | 2.75 FAIL | 2.46 FAIL |

**1. "ONE eyebrow style" is self-contradictory as written.** The canonical
eyebrow was designed against the blog's LIGHT surfaces. Applied site-wide it
drops home-services-eyebrow and home-clients-eyebrow to 4.10:1 on their dark
band - below AA for normal text, which 12px unambiguously is. I swept all 41
rules, measured, and reverted rather than ship it.

**2. Migrating the dark bands to --surface-ink makes contrast WORSE.** #14110f
is lighter than #000, so ruby-on-dark falls 4.10 -> 3.67. That links the two
blocked items: the footer/dark-surface work cannot ship before the on-dark
accent is decided, or it degrades contrast on every band it touches. Nothing in
the plan connects them, and a sweep done in either order alone would have
regressed accessibility silently - those homepage sections carry no contrast
test.

Only --color-ruby-hover clears AA on both grounds, and it is named for a hover
state, so using it as a static accent is semantically wrong. The ramp has
--ruby-700 for "text-on-light where AA needs more" and no counterpart for dark.
Naming one is a design call, not a sweep, so it goes to Outstanding rather than
into a commit.

Docs only. bin/hugo-build clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two review findings, both verified and fixed.

**friday-report had no screenshot coverage, and the test said to add it here.**
`desktop_site_test.rb` carried, verbatim: "No screenshot baseline yet: the 2608
site-wide recolour is mid-flight and would churn it immediately. Record with the
bundle's first visual PR." This IS that PR - 1a.4 moved `.friday-eyebrow` onto
the canonical style - so computed-style checks alone were the only thing
guarding a visual change.

Adding the assertion was not enough on its own: `friday_report` is not in
`CRITICAL_TESTS`, so `bin/test` skipped it and the suite reported 34 runs / 53
screenshots exactly as before. Coverage that never executes is the same
"looks like work, does nothing" failure as the careers eyebrow earlier on this
branch, so `friday_report` is now in the critical list. Suite goes 34 -> 36 runs,
87 -> 99 assertions, and two baselines record.

Baselines reviewed before committing, not merely captured: mean brightness 0.963
(not a black or error frame) and the render read end to end - nav, hero, the
canonical ruby eyebrow, canon-compliant proof chips, disclaimer, report body.

**The AA blocker was only in the project README, which is not on the path.**
`design/site-palette.md` now carries the measured table and a standing rule:
until an on-dark ruby is named, do not apply a ruby text token to any of the
three deliberate dark surfaces. AGENTS.md routes sessions through OKF, not
through a project README - the same "recorded but not on the path" failure this
branch already hit once with the stitch route.

Gate: bin/test - 36 runs, 99 assertions, 0 failures, [snap_diff] 53 screenshots
compared, no failures, plus 2 newly recorded. bin/hugo-build clean.
okf_validate .okf exits 0 conformant; --strict exits 1 by design on this bundle.

NOT done, and flagged for the fourth time: Linux baselines. Scoped out by Paul
("handled in parallel PR, ignore for now") and unrecordable locally without
planting false drift.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@pftg
pftg merged commit 84cf3f3 into master Aug 21, 2026
4 of 5 checks passed
@pftg
pftg deleted the phase-1a4-eyebrow branch August 21, 2026 02:52
pftg added a commit that referenced this pull request Aug 21, 2026
**My PurgeCSS check was a false green.** I cited `grep -rc 'c-button--primary'
_dest/public-dev/css/*.css` returning nothing. That command returns nothing
whether or not the component is adopted: public-dev is built in DEV mode where
PurgeCSS is disabled, and this CSS is emitted INLINE in the HTML rather than
under css/*.css. Re-verified against the production tree - `grep -rl 'c-button'
_dest/public-test/` returns nothing and components.css is not referenced from
index.html - so the conclusion survives, but the evidence I published for it did
not. Both are now recorded, because "right answer, wrong proof" is the failure
that makes the NEXT claim untrustworthy.

**The live buttons are five families, not one.** `.fl-button`, `.btn`/
`.btn-primary` (navigation), `.btn--primary` (shortcodes/cta), `.action-button`
(use-cases) and `.pp-button` (services). My "sweep `.fl-button`" framing would
have left four families outside the roles.

**The tags deliverable had vanished from the matrix.** My four-row table
silently replaced "tags to ink site-wide" - an actual scope item - with the
`--rr-*` alias deletion, which the brief lists separately. An executor reading
it would skip the 17-bundle tag work entirely. Tags restored as an OPEN row,
honestly marked NOT investigated, and the alias row relabelled as the separate
deletion item.

**The plan linked to a blocker absent from this branch.** It was cut from a
stale origin/master predating #528. Rebased onto current master; the ruby-on-dark
Outstanding entry the note points at now exists here.

Docs only; bin/hugo-build clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
pftg added a commit that referenced this pull request Aug 21, 2026
…ed (#529)

* "Three button roles" is already built and never adopted

Investigated the last untouched 1a.4 item. `components/c-button.css` already
defines exactly three roles - --primary (ruby/white), --secondary (white/dark),
--tertiary (transparent/ruby) - and is already tokenised apart from four
`#ffffff` literals.

I tokenised those literals, then checked whether it mattered and reverted:
**`c-button--*` appears ZERO times in any template or content file**, and
PurgeCSS strips it from every shipped bundle - `grep -rc 'c-button--primary'
_dest/public-dev/css/*.css` returns nothing. The component was built and never
adopted; the live buttons are FL Builder's `.fl-button`.

Reverting rather than shipping was the point. A tidy diff against dead code
reads as "button roles: done" to the next person and changes nothing a visitor
sees - the third instance of that failure on this phase, after the careers
eyebrow (overridden by a later file) and the friday screenshot assertion
(excluded from CRITICAL_TESTS).

The real item is either adopting c-button in templates (a markup migration) or
bringing `.fl-button` onto the three roles (another sweep through the per-page
FL export CSS). Both are larger than the plan line implies, and the second is
the same legacy-export problem as the footer and eyebrow items. The tertiary
role also sits on the on-dark AA blocker wherever it lands on a dark band.

Recorded in Outstanding. Docs only; bin/hugo-build clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Correct the 1a.4 plan against what execution actually found

Recording measured reality in the plan itself, because the next session reads
the plan and would otherwise re-estimate the same four items the same way.

One of four items was what the line said. The alias deletion shipped first try
with zero visual delta. The other three are materially different work:

* Footer onto surface-ink is not a footer change - it is one edge of a dark
  region built from a CSS background, an SVG fill on a pointer-events:none
  shape layer, and a painter still unidentified. 7+ bundles, 1b in size.
* One eyebrow style cannot be one style - the canonical form was designed
  against LIGHT surfaces and drops two homepage eyebrows to 4.10:1 when applied
  site-wide. Swept, measured, reverted.
* Three button roles is already built and never adopted - c-button--* appears
  zero times in markup and PurgeCSS strips it from every bundle.

And the connection nothing in the plan made: **two of them are gated by the same
missing token.** --color-ruby is 4.10:1 on #000 and 3.67:1 on --surface-ink, so
the dark-surface migration makes contrast WORSE. Eyebrows and the tertiary
button role both sit on it.

Also recorded, because it is the transferable part: the estimates were written
against the TOKEN layer while the work is against the FL-Builder export CSS that
redeclares everything per page. Three changes this phase looked complete, passed
every gate, and did nothing at runtime - an eyebrow rule overridden by a later
file, a screenshot assertion excluded from CRITICAL_TESTS, and a tokenisation of
purged dead code. The plan now tells the next session to verify a change reaches
the rendered page before counting it done.

Docs only; bin/hugo-build clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Fix all four review findings, including a false green in my own evidence

**My PurgeCSS check was a false green.** I cited `grep -rc 'c-button--primary'
_dest/public-dev/css/*.css` returning nothing. That command returns nothing
whether or not the component is adopted: public-dev is built in DEV mode where
PurgeCSS is disabled, and this CSS is emitted INLINE in the HTML rather than
under css/*.css. Re-verified against the production tree - `grep -rl 'c-button'
_dest/public-test/` returns nothing and components.css is not referenced from
index.html - so the conclusion survives, but the evidence I published for it did
not. Both are now recorded, because "right answer, wrong proof" is the failure
that makes the NEXT claim untrustworthy.

**The live buttons are five families, not one.** `.fl-button`, `.btn`/
`.btn-primary` (navigation), `.btn--primary` (shortcodes/cta), `.action-button`
(use-cases) and `.pp-button` (services). My "sweep `.fl-button`" framing would
have left four families outside the roles.

**The tags deliverable had vanished from the matrix.** My four-row table
silently replaced "tags to ink site-wide" - an actual scope item - with the
`--rr-*` alias deletion, which the brief lists separately. An executor reading
it would skip the 17-bundle tag work entirely. Tags restored as an OPEN row,
honestly marked NOT investigated, and the alias row relabelled as the separate
deletion item.

**The plan linked to a blocker absent from this branch.** It was cut from a
stale origin/master predating #528. Rebased onto current master; the ruby-on-dark
Outstanding entry the note points at now exists here.

Docs only; bin/hugo-build clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
pftg added a commit that referenced this pull request Aug 21, 2026
The re-record was dispatched to CI on this branch (run 32526977795, native
amd64 - the only trusted way to record linux/ baselines; local ARM Docker
plants false drift) and succeeded. FORCE_SCREENSHOT_UPDATE re-records
EVERYTHING, so the bot commit touched 83 files and needed screening.

Screened with the gate's OWN instrument rather than by eye or by file size:
capybara-screenshot-diff counts a pixel as differing only above
perceptual_threshold = 2.0 dE00, and the default tolerance is now 0.0001 of
the frame. Computing that same libvips dE00 fraction for every file in the bot
commit splits it cleanly:

  keep  77  would FAIL the gate, so the rewrite is real
  drop   6  difference_level exactly 0.00000000 - would have PASSED

The 6 dropped are pure encoder churn with zero perceptual difference:
desktop+mobile course/landing, desktop+mobile blog/special/codeblocks/text,
clients/agent-inbox/_overview, clients/single-full.

Of the 77 kept, 8 are NEW keys with no linux baseline at all - friday_report
(desktop+mobile) and the three register pilots next/{editorial,precision,
rescue-room} (desktop+mobile) - and the rest span 0.000139 to 0.973.

Why the Linux set is broader than the macOS one (77 vs 50), which looks wrong
until you check it: earlier PRs re-recorded only the macOS side. #528 committed
macos/mobile/services.png and left its linux/ twin stale, so linux/mobile/services
carries a visible vertical shift that macOS no longer has - confirmed by
painting the dE00 mask over the capture: identical content, offset position.
The reverse also holds - linux/desktop/blog/special/inline_style_post barely
moves (0.000139) because the Linux blog baselines were already re-recorded
after #520, while macOS was 25% stale. Neither OS is a subset of the other.

Two kept files were inspected directly rather than accepted on their number:
linux/desktop/404.png (0.001235) is a 4px-wide strip at the right edge - a
scrollbar artifact, page content identical, and 4 x 640 px is exactly the
reported difference_level; linux/mobile/services.png (0.1389) is the #528
shift above. Both render correctly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011SP5gaqXEgUie8pdFrmbeJ
pftg added a commit that referenced this pull request Aug 21, 2026
…pting

The macOS visual suite has been red on master since #540. `bin/rake
test:system` at clean HEAD: 79 runs, 26 failures, 134 screenshots compared,
48 failures. All 48 are correctly-detected staleness, not regressions -
nothing here was verified by difference_level alone.

Why they went stale: a green run restores the git-HEAD image over the fresh
capture, so any change small enough to pass fossilizes the baseline. #540
was large enough to fail and simply was not re-recorded; the rest had been
accumulating under the old 0.02 default since #520 and #528.

Classification (baseline-vs-candidate pixel transitions, not diff levels):

  42  #540 dark-surface recolour. rgb(0,0,0) -> rgb(20,17,15) accounts for
      94-100% of every changed pixel in each file (--surface-ink #14110f).
      Footers, CTA bands, contact_us, both pagination frames, codeblock
      frames - the same surfaces #540's own commit message lists as failing.

   3  Post `description` backfilled by #520, which the post template renders
      as a grey lead above the body, pushing content down. Verified the
      rendered text is the frontmatter string verbatim, e.g.
      stimulus-keyboard-event-filter: "In the last update of Stimulus
      v3.2.2, the ability to filter the event by PageDown and PageUp keys
      was added." (desktop+mobile code_highlight_post, inline_style_post)

   2  Course module eyebrow added by #552 - "MODULE 1 - HYPOTHESIS & SMOKE
      TEST" now renders in ruby above the H1, shifting the page down.
      (desktop+mobile course/chapter)

   1  services/_testimonials-header: a 1px vertical offset from #528's
      eyebrow consolidation. #528 touched services-critical.css and
      content-block.css and re-recorded only the two sections that exceeded
      0.02; the sections below the eyebrow shifted 1px, passed under the old
      default, and fossilized. Measured, not inferred: realigning the
      baseline by dy=+1 drops the residual to exactly 0.0000, i.e. the
      content is pixel-identical and only its position moved.

Evidence that these are stale baselines rather than flaky rendering: two
consecutive runs of DesktopSiteTest#test_services reported difference_levels
identical to 17 decimal places (_technologies 0.013838252314814815,
_use-cases 0.01749807098765432, _testimonials-header 0.005922550154320988),
and a second full-suite run produced the same 48-file set.

One instrument error worth recording, since it nearly became a finding: the
first transition script loaded each baseline from a reused temp filename, and
libvips caches operations by filename - so runs 2..n silently re-compared
image 1. It reported services/_footer's baseline as a pure-black frame. A
brightness audit over all 155 tracked macOS baselines refuted it (darkest
mean is 69/255, none below 1.0), and reloading from git buffers fixed it. The
control that keeps the rewritten script honest: an unchanged baseline
(desktop/vibe_code_rescue) reports exactly 0 changed pixels.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011SP5gaqXEgUie8pdFrmbeJ
pftg added a commit that referenced this pull request Aug 21, 2026
…easured

SECTION_CONFIGS was introduced one day ago as an explicitly TEMPORARY shield
holding ~22 section screenshots at the old 0.02 tolerance while their drift
was unmeasured, with the exit condition written into its own comment: "delete
it once they are measured and re-recorded."

Measured both ways on the full macOS system suite, same environment, only this
variable changed:

  shield in place    134 screenshots compared, 48 failures
  shield emptied     134 screenshots compared, 50 failures

Cost of deleting it: +2 screenshots, both on /services/ -
desktop/services/_use-cases (0.017498) and desktop/services/_technologies
(0.013838). Both are the same 1px vertical offset from #528's eyebrow
consolidation already classified in the previous commit: realigning baseline
to candidate by one pixel drops the residual to exactly 0.0000 in several
bands, so the content is pixel-identical and only its position moved. Both are
re-recorded here, which is the last thing the exit condition asked for.

Deleted: SECTION_CONFIGS, screenshot_config_for, extract_section_key,
test/support/screenshot_section_config.rb and its unit test. That file existed
so the table could be unit-tested without booting Hugo or Capybara; with the
table gone there is no logic left to test, only a constant.
DEFAULT_SCREENSHOT_CONFIG moves onto ApplicationSystemTestCase unchanged at
0.0001. Per-call tolerances are untouched - ~30 calls pin their own, mostly
0.03, and those still win over the default.

Gates, all on this exact tree:
  bin/rake test:unit    287 runs, 6143 assertions, 0 failures
  bin/hugo-build        clean, 8 validators passed
  bin/rake test:system  79 runs, 218 assertions, 0 failures
                        [snap_diff] 134 screenshots compared, no failures

The suite was run with the shield deleted BEFORE these two baselines were
accepted, and reported exactly the 2 predicted failures and no others - so the
deletion is measured, not assumed.

.okf/build/test-gates.md is updated: the shield paragraph is replaced by the
measurement, and a misattribution is corrected. That file claimed all five
failing /services/ screenshots were the #540 dark-surface recolour; only the
two large ones are. The three small ones carry 0.13% or less of the
black->ink transition and are #528's 1px shift.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011SP5gaqXEgUie8pdFrmbeJ
pftg added a commit that referenced this pull request Aug 21, 2026
The re-record was dispatched to CI on this branch (run 32526977795, native
amd64 - the only trusted way to record linux/ baselines; local ARM Docker
plants false drift) and succeeded. FORCE_SCREENSHOT_UPDATE re-records
EVERYTHING, so the bot commit touched 83 files and needed screening.

Screened with the gate's OWN instrument rather than by eye or by file size:
capybara-screenshot-diff counts a pixel as differing only above
perceptual_threshold = 2.0 dE00, and the default tolerance is now 0.0001 of
the frame. Computing that same libvips dE00 fraction for every file in the bot
commit splits it cleanly:

  keep  77  would FAIL the gate, so the rewrite is real
  drop   6  difference_level exactly 0.00000000 - would have PASSED

The 6 dropped are pure encoder churn with zero perceptual difference:
desktop+mobile course/landing, desktop+mobile blog/special/codeblocks/text,
clients/agent-inbox/_overview, clients/single-full.

Of the 77 kept, 8 are NEW keys with no linux baseline at all - friday_report
(desktop+mobile) and the three register pilots next/{editorial,precision,
rescue-room} (desktop+mobile) - and the rest span 0.000139 to 0.973.

Why the Linux set is broader than the macOS one (77 vs 50), which looks wrong
until you check it: earlier PRs re-recorded only the macOS side. #528 committed
macos/mobile/services.png and left its linux/ twin stale, so linux/mobile/services
carries a visible vertical shift that macOS no longer has - confirmed by
painting the dE00 mask over the capture: identical content, offset position.
The reverse also holds - linux/desktop/blog/special/inline_style_post barely
moves (0.000139) because the Linux blog baselines were already re-recorded
after #520, while macOS was 25% stale. Neither OS is a subset of the other.

Two kept files were inspected directly rather than accepted on their number:
linux/desktop/404.png (0.001235) is a 4px-wide strip at the right edge - a
scrollbar artifact, page content identical, and 4 x 640 px is exactly the
reported difference_level; linux/mobile/services.png (0.1389) is the #528
shift above. Both render correctly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011SP5gaqXEgUie8pdFrmbeJ
pftg added a commit that referenced this pull request Aug 22, 2026
* Re-record 48 stale macOS baselines: every diff classified before accepting

The macOS visual suite has been red on master since #540. `bin/rake
test:system` at clean HEAD: 79 runs, 26 failures, 134 screenshots compared,
48 failures. All 48 are correctly-detected staleness, not regressions -
nothing here was verified by difference_level alone.

Why they went stale: a green run restores the git-HEAD image over the fresh
capture, so any change small enough to pass fossilizes the baseline. #540
was large enough to fail and simply was not re-recorded; the rest had been
accumulating under the old 0.02 default since #520 and #528.

Classification (baseline-vs-candidate pixel transitions, not diff levels):

  42  #540 dark-surface recolour. rgb(0,0,0) -> rgb(20,17,15) accounts for
      94-100% of every changed pixel in each file (--surface-ink #14110f).
      Footers, CTA bands, contact_us, both pagination frames, codeblock
      frames - the same surfaces #540's own commit message lists as failing.

   3  Post `description` backfilled by #520, which the post template renders
      as a grey lead above the body, pushing content down. Verified the
      rendered text is the frontmatter string verbatim, e.g.
      stimulus-keyboard-event-filter: "In the last update of Stimulus
      v3.2.2, the ability to filter the event by PageDown and PageUp keys
      was added." (desktop+mobile code_highlight_post, inline_style_post)

   2  Course module eyebrow added by #552 - "MODULE 1 - HYPOTHESIS & SMOKE
      TEST" now renders in ruby above the H1, shifting the page down.
      (desktop+mobile course/chapter)

   1  services/_testimonials-header: a 1px vertical offset from #528's
      eyebrow consolidation. #528 touched services-critical.css and
      content-block.css and re-recorded only the two sections that exceeded
      0.02; the sections below the eyebrow shifted 1px, passed under the old
      default, and fossilized. Measured, not inferred: realigning the
      baseline by dy=+1 drops the residual to exactly 0.0000, i.e. the
      content is pixel-identical and only its position moved.

Evidence that these are stale baselines rather than flaky rendering: two
consecutive runs of DesktopSiteTest#test_services reported difference_levels
identical to 17 decimal places (_technologies 0.013838252314814815,
_use-cases 0.01749807098765432, _testimonials-header 0.005922550154320988),
and a second full-suite run produced the same 48-file set.

One instrument error worth recording, since it nearly became a finding: the
first transition script loaded each baseline from a reused temp filename, and
libvips caches operations by filename - so runs 2..n silently re-compared
image 1. It reported services/_footer's baseline as a pure-black frame. A
brightness audit over all 155 tracked macOS baselines refuted it (darkest
mean is 69/255, none below 1.0), and reloading from git buffers fixed it. The
control that keeps the rewritten script honest: an unchanged baseline
(desktop/vibe_code_rescue) reports exactly 0 changed pixels.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011SP5gaqXEgUie8pdFrmbeJ

* Delete the SECTION_CONFIGS shield - its exit condition was met, and measured

SECTION_CONFIGS was introduced one day ago as an explicitly TEMPORARY shield
holding ~22 section screenshots at the old 0.02 tolerance while their drift
was unmeasured, with the exit condition written into its own comment: "delete
it once they are measured and re-recorded."

Measured both ways on the full macOS system suite, same environment, only this
variable changed:

  shield in place    134 screenshots compared, 48 failures
  shield emptied     134 screenshots compared, 50 failures

Cost of deleting it: +2 screenshots, both on /services/ -
desktop/services/_use-cases (0.017498) and desktop/services/_technologies
(0.013838). Both are the same 1px vertical offset from #528's eyebrow
consolidation already classified in the previous commit: realigning baseline
to candidate by one pixel drops the residual to exactly 0.0000 in several
bands, so the content is pixel-identical and only its position moved. Both are
re-recorded here, which is the last thing the exit condition asked for.

Deleted: SECTION_CONFIGS, screenshot_config_for, extract_section_key,
test/support/screenshot_section_config.rb and its unit test. That file existed
so the table could be unit-tested without booting Hugo or Capybara; with the
table gone there is no logic left to test, only a constant.
DEFAULT_SCREENSHOT_CONFIG moves onto ApplicationSystemTestCase unchanged at
0.0001. Per-call tolerances are untouched - ~30 calls pin their own, mostly
0.03, and those still win over the default.

Gates, all on this exact tree:
  bin/rake test:unit    287 runs, 6143 assertions, 0 failures
  bin/hugo-build        clean, 8 validators passed
  bin/rake test:system  79 runs, 218 assertions, 0 failures
                        [snap_diff] 134 screenshots compared, no failures

The suite was run with the shield deleted BEFORE these two baselines were
accepted, and reported exactly the 2 predicted failures and no others - so the
deletion is measured, not assumed.

.okf/build/test-gates.md is updated: the shield paragraph is replaced by the
measurement, and a misattribution is corrected. That file claimed all five
failing /services/ screenshots were the #540 dark-surface recolour; only the
two large ones are. The three small ones carry 0.13% or less of the
black->ink transition and are #528's 1px shift.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011SP5gaqXEgUie8pdFrmbeJ

* chore: update screenshot baselines [ci skip]

* Screen the CI Linux record: drop 6 zero-difference rewrites, keep 77

The re-record was dispatched to CI on this branch (run 32526977795, native
amd64 - the only trusted way to record linux/ baselines; local ARM Docker
plants false drift) and succeeded. FORCE_SCREENSHOT_UPDATE re-records
EVERYTHING, so the bot commit touched 83 files and needed screening.

Screened with the gate's OWN instrument rather than by eye or by file size:
capybara-screenshot-diff counts a pixel as differing only above
perceptual_threshold = 2.0 dE00, and the default tolerance is now 0.0001 of
the frame. Computing that same libvips dE00 fraction for every file in the bot
commit splits it cleanly:

  keep  77  would FAIL the gate, so the rewrite is real
  drop   6  difference_level exactly 0.00000000 - would have PASSED

The 6 dropped are pure encoder churn with zero perceptual difference:
desktop+mobile course/landing, desktop+mobile blog/special/codeblocks/text,
clients/agent-inbox/_overview, clients/single-full.

Of the 77 kept, 8 are NEW keys with no linux baseline at all - friday_report
(desktop+mobile) and the three register pilots next/{editorial,precision,
rescue-room} (desktop+mobile) - and the rest span 0.000139 to 0.973.

Why the Linux set is broader than the macOS one (77 vs 50), which looks wrong
until you check it: earlier PRs re-recorded only the macOS side. #528 committed
macos/mobile/services.png and left its linux/ twin stale, so linux/mobile/services
carries a visible vertical shift that macOS no longer has - confirmed by
painting the dE00 mask over the capture: identical content, offset position.
The reverse also holds - linux/desktop/blog/special/inline_style_post barely
moves (0.000139) because the Linux blog baselines were already re-recorded
after #520, while macOS was 25% stale. Neither OS is a subset of the other.

Two kept files were inspected directly rather than accepted on their number:
linux/desktop/404.png (0.001235) is a 4px-wide strip at the right edge - a
scrollbar artifact, page content identical, and 4 x 640 px is exactly the
reported difference_level; linux/mobile/services.png (0.1389) is the #528
shift above. Both render correctly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011SP5gaqXEgUie8pdFrmbeJ

* OKF: how to screen a CI re-record, and why neither OS baseline set is a subset of the other

Records the Linux leg of the re-record in .okf/log.md and lifts two rules into
test-gates.md that were learned doing it:

- Screen a FORCE_SCREENSHOT_UPDATE record with the gate's own instrument (the
  libvips dE00 fraction above perceptual_threshold 2.0, compared to the 0.0001
  default), not by eye or file size. 83 files split 77 keep / 6 drop with no
  judgement call.
- Neither OS baseline set is a subset of the other, because earlier PRs
  re-recorded only the OS they ran on. Do not reason about one from the other.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011SP5gaqXEgUie8pdFrmbeJ

* Correct four overclaims the re-record itself introduced

Independent review measured what the evidence actually supports:
- run-to-run noise is ~1e-6, not 0 (test-gates said 0.0 while the code
  comment in the same PR said 1e-6 - one PR, two numbers)
- realigning 1px zeroes SEVERAL BANDS, not the page: a sub-pixel shift
  re-rasterizes glyphs, so _use-cases still differs in 22,895 px after the
  best alignment. Content identical on inspection; the conclusion holds,
  the sentence did not
- "nothing unexplained" is really "every dominant cause explained" - three
  files carry a secondary 1px shift and two carry #540's sibling
  --ruby-on-ink token
- 20.10 still pointed at test/support/screenshot_section_config.rb, which
  this PR deletes

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011SP5gaqXEgUie8pdFrmbeJ

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant