Skip to content

feat: session-replay backend + PWA install/nav/SSE polish + deploy scripts - #36

Merged
mlnomadpy merged 6 commits into
masterfrom
mlnomadpy/remove-android-cruft
May 24, 2026
Merged

feat: session-replay backend + PWA install/nav/SSE polish + deploy scripts#36
mlnomadpy merged 6 commits into
masterfrom
mlnomadpy/remove-android-cruft

Conversation

@mlnomadpy

@mlnomadpy mlnomadpy commented May 24, 2026

Copy link
Copy Markdown
Owner

Summary

Originally a session-replay backend PR; expanded to bundle the connected PWA polish (install + nav + SSE), the no-fake-fallback sweep, and the deploy/phone/ ladder.

Bridge — session replay

  • POST /session/replay/start {source_session_id, speed?, loop?} → 200 with total_frames + est_duration_s. 404 if no rows, 409 if already running, 503 if no DB.
  • POST /session/replay/stop{stopped, frames_emitted} within ~1 s.
  • GET /session/replay/statusframe_idx / elapsed_s / est_remaining_s.
  • Single-publisher invariant: gracefully stops CAN reader at start so frames don't interleave on the SSE stream.
  • state.active_session_id flips to source_session_id so /health and the PWA telemetry store auto-bind to the replay.

PWA — install + key contract + nav (PR-A, PR-B, PR-C, mount)

  • Key contract unified globally: ESC → router.back(), P → pauseStore (only on meta.allowPause routes like /hud). Pause overlay ESC dismisses overlay instead of route.
  • InstallPrompt widget captures Chrome's beforeinstallprompt, sticky banner, 7-day dismiss cooldown, no-op on iOS/standalone. Mounted globally in App.vue.
  • FullscreenToggle persists user choice via localStorage (fullscreenPreferred) with private-mode safety.
  • Pit Stall: "OPEN LIVE PIT WALL" button (L key) + second watcher on bridgeStore.health?.active_session_id so SSE subscribes during replay.
  • OnTrackHud: corner hint badge "ESC · BACK / P · PAUSE", 10×10 status pip wrapped in 44×44 touch target.
  • Garage Hub: B/C/H key hints.
  • TelemetryReplay: SSE subscribe on active_session_id.
  • CoachFloat: z-index 20 → 100 so it sits above page content.
  • HintBar: dev-only stub with TODO.
  • SaveSelect: removed double keyup binding.
  • v-for key fixes: OnboardingFlow, AvatarSelect, PedalProfile, GlobalLeaderboard, SqlConsole, DriverEvolution, AskCoachMode (composite keys instead of array index).
  • HardwareDetail: defensive nulling on unmount.

PWA — no fake fallbacks

  • Removed templated/hardcoded fallbacks across NotificationCenter, CornerMastery, TrackWalk, TrainerCard, EndOfDay, LapTimesHall, StraightsAndSpeed, CoachCodexMode. Screens now show real bridge data or an honest "unavailable" panel.
  • PreBrief integrates BriefSourcePicker + SessionStartPicker to pick the replay session that drives /coach/brief and the live HUD; "Settle in. Peak grip…" fake text removed; surfaces briefError panel.
  • Real-data notificationStore + leaderboardStore (SSE-aware, no mocks).

PWA — installability

  • PNG 192/512/maskable + apple-touch-icon-180.png under src/pwa/public/icons/.
  • Manifest fix in vite.config.ts, viewport + icon link refs in index.html.

Backend — no fake fallbacks

  • coaching/litert_coach.py: drop _templated_pre_brief; on LLM failure return ('', [], 'neutral') and record llm_friction.
  • coaching/bp_coaching.py: /coach/brief response exposes error field from latest llm_friction row.

