docs(07-01): complete fullscreen capability layer plan — SUMMARY

- 4/4 tasks, 157/157 web tests, typecheck clean; PX-04 fullscreen + safe-area re-resolution shipped
This commit is contained in:
araharah-executor
2026-10-25 03:47:47 +08:00
parent 5b124e0b75
commit 99e28de6a1
@@ -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)