Skip to main content

Persistent Browser Call — Expected Output Spec

§0 Why a modal alone cannot fix it

Nothing inside a page survives a full page load. The fix is structural: (1) the live-call state/audio mounts in a PERSISTENT LAYOUT above the pages, and (2) internal navigation becomes client-side (next/link soft navigation), so the layout — and the call in it — never unmounts. Both, or the call still dies on the first click.

§1 Expected output

  1. src/app/s/[slug]/layout.tsx (new): wraps every RE site page; hosts a client CallProvider that owns the LiveKit room lifecycle + remote audio. The provider is inert (renders nothing, connects nothing) until a call starts.
  2. Starting a call (the existing AI-band button and any Talk affordance) drives the provider. While a call is active, a floating CALL BAR renders above all pages: brand-styled, showing live timer, mute toggle, and hang up — visible at every scroll position and on every page.
  3. The 43 internal <a href="/s/..."> anchors in the RE template convert to next/link Link so navigation is soft. NOT converted: external links, tel:/mailto:, same-page # anchors, and any link that deliberately forces a reload. The Link conversion must not break the middleware host rewrites (verify one converted link on the preview's rewritten host).
  4. ACCEPTANCE MOMENT (the founder's exact scenario): start a call on a listing page → click into guides → open a guide → the call is still live, the bar shows the running timer, and Aria heard you the whole time.
  5. Ending states: hang up from the bar; the 20-minute cap still enforced; closing the tab ends the call (accepted); a FULL reload (typed URL/refresh) ends the call — the bar simply does not reappear, no error.

§2 Constraints

  • Existing WebRTC hardening MUST survive the move: mic pre-check stream stopped, error beacons (mic_permission/connect/media_devices/ mid_call_disconnect/token_rate_limited), audiocheck settings beacon and its privacy gate, friendly error copy, intentional-vs-unexpected disconnect distinction, contact pre-call form flow, per-call unique rooms. The echo-cancellation contract tests and the two-beacon privacy contract keep passing unmodified (they scan the component source — if logic moves files, update the tests' file lists in the same PR with the same assertions).
  • Church/funeral/vet surfaces byte-unchanged (this is all under /s/[slug] RE-vertical rendering; the shared WebRTCDemoButton must keep working standalone where other verticals mount it WITHOUT the provider).
  • No hydration divergence; provider state client-only.

§3 Acceptance checks

  1. Playwright on the preview: the §1.4 scenario, with an assertion the room connection object is the SAME across two navigations (e.g. window-scoped call id logged at connect, unchanged after nav).
  2. Soft-nav coverage: every converted link navigates without a document reload (spot-check ≥5 across header/footer/cards on the preview).
  3. Standalone mounts (funeral/vet hero, playground) still work without a provider — contract test.
  4. All existing browser-call contract tests green, unmodified in substance.

§4 Follow-on, specced now (founder ask 2026-09-04): page-aware Aria

Once the call persists, the provider publishes a small LiveKit DATA MESSAGE on every route change ({path, title, mls?}). The voice agent stores the latest and injects one context line per turn ("caller is currently viewing: ...") so Aria references what the caller actually sees — including exact-listing pulls when the path carries an MLS number. Voice-side lands in a later deploy; THE BUILDER OF THIS SPEC MUST LEAVE THE CLIENT HOOK READY: a provider-level publishPageContext(path, title) called on route change, no-op if the data channel is unavailable.

§5 Out of scope

Cross-tab persistence; surviving full reloads; call UI on non-RE verticals; picture-in-picture; the agent-side half of page-awareness (fast-follow).