Files
retire-planner/HEADER-LANG-RIGHT-REPORT.md
T
krisf 043fa8a683 Header: right-align EN|FR toggle in .hdr-in (top-right of page)
One CSS line: .lang-toggle{order:99;margin-left:auto}. Spec's literal order:99 alone
failed its own <=2px right-edge acceptance (gap 45.6-467.6px, 18/24 cases — order
reorders, .hdr-in is left-packed; geo-literal-order99.json), so margin-left:auto on
the toggle absorbs the free space; pill/logo untouched (spec-forbidden zone respected).

Battery: A1 gap=0px + rightmost + nothing-past-left in 24/24 (320/360/390/1280 x EN/FR
x intro/quiz/results); A2 header height unchanged 56/66px; A3 320px no overflow/wrap,
FR logo ellipsis intact, elementFromPoint hit-tests lang buttons + Start over pass;
A4 FR/EN clicks switch lang+title+aria-pressed; A5 24 screenshots before/after; A6
engine 36 + i18n 48 + smoke 92 + ps-pension 70 green; A7 desktop pixdiff header-strip-
only, 0 px below header at every device, self-jitter bit-identical; A8 DEPLOY-OK,
origin marker=1 + byte-identical CSS, off-LAN troublebox 200+marker+rule OFFLAN-OK.
2026-10-09 13:32:40 -04:00

6.7 KiB
Raw Blame History

HEADER-LANG-RIGHT REPORT — EN|FR toggle right-aligned in .hdr-in (top-right of page)

Run: 2026-10-09, DSH. Spec: /home/ubuntu/projects/.hermes/plans/2026-10-09_header-lang-toggle-rightalign.md (v1). Baseline: main @ 4c8fcad (PS-pension stage B + Hermes post-deploy verification), tree clean, tests green. Result: SHIPPED — deployed, origin + off-LAN verified, committed and pushed.


1. The change

One added line in site/style.css (top level, directly after the existing .lang-toggle rule at line 27):

.lang-toggle{order:99;margin-left:auto} /* right-align the EN|FR toggle inside .hdr-in ... */

No DOM, JS, or other-CSS changes. git diff site/ = 1 insertion, 0 deletions.

Deviation from the spec's literal rule (measured, justified)

The spec preferred .lang-toggle{order:99} alone. Applied literally, it failed the spec's own acceptance 1 in 18/24 cases: order reorders flex items but .hdr-in is left-packed (no justify-content), so the toggle became the rightmost element yet sat flush against Start over, order:99 alone → gap (toggle.right → hdrIn.right−padR) 188.1–467.6px on desktop, 45.6–160px on mobile (full matrix in audit/lang-right/geo-literal-order99.json — re-measured post-hoc against a site copy carrying the literal rule; the after/ dir is the shipped rule). The spec's DOM-move fallback fails the same ≤2px criterion for the same reason (a last child packs against the previous item). The spec's "order misbehaves" escape clause covers this: added margin-left:auto on the toggle itself — the auto margin absorbs the free space so the toggle sits flush at the header's right padding edge. The spec only forbade margin-left:auto on the pill/logo ("leave the other elements left-packed exactly as today") — untouched, verified: all non-header-strip pixels on desktop are bit-identical (section 4). Resulting visual order = exactly the spec's drawing: [logo] [pill] [Start over] ………… [EN|FR] desktop; [logo] [Start over] …… [EN|FR] at ≤700px.

2. Geometry — acceptance 1/2/3 (24 cases: 320/360/390/1280 × EN/FR × intro/quiz/results)

Measured with getBoundingClientRect via Playwright (audit/lang_right_capture.py); gap = (hdrInRect.right − paddingRight) − toggleRect.right, spec tolerance ≤2px.

