diff --git a/src/components/ClampText.css b/src/components/ClampText.css index 1c320be..08de61e 100644 --- a/src/components/ClampText.css +++ b/src/components/ClampText.css @@ -57,3 +57,27 @@ .clamp.is-open .clamp__more svg { transform: rotate(180deg); } + +/* The headline lifted out of an AI paragraph. Slightly heavier than the body + and never clamped — it is one sentence by construction, and the point of it + is that the finding can be read without opening anything. */ +.clamp__lead { + margin: 0; + font-size: 0.9rem; + line-height: 1.5; + color: var(--zk-ink); + font-weight: 500; +} +.clamp__lead + .clamp__text { margin-top: 8px; } + +/* The finding, lifted out of an AI paragraph. Heavier than the body and never + clamped — it is one sentence by construction, and the whole point is that it + can be read without opening anything. */ +.clamp__lead { + margin: 0; + font-size: 0.9rem; + line-height: 1.5; + font-weight: 500; + color: var(--zk-ink); +} +.clamp__lead + .clamp__text { margin-top: 8px; } diff --git a/src/components/ClampText.jsx b/src/components/ClampText.jsx index d0be5c1..4e559ff 100644 --- a/src/components/ClampText.jsx +++ b/src/components/ClampText.jsx @@ -1,15 +1,33 @@ import { useState } from 'react' 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() } +} + /** * Prose, folded. * * The AI employees write at length — a recommendation rationale runs past 1,500 * characters — and a lead file that prints all of it is a document, not a - * screen. Anything long opens as a few lines with the rest one click away, so - * the page can be scanned and still holds everything for whoever wants it. + * screen. Anything long shows its finding, with the reasoning one click away. * - * Whether to fold is decided on length, not on measuring the rendered box: a + * 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. */ @@ -19,11 +37,30 @@ export default function ClampText({ text, lines = 3, threshold = 150 }) { if (value.length <= threshold) return

{value}

+ const split = splitLead(value) + + // No sensible headline — fold the whole thing, as before. + if (!split) { + return ( +
+

{value}

+ +
+ ) + } + return (
-

{value}

+

{split.lead}

+ {open ?

{split.rest}

: null}