import { useEffect, useRef, useState } from 'react'; import type { Resource } from 'fhir/r4'; import { type AnchorResolveResult, type AnchorTemporal, useAuthServiceContext, useCareViewAnchorServiceContext, usePatientServiceContext, } from '../../services'; import { useAnchorResults } from '../../hooks/useAnchorResults'; import { SnapshotChart, Spinner } from '../PatientSnapshot/SnapshotChart'; import { Card, CardContent, CardHeader, CardTitle } from '../ui/card'; import { type ExportAuthority, ExportLegalRecordDialog } from './ExportLegalRecordDialog'; import { TamperedBanner } from './TamperedBanner'; import { describeFetchFailure } from '../../utils/fetchFailureMessage'; import { onViewerChange, viewerEpoch } from '../../utils/writeThroughHold'; /** Stable empty references so the pre-resolve render doesn't churn `useAnchorResults`. */ const NO_RESOURCES: Resource[] = []; const NO_REFS: string[] = []; interface ExportOpening { token: number; anchorId: string; edition: string; authority: ExportAuthority; } function sameAuthority(a: ExportAuthority, b: ExportAuthority): boolean { return ( a.viewerId === b.viewerId && a.practiceId === b.practiceId && a.patientId === b.patientId && a.actingAsId === b.actingAsId && a.sessionId === b.sessionId && a.viewerEpoch === b.viewerEpoch ); } interface IntegrityBannerProps { hashVerified: boolean; unresolvedRefs: string[]; } /** Format an ISO instant for display, falling back to the raw value / a neutral phrase. */ function formatInstant(iso?: string): string { if (!iso) return 'the time of care'; const d = new Date(iso); return Number.isNaN(d.getTime()) ? iso : d.toLocaleString(); } /** * The court-grade integrity notice for a resolved anchor. GREEN when the server re-hash matched, * RED when it did not (contents don't match the stored hash), plus an AMBER line when some pinned * versions couldn't be retrieved. Read-only - carries no actions. "Verified" speaks only to the * stored record and its hash; when the record stands in time is the temporal notice's to say. */ function IntegrityBanner({ hashVerified, unresolvedRefs }: IntegrityBannerProps) { return (
{hashVerified ? (

Verified — this record matches its stored hash.

) : ( )} {unresolvedRefs.length > 0 && (

{unresolvedRefs.length} pinned item(s) could not be retrieved.

    {unresolvedRefs.map((ref) => (
  • {ref}
  • ))}
)}
); } const QUALIFICATION_WORDS: Record = { 'boundary-tie': 'Some records changed at the same instant as the start and are not included.', 'subject-differs-at-boundary': 'Some records belonged to another patient at the start and are not included.', 'history-order-unproved': "Some records' history could not be placed in order and are not included.", }; /** * When the record stands, said apart from "Verified". A reconstruction names the visit's start and that which records * existed then is not proven; an unreadable record claims no time; an anchor that records none says nothing. */ export function TemporalNotice({ temporal }: { temporal?: AnchorTemporal }) { if (!temporal || temporal.state === 'not-recorded') return null; if (temporal.state === 'unreadable') { return (

When this record stands could not be read, so no time is claimed for it.

); } const { boundary, qualifications, basisStatus } = temporal.temporal; const limits = qualifications.filter((code) => code !== 'population-unproved').map((code) => QUALIFICATION_WORDS[code]).filter(Boolean); return (

Reconstructed as the record stood at the visit's start, {formatInstant(boundary)}.

Which records existed then is not proven.

{limits.map((line) =>

{line}

)} {basisStatus === 'unavailable' &&

The agreements in force at the start were not kept with it.

}
); } /** Small metadata card describing the anchor (its date, encounter, source). */ function AnchorMeta({ anchor }: { anchor: AnchorResolveResult['anchor'] }) { return ( Care-view anchor
Anchor date: {formatInstant(anchor.date)}
{anchor.encounter &&
Encounter: {anchor.encounter}
} {anchor.source &&
Source: {anchor.source}
} {typeof anchor.resourceCount === 'number' && (
{anchor.resourceCount} pinned item(s)
)}
); } interface AnchorViewProps { anchorId: string; } /** * Route-level, READ-ONLY view of a single care-view anchor. On mount it asks the server to * resolve the anchor (vread each pinned version + re-verify the hash), then renders the resolved * record through the shared `SnapshotChart` under an integrity banner. There are ZERO * create/edit/archive/delete affordances anywhere in this component. */ export function AnchorView({ anchorId }: AnchorViewProps) { const { resolveCareViewAnchor } = useCareViewAnchorServiceContext(); const { currentUser, currentOrganization, actingAs, actingAsSessionRef } = useAuthServiceContext(); const { patientState } = usePatientServiceContext(); const [result, setResult] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [opening, setOpening] = useState(null); const openingRef = useRef(null); const nextOpeningRef = useRef(0); const viewerId = currentUser?.fhir?.id; const practiceId = currentOrganization?.id; const patientId = patientState?.id; const actingAsId = actingAs?.relatedPersonId; const edition = result ? `${result.anchor.id ?? anchorId}:${result.anchor.hash ?? ''}` : ''; // What this render shows, written as it runs. An attempt still awaiting holds the functions of the render it // started in, so they read this rather than their own closure: a later render's patient is what they see. const renderedRef = useRef({ anchorId, edition, viewerId, practiceId, patientId, actingAsId }); renderedRef.current = { anchorId, edition, viewerId, practiceId, patientId, actingAsId }; // Ref and epoch are read when asked, not copied at render: ids alone can return after a switch. const liveAuthority = (): ExportAuthority => { const shown = renderedRef.current; return { viewerId: shown.viewerId, practiceId: shown.practiceId, patientId: shown.patientId, actingAsId: shown.actingAsId, sessionId: actingAsSessionRef?.current, viewerEpoch: viewerEpoch(), }; }; const opensForShown = (candidate: ExportOpening): boolean => candidate.anchorId === renderedRef.current.anchorId && candidate.edition === renderedRef.current.edition && sameAuthority(candidate.authority, liveAuthority()); // Retired by the render that no longer shows its identity, before any effect runs; the same ids returning later // do not revive it. if (openingRef.current && !opensForShown(openingRef.current)) openingRef.current = null; useEffect(() => { let cancelled = false; setLoading(true); setError(null); setResult(null); resolveCareViewAnchor(anchorId) .then((res) => { if (cancelled) return; setResult(res); setLoading(false); }) .catch((err: unknown) => { if (cancelled) return; setError(describeFetchFailure(err, 'Could not load this care-view anchor')); setLoading(false); }); return () => { cancelled = true; }; }, [anchorId, resolveCareViewAnchor]); // A viewer, practice or anchor change kills the opening before its child can answer. useEffect(() => { openingRef.current = null; setOpening(null); }, [anchorId, edition, viewerId, practiceId, patientId, actingAsId]); // Practice/viewer retirement is synchronous, so a retired token cannot revive when its ids return. useEffect(() => onViewerChange(() => { if (!openingRef.current) return; openingRef.current = null; setOpening(null); }), []); useEffect(() => () => { openingRef.current = null; }, []); // Hooks must run unconditionally - feed empty refs until the resolve returns. const mapped = useAnchorResults( result?.resolvedResources ?? NO_RESOURCES, result?.unresolvedRefs ?? NO_REFS, ); if (loading) { return (
); } if (error || !result) { return (

Couldn't load this care-view anchor.

{error &&

{error}

}
); } const openExport = () => { const token = nextOpeningRef.current + 1; nextOpeningRef.current = token; const next: ExportOpening = { token, anchorId, edition, authority: liveAuthority(), }; openingRef.current = next; setOpening(next); }; const closeExport = (token: number) => { if (openingRef.current?.token !== token) return; openingRef.current = null; setOpening(null); }; const isOpeningCurrent = (token: number) => { const current = openingRef.current; return Boolean(current && current.token === token && opensForShown(current)); }; // Only the opening the ref still holds is shown: a retired one is never mounted again. const activeOpening = opening && openingRef.current?.token === opening.token ? opening : null; return (
} emptyLabel="in this anchor" /> {activeOpening && ( )}
); } export default AnchorView;