Deploy

  • deploy/phone/00..99 shell ladder (rooted Pixel 10 + Termux) — prerequisite check, Termux packages, repo stage, deps, recording stage, build PWA, port forwards, start bridge (with SIM=1 / SIM_SPEED / SIM_LAP_SECONDS env knobs), open PWA, stop. _common.sh + status.sh + README.md.

Smoke-tested on phone (12,936-row Sonoma recording)

  • status idle → {running:false}
  • start (speed=10, loop=true) → 254 frames emitted in 3 s ✓
  • SSE delivered canonical wide_snapshot frames ✓
  • stop{stopped:true, frames_emitted:260} in ~1 s ✓
  • /health.active_session_id flipped to track-sonoma-2026-05-23-1
  • PWA installs via WebAPK with proper PNG icons ✓
  • Build: 95 precache entries, 882 KiB ✓

Test plan

  • Mac (DuckDB): boot bridge, POST /session/replay/start, verify SSE cadence.
  • Termux (SQLite): same flow against on-phone session id.
  • Pit Stall page shows live telemetry while replay is active.
  • POST /session/replay/start returns 409 when called twice.
  • CAN reader cleanly stops if running when replay starts.
  • PWA install banner appears in Chrome (non-standalone) and disappears for 7 days after dismissal.
  • ESC pops route stack everywhere except /; P opens pause only on /hud.

Taha Bouhsine added 6 commits May 24, 2026 06:04
Adds a session-replay feature so the PWA can drive a "simulated car"
from a previously-recorded SQLite session. The replay thread reads the
target session's wide rows + tall signals, groups them by nearest wide
timestamp, and re-publishes canonical wide_snapshot frames to
telemetry_bus at the original cadence (or scaled via the `speed` param).

Contract:
  POST /session/replay/start   body {source_session_id, speed?, loop?}
    -> 200 {replay_id, source_session_id, total_frames, est_duration_s,
            speed, loop}
    -> 404 if source has no telemetry rows
    -> 409 if a replay is already running
    -> 503 if no DB backend reachable
  POST /session/replay/stop    -> 200 {stopped, frames_emitted}
  GET  /session/replay/status  -> 200 {running, source_session_id, speed,
                                       loop, frame_idx, total_frames,
                                       elapsed_s, est_remaining_s}

Behaviour:
- Sets state.active_session_id to source_session_id so /health and the
  existing SSE stream pick the replay up automatically (no PWA changes
  required).
- Single-publisher invariant: if a CAN reader is running at start,
  stop it cleanly so frames don't interleave.
- Wide_snapshot uses PWA-facing keys (speed/rpm/brake_pressure/throttle/
  steering/g_lat/g_long/distance/lat/lon) — NOT the v3.0 raw names
  (speed_ms/brake_bar/etc).
- Sleep is capped at 1.0 s per chunk so /stop responds within ~1 s.
- On loop=true, restarts from frame 0 at end of source.

Smoke-tested on the phone against the 12,936-row Sonoma recording:
status -> idle, start (speed=10, loop=true) -> running, 254 frames
emitted in 3 s, SSE delivered canonical-shape frames, stop returned
{stopped:true, frames_emitted:260} in ~1 s.
Memory-leak hardening:
- HardwareDetail: null out liveTimer/histTimer refs after clearInterval
  in onUnmounted (telemetry.close() + clears were already wired).
- PedalProfile: null out refetchTimer ref after clearTimeout in
  onUnmounted.

v-for key stability (replace array index with stable identity):
- OnboardingFlow.vue: step dots -> `step-${i}` (i is 1..N from
  totalSteps; stable per slot).
- AvatarSelect.vue: avatar buttons -> avatar.id.
- PedalProfile.vue: friction G-G samples -> composite of index +
  (gLat,gLong); utilisation histogram bins -> `bin-${i*10}`.
- GlobalLeaderboard.vue: rows -> `rank-${entry.rank}-${entry.initials}`
  (future-proof; store is currently empty).
- SqlConsole.vue: result rows -> `row-${i}-${JSON.stringify(row)}`;
  examples list -> ex.title.
