144 lines
8.4 KiB
Markdown
144 lines
8.4 KiB
Markdown
# 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
|