import React, { useMemo } from 'react'; import { UserRound } from 'lucide-react'; import { Alert, AlertDescription, AlertTitle } from '../../../components/ui/alert'; import { FloatingWindow } from '../../../components/ui/floating-window/FloatingWindow'; import { sideAnchoredRect } from '../../../components/ui/floating-window/floatingGeometry'; import { useActiveEncounterContext } from './ActiveEncounterContext'; import { EncounterDocumentation } from './EncounterDocumentation'; import { useActiveEncounterPatient } from './useActiveEncounterPatient'; /** * sub-task 3 - the encounter clinical note, popped out into a window that follows the * practitioner anywhere in the app. * * EXACTLY ONE note editor exists at a time. While this is open, EncounterWorkspace shows a * placeholder instead of its editor. Two live editors for one note is the shape deletes. * * The window id is the STATIC string 'encounter-notes'. The primitive persists geometry under * `akashic_floating_window_`, so keying it by patient or encounter would write an identifier into a * localStorage key that survives logout on a shared clinic workstation. * * Reuses `EncounterDocumentation` WHOLE, public/private toggle included - a note-only reimplementation * is how the "private - org only, not shown to the patient" distinction gets quietly lost. * * - WHY THIS PANE DOES **NOT** FOLLOW THE SELECTED PATIENT * The card was raised as *"the pane stays bound to the PREVIOUS patient"* after a measured run: with * Jacob Miller's notes popped out, selecting Related Two left this pane reading "Notes - Jacob Miller" * over Related Two's chart. **The measurement is real; the diagnosis was not.** Selecting a queue item * is a PREVIEW - it moves `selectedKey`, never the active slot - so the pane was showing the encounter * the practitioner is actually in, which is Spencer's 2026-08-05 ruling verbatim (see * `ActiveEncounterContext`). Retargeting to the selected patient would break that ruling AND is * incoherent: a previewed patient usually has no open encounter to write to. * * THE DEFECT THAT **IS** REAL is an asymmetry with the docked editor. Docked, the note is unreachable * while you preview someone else - `showWorkspace` requires `selectedKey === openEncounter.item.key`, so * the whole workspace unmounts. Floating, the note stays live over another patient's chart **and the only * thing tying it to its subject was the window title - which is `truncate`d in a small bar and can be * clipped to nothing.** So the pane now states its subject in the BODY, where it cannot be truncated * away, and says why it is not following the screen. * Do not "fix" this by clearing the pane on selection: that destroys an in-progress note, which is the * card's own negative control. */ export const FloatingEncounterNotes: React.FC = () => { const { active, notesLocation, setNotesLocation } = useActiveEncounterContext(); // `patient` is already identity-gated at RENDER time inside the hook - see its comment for why an // effect cannot fix the lag between `patientId` (synchronous) and the fetched `patient` (async). const { patient: current, name } = useActiveEncounterPatient(notesLocation === 'floating'); // IT USED TO OPEN CENTRED, AND THE CENTRE IS WHERE THE QUEUE RAIL IS. The window centres on the // VIEWPORT while the page content centres inside a max-w-[1600px] frame - two different centres on a // wide monitor, and the gap between them is exactly where the left column sits. Measured on a 3440px // display the centred window spanned x=860 to 2580 while the collapsed rail sat at roughly x=920 to // 984, inside it. A clinician with notes open could not click Expand queue to reach their own // patient list without minimising the notes first, every time. // // Computed ONCE, and that is a requirement rather than an optimisation: useFloatingGeometry reads // this only in its state initializer and treats it as mount-stable. A fresh object per render would be // silently ignored - a worse failure than an obvious one, because it looks configured and is not. // It is only a DEFAULT. A rect the clinician has dragged and stored still wins, as it should. const defaultRect = useMemo( () => sideAnchoredRect({ width: window.innerWidth, height: window.innerHeight }), [], ); if (!active || notesLocation !== 'floating') return null; return ( setNotesLocation('docked')} >
{/* 🚨 THE SUBJECT, IN THE BODY. ⛔ Never add `truncate` here — being clipped is precisely the failure mode this exists to cover for the title bar. The second line is not decoration: it answers the question the practitioner actually has ("why is this not the patient I'm looking at?"), which a name alone leaves them to guess at. */} {/* Unsized on purpose — the primitive sizes its own icon (`[&>svg]:size-4`). */}
); };