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.
|
||||
|
||||
@@ -406,14 +406,32 @@ the layout the desktop nav was measured in. One of those reported **0** while th
|
||||
other reported **944 px** on the same 22 pages, and the prose generalised the
|
||||
zero.
|
||||
|
||||
⚠️ **AND THERE ARE THREE MECHANISMS, NOT TWO — the third is the one that defeats
|
||||
every CSS conditional.** Measured 2026-09-01: Chrome's **"Minimum font size"**
|
||||
*floors* computed font sizes instead of scaling them, so text enlarges while
|
||||
`rem` keeps resolving at 16 px. A media query does not see it, and neither does a
|
||||
container query — `@container` `rem`/`em` DO track the root element (that is the
|
||||
one real difference from `@media`, and it is measured), but under a minimum-font-
|
||||
size setting they still resolve at 16 px while `getComputedStyle` reports 32 px.
|
||||
**So no query fires, and only used-value layout — wrapping — reflows.** That path
|
||||
⚠️ **AND THERE ARE THREE MECHANISMS, NOT TWO — the third defeats the `rem`/`em`
|
||||
FAMILY, which is not the same as defeating CSS.** Measured 2026-09-01: Chrome's
|
||||
**"Minimum font size"** *floors* computed font sizes instead of scaling them, so
|
||||
text enlarges while `rem` keeps resolving at 16 px. A media query in `rem` does
|
||||
not see it, and neither does a container query — `@container` `rem`/`em` DO track
|
||||
the root element (that is the one real difference from `@media`, and it is
|
||||
measured), but under a minimum-font-size setting they still resolve at 16 px while
|
||||
`getComputedStyle` reports 32 px.
|
||||
|
||||
⚠️ **BUT "NO CSS CONDITIONAL CAN SEE IT" IS FALSE, AND ASSERTING IT COST A
|
||||
CONFORMANCE FAILURE.** *Corrected 2026-09-01, same day, by `adversarial-reviewer`.*
|
||||
The **font-metric** units read the *used* font size and therefore double:
|
||||
**`ch`, `ex`, `cap`, `lh`, `rlh`** all respond — in property values, in `@media`
|
||||
**and** in `@container` (`ch` 10.608 → 21.216 px; `@media (min-width: 100ch)`
|
||||
flips). Only `rem`, `em`, `ic` and `px` are blind. The false generalisation was
|
||||
written into `docs/02`, `docs/06`, `global.css` and `tokens.css`, and it was then
|
||||
used as the premise for accepting a **WCAG 2.2 SC 2.4.11 (AA)** failure as
|
||||
unfixable — *"the only fix is JS"*. **The lesson is the shape, not the units: "no
|
||||
mechanism can X" is a claim about every mechanism, including the ones you did not
|
||||
enumerate.** Test the family you did not think of before writing "none", and
|
||||
prefer "every construct I measured is blind, and here is the list" — which is
|
||||
falsifiable and was what the measurement actually supported.
|
||||
|
||||
For *reflow* the conclusion is unchanged: **used-value layout — wrapping — is
|
||||
still the right mechanism**, because it needs no threshold and no fitted constant.
|
||||
That path
|
||||
was the worst of the three: on one grid of 22 pages × 16 widths, **219 of 352
|
||||
page-widths overflowed** against root-style's **175**, and it was the only one
|
||||
failing at 320 px and 1024 px. **Always state the grid with the count** — two
|
||||
|
||||
+387
-59
@@ -387,7 +387,28 @@ Not a polish pass. A build requirement.
|
||||
Command, so the numbers are re-runnable rather than quoted: headless Chrome
|
||||
over the built `dist`, `document.documentElement.style.fontSize = '32px'`, then
|
||||
`documentElement.scrollWidth - documentElement.clientWidth`, plus an
|
||||
enumeration of every element wider than `clientWidth` to name the offender.
|
||||
enumeration of every element that either **is wider than `clientWidth`** or
|
||||
**whose own content overflows its own box** (`scrollWidth - clientWidth`), to
|
||||
name the offender. ⚠️ **Both halves of that predicate are load-bearing and the
|
||||
second was added on 2026-09-01, after the first half alone reported "no offender"
|
||||
at three of the four widths that were failing** — the footer email spilled text
|
||||
out of a box that was itself inside the viewport. A sweep that enumerates only
|
||||
over-wide *elements* will name no cause for a whole class of real overflow.
|
||||
|
||||
⚠️ **AND THE RECIPE ABOVE HAS A UNIFORM-PASS TRAP THAT IS NOT VISIBLE IN IT.**
|
||||
Run `document.documentElement.style.fontSize = '32px'` with
|
||||
`prefers-reduced-motion: reduce` in force — launch flag *or* CDP emulation — and
|
||||
it reports **0 of 352 with the enlargement never applied**: brand width, header
|
||||
height and document height all byte-identical to root 16. The reduced-motion
|
||||
reset emits `*,::before,::after{transition-duration:.01ms!important}` over the
|
||||
initial `transition-property: all`, so the root font-size change becomes a
|
||||
`CSSTransition` and a synchronous read returns its START value.
|
||||
`document.getAnimations()` shows `CSSTransition: font-size, running, t=0`; after
|
||||
one `requestAnimationFrame` it is 32 px. **Kill motion with an injected
|
||||
`transition:none;animation:none` sheet rather than by emulating reduced motion,
|
||||
and assert the applied root size per cell** — that assertion is the only thing
|
||||
that distinguishes this from a clean sweep. Found by `adversarial-reviewer`,
|
||||
round 2, running this section's own published recipe.
|
||||
|
||||
✅ **THE LARGE-TEXT NAV OVERFLOW IS FIXED, 2026-09-01.** Pouya reopened the
|
||||
step-1 header decision for the purpose — *"its record attributed the residual to
|
||||
@@ -407,7 +428,7 @@ Not a polish pass. A build requirement.
|
||||
|---|---|---|---|---|
|
||||
| `documentElement.style.fontSize='32px'` (root style) | **16 px → `66rem` = 1056 px** | 32 px | 32 px | media query **no**, property **yes** |
|
||||
| `Page.setFontSizes({standard:32})` (the reader's *default font size*) | 32 px → `66rem` = **2112 px** | 32 px | 32 px | **yes, both** — the breakpoint moves with the type |
|
||||
| `--blink-settings=minimumFontSize=32` (the reader's *minimum font size*) | 16 px → `66rem` = **1056 px** | **16 px** | **32 px** | **NOTHING IN CSS SEES IT** |
|
||||
| `--blink-settings=minimumFontSize=32` (the reader's *minimum font size*) | 16 px → `66rem` = **1056 px** | **16 px** | **32 px** | **NO `rem`/`em` CONSTRUCT SEES IT** — the columns of this table are all `rem`/`em`, and that is the limit of what the row measures. ⚠️ **The FONT-METRIC units DO see it** (`ch`, `ex`, `cap`, `lh`, `rlh`); see the table below |
|
||||
|
||||
*(Row 1's media-query cell read "32 px → 2112 px" for one revision, which
|
||||
contradicted its own verdict column, the paragraph below it, and measurement —
|
||||
@@ -431,9 +452,24 @@ Not a polish pass. A build requirement.
|
||||
32 px while `em` and `rem` keep resolving at 16 px — so `scroll-padding-top:
|
||||
6.0625em` computes to **97 px, not 194 px**, a `66em`-wide box measures
|
||||
**1056 px, not 2112 px**, and `min(0px, calc(100vw - 66em))` is **0px**.
|
||||
Media queries, container queries and length units are all blind to it.
|
||||
**Only used-value layout — wrapping — responds**, which is why wrapping had
|
||||
to be the mechanism and why one residual below cannot be closed in CSS.
|
||||
⚠️ **AND THE SENTENCE THAT USED TO SIT HERE WAS FALSE AND LOAD-BEARING: it
|
||||
said "media queries, container queries and length units are all blind to it"
|
||||
and that "only used-value layout — wrapping — responds".** It is the
|
||||
`rem`/`em` FAMILY that is blind, not CSS. The **font-metric** units read the
|
||||
*used* font size and therefore double, and they do so in all three
|
||||
constructs — measured 2026-09-01 on `/fees/` @1280, headless Chrome, 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 px, `ex` 8.48 → 16.96, `cap` 11.36 → 22.72, `lh` 25.59 → 51.19 |
|
||||
| `@media (min-width: N<unit>)` | blind — threshold never flips | **responds** — `100ch`, `110ex`, `80cap`, `45lh` all flip |
|
||||
| `@container (min-width: N<unit>)` | blind | **responds** — `45ex`, `32cap` flip |
|
||||
|
||||
So wrapping is still the right mechanism for *reflow* — it needs no
|
||||
threshold and no fitted constant — but **"no CSS can detect this setting" is
|
||||
not true, and the residual below is therefore not provably unfixable.**
|
||||
Found by `adversarial-reviewer` and reproduced independently.
|
||||
|
||||
**Before and after, 22 pages × 16 widths (320 → 1920 px) = 352 page-widths per
|
||||
setting, document overflow `documentElement.scrollWidth −
|
||||
@@ -445,7 +481,31 @@ Not a polish pass. A build requirement.
|
||||
| default (root 16) | 0 of 352 | **0 of 352** |
|
||||
| root style 32 px | **175 of 352** | **0 of 352** |
|
||||
| default font size 32 | 0 of 352 | **0 of 352** |
|
||||
| minimum font size 32 | **219 of 352** | **88 of 352** — all of it the FOOTER, see the residual below |
|
||||
| minimum font size 32 | **219 of 352** | **0 of 352** — the header fix left 88, all of them the footer; the footer fix below closed them, 2026-09-01 |
|
||||
|
||||
✅ **SO: ZERO DOCUMENT OVERFLOW UNDER ALL FOUR METHODS AT EVERY WIDTH
|
||||
MEASURED — AND THE SCOPE OF THAT SENTENCE IS EXACTLY THE GRID ABOVE.** 22 built
|
||||
pages × those 16 widths × four enlargement methods = **1,408 page-widths**,
|
||||
document overflow by `documentElement.scrollWidth − documentElement.clientWidth`,
|
||||
webfonts loaded, six nav items, `prefers-reduced-motion` neutralised by an
|
||||
injected sheet rather than emulated. Plus **762** further points for the sticky
|
||||
gate (roots 9–15, roots 16–32, the band seam, both thresholds, all five Chrome
|
||||
presets), 0 failing.
|
||||
|
||||
⚠️ **It is not a claim about anything outside that grid, and "no document
|
||||
overflow" is not "no accessibility defect".** Three known cases sit outside it
|
||||
deliberately. The **fallback-metrics case** below needs the webfonts blocked and
|
||||
a seventh nav item. The **focus-obscuring case** below is an **open WCAG 2.2
|
||||
SC 2.4.11 (AA) failure** — `AGENTS.md` Q61 — and document overflow cannot see it
|
||||
at all, because nothing overflows: the header simply covers what has focus. And a
|
||||
**footer nav label** still overruns its own column by 24 px at 640 px under
|
||||
minimum font size with 7.7 px of clearance, again with zero document overflow.
|
||||
None of the three is contradicted by the zeros above, and none of them is
|
||||
measured by them — which is the reason each is recorded in its own right rather
|
||||
than summarised into the table. Widths
|
||||
between the sampled ones are not measured either — the grid is 16 columns, not a
|
||||
continuum, and the two thresholds and the band seam were swept precisely because
|
||||
a 16-column grid can step over a 36 px band.
|
||||
|
||||
**The wide-width figures that were the defect, root style 32 px, and what they
|
||||
are now.** Every one is 0 of 22 pages after the fix, and the nav items and the
|
||||
@@ -616,8 +676,8 @@ Not a polish pass. A build requirement.
|
||||
|
||||
| condition | header | offset | short by | status |
|
||||
|---|---|---|---|---|
|
||||
| `minimumFontSize=32` | 164.58–270.56 px | 97 px | **68–174 px** | live, unfixable in CSS |
|
||||
| fallback font metrics + 7th nav item, **default** text size, 1056–1091 px | 141 px | 97 px | **44 px** | latent on `showInsights` |
|
||||
| `minimumFontSize=32` | 164.58–270.56 px | 97 px | **68–174 px** | 🛑 **OPEN — needs a fresh ruling.** The 2026-09-01 acceptance rested on two facts that are both false; and the real cost is **290 entirely-obscured focus stops, SC 2.4.11 AA**, not a short skip-link landing. **A pure-CSS detector DOES exist.** See below |
|
||||
| fallback font metrics + 7th nav item, **default** text size, 1056–1091 px | 141 px | 97 px | **44 px** | latent on `showInsights`. **HARD GATE: `AGENTS.md` R20 — the seventh item does not ship until this is fixed** |
|
||||
| `defaultFontSize=9` | 120 px | 54.56 px | ~~65 px~~ | **fixed** by the `1rem - 16px` term |
|
||||
| root-style 10 px | 69 px | 60.63 px | ~~8 px~~ | **fixed** by the same term |
|
||||
|
||||
@@ -642,9 +702,16 @@ Not a polish pass. A build requirement.
|
||||
defended forever**, because the reason given is one nobody wants to re-open —
|
||||
which is exactly what happened for four days.
|
||||
|
||||
**TEN FIXES ACROSS THE TWO PASSES — no `overflow-x` was added anywhere.** Eight
|
||||
are narrow-width (2026-08-31); the last two are the large-text nav (2026-09-01).
|
||||
All but one are cause-specific; the exception is marked as the backstop it is:
|
||||
**ELEVEN FIXES ACROSS THREE PASSES, IN TWELVE ROWS — no `overflow-x` was added
|
||||
anywhere.** Eight are narrow-width (2026-08-31), two are the large-text nav
|
||||
(2026-09-01), and the eleventh is the footer email (2026-09-01, the pass that
|
||||
closed the last recorded overflow). ⚠️ **The twelfth row is `.nav-list`, and it
|
||||
is a row without being a fix:** it is here because its removal was *measured*,
|
||||
and it is not counted because the measurement was that it changes nothing. Say
|
||||
so rather than leaving the arithmetic to a reader who counts rows — the heading
|
||||
read "TEN FIXES" over eleven rows until 2026-09-01 for exactly this reason. All
|
||||
but one of the eleven are cause-specific; the exception is marked as the
|
||||
backstop it is:
|
||||
|
||||
| Element | Was | 320 px | Cause |
|
||||
|---|---|---|---|
|
||||
@@ -656,6 +723,7 @@ Not a polish pass. A build requirement.
|
||||
| `/ .hero-h` | `overflow-wrap: anywhere` | 36 px | "contract," at 104 px held `.hero-copy` open |
|
||||
| `/ .feature-body` | `overflow-wrap: anywhere` | 26 px | "party-appointed" at 202 px |
|
||||
| `CredentialRow .credential-label` | `overflow-wrap: anywhere` | 38 px | "designation" needs 143 px in an 88 px track |
|
||||
| `SiteFooter .footer-contact a[href^='mailto:']` | `overflow-wrap: anywhere` | **14 px** | `info@smlcompany.ca` has no break opportunity and demands 310 px. Minimum-font-size only; also 38 / 30 / 19 px at 1024 / 1056 / 1100 px, by a different mechanism — see the footer block above |
|
||||
| `SiteFooter .footer-brand` | `flex-wrap: wrap` | 12 px | Flex item at `min-width: auto` cannot shrink below "Lajevardi" |
|
||||
| `PracticeCard .card` | `padding` clamped | *(the cause under `Pill`)* | The space scale is rem-based, so `--space-6` is 64 px a side at root 32 — 128 px of padding in a ~224 px box |
|
||||
| `Pill` | `overflow-wrap: anywhere` | 33 px → **1 px** | ⚠️ **SYMPTOM, NOT CAUSE.** A one-word pill cannot wrap at a space that is not there |
|
||||
@@ -674,8 +742,14 @@ Not a polish pass. A build requirement.
|
||||
card, escaping its own rounded border. Two lines is the better of those. The
|
||||
clamp does not change any normal size: 10vw holds 32 px from 320 px up.
|
||||
|
||||
**Six instrument findings, and each one hid a real defect. The last three are
|
||||
from the 2026-09-01 pass and all three produced a CLEAN-LOOKING result:**
|
||||
**TEN instrument findings, and each one hid a real defect. Items 4–6 are from the
|
||||
2026-09-01 header pass and all three produced a CLEAN-LOOKING result; 7–10 are
|
||||
from the footer pass the same day. Each is recorded once — the footer block above
|
||||
points here rather than restating them.** ⚠️ **Finding 2 RECURRED in that footer
|
||||
pass**, in a probe written by the person who had written finding 2 down: it is
|
||||
described where it did its damage, beside the footer fix, and not duplicated as
|
||||
an eleventh item.
|
||||
|
||||
|
||||
4. ⚠️ **A UNIFORM PASS FROM A TAUTOLOGY.** The skip-link probe reported
|
||||
`0.0 px of #main covered` on **946 of 946** page-widths, before and after,
|
||||
@@ -704,6 +778,34 @@ Not a polish pass. A build requirement.
|
||||
the direction that makes a width test pass. **Clone a real node** and assert
|
||||
its computed styles match a sibling's.
|
||||
|
||||
7. ⚠️ **A CAUSE READ OFF A TRUNCATED LIST.** The header harness capped its
|
||||
offender array at `slice(0, 20)` while reporting `offenderCount` beside it,
|
||||
and **44 of the 88 failing rows exceeded the cap** — so the enumeration that
|
||||
was supposed to name the footer's cause was silently missing 34 of the 54
|
||||
boxes at 320 px. The truncation was visible only to a reader who compared the
|
||||
two numbers. Same family as the `tail -3` rule in `CLAUDE.md`, through a
|
||||
different pipe.
|
||||
8. ⚠️ **`grep -c` COUNTS LINES, NOT OCCURRENCES, AND MINIFIED CSS IS ONE LINE.**
|
||||
`grep -c 'overflow-wrap:anywhere' dist/_astro/*.css` returned `1` for a file
|
||||
that holds **three** such declarations. It was being used to confirm the new
|
||||
rule had reached `dist/`, which it could not do. Extract and print the bytes
|
||||
instead — the rule that actually settled it was reading the emitted selector,
|
||||
`.footer-contact[data-astro-cid-nns7i3if] a[data-astro-cid-nns7i3if][href^=mailto\:]`.
|
||||
|
||||
9. ⚠️ **`getClientRects().length` IS 1 FOR A `display: flex` ELEMENT HOWEVER MANY
|
||||
LINES OF TEXT IT HOLDS** — it is one block box, not a fragmented inline. The
|
||||
wrap probe therefore reported `lines=1` for the footer email while its own
|
||||
`height` said **102.38 px = 2 × 51.19**, i.e. two lines. Line counts come from
|
||||
`Range` rects over the text, which do fragment per line. **Two metrics of the
|
||||
same quantity disagreeing is the cheapest defect detector in this section** —
|
||||
it is what caught this one.
|
||||
10. ⚠️ **`Page.captureScreenshot`'s `clip` IS IN PAGE COORDINATES, NOT VIEWPORT
|
||||
COORDINATES.** Given a `getBoundingClientRect()` taken after a scroll, it
|
||||
captured a **blank cream plate** — of a footer that is ink. The screenshot
|
||||
existed to be the independent second method for a break the numbers could not
|
||||
judge, so a silently wrong one is worse than none. Add `window.scrollX/Y`, or
|
||||
pass `captureBeyondViewport: true` and page coordinates.
|
||||
|
||||
The original three:
|
||||
|
||||
1. **`overflow-wrap: break-word` permits a break at layout time but does not
|
||||
@@ -727,51 +829,267 @@ Not a polish pass. A build requirement.
|
||||
`documentElement.scrollWidth - documentElement.clientWidth`, which is correct
|
||||
in both modes.
|
||||
|
||||
⚠️ **THE ONE RESIDUAL THAT REMAINS, AND IT IS THE FOOTER RATHER THAN THE
|
||||
HEADER.** Under Chrome's **minimum font size** only, document overflow remains at
|
||||
four widths — **14 px at 320, 38 px at 1024, 30 px at 1056, 19 px at 1100**, on
|
||||
all 22 pages, falling to 0 from 1200 px up. Every instance is
|
||||
`SiteFooter`: `info@smlcompany.ca` has no break opportunity and needs **310 px**
|
||||
inside a 224–243 px footer-contact column, and at 320 px the `.footer-col` box
|
||||
itself measures 310 px inside a 272 px content area. **The FOOTER'S OWN overflow is
|
||||
byte-identical before and after** — `div.footer-grid` 78 px at 1056 and 67 px at
|
||||
1100, `footer.site-footer` 30 px and 19 px, unchanged — and the 320 px and
|
||||
1024 px *document* figures are unchanged too, at 14 and 38. ⚠️ **The 1056 px and
|
||||
1100 px document figures DID change, 414 → 30 and 370 → 19, and this sentence
|
||||
claimed all four were identical.** They changed because the header stopped
|
||||
contributing its own 414 px and 370 px, not because the footer moved; a document
|
||||
figure and a footer contribution are different measurements and only the second
|
||||
is unchanged. Found by `adversarial-reviewer`. It is **out of the scope Pouya set** for this step ("the header and
|
||||
its consumers"), so it was measured and left rather than improvised: the
|
||||
1024–1100 px half looks like one `overflow-wrap: anywhere` on the footer contact
|
||||
links, the 320 px half is a column-sizing question and is not the same fix.
|
||||
Not header-side: **no header element extends the document at any width under any
|
||||
mechanism**, and the nav items and CTA are on-screen in **1408 of 1408**
|
||||
measurements (four text settings × 22 pages × 16 widths). *(This said "946 of
|
||||
946" for one revision. 946 is a different sweep — the four runs made with the
|
||||
tautological skip probe in instrument finding 4 — and reusing it here for the
|
||||
final build was the two-similar-totals mistake this section keeps warning
|
||||
about.)* *(The Practice dropdown panel's contents do overflow their own box
|
||||
by 84 px under this setting, but the panel sits inside a **closed** `<details>`
|
||||
and never extends the document — checked by taking the furthest-right box rather
|
||||
than the largest content overflow.)*
|
||||
✅ **THE FOOTER RESIDUAL IS FIXED, 2026-09-01 — ONE DECLARATION, AND THE
|
||||
EARLIER RECORD MISDIAGNOSED HALF OF IT.** Under Chrome's minimum font size the
|
||||
document overflowed at four widths on all 22 pages — **14 px at 320, 38 px at
|
||||
1024, 30 px at 1056, 19 px at 1100** — and 0 from 1200 px up. One cause at all
|
||||
four: `info@smlcompany.ca` has no break opportunity, so its **min-content width
|
||||
is 310 px**.
|
||||
|
||||
⚠️ **AND ONE THAT CANNOT BE CLOSED IN CSS AT ALL.** Under minimum font size the
|
||||
sticky gate above cannot fire — **both** of its terms evaluate to `0` because
|
||||
`rem` still resolves at 16 px there, so `100vw - 66rem` is positive and
|
||||
`1rem - 16px` is exactly zero — and the header stays sticky at
|
||||
**164.58–270.56 px** and the skip link lands with **68–174 px of `#main` behind
|
||||
it**, on 22 of 22 pages from 1056 px up. It was **10–26 px before**, so this pass
|
||||
widened it. *(These read "228–271 px" and "132–174 px" for one revision; both
|
||||
omitted the single-row band — 68 px at 1200 px and on `/` from 1216 px up.
|
||||
`adversarial-reviewer` measured the widths the first sweep had not.)* The trade is deliberate and is the right way round: what it bought is the
|
||||
removal of **830 px of horizontal overflow with four nav items and the CTA
|
||||
unreachable** under that same setting. Vertical offset leaves every target
|
||||
reachable by scrolling; the horizontal case did not. **No media query, container
|
||||
query or length unit can detect this mechanism** (see the table above), the only
|
||||
candidates left are a fitted magic number tied to the current nav item count or
|
||||
a normal-settings behaviour change, and Pouya's constraint was to stop and report
|
||||
rather than ship either. Carried on `docs/06`'s cutover checklist.
|
||||
| Element | Was | Fix | Effect |
|
||||
|---|---|---|---|
|
||||
| `SiteFooter .footer-contact a[href^='mailto:']` | `overflow-wrap: normal` | `overflow-wrap: anywhere` | **88 of 352 → 0 of 352** |
|
||||
|
||||
⚠️ **TWO MECHANISMS, ONE CAUSE — and this is the part the earlier record got
|
||||
wrong.** It 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.** The column sizing at
|
||||
320 px is *driven by* that same 310 px:
|
||||
|
||||
- **At 320 px** `.footer-grid`'s implicit track is `auto`, whose minimum is
|
||||
min-content, so the track — and every `.footer-col` in it — measures **310 px
|
||||
inside a 272 px content area**. The boxes themselves cross the viewport edge:
|
||||
**54 elements** did, 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 — and the box stays **224 / 232 / 243 px**
|
||||
while the text spills **86 / 78 / 67 px outside it**. **No element's border box
|
||||
crosses the viewport edge at all** at those widths.
|
||||
|
||||
Reducing one min-content width closes both halves.
|
||||
|
||||
⚠️ **AND IT CLOSED A THIRD CASE THAT THE FOUR-METHOD TABLE REPORTED AS CLEAN —
|
||||
"MEASURE THE ELEMENTS, NOT ONLY THE PAGE", WHICH IS ALREADY A RULE IN THIS
|
||||
SECTION.** With the fix disabled as a control, over 420 points (3 pages × 10
|
||||
widths × 6 minimum-font-size 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 a 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 sitting
|
||||
inside 96 px of padding. The spill shrinks as the viewport widens (79 / 71 / 60 /
|
||||
35 / 15 / 0 px at 1024 / 1056 / 1100 / 1200 / 1280 / 1440), which is why the case
|
||||
disappears at 1440 px rather than at a breakpoint.
|
||||
|
||||
**After the fix: 0 of 420, and 0 clipping at every preset and every root size.**
|
||||
The control is what makes that number mean anything — a sweep returning zero on
|
||||
every cell is the shape this section warns about twice, so it was re-run with the
|
||||
declaration forced back to `normal` and it failed 69.
|
||||
|
||||
⚠️ **AND THE ELEMENT SWEEP THAT LOOKED FOR THE CAUSE REPORTED "NONE" AT THREE
|
||||
OF THE FOUR WIDTHS** — instrument finding 2 in this section, committed again by
|
||||
the person who wrote it down. `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 precisely the 1024–1100 px mechanism.
|
||||
The document figure was right and the offender list was empty, and those two
|
||||
facts together are the signature of this bug. **Read `scrollWidth − clientWidth`
|
||||
per element as well as per document.**
|
||||
|
||||
⚠️ **And the harness's offender list was truncated at 20 entries on 44 of the 88
|
||||
failing rows**, so it could not have named the cause either — instrument finding
|
||||
**7** below.
|
||||
|
||||
**What the fix costs, measured rather than asserted.** At the default text size,
|
||||
**nothing**: the address is one line, **44.00 px** tall — exactly the touch-target
|
||||
floor — at all 16 widths, and the normal-settings identity check is 0 differences
|
||||
on 352 page-widths. Under minimum font size it 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, not inferred from a width. The `href`
|
||||
is untouched (`mailto:info@smlcompany.ca`), 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.
|
||||
|
||||
**`anywhere`, NOT `break-word` — and that was established with a NEGATIVE
|
||||
CONTROL rather than from the rule.** `break-word` permits a break at layout time
|
||||
without reducing min-content size, so it leaves the defect untouched: injected in
|
||||
place of `anywhere` it failed **88 of 176** rows, the same rows as the unfixed
|
||||
baseline. 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**, and
|
||||
this section already carries five instrument findings of exactly that shape.
|
||||
|
||||
**On the link, not on the column, and not in the markup.** Three candidates all
|
||||
reach 0 of 176: `anywhere` on the email link, on `.footer-contact`, and on
|
||||
`.footer-col a`. The address is the only string in the footer that produces
|
||||
**document** overflow, so the rule sits on the address and the two broader
|
||||
selectors were declined as broader than the cause.
|
||||
|
||||
⚠️ **THE REASON FIRST GIVEN FOR THAT SCOPING WAS THE WRONG TEST, AND IT IS WORTH
|
||||
MORE THAN THE SCOPING.** It said *"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"*. **A space is not the test. Min-content is
|
||||
set by the longest WORD**, so a label with spaces still overruns its track when
|
||||
one of its words does not fit. `adversarial-reviewer` found one that does, and it
|
||||
reproduced at a width the review had not sampled:
|
||||
|
||||
| width | element | own-box overrun | clearance to the next column | document |
|
||||
|---|---|---|---|---|
|
||||
| 640 px | `.footer-col a` "Construction & Infrastructure" | **24 px** out of a 176 px box | **7.7 px** | 0 |
|
||||
| 640 px | `.footer-col a` "Shareholder & Family Business" | 2 px | 7.7 px | 0 |
|
||||
| 700 px | `.footer-col a` "Construction & Infrastructure" | 4 px out of a 196 px box | 27.7 px | 0 |
|
||||
|
||||
`minimumFontSize=32`, all 22 pages, 12 widths from 320 to 1024 px = 264 rows;
|
||||
clean at every other width sampled, including 768 and 834 px. **"Infrastructure"
|
||||
is 14 characters and does not fit a 176 px track at 32 px** — the space in the
|
||||
label is irrelevant to that.
|
||||
|
||||
⚠️ **AND THE 12-WIDTH GRID STEPPED OVER TWO MORE**: round 2 found the same label
|
||||
at **17 px @660 and 11 px @680**, widths this grid does not sample. That is this
|
||||
section's own caveat holding — *the grid is 16 columns, not a continuum* — and it
|
||||
is the second time in one session that a between-columns width mattered.
|
||||
|
||||
**A FOURTH CASE OF THE SAME FAMILY, and it should be ruled on together with the
|
||||
footer label rather than separately.** `minimumFontSize=32`,
|
||||
`/practice/construction/` @320 px: `nav.crumbs > ol` overruns its own 272 px box
|
||||
by **7 px**, furthest text edge 302.9 px against a 320 px client width,
|
||||
`overflow-x: visible`, no clipping, **document overflow 0**. Clean at 360 / 390 /
|
||||
414 px and on the other five practice pages. **No success criterion fails**, so
|
||||
like the footer label it is recorded rather than fixed — but if
|
||||
`.footer-col a { overflow-wrap: anywhere }` is ruled in, the same call covers
|
||||
`.crumbs` and the ruling should be taken once for both. Raised by
|
||||
`adversarial-reviewer`, round 2.
|
||||
|
||||
**Not fixed here, and the reason is a scope judgement rather than a measurement.**
|
||||
It produces **no document overflow at any width**, so it fails no success
|
||||
criterion; `.footer-col a { overflow-wrap: anywhere }` is measured to close it
|
||||
and to be inert at normal settings. What it would change is how six practice-area
|
||||
names break on a marketing surface under enlargement, which is a copy-adjacent
|
||||
call. ⚠️ **The maintenance risk is the real finding: 7.7 px of clearance is one
|
||||
label rename away from two columns colliding**, and nothing in the build measures
|
||||
it. Pouya's to rule on; `docs/06` carries it. `word-break: break-all` also
|
||||
reaches 0 and was declined for the same reason: it breaks where a normal
|
||||
opportunity exists. **And NOT a `<wbr>` after the `@`**, which would give a
|
||||
prettier break: it splits a §4-registered fact across an element boundary, so
|
||||
`info@smlcompany.ca` would no longer be greppable in `dist/` — the surface
|
||||
`npm run check:claims` reads — and its longest unbreakable run (`smlcompany.ca`,
|
||||
~224 px) does not fit the 224 px column at 1024 px anyway, so it would need
|
||||
`anywhere` as a backstop regardless.
|
||||
|
||||
**Not header-side, and that survives the fix:** no header element extends the
|
||||
document at any width under any of the four mechanisms, and the nav items and CTA
|
||||
are on-screen in **1408 of 1408** measurements. *(This said "946 of 946" for one
|
||||
revision. 946 is a different sweep — the four runs made with the tautological
|
||||
skip probe in instrument finding 4 — and reusing it for the final build was the
|
||||
two-similar-totals mistake this section keeps warning about.)* *(The Practice
|
||||
dropdown panel's contents overflow their own box by 84 px under minimum font
|
||||
size and 40 px at the default, on all 22 pages — unchanged by this fix, and it
|
||||
never extends the document because the panel sits inside a **closed**
|
||||
`<details>`. Re-checked after the fix rather than carried forward.)*
|
||||
|
||||
🛑 **THE MINIMUM-FONT-SIZE STICKY RESIDUAL IS BACK OPEN, AND IT IS NOW A
|
||||
CONFORMANCE FAILURE RATHER THAN A CONVENIENCE LOSS. IT NEEDS A FRESH RULING —
|
||||
THE ONE OF 2026-09-01 WAS TAKEN ON TWO FACTS THAT ARE BOTH WRONG.** Pouya ruled
|
||||
it accepted on the basis *"you proved no CSS mechanism can see
|
||||
minimum-font-size; the only fix is JS and zero-JS is a founding decision."*
|
||||
`adversarial-reviewer` attacked both halves of that and both attacks reproduced.
|
||||
|
||||
**(1) THE PREMISE IS FALSE.** The proof was of `rem`/`em` and of queries written
|
||||
in them. The font-metric units `ch`, `ex`, `cap`, `lh` and `rlh` all read the
|
||||
*used* font size and double under the setting — in property values, in `@media`
|
||||
and in `@container`. The table at the top of this section carries the
|
||||
measurement. **A pure-CSS detector for this mechanism exists**, so the residual
|
||||
is not unfixable and "the only fix is JS" does not hold.
|
||||
|
||||
**(2) THE COST WAS UNDERSTATED, AND BY A CATEGORY RATHER THAN A NUMBER.** The
|
||||
record described it as the skip link landing short — 68–174 px of `#main` behind
|
||||
the header — 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 is actually
|
||||
happening 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 1.4.10 failure it was traded
|
||||
against. **So the asymmetry that was the whole argument does not exist: both
|
||||
sides are AA failures.**
|
||||
|
||||
**Measured, and the instrument is checked in three ways.** Shift+Tab walk — the
|
||||
ordinary way a keyboard user returns to a link they passed — 6 pages × 5 widths
|
||||
≥ 1056 px, 70 steps per cell, elements inside the header 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` (pre-header-fix) | `minimumFontSize=32` | 1,455 | **0** | — |
|
||||
| `fce89d4~1` (pre-header-fix) | default | 1,455 | **0** | — |
|
||||
|
||||
⚠️ **THE THIRD ROW IS THE ONE THAT MATTERS: THE 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; the overflow went sideways instead. After
|
||||
it the masthead wraps to 164.58–270.56 px and covers the 97 px landing. This is
|
||||
a **regression introduced by the 2026-09-01 header fix**, not a condition it
|
||||
inherited — and the ruling to accept it was taken without that comparison
|
||||
existing.
|
||||
|
||||
**Instrument checks, because a focus-obscuring count is easy to fake in both
|
||||
directions.** *(a)* The default-mode run returns **0**, so the predicate is not
|
||||
tautological. *(b)* ⚠️ **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 — so `elementFromPoint` at the focused box's centre is
|
||||
authoritative and geometry only nominates candidates. Under the setting there
|
||||
are 333 geometric candidates and **290** survive the hit test. *(c)* Sampled
|
||||
hits agree 6 of 6, e.g. `/` @1056: focused `a` "Technology, AI & Data" at
|
||||
97 → 199.4 px inside a header at 0 → 228.6 px, `position: sticky`,
|
||||
`inset-block-start: 0px`, background `rgb(250, 247, 242)` — opaque cream.
|
||||
|
||||
**THE RECOMMENDED FIX IS MEASURED AND DELIBERATELY NOT IMPLEMENTED — Q61.** Two
|
||||
candidates exist, and the second is better on the exact ground that deferred the
|
||||
first.
|
||||
|
||||
*Candidate A, round 1:* a third gate term `calc((15px - 1ch) * 100000)`,
|
||||
un-sticking the header under the setting while leaving `inset-block-start: 0px`
|
||||
at the default. **Deferred:** 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.
|
||||
|
||||
*Candidate B, round 2 — recommended:* `scroll-padding-top`, the property that
|
||||
already exists for this job, rather than un-sticking anything. Two declarations
|
||||
in the existing `@media (min-width: 66rem)` block:
|
||||
|
||||
```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)
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
**Verified independently — every value read from `getComputedStyle`, not
|
||||
reasoned:**
|
||||
|
||||
| condition | `1lh` on `<html>` | computed offset | header | verdict |
|
||||
|---|---|---|---|---|
|
||||
| default, fonts loaded | 18 px | **97 px — byte-identical to shipped** | 81 px | clears |
|
||||
| `minimumFontSize=32`, fonts loaded | 37 px | **287 px** | 270.56 px | clears |
|
||||
| default, **every `.woff2` blocked** | **18 px** | **97 px** | 81 px | clears |
|
||||
| `minimumFontSize=32`, **`.woff2` blocked** | **37 px** | **287 px** | 270.56 px | clears |
|
||||
| `minimumFontSize=32`, family Georgia | 36 px | 277 px | 270.56 px | clears |
|
||||
| `minimumFontSize=32`, family Verdana | 39 px | 307 px | 270.56 px | clears |
|
||||
|
||||
⚠️ **Rows 3 and 4 are why B beats A: `1lh` on `<html>` is immune to the webfont
|
||||
fallback.** `<html>` keeps the UA default family — `--font-sans` is set on
|
||||
`body`, and `<html>`'s computed `font-family` measures `Times` — so blocking every
|
||||
font file changes nothing, and the fallback-metrics caveat that deferred
|
||||
candidate A does not apply to B at all.
|
||||
|
||||
**Why B is still not implemented, and none of these is a measurement gap.**
|
||||
`10lh - 83px` is two fitted constants — the difference from A is that a `max()`
|
||||
ramp degrades by pixels if they are off, where A's `* 100000` step flips
|
||||
stickiness the wrong way. **The first declaration is load-bearing:** an engine
|
||||
without `lh` support drops the whole `max()` as invalid and `scroll-padding-top`
|
||||
falls back to `var(--space-4)` = 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 all of this is **Chrome-only**. And **D19 caps the review at two
|
||||
rounds**, so a header change made now would ship without review — the precise
|
||||
failure mode the cap exists to prevent. **The findings are the premise and the
|
||||
cost; the fix is Pouya's ruling.**
|
||||
|
||||
**`docs/06` carries this as an OPEN item again**, restored from the `[x]` this
|
||||
session had written, and the decision is `AGENTS.md` **Q61**.
|
||||
|
||||
⚠️ **AND ONE MORE LATENT CASE, AT THE DEFAULT TEXT SIZE — NO READER SETTING
|
||||
INVOLVED: FALLBACK FONT METRICS PLUS THE SEVENTH NAV ITEM.** With every `.woff2`
|
||||
@@ -800,8 +1118,18 @@ Not a polish pass. A build requirement.
|
||||
here, and deliberately:** the two candidates are raising the desktop breakpoint
|
||||
past 1091 px, which changes the layout at normal settings in that band, or giving
|
||||
Geist a metric-matched `size-adjust` fallback, which is a font-stack change well
|
||||
outside "the header and its consumers". Both are Pouya's call; carried on
|
||||
`docs/06`.
|
||||
outside "the header and its consumers".
|
||||
|
||||
⚠️ **AND IT IS NOW GATED RATHER THAN MERELY PARKED — Pouya's ruling,
|
||||
2026-09-01: NO SEVENTH NAV ITEM SHIPS UNTIL THIS IS FIXED.** Fixing it is a
|
||||
**prerequisite of publishing the second Insights article**, because publishing
|
||||
article #2 is what adds the item — `SiteHeader` computes `showInsights` from the
|
||||
collection, so nothing else stands between that editorial decision and both
|
||||
consequences above. The gate is recorded in three places on purpose: `AGENTS.md`
|
||||
R20, `docs/06`'s `/insights/` state item, and the comment on `showInsights` in
|
||||
`SiteHeader.astro` — the last of those being the only one a person editing an
|
||||
article's front matter is likely to be looking at. The fix itself is *"to be
|
||||
designed then, not now"*.
|
||||
|
||||
**What is still latent, measured and not a defect today:** `.hero-h` has **16
|
||||
scoped rules, one per page, and only 2 carry `overflow-wrap: anywhere`** (`/` and
|
||||
|
||||
+123
-24
@@ -364,6 +364,32 @@ Then invalidate `/*`.
|
||||
|
||||
## Cutover checklist — D11 is a single shot, so run all of it
|
||||
|
||||
**Cutover prep — deferred maintenance, done BEFORE the checklist below**
|
||||
|
||||
This group exists because deferring a thing and forgetting it look identical
|
||||
three weeks later. Each item carries the date it was deferred and the reason, so
|
||||
the decision is re-readable rather than re-litigated.
|
||||
|
||||
- [ ] ⚠️ **TWO DEPENDENCY MAJORS ARE ONE BEHIND, DEFERRED 2026-09-01 BY RULING.**
|
||||
`@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."* `npm audit` reports **0 vulnerabilities**
|
||||
`[verified 2026-09-01]`, so this is a churn decision and not a security one —
|
||||
and it would become a security decision the moment that stops being true.
|
||||
**Take both majors here, before cutover, and read the changelogs rather than
|
||||
the version numbers**: an MDX major can change how `docs/03`'s copy renders,
|
||||
and a TypeScript major can change what `astro check` accepts, which is a
|
||||
gate on both deploy paths. **`AGENTS.md` R11 keeps firing at every phase
|
||||
boundary as designed** — this item is the deferral, not a replacement for the
|
||||
reminder, and R11 is what will raise the next pin that drifts.
|
||||
**The sweep behind this item, `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 are already satisfied by their own 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
|
||||
remaining 13 are current. Re-run the sweep here rather than trusting this
|
||||
list: it is dated for that reason
|
||||
|
||||
**Content and compliance**
|
||||
|
||||
> ⚠️ **THE FIRST TWO ITEMS ARE THE PROJECT'S ONLY FULL CLAIMS PASS — D20, Pouya,
|
||||
@@ -425,6 +451,15 @@ Then invalidate `/*`.
|
||||
- [ ] `/fees/` carries the rates confirmed in D14 and `docs/07-fees.md`, or the page does not ship. **And the reservation sentence sits adjacent to the overtime row** — Q59, ruled 2026-08-31: a reader who takes the $500 and skips *"a full day reserves the day; half-day overtime is subject to availability"* has read a different offer, and the arithmetic then makes the full-day rate look strictly worse
|
||||
- [ ] Privacy policy matches the backend as actually built. **Three of its statements are derived and cannot drift** — the collected-data list from `INTAKE_FIELDS`, the retention period from the handler's own figure, the analytics paragraph from `ANALYTICS.installed`. **If analytics are installed, that flag flips and the policy's last-updated date moves on the same build**; a policy naming a processor that processes nothing is a false disclosure
|
||||
- [ ] **`/insights/` — check which state it is in.** While no article is published the page emits `noindex` (derived from the collection) and `SiteHeader` keeps Insights out of the primary nav until **two** are live. Both clear themselves on publication. D9 means the flip is Pouya's: `src/content.config.ts` refuses `draft: false` without `reviewedByPouya: true`
|
||||
- [ ] ⚠️ **AND PUBLISHING THE SECOND ARTICLE IS GATED — `AGENTS.md` R20, ruled
|
||||
2026-09-01.** The flip that makes `/insights/` indexable is the same flip that
|
||||
adds the **seventh nav item**, and with seven items under fallback font
|
||||
metrics the masthead measures 141 px across 1056–1091 px: a 60 px layout shift
|
||||
on every page and 44 px of `#main` behind the sticky header after the skip
|
||||
link, both at the **default** text size. **No seventh nav item ships until
|
||||
that is fixed**, so the fix is a prerequisite of article #2 — not a follow-up
|
||||
to it. The measurements and the two candidate fixes are on the
|
||||
seventh-nav-item item under **Technical** below
|
||||
- [ ] **The five drafted articles are `draft: true` and therefore invisible to `check:claims`**, which reads `dist/`. They were checked once, on 2026-08-31, by temporarily publishing all five and running it — clean on 27 pages. **Re-run it that way before any of them publishes**, because a draft that changes in between is unchecked
|
||||
|
||||
**Technical**
|
||||
@@ -560,29 +595,77 @@ Then invalidate `/*`.
|
||||
roots 9–15 (70), roots 16–32 (300), the band seam (198), both thresholds
|
||||
(144) and all five default-font-size presets (50). Both findings by
|
||||
`adversarial-reviewer`; `docs/02` §Reflow carries all of it
|
||||
- [ ] ⚠️ **TWO REFLOW RESIDUALS SURVIVE, BOTH UNDER CHROME'S MINIMUM-FONT-SIZE
|
||||
SETTING ONLY, AND BOTH NEED A RULING RATHER THAN A RE-MEASUREMENT.**
|
||||
*(a) The footer, and it is OUT OF the scope Pouya set for the header step.*
|
||||
Document overflow of **14 px at 320, 38 px at 1024, 30 px at 1056 and 19 px
|
||||
at 1100 CSS px**, on all 22 pages, 0 from 1200 px up. Cause:
|
||||
`info@smlcompany.ca` has no break opportunity and needs 310 px inside a
|
||||
224–243 px footer-contact column; at 320 px the `.footer-col` box itself is
|
||||
310 px in a 272 px content area. **Pre-existing: the footer's OWN overflow is
|
||||
byte-identical before and after** (`div.footer-grid` 78 px at 1056, 67 px at
|
||||
1100). The 1056/1100 *document* figures did change, 414 → 30 and 370 → 19,
|
||||
because the header stopped contributing its own — a document figure and a
|
||||
footer contribution are different measurements.
|
||||
*(b) The skip link, and it CANNOT be fixed in CSS.* The header stays sticky
|
||||
at **164.58–270.56 px** under that setting, so "Skip to content" lands with
|
||||
**68–174 px of `#main` behind the header** on 22 of 22 pages from 1056 px
|
||||
up — it was 10–26 px before, so the header fix widened it, deliberately, to
|
||||
buy the removal of 830 px of horizontal overflow with four nav items and the
|
||||
CTA unreachable. **Nothing in CSS can detect that mechanism**: media queries,
|
||||
container queries AND length units all keep resolving `rem`/`em` at 16 px
|
||||
while `getComputedStyle` reports 32 px (measured; `docs/02` §Reflow). The
|
||||
remaining candidates are a fitted constant tied to the nav item count, or a
|
||||
behaviour change at normal settings. **Ticked by Pouya fixing (a) and ruling
|
||||
on (b) — not by re-measuring with a method that reports zero**
|
||||
- [x] ✅ **THE FOOTER REFLOW RESIDUAL IS FIXED, 2026-09-01.** Document overflow of
|
||||
**14 px at 320, 38 px at 1024, 30 px at 1056 and 19 px at 1100 CSS px** on all
|
||||
22 pages under Chrome's minimum-font-size setting, 0 from 1200 px up. One
|
||||
cause at all four: `info@smlcompany.ca` has no break opportunity, so its
|
||||
**min-content width is 310 px**. One declaration closes it —
|
||||
`.footer-contact a[href^='mailto:'] { overflow-wrap: anywhere }` — measured
|
||||
**88 of 352 → 0 of 352**, the other three enlargement methods unchanged at 0
|
||||
of 352, and the normal-settings identity check at **0 differences on 352
|
||||
page-widths across 8 metrics**. It also closed **57 cases of the link
|
||||
overrunning its own box** at root style 26–32 px that the document-level
|
||||
table reports as clean. ⚠️ **The earlier note here said the 320 px half "is a
|
||||
column-sizing question and is not the same fix" — that was wrong**; the
|
||||
column sizing at 320 px *is* that same 310 px min-content, arriving through
|
||||
`.footer-grid`'s `auto` track instead of through text spilling out of a
|
||||
`minmax(0, 1fr)` one. `docs/02` §Reflow carries both mechanisms, the negative
|
||||
control proving `break-word` would not have worked, and the candidates
|
||||
declined as broader than the cause
|
||||
|
||||
- [ ] 🛑 **THE MINIMUM-FONT-SIZE STICKY RESIDUAL — REOPENED THE SAME DAY IT WAS
|
||||
TICKED, AND IT IS A WCAG 2.2 SC 2.4.11 (AA) FAILURE RATHER THAN THE
|
||||
SKIP-LINK INCONVENIENCE IT WAS ACCEPTED AS. NEEDS A FRESH RULING.**
|
||||
It was ruled accepted on 2026-09-01 on the basis *"you proved no CSS
|
||||
mechanism can see minimum-font-size; the only fix is JS and zero-JS is a
|
||||
founding decision"*, and this checklist item was ticked accordingly. Both
|
||||
supporting facts are wrong, and `adversarial-reviewer` found it the same day.
|
||||
**(1) The premise.** The proof covered `rem`/`em` and queries written in them.
|
||||
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`. A pure-CSS detector exists, so "the only fix is JS" does not
|
||||
hold. **(2) The cost.** Not a short skip-link landing but **ordinary keyboard
|
||||
focus landing entirely behind an opaque sticky header**: a Shift+Tab walk over
|
||||
6 pages × 5 widths ≥ 1056 px gives **290 entirely-hidden focus stops of 1,455**
|
||||
under the setting — **254 in the footer and 36 inside `#main`** — against **0**
|
||||
at the default size. Hit-tested with `elementFromPoint`, not inferred from
|
||||
geometry: geometry alone said 43 at the default size and all 43 were the skip
|
||||
link, which is stacked *above* the header on purpose. **(3) And the header fix
|
||||
created it:** a build of `fce89d4~1` measures **0** under the same setting,
|
||||
because `flex-wrap: nowrap` kept the masthead one row and short. So the trade
|
||||
was **830 px of horizontal overflow (SC 1.4.10) for 290 obscured focus stops
|
||||
(SC 2.4.11)** — both Level AA, and the asymmetry that was the whole argument
|
||||
does not exist. **A candidate fix is measured but deliberately not
|
||||
implemented:** a third gate term in the shipping idiom, `calc((15px - 1ch) *
|
||||
100000)`, un-sticks the header under the setting and leaves
|
||||
`inset-block-start: 0px` at the default — but 15 px is fitted to Geist's `ch`
|
||||
and needs checking under fallback metrics and Chrome's other font presets.
|
||||
**This item is ticked only by a ruling from Pouya, not by a re-measurement.**
|
||||
The decision is `AGENTS.md` **Q61**; `docs/02` §Reflow carries the measurements
|
||||
|
||||
- [ ] ⚠️ **A FOOTER NAV LABEL OVERRUNS ITS COLUMN BY 24 px AT 640 px UNDER
|
||||
MINIMUM FONT SIZE, WITH 7.7 px OF CLEARANCE TO THE NEXT COLUMN.** No document
|
||||
overflow at any width, so it fails no success criterion and is cosmetic
|
||||
today — filed because **7.7 px is one label rename away from two columns
|
||||
colliding**, and nothing in the build measures it. `.footer-col a`
|
||||
"Construction & Infrastructure" overruns its 176 px box by **24 px** at
|
||||
640 px and by 4 px at 700 px (clearance 27.7 px); "Shareholder & Family
|
||||
Business" by 2 px at 640 px, and — at widths that 12-column grid steps over —
|
||||
**17 px at 660 px and 11 px at 680 px**. Clean at every other width sampled,
|
||||
including 768 and 834 px; `minimumFontSize=32`, 22 pages × 12 widths = 264
|
||||
rows. **A fourth case of the same family: `nav.crumbs > ol` on
|
||||
`/practice/construction/` @320 px overruns its own 272 px box by 7 px**, also
|
||||
with zero document overflow. **Rule on both together** — if
|
||||
`.footer-col a { overflow-wrap: anywhere }` is ruled in, the same call covers
|
||||
`.crumbs`; if it is declined, decline both and the record is complete.
|
||||
**`.footer-col a { overflow-wrap: anywhere }` is measured to close it and to
|
||||
be inert at normal settings**, and is not applied because it changes how six
|
||||
practice-area names break on a marketing surface under enlargement — a
|
||||
copy-adjacent call, not a technical one. ⚠️ **And it falsified the reason
|
||||
given for scoping the email fix to the address** (*"every other footer string
|
||||
has spaces"*): min-content is set by the longest **word**, not by whether a
|
||||
space exists. Corrected in `docs/02` §Reflow. Raised by
|
||||
`adversarial-reviewer`, 2026-09-01
|
||||
|
||||
- [ ] ⚠️ **THE SEVENTH NAV ITEM PLUS FALLBACK FONT METRICS — A LAYOUT SHIFT *AND*
|
||||
A SKIP-LINK FAILURE, AT THE DEFAULT TEXT SIZE, ARMING ITSELF WHEN THE SECOND
|
||||
@@ -602,7 +685,23 @@ Then invalidate `/*`.
|
||||
because both are outside "the header and its consumers": raise the desktop
|
||||
breakpoint past 1091 px, which changes the normal-settings layout in that
|
||||
band, or give Geist a metric-matched `size-adjust` fallback. Raised by
|
||||
`adversarial-reviewer`, 2026-09-01; `docs/02` §Reflow carries the measurements
|
||||
`adversarial-reviewer`, 2026-09-01; `docs/02` §Reflow carries the measurements.
|
||||
|
||||
⚠️ **THIS IS NOT A CUTOVER BLOCKER AND IT IS NOT TICKABLE HERE — IT IS A HARD
|
||||
PRE-PUBLICATION GATE. Pouya's ruling, 2026-09-01: no seventh nav item ships
|
||||
until the fallback-metrics defect is fixed**, which makes fixing it a
|
||||
**prerequisite of publishing the second Insights article**, since that is what
|
||||
adds the item. It is left unchecked on purpose: a checklist that fires once, at
|
||||
cutover, cannot discharge a defect that arms itself later on an editorial
|
||||
decision. ⚠️ **AND THE GATE IS A BUILD FAILURE, NOT THIS CHECKLIST AND NOT A
|
||||
COMMENT.** `SiteHeader.astro` throws when `published.length >= 2`, with the
|
||||
measurements and the instruction in the message, so it fires on both deploy
|
||||
paths on the machine of whoever publishes. **Proven rather than assumed:** two
|
||||
articles were temporarily flipped to `draft: false` and `npm run build` exited
|
||||
**1** naming R20; the files were restored and the restoration verified by
|
||||
`git diff --exit-code` and an unchanged `dist` digest. It was first written as
|
||||
three prose cross-references and **did not gate** — the build and all five
|
||||
checks passed while both defects shipped. **Owner: `AGENTS.md` R20**
|
||||
|
||||
**Infrastructure**
|
||||
- [ ] S3 versioning enabled
|
||||
|
||||
@@ -244,6 +244,12 @@ const aboutLinks = [
|
||||
color: var(--text-inverse-2);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* `anywhere`, NOT `break-word` — only `anywhere` reduces min-content size, which
|
||||
is the defect: the address has no break opportunity. `docs/02` §Reflow. */
|
||||
.footer-contact a[href^='mailto:'] {
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
/* Not a link, so no target floor — but it shares a column with links and
|
||||
should sit on the same rhythm. */
|
||||
.footer-meta {
|
||||
|
||||
@@ -23,7 +23,24 @@ import InfinityMark from './InfinityMark.astro';
|
||||
import Button from './Button.astro';
|
||||
|
||||
const published = await getCollection('insights', ({ data }) => !data.draft);
|
||||
/* ⚠️ GATED BY A BUILD FAILURE, NOT BY THIS COMMENT — `AGENTS.md` R20 and Q61.
|
||||
The seventh nav item arms two measured header defects under fallback font
|
||||
metrics. Prose cross-references did not gate it: every check passed while it
|
||||
fired. Delete the throw as part of the fix, not before it. */
|
||||
const showInsights = published.length >= 2;
|
||||
if (showInsights) {
|
||||
throw new Error(
|
||||
'AGENTS.md R20 — the seventh nav item is gated and this build would ship it.\n' +
|
||||
`A second Insights article is published (${published.length} live), which puts ` +
|
||||
'Insights in the primary nav. With seven items under FALLBACK font metrics — ' +
|
||||
'the font-display: swap window, at the default text size, no reader setting ' +
|
||||
'involved — the masthead measures 141px across 1056-1091px instead of 81px: a ' +
|
||||
'60px shift on every page against the CLS < 0.05 budget, and 44px of #main ' +
|
||||
'behind the sticky header after "Skip to content".\n' +
|
||||
'Fix that first (docs/02 §Reflow lists the two candidate fixes), then delete ' +
|
||||
'this guard. Do not work around it by unpublishing the article.',
|
||||
);
|
||||
}
|
||||
|
||||
const items = PRIMARY_NAV.filter(
|
||||
(item) => item.href !== '/insights/' || showInsights,
|
||||
|
||||
@@ -124,8 +124,12 @@ html {
|
||||
`--header-h` is defined in tokens.css beside the value it has to match, and it
|
||||
is a FLOOR — above the default text size the masthead reflows and is taller, and
|
||||
`SiteHeader` gates stickiness so the offset is generous rather than short.
|
||||
⚠️ TWO SETTINGS DEFEAT THAT GATE and leave this offset short; `docs/02` §Reflow
|
||||
names them and shows why no CSS can detect either. */
|
||||
⚠️ TWO CASES DEFEAT THAT GATE and leave this offset short — one a reader
|
||||
setting, one the webfont swap window. Under Chrome's minimum font size the
|
||||
shortfall is an OPEN WCAG 2.2 SC 2.4.11 (AA) failure, not an accepted one:
|
||||
`AGENTS.md` Q61. **CSS CAN detect that setting** — the font-metric units
|
||||
(`ch`, `ex`, `cap`, `lh`) respond to it where `rem`/`em` do not — so a fix is
|
||||
possible here and is awaiting a ruling. Measurements: `docs/02` §Reflow. */
|
||||
@media (min-width: 66rem) {
|
||||
html {
|
||||
scroll-padding-top: calc(var(--header-h) + var(--space-4));
|
||||
|
||||
@@ -142,9 +142,9 @@
|
||||
is why the nav no longer runs off-screen. `global.css` drives
|
||||
`scroll-padding-top` off it and `SiteHeader` gates stickiness so the offset is
|
||||
generous rather than short — **but not in every case**: `docs/02` §Reflow lists
|
||||
the two settings where it is still short and why no CSS can see them. If
|
||||
SiteHeader's padding or nav sizing changes, re-measure and change this with
|
||||
it — one fact living in two files.
|
||||
the two cases where it is still short, one of them an OPEN SC 2.4.11 (AA)
|
||||
failure (`AGENTS.md` Q61). If SiteHeader's padding or nav sizing changes,
|
||||
re-measure and change this with it — one fact living in two files.
|
||||
[measured 2026-08-26, re-measured 2026-09-01 — headless Chrome, six nav items
|
||||
and a seventh injected. 81.00px at all EIGHT widths from 1056 to 1920px
|
||||
(1056/1100/1216/1240/1280/1440/1600/1920): 32 padding + 48 reserved brand
|
||||
|
||||
Reference in New Issue
Block a user