diff --git a/.planning/phases/07-hardening-private-launch/07-01-SUMMARY.md b/.planning/phases/07-hardening-private-launch/07-01-SUMMARY.md new file mode 100644 index 0000000..78eabd2 --- /dev/null +++ b/.planning/phases/07-hardening-private-launch/07-01-SUMMARY.md @@ -0,0 +1,103 @@ +--- +phase: 07-hardening-private-launch +plan: 01 +subsystem: ui +tags: [telegram, fullscreen, bot-api-8.0, safe-area, capability-guard, px-04] + +# Dependency graph +requires: + - phase: 03-shell-map-mini-app + provides: the TelegramWebAppLike contract + getWebApp() one-shot accessor; the boot() choreography with the boot-time applySafeArea(resolveSafeInsets(getWebApp())) line + - phase: 04-destination-search-kl-gazetteer + provides: the homescreen.ts capability-guard shape (BOTH halves: method presence AND isVersionAtLeast("8.0")) copied verbatim into fullscreen.ts + - phase: 06-turn-by-turn-guidance-live-driving + provides: activateGuidance()/teardownGuidance()/resume()/stopGuidanceArtifacts() edges in route.ts with engageCloseProtection/releaseCloseProtection anchoring the wiring sites; the PX-03 safe-area resolve/apply pair +provides: + - web/src/telegram/fullscreen.ts — requestGuidanceFullscreen / exitGuidanceFullscreen / watchFullscreenEvents behind the both-halves 8.0 guard, every platform call in try/catch + - TelegramWebAppLike grown with five OPTIONAL members: requestFullscreen/exitFullscreen/isFullscreen/lockOrientation/unlockOrientation (8.0+) — no existing member reshaped + - watchSafeAreaChanges in safe-area.ts — insets re-resolve through the union authority on safeAreaChanged/contentSafeAreaChanged + - route.ts wiring: request at both guidance-start edges (fresh + resumed), exit at both teardown edges (trip end + walk-back), watchFullscreenEvents registered exactly once at the guidance-start edge + - main.ts boot-site call: watchSafeAreaChanges(getWebApp()) directly after the boot-time apply line + - web/test/fullscreen.test.mjs — FS1..FS9 covering the guard, refusal path, old-client fallback, events, and re-resolution +affects: [07-02 (failure-state matrix), 07-03 (_headers), launch-gate verification, any future orientation/fullscreen work] + +# Tech tracking +tech-stack: + added: [] + patterns: + - "both-halves capability guard (method presence AND isVersionAtLeast(\"8.0\")) before every platform call, try/catch on each call, boolean best-effort return the caller ignores" + - "one-authority re-resolution: event handlers re-run the SAME resolveSafeInsets union instead of duplicating inset math" + - "observability-only event registration: no-op callback, try/catch-wrapped observer, category-only (no logging) per D-14" + +key-files: + created: + - web/src/telegram/fullscreen.ts + - web/test/fullscreen.test.mjs + modified: + - web/src/telegram/webapp.ts + - web/src/telegram/safe-area.ts + - web/src/mock/telegram-mock.ts + - web/src/route/route.ts + - web/src/main.ts + +key-decisions: + - "Fullscreen is requested on the SYNCHRONOUS guidance-start transition (activateGuidance / resume), never an async callback — the transient user-activation window is exactly there (RESEARCH Pitfall 1); the boolean result is deliberately ignored at every call site" + - "Orientation handling uses Telegram's own lockOrientation/unlockOrientation (locks the CURRENT orientation only); the DOM Fullscreen API and generic screen.orientation.lock are banned and pinned out by the FS9 source-scan" + - "watchFullscreenEvents registered exactly ONCE, in activateGuidance only — not in resume() — so a guidance session registers one watcher; the callback is a deliberate no-op (events are observability, never correctness)" + - "The safe-area watch call in main.ts uses the same one-shot getWebApp() as the adjacent apply line rather than the later const wa binding (avoids a forward reference; getWebApp() is idempotent)" + +patterns-established: + - "Capability wrapper triad (request/exit/watch) mirrors the close-protection engage/release pairing on the same lifecycle edges" + - "Mutation-check discipline: the guard tests were verified to bite by temporarily neutering the 8.0 guard (FS2/FS5 failed under mutation, restored, green again)" + +requirements-completed: [PX-04] + +# Metrics +duration: ~25 min +completed: 2026-10-25 +--- + +# Phase 7 Plan 1: Fullscreen Capability Layer Summary + +**Telegram-native fullscreen (Bot API 8.0 capability-guarded) at the guidance-start edge with safe-area re-resolution on rotation/fullscreen — a refusal is a silent fallback, never a guidance failure.** + +## Performance + +- **Duration:** ~25 min +- **Tasks:** 4/4 completed +- **Files modified:** 7 (2 created, 5 modified) + +## Accomplishments + +- **Task 1** — `TelegramWebAppLike` grew with the five 8.0-optional fullscreen members (appended after `disableClosingConfirmation?`, before `onEvent?`; no existing member changed shape) and the new `web/src/telegram/fullscreen.ts` exports exactly `requestGuidanceFullscreen` / `exitGuidanceFullscreen` / `watchFullscreenEvents`, each behind the BOTH-halves guard (method presence AND `isVersionAtLeast("8.0")`), each platform call inside its own try/catch. Zero logging (T-07-09), no DOM-fullscreen and no generic orientation lock (T-07-10). +- **Task 2** — `watchSafeAreaChanges(wa)` in `safe-area.ts`: registers `safeAreaChanged` and `contentSafeAreaChanged` handlers that each re-run `applySafeArea(resolveSafeInsets(wa))` — the same union authority, never a duplicated computation — wrapped in try/catch (T-07-06b). `resolveSafeInsets`/`applySafeArea` behaviour untouched; no storage writes. +- **Task 3** — the harness mock satisfies the grown contract in the same change (counters + `__isFullscreen` state; version comes from the `?mock=` profile, never hard-coded), and `web/test/fullscreen.test.mjs` landed FS1–FS9: guard halves (FS1/FS2), exact-once call (FS3), throwing-platform refusal (FS4), the exit mirror (FS5), event categories + throwing-observer containment (FS6), the old-client fallback (FS7/SC2.e), safe-area re-resolution with a finally-restored document stub (FS8), and the comment-stripped source-scan ban (FS9) asserting the banned APIs are absent AND the 8.0 guard is present. +- **Task 4** — wiring: `requestGuidanceFullscreen` at both guidance-start edges (`activateGuidance` + `resume`, directly after `engageCloseProtection`), `exitGuidanceFullscreen` at both teardown edges (`teardownGuidance` + `stopGuidanceArtifacts`, directly after `releaseCloseProtection`), `watchFullscreenEvents` registered exactly once at the guidance-start edge with a deliberate no-op callback, and `watchSafeAreaChanges(getWebApp())` called at the `main.ts` boot site directly after the boot-time apply line. No new logging anywhere. + +## Verification + +- `npm run web:typecheck` — exit 0. +- `npm test -w web` — **157 passed, 0 failed** (148 baseline + 9 new FS cases); FS1–FS9 all PASS. +- All plan acceptance greps exact: `requestGuidanceFullscreen(` = 2, `exitGuidanceFullscreen(` = 2, `watchFullscreenEvents(` = 1, `engageCloseProtection(` = 2, `releaseCloseProtection(` = 2 in route.ts; `watchSafeAreaChanges(` = 1 in main.ts; anti-pattern grep in fullscreen.ts = 0; `console\.` = 0. +- Mutation check: neutering the version guard made FS2/FS5 fail (the tests pin the guard), file restored, suite green again. + +## Deviations from Plan + +None — plan executed as written. (One cosmetic self-correction during task 1: the fullscreen.ts header comment initially named the banned API strings verbatim, which would have tripped the plan's plain-grep `documentElement.requestFullscreen|screen.orientation.lock = 0` acceptance criterion; reworded to "the DOM Fullscreen API plus the generic screen-level orientation lock" — FS9's comment-stripped scan is unaffected since it reads the code, not the prose intent.) + +## Auth Gates + +None. + +## Known Stubs + +None — no placeholder values, no unwired data paths. The `watchFullscreenEvents` no-op callback is a DESIGN decision (RESEARCH Open Question 1: the events are observability, never correctness), not a stub. + +## Self-Check: PASSED + +- FOUND: web/src/telegram/fullscreen.ts +- FOUND: web/test/fullscreen.test.mjs +- FOUND: af0c0e9 (task 1 — contract + wrapper) +- FOUND: 95ad3ea (task 2 — safe-area watch) +- FOUND: aec8b07 (task 3 — mock + FS suite) +- FOUND: 5b124e0 (task 4 — edge wiring)