diff --git a/web/src/main.ts b/web/src/main.ts index f87a3dd..dfe8518 100644 --- a/web/src/main.ts +++ b/web/src/main.ts @@ -33,7 +33,7 @@ import { createRecenterControl } from "./ui/recenter.ts"; import { getWebApp } from "./telegram/webapp.ts"; import { watchTheme } from "./telegram/theme.ts"; import { wireBackButton } from "./telegram/backbutton.ts"; -import { resolveSafeInsets, applySafeArea } from "./telegram/safe-area.ts"; +import { resolveSafeInsets, applySafeArea, watchSafeAreaChanges } from "./telegram/safe-area.ts"; import { createSession } from "./session/client.ts"; import { getSession, isSessionStale } from "./session/store.ts"; import { acquireFix, isFixStale, fixAgeSeconds } from "./location/chain.ts"; @@ -72,6 +72,11 @@ void boot(); async function boot(): Promise { // 1 — safe-area vars are set before any layout reads them (PX-03 groundwork). applySafeArea(resolveSafeInsets(getWebApp())); + // PX-04/D-03: the insets re-resolve at RUNTIME too — rotation and fullscreen + // transitions re-apply through the same union authority on the client's + // inset events. Same one-shot getWebApp() value as the apply line above; + // internally guarded (null WebApp / missing onEvent → returns immediately). + watchSafeAreaChanges(getWebApp()); // 3 (declared ahead of their first use inside the closures below) — the // single-shell state the BackButton resolver and the stale branch read: diff --git a/web/src/route/route.ts b/web/src/route/route.ts index 4389456..65e8256 100644 --- a/web/src/route/route.ts +++ b/web/src/route/route.ts @@ -60,6 +60,7 @@ import { clearRouteLayers, setRouteDrawn } from "./route-layers.ts"; import type { buildRouteCard, buildRoutePendingCard } from "./route-card.ts"; import { createGuidanceCamera } from "../guidance/camera.ts"; import { engageCloseProtection, releaseCloseProtection } from "../guidance/close-protection.ts"; +import { requestGuidanceFullscreen, exitGuidanceFullscreen, watchFullscreenEvents } from "../telegram/fullscreen.ts"; import { fireHaptic } from "../guidance/haptics.ts"; import { buildConfirmCard, @@ -339,6 +340,10 @@ export function createRouteController(deps: RouteControllerDeps): { if (g === null || g.active) return; g.active = true; engageCloseProtection(deps.wa ?? null); + // PX-04/D-03: the native fullscreen request is BEST EFFORT — a refusal is a supported fallback, never a guidance failure. + requestGuidanceFullscreen(deps.wa ?? null); + // PX-04/D-03: the fullscreen events are observability only — no state, no logging (D-14 category-only law). + watchFullscreenEvents(deps.wa ?? null, () => { /* observability only — no state, no logging */ }); if (deps.shellRoot !== undefined) deps.shellRoot.dataset.guidance = "active"; foldEta(g); camera.easeToSnapped(g.lastSnapped); // the locked-follow pose over the SNAPPED line @@ -479,6 +484,8 @@ export function createRouteController(deps: RouteControllerDeps): { stopWatch(); deleteEnvelope(); releaseCloseProtection(deps.wa ?? null); + // PX-04/D-03: the trip end releases fullscreen alongside the close protection — best effort, same law. + exitGuidanceFullscreen(deps.wa ?? null); if (deps.shellRoot !== undefined) delete deps.shellRoot.dataset.guidance; stopBlink(); if (deps.bannerHost !== undefined) deps.bannerHost.replaceChildren(); @@ -614,6 +621,8 @@ export function createRouteController(deps: RouteControllerDeps): { }; envelopeId = trip.envelopeId; engageCloseProtection(deps.wa ?? null); + // PX-04/D-03: a resumed trip is a live guidance session — the same best-effort fullscreen request as a fresh start. + requestGuidanceFullscreen(deps.wa ?? null); if (deps.shellRoot !== undefined) deps.shellRoot.dataset.guidance = "active"; setRouteDrawn(deps.map, { points: trip.model.points }, []); const first = points[0]; @@ -690,6 +699,8 @@ export function createRouteController(deps: RouteControllerDeps): { stopWatch(); if (guidance === null) return; releaseCloseProtection(deps.wa ?? null); + // PX-04/D-03: the walk-back teardown releases fullscreen alongside the close protection. + exitGuidanceFullscreen(deps.wa ?? null); if (deps.shellRoot !== undefined) delete deps.shellRoot.dataset.guidance; stopBlink(); if (deps.bannerHost !== undefined) deps.bannerHost.replaceChildren();