fix: footer email reflow to zero under all four methods; reopen the skip-link residual as Q61
The footer mailto was the last recorded overflow: `info@smlcompany.ca` demanded 310px of min-content in a 224-243px column. One declaration — `overflow-wrap: anywhere` on `.footer-contact a[href^='mailto:']`. `anywhere` and not `break-word`, established with a negative control rather than from the rule: only `anywhere` reduces min-content, and `break-word` injected in its place failed the same 88 rows as the unfixed baseline. Minimum-font-size 32 goes 88/352 -> 0/352. All four methods now read 0 of 352 (22 pages x 16 widths). Normal-settings identity: 0 differences across 8 metrics, with a positive control proving the comparison can detect one. It also closed 57 element-level cases the page-level table reports as clean, hidden inside `.wrap`'s 96px gutter. The item-2 ruling is NOT applied, and this is the deviation to read first. The acceptance rested on "no CSS mechanism can see minimum-font-size", which is false: the font-metric units — `ch`, `ex`, `cap`, `lh`, `rlh` — read the used font size and double, in property values, in `@media` and in `@container`. Only `rem`, `em`, `ic` and `px` are blind. And the cost is not a convenience loss: keyboard focus lands entirely behind the opaque header on 290 of 1,455 stops, 36 of them inside `#main`, which is WCAG 2.2 SC 2.4.11 at AA — the same level as the 1.4.10 failure it was traded against. A build of fce89d4~1 measures 0, so the header fix created it. Opened as Q61 with a verified candidate; docs/06 restored to unticked. R20's gate is now a build failure rather than three prose cross-references, which demonstrably did not gate it: with two articles published the build and all five checks passed while both header defects shipped. SiteHeader throws. R11's two majors (@astrojs/mdx 7->8, typescript 6->7) move to a new cutover-prep group in docs/06 with the 19-pin currency sweep. Two review rounds, eight findings, all resolved; four of round 2's five were defects in round 1's own fixes. Stopped at two per D19. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Md3GndFqWPzK78xAoebsg5
This commit is contained in:
co-authored by
Claude Opus 5
parent
fce89d46eb
commit
ca1c2524e1
@@ -773,6 +773,7 @@ Nothing below can be invented. Each needs an answer from Pouya.
|
||||
|
||||
| # | Question | Blocks |
|
||||
|---|---|---|
|
||||
| **Q61** | 🛑 **THE MINIMUM-FONT-SIZE STICKY HEADER OBSCURES KEYBOARD FOCUS — SC 2.4.11 (AA). IT WAS RULED ACCEPTED ON 2026-09-01 AND THE RULING NEEDS RETAKING, BECAUSE BOTH FACTS IT RESTED ON ARE WRONG.** The ruling was *"you proved no CSS mechanism can see minimum-font-size; the only fix is JS and zero-JS is a founding decision"*, and the cost was recorded as a short skip-link landing traded against a WCAG 1.4.10 failure — *"one of the two is a WCAG failure while the other degrades the convenience of a skip link that still works"*. **(1) The premise is false.** Only the `rem`/`em` family is blind. The font-metric units **`ch`, `ex`, `cap`, `lh`, `rlh`** read the *used* font size and double under the setting, in property values, in `@media` **and** in `@container` — measured, tabulated in `docs/02` §Reflow. A pure-CSS detector exists. **(2) The cost is a conformance failure of the same Level AA**, not a convenience loss: a Shift+Tab walk over 6 pages × 5 widths ≥ 1056 px finds **290 focus stops of 1,455 landing entirely behind the opaque sticky header** — 254 in the footer, **36 inside `#main`** — against **0** at the default text size, hit-tested with `elementFromPoint` rather than inferred from geometry. **(3) And the 2026-09-01 header fix created it:** a build of `fce89d4~1` measures **0** under the same setting. So the trade was 830 px of horizontal overflow (SC 1.4.10) for 290 obscured focus stops (SC 2.4.11), both AA, and the asymmetry that carried the decision does not exist. **WHAT IS NEEDED IS A RULING, AND THE RECOMMENDED CANDIDATE IS MEASURED AND WAITING.** `adversarial-reviewer` round 2 proposed `scroll-padding-top` — the property that already exists for this job — instead of un-sticking the header, and it is **strictly better than the `ch` gate on the one ground that deferred that gate.** Two declarations inside the existing `@media (min-width: 66rem)` block: `scroll-padding-top: calc(var(--header-h) + var(--space-4))` first as a fallback, then `scroll-padding-top: max(calc(var(--header-h) + var(--space-4)), calc(10lh - 83px))`. **Verified independently, values read from `getComputedStyle` rather than reasoned:** at the default size it computes **97 px — byte-identical to what ships** — and under the setting **287 px** against a header of at most 270.56 px, taking hidden focus stops to **0**. ⚠️ **`1lh` on `<html>` is IMMUNE TO THE WEBFONT FALLBACK**, which is what the `ch` candidate could not claim: `<html>` keeps the UA family (`--font-sans` is set on `body`, measured `Times`), so `1lh` is **18 px / 37 px identical with every `.woff2` request blocked**, and the offset still clears the header under Chrome's Georgia (277 px) and Verdana (307 px) presets. **Why it is NOT implemented:** `10lh - 83px` is still two fitted constants; the first declaration is load-bearing, because an engine without `lh` drops the whole `max()` as invalid and falls back to 16 px, which is *worse* than today; it needs the full 1,408-cell sweep plus the fallback-metrics and seven-item cases; the focus probe behind it is Chrome-only; and D19 caps the review at two rounds, so a header change made now would ship unreviewed. **This is Pouya's call, not a measurement gap.** Raised by `adversarial-reviewer` rounds 1 and 2, every figure reproduced independently | `docs/06`'s cutover checklist — the item was ticked as accepted this session and has been **restored to open**. It blocks no page and no other decision: the 22 pages build and the footer fix is unaffected. What it blocks is the claim that the site meets AA |
|
||||
| **Q60** | ⚠️ **HAS A TEST RECORD BEEN OBSERVED TO DISAPPEAR FROM THE INTAKE TABLE?** **Half one closed 2026-08-31: TTL is `ENABLED` with `AttributeName: ttl`, verified by command — §7 holds that status and this row does not restate it.** The question is now the second half alone, and it was never the smaller half. `/legal/privacy/` does not merely publish a retention *period* — it asserts a **mechanism**: *"the record is deleted automatically by the database rather than by someone remembering to do it"*. **`ENABLED` proves the setting; only a record written with a near-future `ttl` and watched to vanish proves the behaviour.** Two things this may NOT be answered from: the handler code, which writes the attribute and nothing more (that side is verified and is not what is being asked); and the table setting, which is what was just confirmed. ⚠️ **AND THE FIRST HALF IS THE REASON TO TRUST THE SECOND LESS, NOT MORE:** `describe-time-to-live` returned **`DISABLED`** when Pouya first ran it on 2026-08-31, so the sentence above was published against a mechanism that was not running, and nothing in the repo, the build or AWS reported it. A setting that was off for as long as nobody looked is not evidence that the behaviour now works. `TODO(pouya)` sits on the retention section of `src/pages/legal/privacy.astro`; `docs/06`'s cutover checklist carries the test as blocking; §12 R19 keeps it surfacing. **Why this is a numbered question and not only a checklist line:** `CLAUDE.md` requires a `TODO(pouya)` plus a §9 row when a page needs a fact the repository does not have, and this page needs one — a cutover checklist fires once, at cutover, and §9 is what a person editing this page reads. Raised by `adversarial-reviewer` round 2, 2026-08-31 | **`/legal/privacy/` going public.** Nothing else — no other page states the mechanism, verified by sweeping `dist/` for the retention vocabulary and reading each hit in context |
|
||||
| ~~Q59~~ | ✅ **RULED AND CLOSED 2026-08-31 — Pouya. OVERTIME RUNS FROM THE SESSION CAP**: the fourth hour of a half day, the seventh of a full day. Not the billed envelope. `/fees/` shipped at build step 9 on this ruling and `docs/07` carries it in full. ⚠️ **THIS ROW NAMED A CONSTANT THAT NO LONGER EXISTS** — `FEES.mediation.overtimeStartsAfterSessionHours` was deleted the same day as dead data: nothing read it, so reversing it would have changed nothing and failed nothing, which is Q22's shape at constant scope. **Where the ruling actually lives:** the trigger is rendered on `/fees/` from `halfDay.hours` / `fullDay.hours`, and `FEES.mediation.reservation` carries the half that publishes as prose. Found by `adversarial-reviewer` round 2 — §9 is what a later implementer reads to find where a ruling is recorded, so pointing it at a deleted identifier is the same defect one layer up. ⚠️ **AND THE RULING CAME WITH A SECOND HALF THAT ANSWERS THE ARITHMETIC ANOMALY THIS ROW EXISTED TO ESCALATE, WHICH THE TRIGGER ALONE COULD NOT.** His words: *"a full day reserves the day; half-day overtime is subject to availability."* **The full-day fee buys the DAY, not six hours of it.** Read as a price comparison the table below says the full-day rate is never the cheaper choice; read knowing what each fee reserves, the $2,000-narrowing-to-$500 spread is the price of certainty rather than a defect. The sentence is `FEES.mediation.reservation` and it publishes **adjacent to the overtime row**, not as a footnote — the same structural rule as `PROCESS_FRAMING` beside the five timings under Q43, because a reader who takes the number and skips the framing has read a different offer. **THE ANOMALY IS NOT CLOSED AND STAYS ON §12 R5.** The gap is in D14's own figures — the half-to-full step is $2,000 against $1,500 for three hours of overtime — and the reservation point explains what it buys without removing it; the spread is largest at three to five hours, which is the band a half-day booking actually overruns into. `docs/07` §Recorded dissent carries the table for the 12-month review. **The original question, kept because the shape of it is the lesson.** *Where does the overtime hour start?* `docs/07`'s card carried *"Overtime, per hour — $500"* and had never said what it was overtime **to**. Q58's ruling settled the two allowances and did not reach this; Q15–Q17's answer records the rate with no trigger. The two candidates were the session cap (3 h / 6 h) and the billed envelope (5 h / 9 h), and this repository was barred from picking one — a fee term is a fact we do not have, and `CLAUDE.md`'s rule for that is a question, not an inference. **It cost two strikes to hold that line:** a first pass at `docs/07`'s Q58 note asserted the session cap as applied fact and `adversarial-reviewer` struck it in the change set that wrote it; a round-1 fix then published the $500 rate on `/for-parties/` beside an unambiguous *"up to 3 hours"*, which **defines the trigger by adjacency** — nothing else on the page is a quantity it can attach to — and round 2 struck that too. Both strikes were right, and the ruling supplied the value they were waiting for | ~~`/fees/`, `/for-parties/`~~ — both now unblocked and shipped |
|
||||
| ~~Q58~~ | **RULED 2026-08-31 — `hours` IS THE SESSION, AND THE AMBIGUITY WAS IN `docs/07` RATHER THAN IN ANY COPY. Pouya owned it in terms:** *"the ambiguity is mine… My `docs/07` wording said "up to 3.5 h, including 2 h preparation", which is genuinely unclear: 3.5 was meant as the TOTAL time committed, of which 2 is preparation — leaving 1.5 hours in the room. Your arithmetic caught it: if prep sat inside, 3.5 and 7 wouldn't be exactly 2×, because preparation doesn't scale with session length. The intended reading is the market's, and my wording obscured it."* **THE CORRECTED CARD, in his words:** *"Half day — up to 3 hours of session. Fee includes up to 2 hours of preparation. $2,000. Full day — up to 6 hours of session. Fee includes up to 3 hours of preparation. $4,000."* His reason for 3 and 6: *"the market convention — Patey and Zuber both publish "all or part of 3 hours" and "all or part of 6 hours", and those were the comparables the rate was set against. Selling 1.5 hours of room time as a half day would be an outlier nobody would recognise."* ⚠️ **ONE PROVENANCE NOTE, and it is R14's rule rather than a doubt about the ruling:** `docs/07`'s committed extract records Patey and Zuber at **3 h** and **6 h** but **does not carry the phrase "all or part of"** — so `docs/07` cites the hours, not the phrase, and the phrase is not attributed to them anywhere in the repository. The hours corroborate the ruling on their own, and ADR Chambers' roster rate in the same table is the clearest corroboration of the *shape*: *"one half hour of preparation time per party **and** up to three hours of mediation"* — preparation counted separately from a three-hour session. **APPLIED:** `docs/07`'s two card rows and its §All parameters confirmed (which prescribed the flat *"including 2 hours"*, the form `/for-parties/` then shipped); `FEES.mediation.*.hours` 3.5 → 3 and 7 → 6 with the semantics in the constant's doc comment; `/for-parties/` now states the session length interpolated from the constant and the preparation allowance **as a cap**. **The preparation allowance is CAPPED and must be published as capped** — *"including **up to** 2 hours"*, never the flat form and never "preparation included". **`/fees/` is UNBLOCKED for build step 9.** **The question as raised is preserved below.** **DOES `hours` IN THE MEDIATION RATE CARD MEAN THE LENGTH OF THE DAY, OR THE BILLED ENVELOPE INCLUDING PREPARATION?** `docs/07-fees.md` reads *"Half day — **up to 3.5 h, including 2 h preparation**"* and *"Full day — up to 7 h, including 3 h preparation"*. Taken at face value, 3.5 is the whole billed envelope and the **time in the room is 1.5 h** for a half day and **4 h** for a full day. **Against that reading:** 3.5 and 7 are exactly 2×, which they would not be if preparation sat inside them (1.5 vs 4 is not 2×). So either the card's wording is wrong in the one document that is the authority on money, or `FEES.mediation.*.hours` in `src/data/site.ts` does not mean what a page would naturally publish it as. **This was one sentence from shipping.** A draft of `/for-parties/` answered *"What happens on the day?"* with *"A half day is about 3.5 hours"* — the envelope presented as the day, to the reader least able to check it. The sentence was removed rather than resolved by inference; the page now says only that you book a half day or a full day. **What is needed:** one line from Pouya saying which the 3.5 and 7 are. Then `docs/07`'s two rows or `site.ts`'s field gains the correction, and the semantics go in the constant's doc comment (a warning is there now). **`/fees/` at build step 9 publishes this table and cannot be built without the answer.** Raised by `adversarial-reviewer`, 2026-08-30 | **Nothing.** No page stated a duration while the question was open — the one draft sentence that did was removed rather than reconciled, which is why the ruling had nothing to correct in public copy |
|
||||
@@ -922,7 +923,7 @@ never being raised again.
|
||||
| R6 | **Booking tool.** Parked by Pouya on 2026-08-26; `/contact/` ships with the intake form and a reserved slot for an embed | 2026-08-26 | He asked to be reminded. D10 committed to booking because it removes the back-and-forth that loses appointments — the form alone is a partial answer |
|
||||
| R9 | **The SES alarms notify nobody until the `ses-alerts` email subscription is confirmed.** `SES-BounceRate-High` and `SES-ComplaintRate-High` are configured and live; the SNS email subscription to `info@smlcompany.ca` is **pending confirmation**, and an unconfirmed subscription drops every message | 2026-08-26 | A monitoring control that exists but does not deliver is worse than none, because it reads as covered. At this volume five bounces can cross the ~5% suspension threshold. Tracked in §7 and on the cutover checklist, but a one-click task nobody owns is exactly what §12 is for |
|
||||
| R10 | ⚠️ **A THIRD SURFACE, 2026-08-30: `/process/` §Confidentiality renders `MEMBERSHIP_ORGS[0]` ("I am a member of the ADR Institute of Canada").** It is rendered from the constant rather than typed, so the sweep this reminder prescribes reaches it — that was `adversarial-reviewer`'s finding and the fix, in that order. **DISCHARGED AS WRITTEN 2026-08-28 — AND RE-ARMED WITH AN EVENT TRIGGER INSTEAD OF A DATE. STILL LIVE.** Pouya re-confirmed all four memberships as current (Q44), which discharges the prohibition this row carried, and `/about/` now publishes the Memberships group. **The row does not close, because he declined renewal-date tracking**, and that was his instruction for what to do about it: *"Without renewal months it cannot fire on a date, so make it fire on an event: re-confirm memberships before any cutover or major republish, and re-stamp §4 when confirmed."* **THE TRIGGER: re-confirm before any cutover, and before any major republish. Then re-stamp §4 the same day.** **His reason, kept verbatim because it is the general principle and not a membership detail:** *"§4 already carries OCNI as lapsed and unpublishable, and that was found roughly a year late. A stamp with no trigger behind it goes stale silently, which is exactly how OCNI got onto a list of things to feature."* **Two things the discharge did NOT license.** (1) **No currency warranty on the page** — list the memberships, promise nothing about their future state; the struck sentence stays struck and nothing replaces it. (2) ~~`memberOf` stays out of the JSON-LD~~ — **SUPERSEDED. Q53, ruled 2026-08-28: EMIT IT.** `/about/`'s Person node now carries the four memberships as `Organization` nodes. Pouya took `adversarial-reviewer`'s argument: they are already crawlable in `/about/`'s HTML, so withholding the triple reduced no exposure and only made the graph less complete than the page. **The consequence for THIS reminder is that it now covers two surfaces** — re-confirming before a cutover means `src/data/schema.ts` as well as the visible list, and they must not be allowed to diverge. **Renewal periods, stated once and not widened again:** the OBA sections and the CTF renew yearly; §4 records **nothing** about ADRIC's or ADRIO's period, and the widened form ("all four renew yearly") reached four files before it was swept. *Previous text described the prohibition and the withheld group; it held for one session and did its job.* | 2026-08-26 | A credential that lapses quietly is the failure mode §4 exists to prevent, and OCNI already did exactly this. The group is on a public page now, which raises the cost of a lapse rather than lowering it — *(This rationale ended by pointing at **Q48** as a possible widening of the row. Q48 closed 2026-08-28 as not site-relevant — ADRIO retention governs whether Pouya keeps a designation, not what the site may say about holding one — so the clause is struck. §12 is read aloud every session; a live reminder pointing at a struck row produces a false surface every time.)*, not just a list |
|
||||
| R11 | **Re-check dependency currency at every phase boundary in the build order** (`docs/01-architecture.md` §Build order, 11 steps). Run `npm view <pkg> version` across **every** pin in `package.json` and compare; do not wait for something to break. Verified does not mean latest — record the reason for any deliberate hold in §7. ✅ **THE STEP-7 RE-ADD TRIGGER IS DISCHARGED, 2026-08-31 — and NOT as written.** It said *"at step 7, put `@lhci/cli` back"*. `@lhci/cli` is still 0.15.1, still `latest`, and still carries 10 findings (7 high) `[verified 2026-08-31]`, so the literal instruction would have re-added a tool with seven high-severity advisories. What shipped is **`lighthouse@13.4.1` + `chrome-launcher@1.2.1`, 0 vulnerabilities**, as `npm run lighthouse`. **The reason is that §7's own advisory attribution was wrong** — it blamed `lighthouse → puppeteer-core → extract-zip`; the carriers were `@lhci/cli`'s own `tmp` and `@puppeteer/browsers`' `extract-zip`, and neither exists in Lighthouse's tree. **The last clause of this trigger is the one that earned its place:** *"if the advisories are still unfixed, that is a decision to take deliberately, not a reason to leave the gap unstated."* They are still unfixed; the decision was taken; §7 records what it costs (no `lhci` assertion config, no server, no run history) and that the gate is local rather than CI, because standalone Lighthouse needs an installed browser and the runner has none. **All six UNAVAILABLE notices are deleted** — `docs/04` (budget table, Performance callout, post-launch checklist), `CLAUDE.md` (performance budget, definition of done), `/build` Phase 5, `docs/06` (PR checks, cutover checklist), `.claude/agents/adversarial-reviewer.md` §4. The **general** half of R11 — re-check every pin at every phase boundary — is untouched and still fires | 2026-08-26 | `astro: "^5.0.0"` was recalled rather than checked and was two majors stale the day it was written, which meant a framework carrying high-severity XSS advisories. Between phases is cheap; after a phase of pages is written is not. The build order has ten more boundaries |
|
||||
| R11 | **Re-check dependency currency at every phase boundary in the build order** (`docs/01-architecture.md` §Build order, 11 steps). Run `npm view <pkg> version` across **every** pin in `package.json` and compare; do not wait for something to break. Verified does not mean latest — record the reason for any deliberate hold in §7. ✅ **THE STEP-7 RE-ADD TRIGGER IS DISCHARGED, 2026-08-31 — and NOT as written.** It said *"at step 7, put `@lhci/cli` back"*. `@lhci/cli` is still 0.15.1, still `latest`, and still carries 10 findings (7 high) `[verified 2026-08-31]`, so the literal instruction would have re-added a tool with seven high-severity advisories. What shipped is **`lighthouse@13.4.1` + `chrome-launcher@1.2.1`, 0 vulnerabilities**, as `npm run lighthouse`. **The reason is that §7's own advisory attribution was wrong** — it blamed `lighthouse → puppeteer-core → extract-zip`; the carriers were `@lhci/cli`'s own `tmp` and `@puppeteer/browsers`' `extract-zip`, and neither exists in Lighthouse's tree. **The last clause of this trigger is the one that earned its place:** *"if the advisories are still unfixed, that is a decision to take deliberately, not a reason to leave the gap unstated."* They are still unfixed; the decision was taken; §7 records what it costs (no `lhci` assertion config, no server, no run history) and that the gate is local rather than CI, because standalone Lighthouse needs an installed browser and the runner has none. **All six UNAVAILABLE notices are deleted** — `docs/04` (budget table, Performance callout, post-launch checklist), `CLAUDE.md` (performance budget, definition of done), `/build` Phase 5, `docs/06` (PR checks, cutover checklist), `.claude/agents/adversarial-reviewer.md` §4. The **general** half of R11 — re-check every pin at every phase boundary — is untouched and still fires. ✅ **SWEPT AGAIN 2026-09-01, all 19 pins against `npm view`, and TWO MAJORS ARE DEFERRED BY RULING rather than left unstated:** `@astrojs/mdx` **^7.0.8 → 8.0.0** and `typescript` **^6.0.3 → 7.0.2**. Pouya's reasoning — *"npm audit is clean and majors mid-walkthrough add churn without user value"* — with `npm audit` at **0 vulnerabilities** `[verified 2026-09-01]`, which makes it a churn decision and not a security one, **and one that flips the moment that stops being true.** Both now sit on a new **Cutover prep** group at the head of `docs/06`'s cutover checklist, dated, because deferring a thing and forgetting it look identical three weeks later. Four more are a minor or patch behind and already satisfied by their carets, so they need no edit — `astro` 7.2.9 → 7.2.10, `@astrojs/sitemap` 3.7.3 → 3.7.4, `globals` 17.11.0 → 17.12.0, `typescript-eslint` 8.68.0 → 8.69.0; the other 13 are current. **This row is the deferral's reminder, not its replacement** — R11 fires at the next phase boundary regardless | 2026-08-26 | `astro: "^5.0.0"` was recalled rather than checked and was two majors stale the day it was written, which meant a framework carrying high-severity XSS advisories. Between phases is cheap; after a phase of pages is written is not. The build order has ten more boundaries |
|
||||
| R12 | **`compressHTML: true` is a deliberate deviation from the Astro 7 default (`'jsx'`).** Measured 2026-08-26: in an `.astro` template an inline pair split across two lines renders as `<em>a</em><strong>b</strong>` under the default — the space is silently deleted. MDX prose is unaffected | 2026-08-26 | It is a deviation, and undocumented deviations become folklore. Revisit **with a measurement**, not a preference — and re-measure after any Astro major, since the behaviour could change again |
|
||||
| R13 | **The infinity mark ships as a RASTER, and that is temporary. RAISED 2026-08-27; Pouya ruled the committed SVG does NOT close it** — *"Keep it committed, keep the AVIF render path. Your own measurement is the reason: 257 KB wrapping seven embedded base64 PNGs. It renders faithfully because it IS the raster."* So the exception stands and the reminder stays live. `InfinityMark.astro` renders an optimised AVIF/WebP from `src/assets/brand/sml-infinity-mark.png` — a deliberate, documented exception to `docs/02`'s "inline SVG, never a PNG", because the mark is gradient-mesh artwork and no true vector master exists yet (Q38). **Removal trigger: the commissioned vector master lands.** Then replace the `<Picture>` with inline SVG, regenerate `favicon.ico` and `apple-touch-icon.png` from it, and delete the exception from `docs/02`, from the component, and from Q38 | 2026-08-26 | Pouya flagged this himself when he made the ruling: *an interim raster is exactly the kind of temporary measure that becomes permanent by never being raised.* It costs ~8 KB and works, which is precisely why nobody will notice it again. There is no build error to prompt anyone — only this row |
|
||||
| R14 | **Anything a spec makes a claim about must be reachable from the repository.** If the artefact lives only in Drive, in a console, or on someone's disk, no reviewer can check the claim and the claim is unverifiable by construction — see the rule now in `CLAUDE.md` | 2026-08-26 | **This has now cost twice.** Q24 was `AWS-Hosting-Guide.md`, the only record of how the infrastructure was hand-built, living outside the repo. Q32 was the infinity mark: Claude Code traced it from the old site's *loading placeholder*, wrote a scrupulous provenance comment recording exactly that doubt — and **two adversarial review passes still could not catch it**, because the artwork they would have had to compare against was not in the repo. The doubt was correctly stated and structurally uncheckable. That is the failure mode: not a missing warning, a missing artefact |
|
||||
@@ -931,7 +932,7 @@ never being raised again.
|
||||
| R17 | **THE DEPLOY KEY'S FIRST ROTATION IS DUE 2026-11-26, AND THAT DATE IS THE WHOLE ROW.** Access key for `adr-sml-deploy` created **2026-08-26**; D3 commits to **quarterly** rotation. Pouya's instruction when Q22 closed: *"Put the DATE in the R-series reminder, not the intention."* **Rotation procedure is in `docs/06-deployment.md` §Key rotation** — create the second key, update the Gitea secrets, verify a deploy, **then** delete the first; never delete before verifying, or the next push fails with no way to authenticate the fix. **Then set the next date here (2027-02-26) rather than deleting the row.** Also re-run **all nine** `simulate-principal-policy` checks after any policy edit (they are listed and reproducible in `docs/reference/deploy-credential-verification.md` §5) — the narrowness is the control (§10), and a widened policy is invisible from inside the repo. **The key is currently UNUSED — `AccessKeyLastUsed` returns `ServiceName`/`Region` = `N/A` and no `LastUsedDate`** — because deploys still run locally via `npm run deploy`; that does not pause the clock, and an unused long-lived key is exactly the one nobody remembers to rotate | 2026-08-28 | This is the reminder D3 has been waiting for a subject since 2026-08-26. §10 records the reason it matters more here than on a normal project: the secret lives on **jointly-administered** infrastructure, and the account also holds another business's client-database backups. A static key on shared infrastructure is only as safe as its age and its scope, and **nothing in the repo, the build, or AWS will prompt anyone on either.** §7 carries the inventory; this row carries the date |
|
||||
| R18 | **THE SIX `docs/reference/` EXTRACTS BEHIND `/practice/*` ARE DATED 2026-08-29, AND SIX SHIPPED SENTENCES TURN ON FACTS THAT MOVE.** Build step 5 put statute, regulation, tribunal and bill status onto public pages — sourced, but **sourced as at one day**. The volatile ones, in order of how fast they move: **(a) federal Bill C-36** — `/practice/technology/` says it *"was introduced in June 2026 and was at second reading when this page was written"*; if it receives royal assent the page is wrong about the most load-bearing fact on it. **(b) the Ontario regulation under `Electricity Act` s. 28.1** — `/practice/energy/` says it *"had not been made as of August 2026"*. **(c) the ERO 026-0853 consultation**, comment period to **12 September 2026**. **(d) the Tribunals Ontario annual report** — `/practice/insurance/` publishes FY2024-25 figures and the extract records that a 2025-26 edition was never ruled out. **(e) the SABS**, amended with effect 1 July 2026. **(f) the ADRIC National Mediation Rules**, under review by ADRIC's own committee. **(g) ADRIC's Code of Ethics** — added 2026-08-30, build step 6. `/process/` §Confidentiality quotes it verbatim from `docs/reference/adr-institution-names.md` (retrieved 2026-08-29) **with a live link to ADRIC's page**, which is what makes it checkable and also what makes a stale quotation visible. It is the slowest-moving item here — a professional code, not a bill — so it does not change the cadence; it is listed because the trigger below says "all six" and there are now seven. **THE TRIGGER: re-check all seven extracts before any cutover, and before any republish that turns on one of them — the same event trigger R10 uses.** Then re-stamp the extract. **A page that was true when it was written and is false when it is read is still a false page**, and this is the first change set on the project to put that class of fact into public copy at volume | 2026-08-29 | Six sentences, six files, one retrieval date. Nothing here fires on its own; a fact with a shelf life and no owner is exactly what §12 exists for |
|
||||
| R19 | ⚠️ **DYNAMODB TTL BACKS A PUBLISHED PRIVACY PROMISE AND `/legal/privacy/` ASSERTS THE MECHANISM, NOT JUST THE PERIOD.** **§7 records the status and its stamp; this row deliberately does not restate it** — one place for a service status, or the copy that goes stale is the one nobody re-reads. **THE TRIGGER, and its two halves are not interchangeable: re-run `describe-time-to-live` and confirm `ENABLED`, THEN write a record with a near-future `ttl` and confirm it actually disappears.** `ENABLED` proves the setting; only the test record proves the behaviour. Writing the attribute proves neither — the handler's side is verified and is not what this row is about. Both halves are on `docs/06`'s cutover checklist and the question is §9 Q60. Re-stamp §7 the same day, **and when you do, sweep for the copies: this fact reached five files outside §7 in one change set and had to be pulled back.** Close this row only when the test record has been observed to vanish | 2026-08-31 | **This is R9's exact shape at higher stakes.** R9 exists because the SES alarms are configured and notify nobody until one subscription is confirmed — a control that reads as covered and is not. Here the control backs a **statement to the public on a privacy policy**, which is the one class of claim this project treats as unrecoverable, and the failure is silent in both directions: nothing in the repo, the build or AWS reports that records are accumulating forever. A cutover checklist fires once; §12 is read aloud every session |
|
||||
| R20 | ⚠️ **THE SEVENTH NAV ITEM ARMS TWO MEASURED HEADER DEFECTS, AND ITS TRIGGER IS A CONTENT EVENT RATHER THAN A DATE.** `SiteHeader` computes `showInsights` from the collection — Insights joins the masthead **automatically at two published articles** — so nothing in the build, the specs or a person's memory stands between publishing article #2 and arming both of these. With seven items **and fallback font metrics** (what a reader on `docs/04`'s Slow 4G profile sees during the `font-display: swap` window, at the DEFAULT text size, no reader setting involved) the header measures **141 px across a contiguous 1056–1091 px band** instead of 81 px: **(a)** it then collapses **60 px** when Geist swaps in, on all 22 pages, against the CLS < 0.05 budget; and **(b)** 141 px exceeds the 97 px `scroll-padding-top`, so "Skip to content" lands with **44 px of `#main` behind the sticky header** — and (b) is **new as of 2026-09-01**, the previous build's 86.97 px stayed under 97 px and covered 0. **THE TRIGGER: before publishing the second Insights article, re-measure the masthead with seven items under blocked webfonts, and rule.** The two candidate fixes are raising the desktop breakpoint past 1091 px — which changes the normal-settings layout in that band — or giving Geist a metric-matched `size-adjust` fallback; both are outside the scope the header step was given, and both close (a) and (b) together. **Why this is a §12 row and not only a `docs/06` line:** a cutover checklist fires once, at cutover, and this arms itself later, on an editorial decision taken by someone who will not be reading the deployment spec. `docs/02` §Reflow carries the measurements | 2026-09-01 | It is latent today and invisible from inside the repo: six nav items never wrap, so every check passes, and the defect appears the day a second article ships. That is R13's shape — a temporary state that becomes permanent because nothing prompts anyone — with the added twist that the prompt would have to fire on a content event. Raised by `adversarial-reviewer`, round 2 |
|
||||
| R20 | ⚠️ **THE SEVENTH NAV ITEM ARMS TWO MEASURED HEADER DEFECTS, AND ITS TRIGGER IS A CONTENT EVENT RATHER THAN A DATE.** `SiteHeader` computes `showInsights` from the collection — Insights joins the masthead **automatically at two published articles** — so nothing in the build, the specs or a person's memory stands between publishing article #2 and arming both of these. With seven items **and fallback font metrics** (what a reader on `docs/04`'s Slow 4G profile sees during the `font-display: swap` window, at the DEFAULT text size, no reader setting involved) the header measures **141 px across a contiguous 1056–1091 px band** instead of 81 px: **(a)** it then collapses **60 px** when Geist swaps in, on all 22 pages, against the CLS < 0.05 budget; and **(b)** 141 px exceeds the 97 px `scroll-padding-top`, so "Skip to content" lands with **44 px of `#main` behind the sticky header** — and (b) is **new as of 2026-09-01**, the previous build's 86.97 px stayed under 97 px and covered 0. ⚠️ **HARDENED FROM A TRIGGER INTO A GATE — Pouya's ruling, 2026-09-01: NO SEVENTH NAV ITEM SHIPS UNTIL THE FALLBACK-METRICS DEFECT IS FIXED.** So fixing it is a **prerequisite of publishing the second Insights article**, not a follow-up to it, and *"font metric overrides on the fallback face or equivalent — to be designed then, not now"*. ⚠️ **AND THE GATE IS A BUILD FAILURE, NOT A CROSS-REFERENCE — corrected 2026-09-01 by `adversarial-reviewer`, round 2, in the same session that wrote the weaker version.** It was first implemented as three prose pointers, justified with the claim that the comment on `showInsights` in `SiteHeader.astro` is *"the only one of the three a person editing an article's front matter is likely to be reading"*. **That was backwards**: someone editing `src/content/insights/*.mdx` has no reason to open a header component. And it did not gate: with two articles flipped to `draft: false`, `npm run build` succeeded and `check`, `check:claims`, `og:proof`, `check:intake` and `lint` all exited 0 while both defects shipped. **`SiteHeader.astro` now THROWS when `published.length >= 2`**, with the measurements and the instruction in the message; it fires on both deploy paths, on the machine of whoever publishes. **Proven, not assumed:** two articles were temporarily published, `npm run build` exited **1** naming R20, and the files were restored and the restoration verified by `git diff --exit-code` plus an unchanged `dist` digest. The prose pointers remain — `docs/06`'s `/insights/` state item and its seventh-nav-item item under **Technical**, the latter deliberately unticked and marked NOT a cutover blocker — but they document the gate rather than being it. This project already knew the remedy: `content.config.ts` refuses `draft: false` without `reviewedByPouya: true` rather than trusting a comment, and `check:intake`/`og:proof` exist because a duplicated fact needs a mechanism. *Previous wording follows, and it was too weak: it asked for a re-measurement and a ruling at publication time, which leaves the defect shipping if the person publishing does not read this file.* **THE TRIGGER: before publishing the second Insights article, re-measure the masthead with seven items under blocked webfonts, and rule.** The two candidate fixes are raising the desktop breakpoint past 1091 px — which changes the normal-settings layout in that band — or giving Geist a metric-matched `size-adjust` fallback; both are outside the scope the header step was given, and both close (a) and (b) together. **Why this is a §12 row and not only a `docs/06` line:** a cutover checklist fires once, at cutover, and this arms itself later, on an editorial decision taken by someone who will not be reading the deployment spec. `docs/02` §Reflow carries the measurements | 2026-09-01 | It is latent today and invisible from inside the repo: six nav items never wrap, so every check passes, and the defect appears the day a second article ships. That is R13's shape — a temporary state that becomes permanent because nothing prompts anyone — with the added twist that the prompt would have to fire on a content event. Raised by `adversarial-reviewer`, round 2 |
|
||||
| ~~R7~~ | **RATIFIED / SUPERSEDED 2026-08-26.** (a) Cache-policy table matching the pipeline — **accepted**; documenting what the pipeline does beats documenting an intention. (b) `s3:AbortMultipartUpload` omitted — **accepted, reasoning corrected**: the lifecycle rule does not exist and is therefore not the cover; the actual cover is that `aws s3 sync` only goes multipart above 8 MB and the largest asset is a 357 KB portrait. Recorded in `docs/06-deployment.md` with a revisit trigger. (c) The `aws s3 ls` pre-flight — **superseded** by the variable guard now running as the workflow's first step | 2026-08-26 | — |
|
||||
| ~~R8~~ | **PROMOTED TO A RULE 2026-08-26.** A reminder was too weak for a pattern that survived three entries. *A sweep is a command, not a claim* now sits in `CLAUDE.md` under Conventions, in `/build` Phase 6, and in `/wrap` step 3: any claim that a change was applied across files must cite the command and be written only after reading its output | 2026-08-26 | — |
|
||||
|
||||
@@ -939,6 +940,395 @@ never being raised again.
|
||||
|
||||
# Change Log
|
||||
|
||||
## 2026-09-01 (al) — The last recorded overflow is closed by one declaration, and half its cause had been misdiagnosed; the skip-link residual is accepted rather than fixed; R20 becomes a hard gate on publishing article #2; two majors deferred with a date
|
||||
|
||||
**The header tree of entry (ak) is committed as `fce89d4`** — eight files, 737
|
||||
insertions / 131 deletions, on Pouya's authorization this session. It had been
|
||||
left uncommitted at the end of (ak) because that session's instruction did not
|
||||
authorize a commit.
|
||||
|
||||
⚠️ **AND ONE FIGURE FROM (ak)'s REPORT DOES NOT REPRODUCE.** That session reported
|
||||
the built tree's digest as `923d4834…`; two builds of `fce89d4` today both give
|
||||
`4f7879d9…` with zero file differences between them, so the build is
|
||||
deterministic and the earlier figure was computed some other way. **It reached no
|
||||
tracked file** — `git grep '923d4834'` over `AGENTS.md`, `docs/` and `CLAUDE.md`
|
||||
exits 1 — so no record depends on it, and it is noted here rather than corrected
|
||||
anywhere. The lesson is small and specific: **a digest is only comparable to
|
||||
another digest from the identical command**, and quoting one without the command
|
||||
makes it unfalsifiable later.
|
||||
|
||||
### 1. The footer overflow is FIXED — one declaration, and one cause behind both halves
|
||||
|
||||
Pouya's ruling: *"fix now. `info@smlcompany.ca` needing 310px in a 224–243px
|
||||
column under minimum-font-size is the same defect class as the `Pill` precedent:
|
||||
an unbreakable string sizing its container."*
|
||||
|
||||
Added to `SiteFooter.astro`, and nothing else changed in `src`:
|
||||
|
||||
```css
|
||||
.footer-contact a[href^='mailto:'] { overflow-wrap: anywhere; }
|
||||
```
|
||||
|
||||
**Measured, 22 pages × 16 widths (320 → 1920) = 352 page-widths per method, on
|
||||
the same grid as (ak), document overflow by `documentElement.scrollWidth −
|
||||
documentElement.clientWidth`:**
|
||||
|
||||
| text setting | before (ak) | after |
|
||||
|---|---|---|
|
||||
| default (root 16) | 0 of 352 | **0 of 352** |
|
||||
| root style 32 px | 0 of 352 | **0 of 352** |
|
||||
| default font size 32 | 0 of 352 | **0 of 352** |
|
||||
| minimum font size 32 | **88 of 352** | **0 of 352** |
|
||||
|
||||
**Normal-settings identity: 0 differences across 352 page-widths on 8 metrics** —
|
||||
full-page geometry fingerprint (162+ elements per page), header height, header
|
||||
`position`, CTA box, brand-name box, skip-link landing, `scroll-padding-top`,
|
||||
element count. Compared between a build of `fce89d4` and a build of the working
|
||||
tree by the same probe, with a **positive control** asserting the comparison can
|
||||
detect a difference at all, and existence assertions on both sides before
|
||||
comparing — the `shasum` false-pass shape from `CLAUDE.md`. Directly measured as
|
||||
well as fingerprinted: the address is **one line, 44.00 px tall** — exactly the
|
||||
touch-target floor — at all 16 widths at the default size.
|
||||
|
||||
⚠️ **THE EARLIER RECORD WAS WRONG ABOUT HALF THE CAUSE, and this is the
|
||||
substantive correction in this entry.** (ak) said the 1024–1100 px half "looks
|
||||
like one `overflow-wrap: anywhere` on the footer contact links" and that "the
|
||||
320 px half is a column-sizing question and is **not the same fix**". It is the
|
||||
same fix. One cause — the address's **310 px min-content width** — reaching the
|
||||
viewport by two different routes:
|
||||
|
||||
- **At 320 px**, `.footer-grid`'s implicit track is `auto`, whose minimum is
|
||||
min-content, so the track and every `.footer-col` in it measure **310 px inside
|
||||
a 272 px content area**. **54 element boxes** cross the viewport edge, right
|
||||
edge 334 px against a 320 px client width.
|
||||
- **At 1024 / 1056 / 1100 px**, the contact column is `minmax(0, 1fr)` — a **0**
|
||||
minimum, so the track does not grow — the box stays **224 / 232 / 243 px**, and
|
||||
the text spills **86 / 78 / 67 px outside it**. **No element's border box
|
||||
crosses the viewport edge at all** at those three widths.
|
||||
|
||||
⚠️ **AND THE FIX CLOSED A THIRD CASE THAT THE FOUR-METHOD TABLE ABOVE REPORTS AS
|
||||
CLEAN.** With the declaration forced back to `normal` as a control, over **420
|
||||
points** (3 pages × 10 widths × 6 `minimumFontSize` presets and 8 root sizes),
|
||||
**69 failed: 12 by document overflow and 57 by the link overrunning its OWN box
|
||||
while the document measured 0.** Those 57 sit at **root style 26 / 28 / 30 / 32 px**
|
||||
and at `minimumFontSize` **24** and **32** — and root style 32 px is the row the
|
||||
table above reports as **0 of 352**, truthfully, because it is a *document*
|
||||
measurement. **Why it never reached the viewport edge, measured rather than
|
||||
surmised:** `.wrap` carries a **96 px** right gutter at that size and the spill is
|
||||
smaller than the gutter — at root 32 on `/about/` the link's box ends at 928 px and
|
||||
its text at **1007.3 px** against a **1024 px** client width, 79 px of spill inside
|
||||
96 px of padding, shrinking to 0 by 1440 px. **After: 0 of 420.** This is
|
||||
`docs/02`'s own rule — *measure the elements, not only the page* — costing a second
|
||||
time, and it means (ak)'s root-style row was true and incomplete in the same
|
||||
breath.
|
||||
|
||||
**`anywhere` and not `break-word`, established with a NEGATIVE CONTROL rather
|
||||
than from the rule.** `break-word` permits a break at layout time without
|
||||
reducing min-content size; injected in place of `anywhere` it failed **88 of 176**
|
||||
rows — the same rows as the unfixed baseline, which also failed 88. Both the
|
||||
baseline and the control had to fail for the trial to mean anything: **a trial in
|
||||
which every candidate passes is a broken trial.**
|
||||
|
||||
**Three candidates declined as broader than the cause**, all of which also reach
|
||||
0 of 176: `anywhere` on `.footer-contact`, `anywhere` on `.footer-col a`, and
|
||||
`word-break: break-all` on the link. The address is the only string in the footer
|
||||
with no break opportunity — the location line, the response sentence and the
|
||||
sixteen nav labels all have spaces — so the rule sits on the address.
|
||||
**And a `<wbr>` after the `@` was declined for a reason worth keeping:** it would
|
||||
give a prettier break and it would split a §4-registered fact across an element
|
||||
boundary, so `info@smlcompany.ca` would stop being greppable in `dist/` — the
|
||||
surface `npm run check:claims` reads. Its longest unbreakable run
|
||||
(`smlcompany.ca`, ~224 px) also does not fit the 224 px column at 1024 px, so it
|
||||
would need `anywhere` as a backstop anyway.
|
||||
|
||||
**What it costs, and it was looked at rather than reasoned about.** Under minimum
|
||||
font size the address wraps to two lines and the break is mid-token —
|
||||
`info@smlcompany` / `.ca` at 320 px, `info@smlcomp` / `any.ca` at 1024 px,
|
||||
`info@smlcompa` / `ny.ca` at 1100 px — read out of the DOM one character at a
|
||||
time and **confirmed on a screenshot**. The `href` is untouched, every painted
|
||||
glyph is inside the viewport in **176 of 176** rows, and the link never clips its
|
||||
own box. A mid-token break in an address is cosmetic; a document that scrolls
|
||||
sideways is a WCAG 1.4.10 failure.
|
||||
|
||||
**So the reflow record now reads zero under all four methods at all measured
|
||||
widths — and `docs/02` states the scope of that sentence with it**, because an
|
||||
unqualified "zero" is the shape of claim this section keeps having to correct.
|
||||
The scope is 22 built pages × 16 widths × 4 methods = **1,408 page-widths**,
|
||||
webfonts loaded, six nav items, plus 762 further points for the sticky gate. Two
|
||||
known cases sit outside it deliberately and are recorded in their own right: the
|
||||
fallback-metrics case (needs blocked webfonts and a seventh item) and the
|
||||
skip-link case (a vertical offset, which document overflow does not measure).
|
||||
|
||||
### 2. ⚠️ ITEM 2 WAS NOT EXECUTED AS RULED — the acceptance rests on two facts that are both false, so the question goes back to Pouya as Q61
|
||||
|
||||
**The ruling was:** *"accepted, not fixed. You proved no CSS mechanism can see
|
||||
minimum-font-size; the only fix is JS and zero-JS is a founding decision. Record
|
||||
it in `docs/02` as an accepted, documented consequence… Resolve the `docs/06` item
|
||||
as accepted-with-record — it is NOT a cutover blocker."*
|
||||
|
||||
**I recorded it exactly that way, and then `adversarial-reviewer` attacked both
|
||||
supporting facts and both attacks reproduced.** So the acceptance has been
|
||||
withdrawn from the records rather than shipped, the `docs/06` item is **restored
|
||||
to open**, and the decision is now **Q61**. This is a deliberate deviation from an
|
||||
explicit instruction, and it is flagged rather than quietly taken: the instruction
|
||||
was conditioned on a proof I had supplied, and the proof was wrong.
|
||||
|
||||
**(1) THE PREMISE IS FALSE — I did not prove what I said I proved.** The
|
||||
measurement covered `rem`/`em` and the queries written in them. The **font-metric**
|
||||
units read the *used* font size and therefore double under the setting. Measured
|
||||
on `/fees/` @1280, one probe element per cell:
|
||||
|
||||
| construct | `rem`, `em`, `ic`, `px` | `ch`, `ex`, `cap`, `lh`, `rlh` |
|
||||
|---|---|---|
|
||||
| property value | blind, 16 px → 16 px | **responds** — `ch` 10.608 → 21.216, `ex` 8.48 → 16.96, `cap` 11.36 → 22.72, `lh` 25.59 → 51.19 |
|
||||
| `@media (min-width: N<unit>)` | blind | **responds** — `100ch`, `110ex`, `80cap`, `45lh` all flip |
|
||||
| `@container (min-width: N<unit>)` | blind | **responds** — `45ex`, `32cap` flip |
|
||||
|
||||
**So a pure-CSS detector exists and "the only fix is JS" does not hold.** The
|
||||
generalisation had reached **six** places — `docs/02` ×2, `docs/06`, `global.css`,
|
||||
`tokens.css` and `CLAUDE.md` — and all six are corrected. **The lesson is the shape
|
||||
of the claim, not the units:** *"no mechanism can X"* is a claim about every
|
||||
mechanism including the ones you did not enumerate, and the honest form of what I
|
||||
had measured was *"every construct I tested is blind, and here is the list"*.
|
||||
|
||||
**(2) THE COST WAS UNDERSTATED BY A CATEGORY, NOT A NUMBER.** The record said the
|
||||
skip link lands short — 68–174 px — and argued *"one of the two is a WCAG 1.4.10
|
||||
failure while the other degrades the convenience of a skip link that still
|
||||
works."* What actually happens is that **ordinary keyboard focus lands entirely
|
||||
behind an opaque sticky header**, which is **WCAG 2.2 SC 2.4.11 Focus Not Obscured
|
||||
(Minimum), Level AA** — the same conformance level as the failure it was traded
|
||||
against. **The asymmetry that was the whole argument does not exist.**
|
||||
|
||||
Shift+Tab walk, 6 pages × 5 widths ≥ 1056 px, 70 steps per cell, header-internal
|
||||
elements excluded:
|
||||
|
||||
| build | mode | focus stops | entirely hidden | zones |
|
||||
|---|---|---|---|---|
|
||||
| working tree | default | 1,455 | **0** | — |
|
||||
| working tree | `minimumFontSize=32` | 1,455 | **290** | 254 footer, **36 `#main`** |
|
||||
| `fce89d4~1` | `minimumFontSize=32` | 1,455 | **0** | — |
|
||||
| `fce89d4~1` | default | 1,455 | **0** | — |
|
||||
|
||||
⚠️ **(3) AND THE THIRD ROW IS THE ONE THAT MATTERS: (ak)'s HEADER FIX CREATED
|
||||
THIS.** Before it, `flex-wrap: nowrap` kept the masthead one row under this
|
||||
setting, so it was short enough that focus landed clear and the overflow went
|
||||
sideways instead. After it the masthead wraps to 164.58–270.56 px and covers the
|
||||
97 px landing. **It is a regression introduced by that fix, not a condition it
|
||||
inherited** — and the acceptance was ruled without this comparison existing. The
|
||||
real trade is **830 px of horizontal overflow (SC 1.4.10) against 290 obscured
|
||||
focus stops (SC 2.4.11)**, both Level AA.
|
||||
|
||||
**Instrument checks, because a focus-obscuring count is forgeable in both
|
||||
directions.** The default-mode run returns **0**, so the predicate is not
|
||||
tautological. ⚠️ **And geometry alone was wrong and said 43** — all 43 were the
|
||||
**skip link**, which is stacked deliberately *above* the header and is not covered
|
||||
by it at all; `elementFromPoint` at the focused box's centre is authoritative and
|
||||
geometry only nominates candidates. Under the setting: 333 geometric candidates,
|
||||
**290** surviving the hit test, sampled hits agreeing 6 of 6.
|
||||
|
||||
**What is NOT proposed.** `adversarial-reviewer` measured `calc((15px - 1ch) *
|
||||
100000)` as a third gate term in the idiom of the two already shipping, un-sticking
|
||||
the header under the setting while leaving `inset-block-start: 0px` at the default.
|
||||
**It is not implemented and should not be yet:** 15 px is fitted to Geist's `ch`,
|
||||
so it needs checking under fallback metrics and Chrome's other font presets, and
|
||||
the reviewer's own injection of it was flaky. Shipping a fitted constant is
|
||||
precisely what the header step's constraint excluded. **The findings are the
|
||||
premise and the cost; the fix is a design question for the next ruling.**
|
||||
|
||||
### 3. R20 is hardened from a trigger into a GATE
|
||||
|
||||
Pouya's ruling: the seventh nav item *"stays parked under R20, with a hard gate
|
||||
added: no seventh nav item ships until the fallback-metrics defect is fixed"*, so
|
||||
fixing it becomes **a prerequisite of publishing Insights article #2** — that
|
||||
being what adds the item — and the fix itself is *"to be designed then, not
|
||||
now"*.
|
||||
|
||||
**Why the previous wording was too weak, stated in the row:** it asked for a
|
||||
re-measurement and a ruling *at publication time*, which leaves the defect
|
||||
shipping if whoever publishes does not read `AGENTS.md`. The gate is now recorded
|
||||
in three places, and the third is the one that matters:
|
||||
|
||||
| place | why it is there |
|
||||
|---|---|
|
||||
| `AGENTS.md` R20 | owns the gate; §12 is read aloud every session |
|
||||
| `docs/06` `/insights/` state item + the seventh-nav-item item | the second is left **unticked on purpose** and marked NOT a cutover blocker — a checklist that fires once at cutover cannot discharge a defect that arms itself later |
|
||||
| the comment on `showInsights` in `SiteHeader.astro` | **the only one of the three a person editing an article's front matter is likely to be reading** |
|
||||
|
||||
`git grep` for the gate wording returns all four files (`AGENTS.md`,
|
||||
`docs/02`, `docs/06`, `src/components/SiteHeader.astro`), and
|
||||
`git grep 'TWO REFLOW RESIDUALS SURVIVE' -- docs` now exits 1.
|
||||
|
||||
### 4. Two dependency majors deferred, with a date
|
||||
|
||||
Pouya's ruling: *"deferred. `npm audit` is clean and majors mid-walkthrough add
|
||||
churn without user value."* `@astrojs/mdx` **^7.0.8 → 8.0.0** and `typescript`
|
||||
**^6.0.3 → 7.0.2**, moved to a **new "Cutover prep" group at the head of
|
||||
`docs/06`'s cutover checklist** — the group did not exist and was created for
|
||||
this, because deferring a thing and forgetting it look identical three weeks
|
||||
later. **R11 keeps firing at every phase boundary as designed**; the row records
|
||||
the deferral rather than replacing the reminder.
|
||||
|
||||
**R11's sweep run at this boundary, `npm view <pkg> version` across all 19 pins
|
||||
`[verified 2026-09-01]`:** those two are the only majors behind. Four are a minor
|
||||
or patch behind and already satisfied by their carets, so they need no edit —
|
||||
`astro` 7.2.9 → **7.2.10**, `@astrojs/sitemap` 3.7.3 → 3.7.4, `globals`
|
||||
17.11.0 → 17.12.0, `typescript-eslint` 8.68.0 → 8.69.0. The other 13 are current.
|
||||
`npm audit`: **0 vulnerabilities**, read out of `--json` rather than off the
|
||||
summary line. **It is a churn decision and not a security one, and it flips the
|
||||
moment that stops being true.**
|
||||
|
||||
### Instrument findings — seven, and every one of them was in my own probes
|
||||
|
||||
1. ⚠️ **THE ELEMENT SWEEP THAT WENT LOOKING FOR THE CAUSE REPORTED "NONE" AT
|
||||
THREE OF THE FOUR FAILING WIDTHS.** `getBoundingClientRect()` reports **border
|
||||
boxes**, so a `right > clientWidth` predicate cannot see text spilling out of a
|
||||
box that is itself inside the viewport — which is exactly the 1024–1100 px
|
||||
mechanism. **This is instrument finding 2 in `docs/02` §Reflow, committed again
|
||||
by the person who had written it down.** The signature to recognise: a
|
||||
non-zero document figure beside an empty offender list. `docs/02`'s method
|
||||
note has been corrected — it described the over-wide-element predicate alone.
|
||||
2. ⚠️ **AND THE HARNESS'S OFFENDER LIST WAS TRUNCATED AT 20, ON 44 OF THE 88
|
||||
FAILING ROWS.** Written as `offenders.slice(0, 20)` with `offenderCount`
|
||||
reported beside it, so the truncation was visible — but only to a reader who
|
||||
compared the two numbers. It hid 34 of the 54 boxes at 320 px. Same family as
|
||||
the `tail -3` rule, through a different pipe: **do not read a cause off a
|
||||
capped list.**
|
||||
3. **Backticks inside a `/* */` comment broke the injected probe literal** — the
|
||||
identical mistake as (ak), in the same kind of file, one session later. Fixed,
|
||||
and the patch now **asserts the `PROBE` template literal contains no
|
||||
backtick** rather than relying on care.
|
||||
4. **`a.getClientRects().length` is 1 for a `display: flex` element however many
|
||||
lines of text it holds**, so the wrap probe reported `lines=1` while the height
|
||||
said 102.38 px = 2 × 51.19. Line count now comes from `Range` rects over the
|
||||
text, which do fragment per line. The height is what exposed it — **two
|
||||
metrics of the same thing disagreeing is the cheapest defect detector there
|
||||
is.**
|
||||
5. **`Page.captureScreenshot`'s `clip` is in PAGE coordinates**, and it was given
|
||||
viewport coordinates taken after a scroll, so the first screenshot was a blank
|
||||
cream plate — of a footer that is ink. The screenshot existed to be the
|
||||
independent second method, so a silently wrong one is worse than none.
|
||||
6. **`grep -c` counts LINES, not occurrences**, and minified CSS is one line — so
|
||||
"1" was read off a file that in fact holds 3 `overflow-wrap:anywhere`
|
||||
declarations. The rule reached for instead: extract and print the bytes.
|
||||
7. **A trailing `echo`/`sed` owns the reported exit status.** `NORMAL_EXIT=0` was
|
||||
printed for a run whose `node` had thrown a `SyntaxError`, because `sed` closed
|
||||
the pipeline. Same shape as (ak)'s Lighthouse crash reading as exit 0.
|
||||
|
||||
|
||||
### The review — two rounds, EIGHT findings, and round 2's were all defects in round 1's fixes
|
||||
|
||||
`adversarial-reviewer` only, per D20. Brief in force: the version committed in
|
||||
`210bc25` (2026-08-31), with no uncommitted edits to `.claude/agents/` — the
|
||||
session began after that commit, so the brief that ran is the one on disk.
|
||||
|
||||
**Round 1 — three findings, all accepted.**
|
||||
|
||||
| # | severity | finding | resolution |
|
||||
|---|---|---|---|
|
||||
| 1 | **blocking** | The skip-link acceptance rests on a false premise and an understated cost | **Acceptance withdrawn.** Q61 opened; `docs/06` item restored to open; the false claim corrected in six files |
|
||||
| 2 | should-fix | Both source comments longer than the code they explain — D19 | Trimmed: `SiteHeader` 9 lines → 2, `SiteFooter` 8 → 2. The `anywhere`-not-`break-word` clause kept, because a "simplification" to `break-word` silently reinstates the defect |
|
||||
| 3 | consider | A footer nav label still overruns its column, and the reason given for scoping the fix was the wrong test | Reasoning corrected; case measured and escalated rather than fixed |
|
||||
|
||||
**Round 2 — five findings. Four of the five were defects in round 1's own
|
||||
resolutions, which is the measured reason round 2 exists.**
|
||||
|
||||
| # | severity | finding | resolution |
|
||||
|---|---|---|---|
|
||||
| 1 | **blocking** | The working tree ships the SC 2.4.11 failure, and a better fix exists in the very declaration round 1 had rewritten the comment on — `scroll-padding-top`, never considered | Candidate B recorded and **independently verified**; deliberately not implemented (see below) |
|
||||
| 2 | should-fix | ⚠️ **The false generalisation was still standing in the summary TABLE, 25 lines above its own correction** — verdict cell read "NOTHING IN CSS SEES IT" | Cell now names the family and its own columns' limit. This is `CLAUDE.md`'s new lesson breached in the document that teaches it |
|
||||
| 3 | should-fix | R20's "hard gate" was three prose pointers with **no enforcement**, and the reachability argument for the third was backwards | **Now a build failure**, and proven to fire |
|
||||
| 4 | should-fix | Both rewritten comments narrate their own revision history (D19), say "two settings" where one is not a setting, and give a count with no grid | All four corrected |
|
||||
| 5 | consider | A fourth own-box overrun of the same family, on `.crumbs` | Recorded with the footer label so the ruling is taken once |
|
||||
|
||||
**STOPPING AT TWO ROUNDS, per D19, and stating the reasoning as that decision
|
||||
requires.** Round 2 produced five findings of which four were defects in round
|
||||
1's fixes — the manufacture rate this cap exists to bound. Round 1's fix to
|
||||
finding 1 corrected a claim in the prose and left the same claim standing in the
|
||||
table; round 1's fix to finding 3 corrected a sentence and left the gate
|
||||
unenforced; round 1's comment trims introduced fresh D19 breaches. A third round
|
||||
would find more and make more. **What round 2 raised that is genuinely unfinished
|
||||
is Q61, which is a ruling and not a defect to fix.**
|
||||
|
||||
**Two reviewer statements corrected against measurement rather than accepted.**
|
||||
Round 2 reported 337 hidden focus stops of 1,645 where I measure **290 of
|
||||
1,455** — the difference is the page set, not a disagreement, and *"always state
|
||||
the grid with the count"* is why it reads as one. And its footer-label clearance
|
||||
of 8.0 px is my 7.7 px, same measurement, different rounding. Its two
|
||||
*additional* widths — 17 px @660 and 11 px @680 — are real and are now in the
|
||||
record; my 12-column grid stepped over them.
|
||||
|
||||
### Candidate B — verified, recommended, and deliberately not shipped
|
||||
|
||||
Round 2's fix for the SC 2.4.11 failure is `scroll-padding-top`, the property
|
||||
that already exists for the job, rather than un-sticking the header:
|
||||
|
||||
```css
|
||||
html {
|
||||
scroll-padding-top: calc(var(--header-h) + var(--space-4)); /* fallback: no `lh` */
|
||||
scroll-padding-top: max(calc(var(--header-h) + var(--space-4)), calc(10lh - 83px));
|
||||
}
|
||||
```
|
||||
|
||||
**Every figure re-measured here, read from `getComputedStyle`:**
|
||||
|
||||
| condition | `1lh` on `<html>` | computed offset | header | verdict |
|
||||
|---|---|---|---|---|
|
||||
| default, fonts loaded | 18 px | **97 px — byte-identical to shipped** | 81 px | clears |
|
||||
| `minimumFontSize=32` | 37 px | **287 px** | 270.56 px | clears |
|
||||
| default, **`.woff2` blocked** | **18 px** | **97 px** | 81 px | clears |
|
||||
| `minimumFontSize=32`, **`.woff2` blocked** | **37 px** | **287 px** | 270.56 px | clears |
|
||||
| `minimumFontSize=32`, Georgia | 36 px | 277 px | 270.56 px | clears |
|
||||
| `minimumFontSize=32`, Verdana | 39 px | 307 px | 270.56 px | clears |
|
||||
|
||||
⚠️ **Rows 3–4 are why B beats candidate A: `1lh` on `<html>` is immune to the
|
||||
webfont fallback**, because `<html>` keeps the UA family — `--font-sans` is set on
|
||||
`body`, and `<html>`'s computed `font-family` measures `Times`. The
|
||||
fallback-metrics caveat that deferred A does not apply to B at all.
|
||||
|
||||
**Not shipped, and none of the reasons is a measurement gap:** `10lh - 83px` is
|
||||
two fitted constants; the plain first declaration is load-bearing, because an
|
||||
engine without `lh` drops the whole `max()` and falls back to 16 px, *worse* than
|
||||
today; it needs the full 1,408-cell sweep plus the fallback-metrics and seven-item
|
||||
cases; the focus probe is Chrome-only; and **D19 caps the review at two rounds, so
|
||||
a header change made now ships unreviewed** — the exact failure the cap prevents.
|
||||
**Q61 is a ruling, not a task.**
|
||||
|
||||
### R20's gate is now a mechanism, and it was proven to fire
|
||||
|
||||
`SiteHeader.astro` throws when `published.length >= 2`. **Proven rather than
|
||||
asserted:** two articles temporarily flipped to `draft: false`, `npm run build`
|
||||
exited **1** naming R20 and carrying the measurements; the files were restored and
|
||||
**the restoration verified** by `git diff --exit-code` (0) and an unchanged `dist`
|
||||
digest. Today `published.length` is 0, so the build passes and `dist` is untouched.
|
||||
|
||||
This closes the gap Pouya's own instruction named — *"cross-reference from the
|
||||
Insights publish procedure so the trigger can't fire ungated"* — which three prose
|
||||
pointers did not: with two articles published, `build`, `check`, `check:claims`,
|
||||
`og:proof`, `check:intake` and `lint` all passed while both defects shipped.
|
||||
|
||||
### Verification — every gate run, exit status read, nothing truncated
|
||||
|
||||
| check | result |
|
||||
|---|---|
|
||||
| `npm run check` | **exit 0** — 0 errors / 0 warnings / 0 hints |
|
||||
| `npm run build` | **exit 0** — 22 pages; digest `75a175ef` |
|
||||
| `npm run check:claims` | **exit 0** — every pattern firing on its fixture |
|
||||
| `npm run og:proof` | **exit 0** — 21 og:image targets, 20 headlines matched their `<h1>` |
|
||||
| `npm run check:intake` | **exit 0** — 12 form fields = 12 handler fields |
|
||||
| `npm run lint` | **exit 0** |
|
||||
| `npm run lighthouse` | **exit 0** — 22 pages, no category below 95, CLS 0.000 on all 22, LCP 1.58–2.03 s (`/` at 2.03 s, pre-existing and unchanged) |
|
||||
| minifier tripwire | **exit 1 — clean** |
|
||||
| `npm audit` | 0 vulnerabilities, read from `--json` |
|
||||
| internal links | 899 hrefs, 0 unresolved |
|
||||
| JS-disabled | the address is in the raw HTML of 22/22 pages; 22 `<script>` tags, **all `application/ld+json`**, 0 `.js` files in `dist/` |
|
||||
| `overflow-x` in shipped CSS | **0 declarations** |
|
||||
| `public/pouya-lajevardi-bio.pdf` | byte-identical to HEAD, blob `caf0b4ca` |
|
||||
| untracked files | 0 |
|
||||
|
||||
**Four-method grid re-run on the final bytes: 0 of 352 in every mode.** `dist` is
|
||||
byte-identical to the tree every figure in this entry was measured against, so the
|
||||
numbers describe what would ship rather than an earlier draft.
|
||||
|
||||
## 2026-09-01 (ak) — The large-text nav overflow is FIXED and the step-1 header decision is formally reopened; the masthead may wrap and is sticky only while it is one row; three enlargement mechanisms measured, one of them provably invisible to CSS — and round 1's own fix shipped a regression that only an intermediate text size could show
|
||||
|
||||
**Prior commits:** `64bce10` (entry (ai)'s tree, as-is) and `0f7595b` (entry (aj)'s five-item step). Tree was clean at the start of this session.
|
||||
|
||||
Reference in New Issue
Block a user