refactor: shared call-core — one tested reducer behind both call drivers#630
Merged
Conversation
Extracts the pure call bookkeeping both platforms duplicated (with drift)
into @atrium/surface-client call-core: state types, participant/phase
bookkeeping, comparator-parameterized sortLiveCalls, merge-aware
upsertUser, user enrichment, and the CallEvent reducer as a pure
(state, event) -> { state, effects } transition. The web and mobile
useCall hooks stay as thin drivers owning the LiveKit Room and (mobile)
every CallKit/AudioSession call site — all 28 verified byte-identical
and in original order against the design's site table. No livekit-client
types cross into shared (web and mobile pin different versions).
Deliberate behavior changes:
- web ring timeout 45s -> shared CALL_RING_TTL_MS (60s): the old value
raced the server sweeper and could show 'No answer' while the server
still considered the call ringing
- mobile adopts web's merge-aware upsertUser: fallback participants
upgrade to resolved users instead of being permanently add-only
Also: both drivers advance their state refs immediately after the
reducer runs (before React renders), so back-to-back CallEvents can
never read a stale snapshot and drop a transition; web's third
duplicate (callPresentation.ts) is deleted.
Honest accounting: production LOC rises ~187 (1,667 -> 1,854) — the
scaffolding to preserve both platforms' real divergences costs more
than the removed duplication saved. Shipped for the drift protection:
shared/test/callCore.test.ts (28 tests) pins each platform's semantics
across all six call lifecycles.
Design doc: docs/archive/notes/2026-07-21-call-core-design.md.
Validation: lint/typechecks/build:ci green; shared 496, web 860,
mobile 233 unit tests; e2e 120/120; two-user browser call smoke vs
local LiveKit (start/ring/join/two-party/hangup, zero page errors).
CallKit half needs a real-iPhone pass (28-site checklist in the design
doc) before reaching the deploy branch.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Extracts the pure call bookkeeping web and mobile duplicated (with real drift) into
@atrium/surface-clientcall-core: state types, participant/phase bookkeeping, comparator-parameterizedsortLiveCalls, merge-awareupsertUser, enrichment helpers, and theCallEventreducer as a pure(state, event) → { state, effects }transition. The twouseCallhooks remain thin drivers: web keeps the Room + audio-track attachment model; mobile keeps the Room + all 28 CallKit/AudioSession call sites, verified byte-identical and in original order (site-by-site table in the committed design doc). Nolivekit-clienttypes cross into shared — web and mobile pin different versions.Deliberate behavior changes
CALL_RING_TTL_MS(60s): the old value raced the server sweeper (premature "No answer")upsertUser: fallback participants now upgrade to resolved usersHardening found in review: both drivers now advance their state refs immediately after the reducer runs, so back-to-back
CallEvents can never read a stale snapshot (the old web code was burst-safe via functional updaters; the ref pattern wasn't until this).Honest accounting: production LOC rises ~187 (1,667 → 1,854) — preserving both platforms' genuine divergences (sort direction, dismissed-state storage, accepted-branch policy) costs more scaffolding than the deleted duplication saved. Shipped for the drift protection:
shared/test/callCore.test.ts(28 tests) pins each platform's semantics across all six call lifecycles, ending the three-copy drift that bred bugs elsewhere in this arc. Design + full analysis:docs/archive/notes/2026-07-21-call-core-design.md.Validation
build:ci(LiveKit stays a web-only chunk — the packaging constraint held)The CallKit surface (
NATIVE_CALL_UI) only runs on a real device. All 28 sites are structurally intact, but run the §1.8 device checklist from the design doc on a real iPhone: system-UI answer/decline/end/mute, AudioSession route restore, VoIP-push →reportIncomingCall, recents grouping.