// Which door a collaboration team's case-conference control opens. // // THE DEFECT THIS EXISTS FOR (walked 2026-09-16): the team card rendered "Start case conference" // whenever no call was running here, so a practitioner whose team already had a live conference // pressed a control that calls `POST /R4/video-session` - which the server correctly refuses // **409 `CONTEXT_TAKEN`**. The re-entry door was open the whole time and this surface never knocked // on it. The card invited the one press that could only be refused. // // It is `encounterVideoEntry`'s sibling, not a reuse of it: that resolver is built around an // encounter id and an encounter STATUS rule, and a team has neither. What carries over unchanged is // the reasoning, because the same three mistakes are available here. import { collabTeamContext, type VideoContext } from '../../services/video/videoContract'; import type { AvailableMeeting, CallPhase } from '../../services/video/callMachine'; export type CaseConferenceEntry = /** This team's conference is running for this user right now - there is no entry to offer. */ | { kind: 'live' } /** Nothing exists for this team: creating one is correct. */ | { kind: 'start' } /** A conference is live for this team and this user has not been in it. */ | { kind: 'join'; sessionId: string } /** A conference is live for this team and this user has already been in it. */ | { kind: 'rejoin'; sessionId: string }; interface Args { teamId: string; phase: CallPhase; /** The context of the call this user is IN, if any. */ callContext?: VideoContext; /** The meeting on offer - push, late-joiner lookup, or the re-offer a departure makes. */ available?: AvailableMeeting; } export function caseConferenceEntry({ teamId, phase, callContext, available }: Args): CaseConferenceEntry { const context = collabTeamContext(teamId); // Anchored on `idle` rather than listing the live phases, so a phase added later fails SAFE - it // keeps reporting live instead of offering a second entry into a call already coming up. const inACall = phase !== 'idle'; if (callContext === context && inACall) return { kind: 'live' }; // SCOPED TO THIS TEAM'S CONTEXT, and that is not tidiness. There is one `available` slot // app-wide; an unscoped read would let a meeting offered for one patient's team capture another // team's button and walk the practitioner into the wrong conference. That is a PHI exposure, not // a wrong label. if (available?.context === context) { // Absent `returning` reads as "first invitation", never as rejoin. It is SERVER-derived (the // caller's own `leftAt`) because a reload destroys whatever the client knew, and a reload is // precisely the case the grace window exists for. return { kind: available.returning ? 'rejoin' : 'join', sessionId: available.sessionId }; } return { kind: 'start' }; }