import { Check, CheckCheck } from "lucide-react"; import { readerLabel, type MessageStatus } from "../../utils/messaging/messageStatus"; /** * - what the patient is told about their own last message. * * IT REPLACES A GREEN DOUBLE TICK THAT WAS RENDERED UNCONDITIONALLY, on every outbound message, in * every thread. The universal symbol for *somebody has read this*, shown the instant you press send. * * THE LADDER, ruled by Spencer 2026-08-16: * | state | indicator | words | * |---|---|---| * | accepted by the server | one grey tick | *Sent to front desk* | * | a receptionist opened it | two green ticks | *Viewed by Sarah Chen* | * | a second one opened it, still no reply | two green ticks | a SECOND line naming them | * | any staff reply landed | - | **nothing at all** | * | the send failed | no tick | an explicit failure, with a retry | * * `answered` RENDERS NOTHING, and that is a decision rather than an omission: a reply says everything * the status line was there to say, and leaving it up would clutter the transcript with the history of a * question that is over. */ export interface MessageStatusLineProps { status: MessageStatus; /** Display names by reference, for the readers we can resolve. */ nameByReference: Record; /** The fallback when a reader does not resolve - true, and never an id. */ practiceName: string; /** * Spencer's ruling: a thread with ONE possible reader gets the honest one-ticktwo-tick * transition and NO name, because naming the only person it could be adds nothing. * It also selects the WORDING, and deliberately so - both differences come from the same fact. * *"Sent to front desk"* is false on a thread with your own doctor. */ showNames: boolean; onRetry?: () => void; } /** * `data-tick-count` RATHER THAN COUNTING ELEMENTS, and the reason is a real one rather than a * preference: `CheckCheck` is a SINGLE glyph that depicts two ticks, so a test counting `[data-tick]` * nodes reads 1 for both states and cannot tell the fix from the defect. The attribute states the * semantic - how many ticks this indicator MEANS - which is the thing worth asserting, and it keeps the * property the plan was after: no implementation can satisfy both "1 when queued" and "2 when viewed" * without actually distinguishing them. */ const OneTick = () => ( ); const TwoTicks = () => ( ); export const MessageStatusLine = ({ status, nameByReference, practiceName, showNames, onRetry, }: MessageStatusLineProps) => { // Nothing at all. See the header: a reply has already said it. if (status.kind === "answered") return null; if (status.kind === "failed") { return (
{/* ⛔ NO TICK. A tick on a message that never left the browser is the same lie with an extra step — and until this card, failure and success were indistinguishable here. */} Not sent {onRetry && ( )}
); } if (status.kind === "queued") { return (
{showNames ? "Sent to front desk" : "Sent"}
); } // viewed - one line per reader, in the order they actually looked. return (
{showNames ? status.readers.map((reader) => { const label = readerLabel(reader.reference, nameByReference, practiceName); return ( {/* An unresolvable reader yields an empty label, and then "Viewed" alone is still true — ⛔ never a raw reference and never "Unknown". */} {label ? `Viewed by ${label}` : "Viewed"} ); }) : ( Viewed )}
); }; export default MessageStatusLine;