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
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.- 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.
- The 43 internal
<a href="/s/...">anchors in the RE template convert tonext/linkLinkso 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). - 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.
- 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
- 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).
- Soft-nav coverage: every converted link navigates without a document reload (spot-check ≥5 across header/footer/cards on the preview).
- Standalone mounts (funeral/vet hero, playground) still work without a provider — contract test.
- 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).