- DriverEvolution.vue: heatmap cells -> `${c.id}-lap-${i}` composite.
- AskCoachMode.vue: conversation turns -> t.recorded_at when present,
  else composite of (i, role, text-prefix).
Unify the keyboard contract across the PWA: ESC is now "go back" on every
page (router.back) and P is the pause overlay (in-session pages only,
opted in via route.meta.allowPause). Fixes a bag of nav-discoverability
papercuts shipped on landscape phones.

1. App.vue — ESC routes back globally (or dismisses pause if visible);
   P only toggles pauseStore on routes with meta.allowPause === true.
   Removed the old ESC-as-pause-anywhere global handler.
2. router/index.ts + OnTrackHud.vue — /hud opts into meta.allowPause;
   HUD's local handler now uses P for its pause overlay and treats B
   as a resume alias.
3. GarageHub.vue + CoachFloat.vue — raised CoachFloat z-index from 20
   to 100 so the welcome dialogue can never be covered by a focused
   spatial tile. Hint bar now lists B/C/H so previously-hidden
   shortcuts (CALIBRATE, HARDWARE) are discoverable.
4. PreBrief.vue — already had WALK · W in its actions array; verified.
5. OnTrackHud.vue — added a top-right ESC · BACK / P · PAUSE corner
   hint so the immersive HUD has a visible escape hatch; wrapped the
   10px status-pip in a 44x44 hit target (WCAG 2.5.5).
6. TelemetryReplay.vue — decoupled from can.connected; SSE subscribes
   whenever bridgeStore.health.active_session_id is non-null, mirroring
   PitStall's watch shape, so replay-only sessions are not blocked by
   missing CAN.
7. HintBar.vue — dev-only sanity-check stub for missing key hints,
   tree-shaken from prod; TODO documents the useKeyboard registry it
   would consume.
8. SaveSelect.vue — dropped the redundant window.addEventListener
   keydown listener; HOLD-B-DELETE now goes through useKeyboard with a
   single narrow keyup listener for the release edge.
Wires up the InstallPrompt widget added in 7f42f1a (PR-C) so Chrome's
beforeinstallprompt event surfaces a sticky banner. No-ops on iOS
Safari, in standalone contexts, and for 7 days after dismissal.
…scripts, PWA icons

PWA:
- PreBrief: BriefSourcePicker + SessionStartPicker widgets to choose
  the replay session that drives /coach/brief and the live HUD
- Remove templated/hardcoded fallbacks across pages so screens show
  real bridge data or an honest 'unavailable' state (NotificationCenter,
  CornerMastery, TrackWalk, TrainerCard, EndOfDay, LapTimesHall,
  StraightsAndSpeed, CoachCodexMode)
- Real-data notificationStore + leaderboardStore (SSE-aware, no mocks)
- PWA installability: PNG 192/512/maskable + apple-touch-icon, manifest
  fix in vite.config.ts and index.html
- Tests: sessionStartPicker widget test, hardened SSE reconnect tests

Backend:
- coaching/litert_coach: drop _templated_pre_brief synthesis; on LLM
  failure return ('', [], 'neutral') and record llm_friction
- coaching/bp_coaching: /coach/brief response now exposes error field
  from latest llm_friction row

Deploy:
- deploy/phone/00..99 ladder of shell scripts (rooted Pixel 10 +
  Termux), README + _common.sh + status.sh, SIM env knobs for the
  synthetic simulator path
@mlnomadpy mlnomadpy changed the title feat(bridge): session replay endpoints for simulated car feat: session-replay backend + PWA install/nav/SSE polish + deploy scripts May 24, 2026
@mlnomadpy
mlnomadpy merged commit f8733a1 into master May 24, 2026
0 of 4 checks passed
@mlnomadpy
mlnomadpy deleted the mlnomadpy/remove-android-cruft branch June 4, 2026 01:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant