import type { NoteVisibilityValue } from './NoteVisibilitySelect'; /** * - in-memory draft store for the org-internal patient note, keyed by patient. * * THE DEFECT THIS FIXES. `PatientNotes` keeps everything the user types in local `useState`, so any * remount of its container silently discards it. That was unreachable in practice until sub-task 3 * shipped the floating notes pane, which remounts the editor three ways during a live encounter: pop out, * dock back, and minimise (the minimised window renders only its corner button, not its children). * * THE ASYMMETRY IS THE FINDING. The PUBLIC clinical note already survives all three, because every * keystroke is mirrored into an App-level store. The PRIVATE note - the one labelled *"org only, not shown * to the patient"* - had no mirror at all. This gives it one. * * WHY MODULE SCOPE RATHER THAN REACT CONTEXT, which is how the public note does it: * `PatientNotes` has three unrelated consumers - the patient-chart notes route, the receptionist workspace * dialog, and the encounter's private tab - and two of them have nothing to do with an encounter session. * A context would mean a provider wrapping the whole app for a component that is usually not mounted, and * putting a PATIENT-scoped store inside the encounter-scoped `EncounterSessionProviders` would make that * component's name lie. Module scope outlives a remount, which is the entire requirement. * * IN MEMORY ONLY. NO localStorage, NO sessionStorage. Note text is PHI and there is to be none at rest - * the same rule the public clinical note follows. A hard refresh discarding an uncommitted draft is the * DESIGN, not a limitation; surviving a REMOUNT is the requirement. Do not "improve" this by persisting it. */ export interface PatientNoteDraft { /** The note being composed in the add area. */ newBody: string; newVisibility: NoteVisibilityValue; /** An in-progress edit of an EXISTING note - lost on remount too, and the worse half of the defect. */ editingId?: string; editBody: string; editVisibility: NoteVisibilityValue; } export const EMPTY_DRAFT: PatientNoteDraft = { newBody: '', newVisibility: { mode: '', categoryLabels: [] }, editingId: undefined, editBody: '', editVisibility: { mode: '', categoryLabels: [] }, }; /** * Session-lifetime, per patient. Same memory profile as the public note's store, which also holds drafts * for the life of the tab. */ const drafts = new Map(); /** Copy on the way in and out - the store must not alias a caller's object in either direction. */ const snapshot = (draft: PatientNoteDraft): PatientNoteDraft => ({ ...draft, newVisibility: { ...draft.newVisibility, categoryLabels: [...draft.newVisibility.categoryLabels] }, editVisibility: { ...draft.editVisibility, categoryLabels: [...draft.editVisibility.categoryLabels] }, }); const ownerKey = (patientId: string, owner?: string) => JSON.stringify([owner ?? 'legacy', patientId]); export function readDraft(patientId: string, owner?: string): PatientNoteDraft | undefined { const found = drafts.get(ownerKey(patientId, owner)); return found ? snapshot(found) : undefined; } export function writeDraft(patientId: string, draft: PatientNoteDraft, owner?: string): void { drafts.set(ownerKey(patientId, owner), snapshot(draft)); } export function clearDraft(patientId: string, owner?: string): void { drafts.delete(ownerKey(patientId, owner)); }