import { X } from "lucide-react"; import { FrontDeskClaimStrip } from "./FrontDeskClaimStrip"; import { TRIAGE_CLAIM_VOCABULARY, type ThreadClaimState, } from "../../utils/messaging/threadClaim"; import type { ReferralThreadSubject } from "../../utils/messaging/referralThread"; import type { ReferralMatchState } from "../../utils/messaging/referralMatchState"; /** * - AN ARRIVING REFERRAL, OPEN AT THE FRONT DESK. * * Spencer, 2026-08-23: *"No, no reply. It's just referral triage just like they would do if it was * setup to use a referral coordinator in the referral coordinator workspace except they're doing it from * within the front desk message system."* * * SO THIS IS NOT `Chat`, AND THAT IS THE DESIGN RATHER THAN A SHORTCUT. The ruling makes the reply * affordance **absent, not disabled** - and the only way to make something absent by construction is to * render a component that has no composer in it. Adding a `readOnly` flag to `Chat` would put the * affordance one forgotten prop away from reappearing, on a component three other surfaces share. * * WHAT IT KEEPS FROM THE POOL IS THE CLAIM, which is the only reason the pool is the right home: * *"[receptionist name] is reading it"*, and an abandoned claim returning to the queue by itself. * * IT NEVER NAMES THE PATIENT. The receiving practice has no Patient record for the person on an * arriving referral and a referral grants nobody anything, so naming them would disclose a patient * identity to a desk with no relationship to them. The server refuses to deliver a notice that names * them; this side composes nothing of its own, and renders only what it was given. */ export type ReferralArrivalItemProps = { subject: ReferralThreadSubject; /** * - open the patient match for this referral. * * Optional, and its absence is meaningful rather than a default: a surface that cannot match (no * patient search in reach) passes nothing and the action is absent, rather than present and broken. */ onMatchPatient?: () => void; /** * Whether somebody has already matched this referral to a patient. * * The pool thread is composed once, on arrival, and nothing updates it afterwards - so without this * the item goes on offering the match forever, and two receptionists can match one referral to two * different patients with neither told. That defeats the coordination the claim above it buys. * * It never carries a patient identity, and it cannot: it is derived from the coordinator's queue, * which is a projection of state and the sending practice. Saying a referral is already being worked * needs no name. */ matchState?: ReferralMatchState; claim: ThreadClaimState | undefined; viewerAccountId: string | undefined; nameFor: (accountId: string) => string | undefined; onClaim: () => Promise; onRelease: () => Promise; onChanged: (claim: ThreadClaimState) => void; onClose: () => void; }; export const ReferralArrivalItem = ({ subject, claim, viewerAccountId, nameFor, onClaim, onRelease, onChanged, onClose, onMatchPatient, matchState = { kind: "unknown" }, }: ReferralArrivalItemProps) => (

{subject.label}

{/* ⭐ SAYS WHAT KIND OF THING THIS IS, because it sits in a list of conversations and looks like one. A receptionist who reads it as a message will wait for a reply that is never coming. */}

Referral to triage

{/* ⛔ NO `onReplyAnyway`. On a conversation that override focuses the composer; here there is no composer to focus, so the button is absent rather than pointing at nothing. */}
{/* ⛔ Rendered as plain text, deliberately. The notice is composed by the server as one paragraph with no markup — treating it as anything richer would be inventing a format the writer does not produce, and the writer is the side that must never be able to inject a patient's name. */}

{subject.summary}

{/* 🔗 CARD 686 — THE ONE ACTION A WORK ITEM NEEDS, and the only one it gets. 🗣️ Spencer: the patient is identified *"at the point of actual contact"* — so this is what a receptionist reaches for after they have rung the person, and it is the step that turns the item into a referral on that patient's record. ⛔ Still no reply affordance. Matching is not a conversation; it is the work. */} {matchState.kind === "matched" ? ( /* THE ACTION IS GONE, NOT DISABLED. A disabled button says "you may not do this"; the truth is that the work is done, and offering it at all is what let two receptionists match one referral to two different patients. It does not name the patient - see the type's own note. */

Already matched to a patient {matchState.state ? ` — now ${matchState.state} in Referrals` : " and now in Referrals"}.

) : ( onMatchPatient && ( <> {matchState.kind === "unknown" && ( /* THE CHECK DID NOT RUN, AND THAT IS SAID RATHER THAN GUESSED. Treating it as unmatched invites the double match this exists to stop; treating it as matched takes the work away from whoever got here first. So the action stays and the uncertainty is on screen. */

We could not check whether somebody has already matched this one.

)} ) )}
); export default ReferralArrivalItem;