- 4/4 tasks, 157/157 web tests, typecheck clean; PX-04 fullscreen + safe-area re-resolution shipped
7.9 KiB
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 —
TelegramWebAppLikegrew with the five 8.0-optional fullscreen members (appended afterdisableClosingConfirmation?, beforeonEvent?; no existing member changed shape) and the newweb/src/telegram/fullscreen.tsexports exactlyrequestGuidanceFullscreen/exitGuidanceFullscreen/watchFullscreenEvents, each behind the BOTH-halves guard (method presence ANDisVersionAtLeast("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)insafe-area.ts: registerssafeAreaChangedandcontentSafeAreaChangedhandlers that each re-runapplySafeArea(resolveSafeInsets(wa))— the same union authority, never a duplicated computation — wrapped in try/catch (T-07-06b).resolveSafeInsets/applySafeAreabehaviour untouched; no storage writes. - Task 3 — the harness mock satisfies the grown contract in the same change (counters +
__isFullscreenstate; version comes from the?mock=profile, never hard-coded), andweb/test/fullscreen.test.mjslanded 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:
requestGuidanceFullscreenat both guidance-start edges (activateGuidance+resume, directly afterengageCloseProtection),exitGuidanceFullscreenat both teardown edges (teardownGuidance+stopGuidanceArtifacts, directly afterreleaseCloseProtection),watchFullscreenEventsregistered exactly once at the guidance-start edge with a deliberate no-op callback, andwatchSafeAreaChanges(getWebApp())called at themain.tsboot 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.