Files
retire-planner/MOBILE-FIX-REPORT.md
T

60 lines
7.1 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.
# MOBILE PASS — REPORT
Executed by DSH (local 27B) per `MOBILE-FIX.md` v1. All 8 fixes implemented in `site/`, tested, deployed to https://retire.krisforbes.ca, and verified at 390/360/320px.
**Verification method:** every acceptance number below is a **programmatic DOM/geometry assertion** run against the live deployed site with Playwright (real Chromium, iPhone-14 390×844@3x emulation) — see `audit/geo_check.py` → `audit/geo-check-390.json`. Screenshots are archived in `audit/mobile-before/` and `audit/mobile-after/` for human visual review. (Note: the executing model for this run has no image input, so screenshot "vision checks" were performed as DOM/geometry assertions, not by inspecting PNGs; the PNG pairs are included for the user's eye.)
## Issue → fix → evidence
### 1. Header ate ~15% of the phone screen (126px, ~3 rows)
**Fix** (`style.css` ≤860px): `.hdr-in{flex-wrap:nowrap;padding:5px 10px}`, `.hdr{padding-top:env(safe-area-inset-top)}`, `.logo{font-size:15px}`, `.hdr .ghost{padding:6px 9px;font-size:13px}`; ≤700px hides `.hdr .pill` (repeated on intro + footer); `.hdr #btn-print{display:none}` scoped to the header so the results-page Print button in `.btn-row` survives.
**Verified:** `hdr.h = 55px` at 390px (spec target ≤56). Single flex row (`flex-wrap:nowrap`). Screenshots `iphone14-quiz-province.png` / `-results-top.png` (after) vs same names in `mobile-before/`.
**Deviation:** spec suggested `padding:6px 10px`; with `.ghost{min-height:44px}` (Fix 4) 6px padding makes the header 57px (> the ≤56px target), so vertical padding is 5px. Desktop untouched.
### 2. Chart text unreadable (~5.8px at 390px)
**Fix** (`app.js`): `axes()` ticks 11→13, axis-title 11→12; G1 `retire` 10.5→12, `runs out ≈` 12→13; G3 `shortfall from ~` 12→13; G4 ticks 11→13, value labels 12.5→13.5, bar names 10.5→12. (`style.css`, Fix 2b below.)
**Verified:** SVG tick `font-size` attr = 13; rendered tick text at 390px = **11.4 CSS px** (13 × 560/640), up from ~5.8. G4 value labels 13.5 → 11.8px rendered. Spec threshold ≥11px met.
### 2b. Charts horizontal-scroll on phones
**Fix** (`style.css` ≤860px): `.chartbox{overflow-x:auto;overflow-y:hidden}`, `.chartbox svg{min-width:560px}`, CSS-only hint `.chartbox::before{content:"swipe →"}` (sticky, top-right, muted, `pointer-events:none`; desktop unaffected).
**Verified:** at 390px: `svg width=560px` (min-width applied), `#g1.scrollWidth 580 > clientWidth 356` → scrollable, `hasSwipe:true`.
### 3. Bottom sheet looked broken (58px peek, clipped chips, no scrim)
**Fix:** transform peek removed — sheet height is its content, chips always visible; insight hidden until `.open` (tap-header toggle kept, ▲/▼ affordance kept); `bottom:calc(8px + env(safe-area-inset-bottom))`; `.scr{padding-bottom:calc(185px + env(safe-area-inset-bottom))}`; new `#scrim` (`z-index:35` < panel 40, ≤860px only) — JS toggles `hidden` + `.on` in sync with `.panel.open`, tapping scrim closes the sheet, `showScreen()` re-syncs.
**Verified at 390px:** collapsed panel occupies y=618→836 of 844 — all 4 chip **labels and values fully in view** (e.g. "Savings at retirement / $959,835", `fullyInView:true` — no mid-glyph clip); `#btn-next` bottom 535 ≤ panel top 618 (Next never covered); collapsed state shows no insight (`insightShownByDefault:false`); after header tap: `open:true, scrimVisible:true`, z-order scrim 35 < panel 40 (header 30 stays under the scrim); scrim click → `panelOpen:false, scrimHidden:true`. Screenshots `iphone14-sheet-collapsed.png`, `iphone14-sheet-open.png`, `iphone14-quiz-sheet-open.png`.
### 4. Year-by-year table 1.8× horizontal scroll
**Fix** (`style.css` ≤560px): hide columns 4 (Non-reg) and 9 (GIS) — `Unfunded` kept; `table{font-size:13px}`; right-edge fade via `.tbl-wrap::before` gradient (`pointer-events:none`). Sticky first column + sticky header untouched. (Media block placed after the base table rules so the font-size wins.)
**Verified at 390px:** `col4Hidden:true, col9Hidden:true`; table font 13px; scroll ratio 494/326 = **1.52×** (was 1.83×); `fadePresent:true`; with `.tbl-wrap.scrollLeft=168` the Age column stays pinned (`ageColLeftInBox:1px, ageColVisible:true`). Screenshot `iphone14-table-scrolled-right.png`.
### 5. 32px ghost buttons
**Fix** (`style.css`): `.ghost{min-height:44px}`; `button,input{touch-action:manipulation}`.
**Verified:** every visible `.ghost/.opt/.cta` on the quiz at 390px is ≥44px: Back 44, Start over 44, Next 50; `minTouch:44`.
### 6. Vision card race → permanent gradient on direct load into results
**Fix** (`app.js`): manifest fetch wrapped in `visionReady = Promise.race([fetch, 2500ms timeout])`; on resolve **and** on reject, `if (state.screen === 'results') renderVision(state.a)` re-renders; timeout keeps the gradient. `img.onerror` → grad() fallback kept.
**Verified:** fresh context, localStorage screen=results, reload → `visionIsImg:true, visionGrad:false`, img 356×250 (real `vision/dock.jpeg` for dock+travel answers). Screenshot `iphone14-results-top.png` (before = flat gradient).
### 7. Share button lied when clipboard unavailable
**Fix** (`app.js`): `.writeText(url).then(done, fail)`; on reject **or** missing `navigator.clipboard`: `window.prompt('Copy your link:', url)` + label "🔗 Link shown — copy it"; "✅ Link copied" only on real success.
**Verified:** code path + smoke shim (`prompt()` stub, SMOKE-OK); real-clipboard path unchanged in behavior.
### 8. Minor
`<meta name="theme-color" content="#f7f9fc">` added → `themeColor:"#f7f9fc"` in-page; `.legend` 12.5→13px (`legendPx:"13px"`); `.opt small` 11.5→12px; Enter on number steps fires Next (3 lines in `renderStep`).
## Gates (spec Definition of done)
- Tests: `engine.test.js` **36 pass, 0 fail**; `ui.smoke.js` **SMOKE-OK** (run before deploy, and after the CSS-order tweak).
- Deploy: `scripts/deploy.sh` → **DEPLOY-OK**; origin `http://192.168.0.123:8103/` grep `retire-planner v1` → 1; marker comment + privacy text untouched.
- Off-LAN (Midhurst troublebox, paramiko `trouble@100.96.20.61:2222`): `https://retire.krisforbes.ca/` → **200**, `https://retire.krisforbes.ca/vision/dock.jpeg` → **200**.
- Engine files (`site/engine/*.js`) not modified. No frameworks/builds added. Desktop layout unchanged (all mobile rules in `@media ≤860/700/560`).
## Evidence files
- `audit/geo-check-390.json` — full machine-readable assertion output (spec step 6).
- `audit/geo_check.py`, `audit/supp_shots.py`, `audit/mobile_audit_run.py` (workspace copy of the Hermes audit script; only the OUT path differs — the scratch dir was read-only from this sandbox; run with `TMPDIR=/tmp`).
- `audit/mobile-before/` (12 PNGs) vs `audit/mobile-after/` (15 PNGs, incl. sheet-collapsed / sheet-open / table-scrolled-right).
## Deviations from spec text (all forced, all documented)
1. Header `.hdr-in` vertical padding 5px (not 6px) — 6px + 44px ghost min-height = 57px header, breaking the spec's own ≤56px target.
2. Header print-hide rule scoped `.hdr #btn-print` — spec's literal `#btn-print` selector would also hide the results-page Print button the spec explicitly keeps.
3. Sheet-open state on desktop: no scrim, panel unchanged (per spec).