fix: header reflows under enlarged text; reopen the step-1 nowrap decision

The step-1 header decision is formally reopened (AGENTS.md (ak)): its record
attributed the residual to the wrong cause and read a measured 944px functional
failure as a robustness margin.

Mechanism — wrapping, plus a gated sticky offset:
- `flex-wrap: nowrap` removed from `.header-inner` (measured necessary AND
  sufficient) and from `.nav-list` (measured inert; removed so the prohibition
  is not contradicted by a `nowrap` in the same file), with a dead `flex: none`.
- `inset-block-start` becomes a two-band, two-term saturating clamp() so the
  header is sticky only while the masthead is one row. A media query cannot
  express this: its `rem` resolves against the browser DEFAULT font size, a
  property's against the root element. The second term catches a root BELOW the
  default, where the 80rem content cap shrinks faster than the header's px
  minimums; without it 65px of `#main` sat behind the header at 9px.
- Wrapping is the only mechanism that reflows under all THREE enlargement paths,
  because Chrome's minimum-font-size setting is invisible to @media, to
  @container and to every length unit.

Measured, 22 pages x 16 widths = 352 page-widths per method:
  root-style 32px        175/352 -> 0/352   (1280px: 944px overflow -> 0)
  minimum-font-size 32   219/352 -> 88/352  (residual is the footer email)
  default-font-size 32     0/352 -> 0/352
  default (root 16)        0/352 -> 0/352
Nav items and CTA on-screen in 1408/1408. A further 762 points across roots
9-32, both thresholds, the band seam and all five Chrome presets: 0 failing.

Normal-settings identity: 0 differences on 352 page-widths across six metrics,
with six nav items and with a seventh injected. Header 81.00px at all eight
widths >= 1056, CTA gap 0.00px. Lighthouse: 22 pages, no category below 95,
CLS 0.000 on every page.

--header-h is reworded as a FLOOR, not a constant; value unchanged.

Also in this step, per ruling:
- /bio/ print `font-weight` frozen at 400 — the circulated PDF's typography
  changes only when its content is deliberately revised, never as a side effect
  of a screen refactor. Declaration byte-identical; the constraint is recorded.
- CLAUDE.md: the two-simulation rule for enlarged text, the zsh
  no-word-splitting rule, the third (minimum-font-size) mechanism, and
  "state the grid with the count".

