Files
retire-planner/QUIZ-APPSHELL-REPORT.md

131 lines
8.0 KiB
Markdown
Raw Permalink 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.
# QUIZ APP-SHELL FIX — Round 4 Report
**Spec:** `QUIZ-APPSHELL-FIX.md` · **Fix commit:** this round (follows `f4b5698`) · **Status: DEPLOYED & VERIFIED**
The mobile quiz (≤700px) had a pinned/sticky `#btn-next` that sat on top of the last
option row (up to 100% of the last `.opt` covered) and the page itself scrolled
(document 100–306px past the viewport, so the CTA floated over content at every
document scroll position). Fix: app-shell the quiz screen — `#step-body` becomes the
only scroll container; `#btn-next` + `#panel` become static flex children of a fixed
bottom cluster. CSS-only, zero `app.js` changes.
## Root cause
Round 3 kept `#btn-next` `position:sticky` (anchored above `--sheet-h`) and `#panel`
`position:fixed` at ≤700px. Sticky-in-scrolling-document means the CTA hovers over
whatever content scrolls beneath it — on the province step (13 two-column options) the
last row sat exactly under the CTA band. Document-level scroll (`html`,`body` height
100vh with `overflow:hidden` only on `body`) plus `.opt` min-height growth in FR made
it worse. No geometry change made "pinned above content" and "never overlaps options"
coexist — the quiz needed a real app-shell.
## The change — `site/style.css`
1. **Reverted** round-3 FR rule `html[lang="fr"] .opt{font-size:13px;padding:12px 4px}`
(+ its comment). FR `.opt` rows are back to the base 15px / 14px 10px, identical
metrics to EN. Fix `f4b5698` (FR end-anchor in app.js) untouched.
2. **New `@media(max-width:700px)` block appended at end of file** (all of it inside
the media query; desktop untouched):
```css
#scr-quiz.scr{display:flex;flex-direction:column;height:calc(100vh - 69px);height:calc(100dvh - 69px);overflow:hidden;padding:0}
#scr-quiz.scr[hidden]{display:none}
.quiz-wrap{display:flex;flex-direction:column;flex:1;min-height:0}
.quiz-main{display:flex;flex-direction:column;flex:1;min-height:0}
#step-body{flex:1;overflow-y:auto;min-height:0;padding:10px 12px 16px}
#btn-next{position:static;margin:10px 12px 12px}
#panel{position:static;margin:0 8px calc(8px + env(safe-area-inset-bottom,0px))}
main:has(#scr-quiz:not([hidden])){padding-bottom:0}
```
The six spec rules are verbatim. Three deliberate additions, all disclosed in
"Deviations" below: placement at EOF, the `[hidden]` guard, and the scoped
`main:has(...)` padding rule.
## Geometry — province step, scroll 0 (CSS px)
`covLast%` = % of the LAST option row covered by the CTA (visible-true, clipped to
the scroll container); `docOver` = document scroll overflow px.
| device/lang | BEFORE covLast% | BEFORE docOver | AFTER covLast% | AFTER docOver | next t–b | panel t–b |
|---|---|---|---|---|---|---|
| iphone14 390 en | 58.4 | 119 | **0.0** | **0** | 536.3–586.3 | 618.3–836 |
| iphone14 390 fr | 100 | 100 | **0.0** | **0** | 519.3–569.3 | 601.3–836 |
| pixel 360 en | 81.8 | 163 | **0.0** | **0** | 492.3–542.3 | 574.3–792 |
| pixel 360 fr | 58.8 | 176 | **0.0** | **0** | 475.3–525.3 | 557.3–792 |
| se 320 en | 83.3 | 297 | **0.0** | **0** | 375.3–425.3 | 457.3–692 |
| se 320 fr | 62.2 | 306 | **0.0** | **0** | 375.3–425.3 | 457.3–692 |
Pixel proof, same region (old CTA band, 390 FR province, scroll 0): 95.2% CTA-blue
px before → 6.9% after (option grid visible).
## Acceptance (spec §acceptance) — all PASS, local + live
Battery: `audit/appshell_verify.py` — 254 checks per environment, at 320/360/390 ×
EN+FR, province (13 `.opt`) + vision (12 chips) at scroll 0/mid/max, plus full walks.
- **Local: 254/254 APPSHELL-VERIFY-OK** · **Live: 254/254 APPSHELL-VERIFY-OK**
- A1/A2 CTA fully visible (top ≥ header bottom, bottom ≤ panel top, inside viewport)
and zero CTA↔`.opt` visual overlap (rects clipped to `#step-body`): PASS at every
scroll position, all devices/langs, both steps.
- A2 CTA↔panel: `next.bottom ≤ panel.top` with a 32px air gap everywhere; PASS.
- A3 last `.opt` fully visible at max scroll (`lastOpt.bottom ≤ stepBody.rect.bottom−4`):
PASS on 12/12 device/lang/step combos, local and live.
- A4 no document scroll: `scrollHeight ≤ innerHeight + 1` — 12/12 walks cover **all
14 single / all 17 couple steps** at 320/360/390 EN+FR, max docOver **0**.
- A5 panel fully visible (panel.bottom ≤ viewport), 4 chips + insight readable,
sheet expand/scrim/scrim-tap-collapse flow verified at all 3 widths.
- A6 Back + "i / n" counter + progress bar visible on every check.
- A7 tests: engine **36/36**, i18n **48**, smoke **56** (run after CSS change).
- A8 desktop 1280×900 EN+FR province + vision/tall + results: baseline
(`audit/appshell-baseline/`) vs post-change (`audit/appshell-desktop-after/`):
**6/6 identical, 0 px diff.**
- A9 intro/results 390 full-page EN+FR before vs after: **0 px diff** (4/4).
- A10 deploy `DEPLOY-OK`; served `style.css` byte-identical to local; origin marker
`retire-planner v1` count = 1; off-LAN (Midhurst troublebox) `OFFLAN-OK`:
200 + marker + app-shell rules present + FR hack gone (`audit/appshell-geo-check.json`).
### Desktop "pixel-identical" footnote
One live re-run of `desktop-en-results` differed from baseline by 25 px at
channel-delta ≤ 1/255 (0.0022%, bbox on the vision-image rounded-corner AA edge).
Determinism probe: two consecutive screenshots of the *same live page, same CSS*
jittered by the same 25 px between themselves, while each of those matched the
baseline 0 px (`audit/appshell-desktop-live/desktop-en-results-rerun.png` = baseline
exactly). Run-to-run rasterizer jitter, not a CSS effect — every changed rule is
inside `@media(max-width:700px)` and cannot evaluate at 1280px.
## Deviations from spec (disclosed)
1. **Block placement — appended at EOF, not inside the existing 700px block.**
The base rule `#step-body{…min-height:320px;padding:18px}` sits *later in the
file* than the existing `@media(max-width:700px)` block, and media queries add no
specificity — placed there, `min-height:320px` beat `min-height:0`, the shell
overflowed, and the CTA overlapped the panel at 320px. Spec process step 1 says
"append **one new media block**" and note 3 requires "later in file wins", so the
block goes after all base rules (just before `@media print`). The six rules are
verbatim.
2. **Added `#scr-quiz.scr[hidden]{display:none}`.** `display:flex` at id+class
specificity beats the UA `[hidden]{display:none}`, so the hidden quiz shell was
rendering on intro/results pages (+775px of dead shell, caught by full-page pixel
diff). This one-line guard restores spec intent ("intro/results must not change");
intro/results are now pixel-identical.
3. **Added `main:has(#scr-quiz:not([hidden])){padding-bottom:0}`.** `main`'s 12px
bottom padding sits below the exactly-viewport-tall quiz section and added 12px of
document scroll on every quiz step (acceptance A4). Scoped with `:has()` to the
quiz screen (spec note 2 explicitly sanctions this pattern); intro/results
unaffected (proven by A9, 0 px diff, 4/4 full-page).
4. **No `app.js` changes** — spec allows ("if any JS change is needed, stop and
report; prefer CSS-only"). The old `--sheet-h`/`syncSheetH` JS keeps running; the
sticky `#btn-next` rule at ≤860px and fixed `.panel` rule are overridden by the
later static rules (spec note 5: layering leftovers harmless).
## Evidence files
- `audit/appshell-baseline/` — 6 desktop PNGs (pre-change, == deployed round-3, byte-check `live == HEAD` done pre-deploy).
- `audit/appshell-before/` — mobile PNGs + `geo-before.json` (defect: FR@390 last row 100% covered, docOver 100–306px).
- `audit/appshell-after/` — post-change mobile PNGs (320/360/390 × EN+FR, province/vision/maxscroll/sheet-open/intro/results), `geo-after.json`, `geo-live.json`, `verify-after.json`, `verify-live.json` (254/254 both).
- `audit/appshell-desktop-after/`, `audit/appshell-desktop-live/` (+`-rerun.png` determinism shot) — desktop PNGs, 0 px vs baseline.
- `audit/appshell-geo-check.json` — off-LAN check (200 + markers, `OFFLAN-OK`).
- Scripts: `audit/appshell_capture.py`, `audit/appshell_verify.py`, `audit/appshell_pixdiff.py`, `audit/appshell_offlan.py`.