case group gap BEFORE gap AFTER rightmost AFTER other elements past toggle-left AFTER
desk 1280×900 (intro/quiz/results × EN/FR) 733.7–807.4px 0.0px ✓ 6/6 none (6/6)
iphone14 390 95–160px 0.0px ✓ 6/6 none
pixel 360 65–130px 0.0px ✓ 6/6 none
se 320 25–112.5px 0.0px ✓ 6/6 none
  • A1 ✓ every case: gap 0px (≤2px), toggle is the rightmost visible .hdr-in child, no other element's rect extends past the toggle's left edge. Computed order: toggle 99, all siblings 0.
  • A2 ✓ header height unchanged: 56px at 320/360/390 (≤56 required) and 66px at 1280, identical before and after (the .hdr-in{flex-wrap:nowrap;padding:5px 10px} mobile rule keeps holding).
  • A3 ✓ at 320px: scrollWidth == clientWidth == 320 (no overflow), zero wrap at every width (center-Y row test), elementFromPoint center hit-tests pass for #lang-en, #lang-fr and #btn-restart on every screen where they are shown (restart is hidden on intro by design). Logo continuity: EN logo fits 320px (124.6px wide, not truncating — identical to before); FR "Planificateur de retraite" truncates on quiz/results at exactly 102.1px with text-overflow:ellipsis (ellipsisPossible: true), same as before.

3. Toggle behavior — acceptance 4 ✓

In-browser click test at 390px (toggle-click block in both geo JSONs, before and after identical):

  • FR click → documentElement.lang === 'fr', title → “Planificateur de retraite — la retraite canadienne, modélisée comme il faut”, #lang-en aria-pressed=false, #lang-fr true.
  • EN click → back: lang en, original title, aria-pressed true/false. frOk: true, backOk: true.

4. Screenshots + desktop regression — acceptance 5 & 7 ✓

24 screenshots per version ({se,pixel,iphone14,desk}-{en,fr}-{intro,quiz,results}.png) in audit/lang-right/before/ and audit/lang-right/after/; self-jitter pairs in before2//after2/. PNGs are gitignored (ps-pension precedent); files on disk for Hermes re-verification; geo JSONs are committed.

Jitter-controlled pixdiff (audit/lang_right_pixdiff.py, PIL ImageChops):

  • Self-jitter: before vs before2 and after vs after2, 1280×900 intro/results EN+FR: 0 px, bit-identical both times → capture pipeline adds no noise; cross-version diffs are trustworthy.
  • Cross-version before↔after: every diff bbox sits inside the header strip (desk: bbox y 10–56, header 66px; 0 changed px below y=66). Mobile re-checked with DPR-scaled masks (56 CSS-px header → 114/157/171 device-px at dsf 2/2.75/3): 0 px below the header strip at every device — page body pixel-identical below the header on all 24 before/after pairs.

5. Tests — acceptance 6 ✓ (green at preflight and after the change)

suite result
node tests/engine.test.js 36 pass, 0 fail
node tests/i18n.test.js I18N-OK (48)
node tests/ui.smoke.js SMOKE-OK (92) — spec said 56; the suite has grown since spec time (PS rounds), gate is “green”, met
node tests/ps-pension.test.js 70 pass, 0 fail

6. Deploy — acceptance 8 ✓

  • bash scripts/deploy.sh → DEPLOY-OK
  • Origin (http://192.168.0.123:8103/): marker count 1; new rule present 1×; served style.css byte-identical to local.
  • Off-LAN (Midhurst troublebox, audit/lang_right_offlan.py → audit/lang-right-geo-check.json): 200 + marker ✓ + served CSS carries lang-toggle{order:99;margin-left:auto} ✓ → OFFLAN-OK (2026-10-09T17:31:08Z).

7. Files

  • site/style.css — the one-line fix (the only site change).
  • audit/lang_right_capture.py — capture + acceptance 1–4 harness (A3 gates: ellipsis-clean truncation when present; hidden #btn-restart on intro is not a hit-test failure).
  • audit/lang_right_pixdiff.py — header-strip-masked pixdiff + self-jitter.
  • audit/lang_right_offlan.py — off-LAN troublebox check.
  • audit/lang-right/{before,before2,after,after2}/ — screenshots (on disk; PNGs gitignored), geo-before.json / geo-after.json (full 24-case measurements + toggle-click test), geo-before-order99.json (literal-spec-rule evidence).
  • audit/lang-right-geo-check.json — off-LAN proof.

8. Commit

main: site/style.css one-liner + audit scripts/JSONs, pushed to origin/main.

HEADER_LANG_DONE