Files
retire-planner/HEADER-LANG-RIGHT-REPORT.md
T

144 lines
8.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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):
```css
.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`.
## 9. Independent verification (Hermes, 2026-10-09) — 50/50 PASS
Re-ran the acceptance battery independently (not trusting the audit):
- **Toggle position** at 320/360/390/1280 × EN/FR (results screen, seeded plan): flush right
(gap 10–12px = header padding), rightmost `.hdr-in` child, vertically inside the header band,
at every width/language. Pre-fix baseline for contrast: toggle sat at x 273–351 vs header
content edge 1170 at 1280.
- **Header height unchanged**: `.hdr-in` = 66px desktop / 56px mobile, identical pre/post.
- **No horizontal overflow** at any width (320 included).
- **Toggle still switches language**: EN→FR→EN round-trip on a seeded results page, plan text
changes and restores exactly.
- **App-shell intact** at 390/320: CTA below the scroll area, no option/CTA overlap.
- **Pixel-diff vs pre-fix full-page baselines** (en/fr × 1280/390/320, captured before the
deploy): identical below the header strip (0 strongly-differing pixels) — the change is
confined to the header.
- **Tests green**: engine 36/36 · i18n 48/48 · ps-pension 70/70 · smoke 92/92.
- **Off-LAN OK** (marker + rule served from outside the LAN).
- **Vision-checked** header strips at 1280 (EN), 390 (FR), 320 (EN): toggle top-right, isolated,
active language highlighted, other header items unchanged.
**One pre-existing nit (NOT a regression):** at 390px the French brand title ellipsizes
("Planificateur de re…") because `.logo` is `flex:0 1 auto` with `text-overflow:ellipsis` and the
FR title is long. Verified pre-existing: the pre-fix baseline screenshot shows the same
truncation, and the logo width is identical pre/post-fix at 360/320 (142px/102px). Out of scope
for this change; optional follow-up = shorten the FR brand string or relax the ghost button's
padding at ≤420px.
HEADER_LANG_DONE