Two rounds of adversarial-reviewer, eleven findings, all resolved; round 2's
blocking finding was a defect in round 1's own fix. Two suggested fixes declined
with reasons in (ak). claims-auditor deliberately not run — D20.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Md3GndFqWPzK78xAoebsg5
This commit is contained in:
Pouya Lajevardi
2026-09-01 12:34:50 -04:00
co-authored by Claude Opus 5
parent 0f7595b602
commit fce89d46eb
8 changed files with 731 additions and 125 deletions
+371 -66
View File
@@ -145,12 +145,21 @@ alias of it; see the eyebrow rule below.
consumer of the 11 px token. `--text-xs` (12 px) is a separate rung and is
enumerated below.
**The exception, and why it is deliberate rather than residual:** the tagline is
ornamental and layout-constrained. 13 px already took the header to 83.4 px while
`--header-h` is 81 px, which `global.css` feeds into `scroll-padding-top`; and
with a seventh nav item the CTA lands past `.header-inner`'s content edge at
1216, 1240, 1280 and 1440 — it does not resolve as the viewport widens. Insights
is that seventh item. Both measurements are in `SiteHeader.astro`, and document
overflow is 0 in every one of those cases, so no page-level check can see it.
ornamental and layout-constrained. ⚠️ **THE COST WAS RE-MEASURED ON 2026-09-01
AND IT IS A DIFFERENT COST NOW — the masthead may wrap, so what used to overflow
invisibly is visible instead.** At `--text-eyebrow` (14 px) the header stands at
**144.98 px** rather than 81 px — at 1216 px with six nav items, and at **every
width from 1216 px up** with a seventh (measured at 1216 / 1240 / 1280 / 1360 /
1440 / 1600 / 1760 / 1920) — because the row wraps. *(This listed four widths
ending at 1440 for one revision; that list was carried over from the superseded
CTA-past-the-edge measurement, which really did stop at 1440.)* **Document overflow
is 0 and the CTA sits exactly on `.header-inner`'s content edge in every one of
those cases**, where before the fix they were 20 px of overflow at 1216 px, 4 px
at 1280 px, and the CTA 67.8 / 43.8 / 51.8 / 51.8 px past the content edge. So
the two costs this bullet used to cite are **gone**, replaced by one that is
larger and easier to see: 64 px of header height on every page. *(13 px took the
header to 83.4 px; that figure was taken before the masthead could wrap.)*
Insights is that seventh item. The measurements are in `SiteHeader.astro`.
**Three blocks moved to `--text-eyebrow` on 2026-08-31 and their carve-outs are
superseded.** (a) `/contact/`'s `<label>`/`<legend>` set — *raise, do not
ratify*, superseding the "accepted, not ratified" note this bullet carried for
@@ -171,8 +180,17 @@ alias of it; see the eyebrow rule below.
size. *(This read "63,743 bytes" for one revision. That is the `cmp -l`
differing-byte count, not the size delta: the content stream is Flate-compressed,
so a one-property change scrambles most of the file. Two figures, and the wrong
one answered a question nobody asked.)* That freeze is a held decision, not a
design position — see `bio.astro`.
one answered a question nobody asked.)* ⚠️ **THAT FREEZE IS NOW A RULED
CONSTRAINT, 2026-09-01, NOT A DECISION AWAITING POUYA: the circulated PDF's
typography changes only when its CONTENT is deliberately revised, never as a
side effect of a screen refactor.** So the print block keeps 400 whatever the
screen does, and `.eyebrow`'s 500 stops at the `@media print` boundary. **The
reason it is a rule and not a preference** is that the PDF is the one artefact
this project's apparatus sees only when someone re-renders it (`AGENTS.md`
R16): a refactor that silently re-typesets it changes a document already in a
reader's hands, and the diff that would have shown it is a 1,655-byte
compressed blob nobody reads. A screen change that reaches print is therefore
a defect by construction, not a judgement call — see `bio.astro`.
**`--text-xs` (12 px) IS A SEPARATE RUNG AND IT WAS NOT IN SCOPE ON 2026-08-31.
Ten declaration sites, all of them:** `Pill`, `Breadcrumbs`, `CredentialRow`,
`DefinitionGrid`, `ArticleCard`, `ProcessStep` (two), `insights/[...slug].astro`,
@@ -371,70 +389,247 @@ Not a polish pass. A build requirement.
`documentElement.scrollWidth - documentElement.clientWidth`, plus an
enumeration of every element wider than `clientWidth` to name the offender.
⚠️ **THE NARROW-WIDTH RESIDUALS ARE 0. THE 1280 px AND 1920 px ONES ARE NOT, AND
THIS PARAGRAPH CLAIMED THEY WERE.** Pouya ruled the narrow case a defect on
2026-08-31 — *"the 152/112/82 px overflows at 320/360/390 with 32 px root text
violate WCAG 1.4.10 and are not accepted"* — and that case is fixed. The wide
case is a **different cause**, is **not** fixed, and is now stated correctly
rather than reported as zero.
**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
the wrong cause and characterized a measured 944 px functional failure as a
robustness margin"* — and ruled *fix, do not accept*. What follows is the
current state. The narrow case was ruled a defect on 2026-08-31 (*"the
152/112/82 px overflows at 320/360/390 with 32 px root text violate WCAG 1.4.10
and are not accepted"*) and fixed then.
**TWO METHODS OF SIMULATING 200% TEXT, AND THEY ARE NOT EQUIVALENT. This is the
whole of the error.** Every earlier number in this section came from the command
below — `documentElement.style.fontSize = '32px'`. A later sweep used Chrome's
`Page.setFontSizes({standard: 32})` instead and reported 0 everywhere, and the
prose generalised that to *"at root 16 px and root 32 px"*.
**THREE MECHANISMS ENLARGE TEXT, AND THEY ARE NOT EQUIVALENT — this is the
measurement the whole record turned on.** Every figure in this section before
2026-09-01 came from `documentElement.style.fontSize = '32px'`. A later sweep
used Chrome's `Page.setFontSizes({standard: 32})`, reported 0 everywhere, and
the prose generalised that zero. All three are re-measured below.
| | media query `66rem` resolves to | header above 1056 px | 1280 px, root 32 |
|---|---|---|---|
| `documentElement.style.fontSize='32px'` | **1056 px** — matches | `sticky`, `flex-wrap: nowrap` | **944 px over** |
| `Page.setFontSizes({standard:32})` | **2112 px** — never matches | `relative`, `flex-wrap: wrap` | **0** |
| mechanism | `rem` in a media query | `rem`/`em` in a PROPERTY | `getComputedStyle` root | sees the enlargement? |
|---|---|---|---|---|
| `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** |
In a media query `rem` resolves against the **default** font size, not the root
element's. So raising the *default* moves the breakpoints along with the type and
the desktop nav is never reached; setting the *root* doubles the type while
leaving the breakpoints at 16 px, which is the layout the desktop nav was
measured in. **Both are real settings, and there is a THIRD mechanism which is
the one a reader actually reaches.** Chrome exposes "Font size" — the default,
the second row, which moves the breakpoints along with the type and overflows
nothing — and, separately, **"Minimum font size", which FLOORS computed sizes
rather than scaling them and leaves media queries alone.** Measured at 1280 px
with `--blink-settings=minimumFontSize=32`: the `66rem` query matches, the nav is
`sticky` / `nowrap`, `.brand-name` computes to **32 px (floored, not scaled to
42.6 px)**, and the document is over by **830 px**. So the failure is reachable,
and **830 px is the reachable figure while 944 px is the `documentElement`-root
one — they are not the same measurement and must not be quoted as one.** The two
rows below are the two simulations, not the three mechanisms.
*(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 —
and it deleted the one number that explains why the failure is reachable at all.
If `66rem` really resolved to 2112 px under root scaling, the 66 rem desktop
masthead would never be reached at 1280 px and the 944 px overflow could not
happen. Found by `adversarial-reviewer`.)*
**The corrected figures, `documentElement.style.fontSize='32px'`, 22 pages:**
Two consequences, both measured 2026-09-01 and both load-bearing:
| width | pages over | overflow |
1. **In a media query `rem` resolves against the browser's DEFAULT font size,
not the root element's.** So raising the *default* moves the breakpoints
along with the type and the desktop masthead is never reached; setting the
*root* doubles the type and leaves the breakpoints where they were, which is
the layout the desktop nav was measured in. **A container query is the one
construct that differs** — `@container` `rem`/`em` DO resolve against the
root element, measured — which is why the sticky gate below is a property and
not a query.
2. ⚠️ **UNDER A MINIMUM-FONT-SIZE SETTING, `getComputedStyle(el).fontSize` AND
THE `em` UNIT DISAGREE.** Blink floors the *reported computed* font size to
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.
**Before and after, 22 pages × 16 widths (320 → 1920 px) = 352 page-widths per
setting, document overflow `documentElement.scrollWidth
documentElement.clientWidth`. Both columns measured on the SAME grid**, from a
build of the previous commit and a build of the working tree, by the same probe:
| text setting | before | after |
|---|---|---|
| 320 / 360 / 390 / 414 / 640 / 1024 | **0 of 22** | **0** — the ruled targets, fixed |
| 1280 | 22 of 22 | **944 px** on 21 pages, **508 px** on `/` |
| 1920 | 21 of 22 | **304 px** (all but `/`) |
| 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 |
Root 16 is 0 at every width. The old **944 px** figure is **unchanged**; 602 px
became 508 px on `/` only because `.brand-name`'s `nowrap` went.
**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
CTA are on-screen at every width:
⚠️ **AND AT 1280 px IT IS NOT A MARGIN — IT IS UNREACHABLE NAVIGATION.** The
furthest element is `div.header-cta` at **2224 px** against a 1280 px viewport;
the nav clips mid-word after "Arbitration", and **Practice (left 1378), Fees
(1556), Contact (1666) and the header CTA are entirely off-screen** behind a
horizontal scrollbar — including the route to the inquiry form. *(This named
"Med-Arb" for one revision. Med-Arb is in `SECONDARY_NAV` and is not in the
masthead at any width; the item actually lost, and the worst of them, is
Contact.)* That is
WCAG 1.4.4 with loss of functionality. The paragraph struck here called it *"a
robustness margin rather than a failure. Revisit if a real reader hits it."*
| width | before | after |
|---|---|---|
| 1056 | 649 px, 22 of 22 | **0** |
| 1100 | 606 px, 22 of 22 | **0** |
| 1216 | 928 px / 491 px on `/`, 22 of 22 | **0** |
| 1240 | 904 px / 468 px on `/` | **0** |
| 1280 | **944 px** / 508 px on `/` | **0** |
| 1440 | 784 px / 351 px on `/` | **0** |
| 1600 | 624 px / 193 px on `/` | **0** |
| 1920 | 304 px, 21 of 22 | **0** |
**THE CAUSE AT 1280 px IS `flex-wrap: nowrap` ON `.nav-list` ABOVE 66 rem, AND
THAT IS A LOCKED STEP-1 DECISION — SO IT IS AN OPEN DECISION FOR POUYA, NOT A
FIX MADE HERE.** His instruction with the eyebrow ruling was explicit: *"if
anything header-side does overflow, stop and report rather than improvising a
layout change."* The options are an `em`-based breakpoint so the header drops to
its stacked layout when the type grows, or letting the nav wrap above 66 rem and
giving up the constant 81 px `--header-h`. Both change the desktop masthead.
Carried on `docs/06`'s cutover checklist so it cannot ship unresolved.
*(Only 1280 and 1920 were in the earlier record, so it never showed that the
failure ran the whole width of the sticky range. The same run under minimum font
size was 414 / 370 / 854 / 830 / 830 / 750 / 670 / 510 px, of which the record
carried only the 830.)*
⚠️ **AND IT WAS NOT A MARGIN — IT WAS UNREACHABLE NAVIGATION.** The furthest
element was `div.header-cta` at **2224 px** against a 1280 px viewport; the nav
clipped mid-word after "Arbitration", and **Practice, Fees, Contact and the
header CTA sat entirely off-screen** behind a horizontal scrollbar — the route
to the inquiry form among them. WCAG 1.4.4 with loss of functionality. The
paragraph struck here called it *"a robustness margin rather than a failure.
Revisit if a real reader hits it."*
**THE FIX IS TWO DECLARATIONS, AND THE SECOND ONE IS THERE BECAUSE THE FIRST
ONE HAS A CONSEQUENCE.**
**(1) The masthead may wrap.** `flex-wrap: nowrap` is gone from `.header-inner`
and from `.nav-list` above 66 rem, along with the dead `flex: none` beside it.
⚠️ **WHICH OF THE TWO WAS THE CAUSE IS NOW MEASURED, AND THIS PARAGRAPH FIRST
GOT IT WRONG.** It said *"that pair, not any one declaration, was the cause"*.
Restoring each declaration alone on the fixed build, root 32, `/about/`
document overflow / number of nav links and CTA off-screen:
| restored above 66 rem | 1056 | 1216 | 1280 | 1440 | 1920 |
|---|---|---|---|---|---|
| neither (shipped) | 0/0 | 0/0 | 0/0 | 0/0 | 0/0 |
| `.nav-list { flex-wrap: nowrap }` only | 0/0 | 0/0 | 0/0 | 0/0 | 0/0 |
| `.header-inner { flex-wrap: nowrap }` only | 0/0 | 273/1 | 209/1 | 49/1 | 0/0 |
| both (= the state before) | 649/3 | 928/5 | 944/5 | 784/4 | 304/1 |
**`.header-inner`'s `nowrap` was necessary and sufficient.** And the second row
is the one that matters for honesty: **`.nav-list`'s removal is inert** — byte-
identical to shipped at every width, under a 200 % root size *and* under minimum
font size, **with six nav items and with seven**. It is kept removed for two
reasons that are not "it fixed the overflow": the `nowrap` prohibition in
`SiteHeader.astro` would otherwise contradict a `nowrap` still sitting in the
file, and the override only ever re-stated `nowrap` over a base rule that already
wraps. Found by `adversarial-reviewer`; the mechanism sentence about min-content
was true and was not the binding constraint. *(`flex` is a flex-ITEM property and `.nav`
computes `display: block`, measured at 1056 / 1280 / 1920 px — so `flex: none`
set `flex-shrink: 0` on something that is not a flex item and had **no effect**.
Removing it leaves `.nav`'s and `.nav-list`'s geometry byte-identical at all
three widths; the only trace was the computed `flex-shrink`, 0 → 1. Its comment
claimed it was what stopped the nav being squeezed, which it never was.)*
**It cost nothing at any normal size: 0 geometry
differences across 22 pages × 16 widths (352 page-widths)**, header exactly **81.00 px** at every
width from 1056 px up, brand on one line, nav on one line, CTA exactly on
`.header-inner`'s content edge (gap 0.00 px) — **and the same with a seventh nav
item injected**, at 1024 / 1047 / 1056 / 1071 / 1100 / 1150 / 1200 / 1216 / 1240
/ 1280 / 1440 / 1920 px, where all seven share one line from 1056 px up. So the
`nowrap` was not load-bearing at any width above the breakpoint; the 66 rem
breakpoint is what keeps the row intact, and it already accounts for seven items.
**(2) The masthead is sticky only while it is one row**, and this half took two
attempts — the first one shipped a regression, which is recorded because the
reason it failed is the useful part.
Wrapping makes the header **taller** at enlarged text: **244.59350.86 px** at a
200 % root size against 81 px normally. A sticky box that size covers `#main`
after "Skip to content", so stickiness has to go wherever the header can exceed
`scroll-padding-top`. The gate is a **property**, not a query, because in a
property `rem` tracks the root element.
⚠️ **ROUND 1 WROTE `min(0px, calc(100vw - 66rem))` AND IT WAS WRONG IN TWO WAYS
THAT ONLY AN INTERMEDIATE ROOT SIZE EXPOSES.** Both were found by
`adversarial-reviewer` and independently reproduced: **20 of 300**
(page × viewport × root) points left `#main` behind the header, 1069 px, across
roots 1830 — while root 16 and root 32, the only two sizes the first sweep
measured, were both clean. **WCAG 1.4.4 is a requirement up TO 200 %, not AT
200 %**, and a two-point sweep cannot see a diagonal band.
1. **It RAMPED where it needed to STEP.** One pixel inside the threshold the term
lifts the header by one pixel — but the header has already gained a whole row,
so it still covered `#main`. Wrapping is a step function; the response has to
be one too. Hence the `* 100000` saturation, bounded by `-100vh`.
2. **It used the wrong threshold in the wider band.** The tagline appears at
76 rem, and with it the one-row masthead fits only from **1207 px = 75.4 rem**
— the step-1 binary search already recorded that number. Gating the wide band
on 66 rem therefore fired far too late. **`/` measured clean throughout, and
that is what identified the driver:** `/` is the one page that suppresses the
masthead tagline.
⚠️ **AND ROUND 2 FOUND THAT THE GATE WAS ONE-SIDED — it could only fire when the
root was LARGER than the default.** Chrome's "Font size" control has **five**
presets (Very small 9 px, Small 12, Medium 16, Large 20, Very large 24) and the
sweeps had used 16 and 32 — 32 is not one of them. At **9 px the masthead wraps
for the opposite reason**: `--width-content` is `80rem`, so the content column
shrinks to 720 px while the header's `min-inline-size: 44px` / `min-block-size:
44px` floors do not, and the row wraps at **every** viewport width. The header
stood at **120 px against a 54.56 px `scroll-padding-top`** — **65 px of `#main`
behind it on the 21 pages that render the tagline**, at 1056 through 1920 px.
`/` measured 12 px, and that 12 px is **pre-existing**: the previous build is 67 px
against the same 54.56 px offset on all 22 pages. So the wrap fix widened a
pre-existing 12 px defect to 65 px, and the second term closes both.
**The shipped form is two bands, each gating on the width ITS layout requires,
and two terms, each catching one direction:**
```css
@media (min-width: 66rem) { .site-header {
position: sticky;
inset-block-start: clamp(-100vh,
min(calc((100vw - 66rem) * 100000), calc((1rem - 16px) * 100000)), 0px); } }
@media (min-width: 76rem) { .site-header {
inset-block-start: clamp(-100vh,
min(calc((100vw - 76rem) * 100000), calc((1rem - 16px) * 100000)), 0px); } }
```
`1rem - 16px` is negative exactly when the root is below the CSS initial font
size — the 16 px the whole rem scale is built on — and it also catches the
root-style equivalent, where the previous form left the header sticky at 69 px
against a 60.63 px offset at root 10. **The cost, measured and accepted:** a
reader whose default text size is "Small" (12 px) loses the sticky header, where
it currently works — but only just: the header is 73 px against a 72.75 px offset,
a **0.25 px** margin. Trading a 0.25 px margin for a provable gate is the right
way round.
Both thresholds are the existing layout breakpoints, and both sit just above the
measured one-row fit width (1047 px ≈ 65.4 rem without the tagline, 1207 px ≈
75.4 rem with it), so the gate fires **at or before** the wrap rather than after
it. **At every normal size both evaluate to exactly `0px`** — a matched
`min-width: 66rem` guarantees `100vw ≥ 1056 px` and a matched `min-width: 76rem`
guarantees `100vw ≥ 1216 px`, because `100vw` counts a classic scrollbar and the
query width does not, so the term can only be more positive.
**Measured after: 0.00 px of `#main` covered — 0 of 300 grid points across roots
1632 × viewports 10561920 on three pages, 0 of 176 rows at the default size and
at a 200 % root size over all 22 pages, 0 of 144 cases sitting directly on the
two thresholds, and 0 of 198 at the SEAM where the two bands meet** (viewports
11801260 × roots 1632). ⚠️ **`covered: 0` is also what a non-sticky header
gives, so the seam was checked the other way round as well:** at the default size
across 1180 / 1200 / 1210 / 1215 / 1216 / 1217 / 1220 / 1240 / 1260 / 1440 /
1920 px the computed `inset-block-start` is `0px` and the header is still pinned
at top 0.00 when scrolled — stickiness is preserved, not quietly lost. ⚠️ **Saturation leaves a transition band, and it is
arithmetic rather than measured:** a finite factor means the response is only
a near-step, so the band is about (header `scroll-padding-top`) / factor ≈
**0.0007 px** of viewport width. CDP takes integer viewport widths, so that band
was not driven directly — it is bounded by the arithmetic, not by a probe.
*(Lightning CSS folds the factor into the units and emits
`clamp(-100vh,10000000vw - 6600000rem,0px)`. Verified equivalent by measurement,
not by reading: the computed `inset-block-start` is `0px` at every sticky width at
the default size, and the header pins at top 0.00.)*
⚠️ **"AN OFFSET TOO SHORT CANNOT ARISE" WAS WRITTEN HERE AND IN TWO SHIPPED
COMMENTS, AND IT IS FALSE. THE EXCEPTIONS ARE ENUMERATED RATHER THAN WAVED AT**,
because a comment that says "cannot" is the kind that stops the next reader
looking. The gate handles **root-relative** enlargement in both directions.
What it cannot see, measured, header height against computed
`scroll-padding-top`, header `top` = `0px` in every row:
| condition | header | offset | short by | status |
|---|---|---|---|---|
| `minimumFontSize=32` | 164.58270.56 px | 97 px | **68174 px** | live, unfixable in CSS |
| fallback font metrics + 7th nav item, **default** text size, 10561091 px | 141 px | 97 px | **44 px** | latent on `showInsights` |
| `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 |
**`--header-h` IS A FLOOR, NOT A CONSTANT**, and that is the sentence that
had to change. It is the header's height at the **default** text size; above the
default the masthead is deliberately taller, and declaration (2) is what makes
that safe — where the gate can see the header exceed the token it is not sticky,
so a `scroll-padding-top` that is too *generous* is harmless. ⚠️ **It does NOT
make a short offset impossible: the two cases the gate cannot see are tabulated
above.** The token previously asserted "one constant 81 px across
every width where it is sticky", and that sentence is what made a 944 px
overflow read as settled.
**What the narrow-width fix DID establish, and it is worth keeping separate.**
The narrow residual had been charged to that same `flex-wrap: nowrap` — and for
@@ -447,11 +642,15 @@ 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.
**The eight narrow-width fixes — no `overflow-x` was added anywhere.** Seven are
cause-specific; the eighth is marked as the backstop it is:
**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:
| Element | Was | 320 px | Cause |
|---|---|---|---|
| `SiteHeader .header-inner` | `flex-wrap: nowrap` removed above 66 rem | **944 px** at 1280 px, root 32 | **Necessary and sufficient, measured.** A `nowrap` flex line cannot break, so the brand / nav / CTA row could not reflow at all |
| `SiteHeader .nav-list` | `flex-wrap: nowrap` and a dead `flex: none` removed above 66 rem | **0 px — inert** | Kept removed so the `nowrap` prohibition is not contradicted by a `nowrap` in the same file. Byte-identical to shipped at every width and every text setting, six items and seven |
| `SiteHeader .site-header` | `inset-block-start: 0` → a saturating `clamp()` gate, in **two** bands (66 rem, and 76 rem where the tagline shows) | *(consequence of the row above)* | Wrapping makes the header 244.59350.86 px at root 32; sticky at that height covered `#main` by up to 157 px after the skip link. **Round 1's single-band `min()` ramped instead of stepping and left 20 of 300 grid points covered by 1069 px** |
| `SiteHeader .brand-name` | `white-space: nowrap` removed | **63 px, all 22 pages** | Two words glued into one unbreakable box |
| `/bio/ .rates-list` | `overflow-wrap: anywhere` | **152 px** | `https://adr.smlcompany.ca/fees/` has no break opportunity, and the single grid track it sized stretched all five rows to 424 px |
| `/ .hero-h` | `overflow-wrap: anywhere` | 36 px | "contract," at 104 px held `.hero-copy` open |
@@ -475,7 +674,37 @@ 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.
**Three instrument findings, and each one hid a real defect:**
**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:**
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,
under every mechanism. `global.css` sets `scroll-behavior: smooth`, which
makes `scrollIntoView()` **asynchronous**, so a synchronous read afterwards
measures `scrollY = 0`; and at `scrollY = 0` the target sits exactly at the
header's bottom edge, so `covered` is 0 **by construction**. Pass
`behavior: 'instant'` and assert that `scrollY` equals its predicted value —
once fixed the same probe reported 1026 px covered on 22 of 22 pages, a
pre-existing defect the tautology had hidden.
5. ⚠️ **A RUNNING TRANSITION MAKES A PROPERTY READ RETURN THE OLD VALUE.**
Forcing reduced motion — by CDP emulation *or* by
`--force-prefers-reduced-motion` — made `documentElement.style.fontSize =
'32px'` read back as **16 px**, while the inline `style` attribute plainly
said `font-size: 32px`. This codebase's reduced-motion block sets
`transition-duration: 0.01ms !important` on `*`, which starts a transition on
**every** property change, and a synchronous `getComputedStyle` then returns
the transition's *start* value. The instrument now injects
`transition:none;animation:none` instead. Same family as *a running
transition outranks an `!important` author declaration*.
6. **A DOM-INJECTED ELEMENT RECEIVES NO SCOPED STYLES.** Astro scopes component
CSS with a `data-astro-cid-*` attribute, so the hand-built
`<a class="nav-link">` used to test a seventh nav item got **none** of
`.nav-link`'s rules — no `min-inline-size`, no `min-block-size`, no
`font-size` — and measured narrower and shorter than a real item, which is
the direction that makes a width test pass. **Clone a real node** and assert
its computed styles match a sibling's.
The original three:
1. **`overflow-wrap: break-word` permits a break at layout time but does not
reduce min-content size** — `anywhere` does. Five of the fixes are this
@@ -498,6 +727,82 @@ 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 224243 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
10241100 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.)*
⚠️ **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.58270.56 px** and the skip link lands with **68174 px of `#main` behind
it**, on 22 of 22 pages from 1056 px up. It was **1026 px before**, so this pass
widened it. *(These read "228271 px" and "132174 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.
⚠️ **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`
blocked — what a reader on the Slow 4G profile `docs/04` budgets against sees
first, since the fonts are `font-display: swap` — and a seventh nav item cloned
from a real node, the header measures **141 px at every width from 1056 to
1091 px**, and 81 px from 1092 px up. With the webfonts loaded it is 81 px at all
of them. **Two consequences, and the record first carried only the smaller one:**
1. **A 60 px collapse** when Geist swaps in — a CLS contribution on all 22 pages,
against the CLS < 0.05 budget. **Larger, not new:** the previous build gives
86.5986.97 px in the same band, so a ~5.6 px shift already existed.
2. ⚠️ **AND 44 px OF `#main` BEHIND THE STICKY HEADER** after the skip link,
because 141 px exceeds the 97 px `scroll-padding-top`. **This one IS new** —
the previous build's 86.97 px stayed under 97 px, so it covered **0**. Framing
this case as "a CLS contribution" understated it, and a reader ruling on a
layout shift would weigh it differently from one ruling on the skip link.
*(The band was recorded as "1056 / 1064 / 1072 / 1084 … 81 px from 1092 up",
which reads as though 10861090 were checked and clear; they are all 141 px. It
is a contiguous **36 px** band, 10561091. Both errors found by
`adversarial-reviewer`.)*
**It is latent, not shipped:** with six items there is no wrap at any width, and
`showInsights` turns the seventh on only at two published articles. **Not fixed
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`.
**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
`/about/`). The other 14 measure 0 because their headlines' longest words are
+69 -15
View File
@@ -534,21 +534,75 @@ Then invalidate `/*`.
- [ ] Favicon set complete
- [ ] Tested on iOS Safari, Android Chrome, desktop Safari/Chrome/Firefox
- [ ] Tested at 320 px and at 200% zoom
- [ ] ⚠️ **THE 200%-DEFAULT-TEXT RESIDUAL AT 1280 px AND 1920 px IS RULED ON,
EITHER WAY. BLOCKING, AND IT IS A DECISION RATHER THAN A TASK.** With
`documentElement.style.fontSize = '32px'` the site is over by **944 px on 21
pages and 508 px on `/` at 1280 px**, and **304 px on 21 pages at 1920 px**
— and it is not a scroll margin: the nav clips mid-word and **Practice, Fees,
Contact and the header CTA are entirely off-screen — including the route to
the inquiry form**, WCAG 1.4.4 with loss of functionality. The cause is `flex-wrap: nowrap` on `.nav-list` above
66 rem, a **locked step-1 header decision**, so no fix was improvised — see
`docs/02` §Reflow for the measurement, the two non-equivalent ways of
simulating 200% text, and the two candidate fixes. **This item is ticked by
Pouya either fixing it or recording it as knowingly accepted with the
measured consequence — not by re-measuring it with the method that reports
zero.** The narrow widths (320/360/390/414/640/1024) are fixed and are 0.
Raised by `adversarial-reviewer`, 2026-08-31, against a record that had
declared the whole thing fixed
- [x] **THE 200%-TEXT NAV OVERFLOW IS FIXED, 2026-09-01 — THIS ITEM IS
DISCHARGED.** Pouya reopened the step-1 header decision and ruled *fix, do
not accept*. `flex-wrap: nowrap` is gone from `.header-inner` (the necessary
and sufficient one, measured) and from `.nav-list`, and the masthead is
sticky only while it is one row — a saturating `clamp()` gate in two bands,
66 rem and 76 rem. Measured across 22 pages × 16 widths, before and after on
the same grid: **root-style 32 px went from 175 of 352 page-widths over to
0**; minimum font size from 219 to 88, all of the remainder being the footer;
default font size stays 0. Nav items and CTA on-screen in **1408 of 1408**
measurements. The shipped rendering at the default size is
**geometry-identical — 0 differences on 352 page-widths**, header 81.00 px,
CTA on the content edge, with six nav items and with a seventh.
⚠️ **AND IT WAS VERIFIED ACROSS THE INTERMEDIATE ROOT SIZES, WHICH IS WHAT
ROUND 1 DID NOT DO.** WCAG 1.4.4 is a requirement up TO 200 %, not AT 200 %:
round 1 sampled root 16 and root 32 only, both clean, and shipped a gate that
left `#main` behind the header at **20 of 300** grid points across roots
1830. **And round 2 found the gate was one-sided**: Chrome's "Font size" has
five presets (9 / 12 / 16 / 20 / 24 px) and only 16 had been swept, so at
**9 px the masthead wrapped for the opposite reason** — the `80rem` content
cap shrinks while the header's px minimums do not — leaving **65 px of `#main`
behind the header** on 21 of 22 pages at every width. A second gate term
(`1rem - 16px`) closes it, and closes a **pre-existing 12 px** shortfall on
all 22 pages at that setting as well. Now **0 failing across 762 points**:
roots 915 (70), roots 1632 (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
224243 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.58270.56 px** under that setting, so "Skip to content" lands with
**68174 px of `#main` behind the header** on 22 of 22 pages from 1056 px
up — it was 1026 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**
- [ ] ⚠️ **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
INSIGHTS ARTICLE PUBLISHES.** No reader setting is involved: with every
`.woff2` blocked — what a reader on the Slow 4G profile `docs/04` budgets
against sees first, since the fonts are `font-display: swap` — and a seventh
nav item present, the header measures **141 px at every width from 1056 to
1091 px** (a contiguous **36 px** band) and 81 px from 1092 px up; with the
webfonts loaded it is 81 px throughout. Two consequences: **(i)** a **60 px
collapse** when Geist swaps in, on all 22 pages, against the CLS &lt; 0.05
budget — *larger, not new*, since the previous build shifts ~5.6 px in the
same band; and **(ii) 44 px of `#main` behind the sticky header** after "Skip
to content", because 141 px exceeds the 97 px `scroll-padding-top` — **this
half IS new**, the previous build's 86.97 px stayed under 97 px and covered 0.
**Latent, not shipped:** six items never wrap, and `showInsights` turns the
seventh on at two published articles. Two candidate fixes, both Pouya's call
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
**Infrastructure**
- [ ] S3 versioning enabled