import { useEffect, useRef, useState } from 'react' import { createPortal } from 'react-dom' import './ClampText.css' /** * Splits a block of AI prose into a finding and its working. * * The employees write the conclusion first and the evidence after it — * "POSP-77341 is active and empanelled for motor." then four sentences of * registry detail. So the first sentence already IS the summary, and lifting it * out gives one without inventing text or asking the model for a second field * it would have to be trusted to keep in step with the first. * * Returns null when the split would be useless: no sentence terminator, a first * sentence so long it is the paragraph again, or one so short it is a fragment * rather than a finding. Those fall back to plain folding. */ function splitLead(value) { const m = value.match(/^(.{40,220}?[.!?])(\s+)(\S[\s\S]*)$/) if (!m) return null return { lead: m[1].trim(), rest: m[3].trim() } } /** * The full text, in a dialog. * * The reasoning used to expand INLINE, and in the audit rail — 320px wide, one * entry per step — a 1,500-character rationale pushed the rest of the lead's * history off the screen to read one sentence of it. Nobody reads a paragraph * in a sidebar. It opens over the page instead: the finding stays on the line, * the working is one click away and one Escape back. */ function ProseDialog({ title, lead, body, onClose }) { const ref = useRef(null) const restore = useRef(null) useEffect(() => { restore.current = document.activeElement ref.current?.focus() const onKey = (e) => { if (e.key === 'Escape') onClose() } document.addEventListener('keydown', onKey) // The page behind must not scroll under the dialog. const prev = document.body.style.overflow document.body.style.overflow = 'hidden' return () => { document.removeEventListener('keydown', onKey) document.body.style.overflow = prev // Focus goes back to the button that opened this, not to the top of the // document — otherwise a keyboard reader loses their place in the trail. if (restore.current instanceof HTMLElement) restore.current.focus() } }, [onClose]) return createPortal(
e.stopPropagation()} >

{title || 'In full'}

{/* The finding stays at the top and stays emphasised — a reader who opened this to check one thing should not have to find it again. */} {lead ?

{lead}

: null} {body.split(/\n{2,}/).map((para, i) =>

{para}

)}
, document.body, ) } /** * Prose, reduced to its point. * * The AI employees write at length — a recommendation rationale runs past 1,500 * characters — and a screen that prints all of it is a document. Short text is * shown as it is; anything longer shows its finding and puts the working behind * one click. * * Whether to fold is decided on length, not by measuring the rendered box: a * ref-and-measure pass would reflow on every resize to answer a question the * string itself already answers. */ export default function ClampText({ text, title, lines = 3, threshold = 150 }) { const [open, setOpen] = useState(false) const value = String(text) if (value.length <= threshold) return

{value}

const split = splitLead(value) return (
{split ?

{split.lead}

:

{value}

} {open ? ( setOpen(false)} /> ) : null}
) }