diff --git a/components/chat.tsx b/components/chat.tsx index 930fab8..a9cea1a 100644 --- a/components/chat.tsx +++ b/components/chat.tsx @@ -48,7 +48,6 @@ import { PhaseChip, PhaseDivider } from "@/components/phase-divider"; import { CompactionCutDivider, CompactionMomentDivider, - CompactionSummaryDialog, type CompactionDividerData, } from "@/components/compaction-divider"; import { CoverageRing } from "@/components/extraction-coverage"; @@ -630,9 +629,8 @@ export function Chat({ // Compaction (owner round 14): the RUNNING state — compaction-changed // flips it; the send paths, canSend, and the docks gate on it (the // server's registry mutex is the structural gate; this is the - // affordance). The summary dialog state rides with it. + // affordance). const [compacting, setCompacting] = useState(initiallyCompacting); - const [compactionSummary, setCompactionSummary] = useState(null); const canSend = (text.trim().length > 0 || dictation.active) && !pending && !activeProblem && !compacting; @@ -1290,21 +1288,9 @@ export function Chat({ case "data-graph-context": return ; case "data-compaction": - return ( - setCompactionSummary(part.data as CompactionDividerData)} - /> - ); + return ; case "data-compaction-cut": - return ( - setCompactionSummary(part.data as CompactionDividerData)} - /> - ); + return ; case "data-practice-thinking": return ; case "data-practice-next": @@ -1600,7 +1586,6 @@ export function Chat({ )} {planOpen && setPlanOpen(false)} />} setAbortAsk(null)} onConfirm={confirmAbort} busy={busy} /> - setCompactionSummary(null)} /> diff --git a/components/compaction-divider.tsx b/components/compaction-divider.tsx index e2900f9..7511ecc 100644 --- a/components/compaction-divider.tsx +++ b/components/compaction-divider.tsx @@ -1,12 +1,13 @@ "use client"; -import { useEffect } from "react"; -import { createPortal } from "react-dom"; import type { CompactionDetails } from "@/src/core"; /** What one divider carries: the marker's full model-facing content (the - * [SESSION HISTORY COMPACTED] header + the summary) and its details — - * both dividers open the same dialog from the same payload. */ + * [SESSION HISTORY COMPACTED] header + the summary) and its details. + * The content is deliberately UNRENDERED (owner, release polish + * 2026-10-06): the compaction summary is model-facing plumbing — anyone + * who wants to inspect it reads their provider logs. The dividers mark + * WHERE the cut landed and how big it was; nothing opens. */ export interface CompactionDividerData { content: string; details: CompactionDetails; @@ -15,18 +16,14 @@ export interface CompactionDividerData { /** The moment divider (owner round 14): minimal centered small text where * the user currently is — the retrying-message register (SystemLine's * exact classes), NO horizontal rule. The transcript's quiet note that - * history was just summarized; the cut divider below carries the seam. */ -export function CompactionMomentDivider({ data, onOpen }: { data: CompactionDividerData; onOpen: () => void }) { + * history was just summarized; the cut divider below carries the seam. + * Inert since release polish 2026-10-06 — plain text, no affordance. */ +export function CompactionMomentDivider({ data: _data }: { data: CompactionDividerData }) { return (
- +
); } @@ -34,80 +31,18 @@ export function CompactionMomentDivider({ data, onOpen }: { data: CompactionDivi /** The cut divider (owner round 14): the LONGER divider at the actual * compaction cut — the seam where verbatim history ends and the kept * recent tail begins. Phase-divider's geometry (dotted rules + centered - * chip) so it reads as a transcript boundary, not a message. */ -export function CompactionCutDivider({ data, onOpen }: { data: CompactionDividerData; onOpen: () => void }) { + * chip) so it reads as a transcript boundary, not a message. Inert + * since release polish 2026-10-06 — the token count stays, the summary + * is inspectable only in provider logs. */ +export function CompactionCutDivider({ data }: { data: CompactionDividerData }) { const kTok = data.details.tokensBefore > 0 ? ` · ${Math.round(data.details.tokensBefore / 1000)}k tok` : ""; return ( - - ); -} - -/** The dialog's pure body — exported for SSR pins (the delete-dialog / - * node-dialog body pattern; the wrapper owns the portal + Esc effect). */ -export function CompactionSummaryBody({ summary, onClose }: { summary: CompactionDividerData; onClose: () => void }) { - return ( -
-
- Compacted context - -
-
-
-					{summary.content}
-				
-
); } - -/** The full-summary view both dividers open (owner round 14): the - * marker's content verbatim — the compaction header + the summary the - * model itself reads — in a scrollable document block. Esc and the - * Close row both dismiss; the backdrop click does not (the summary is - * reference material, not a decision — an accidental dismissal costs a - * re-click, and a stray click-through mid-read costs more). */ -export function CompactionSummaryDialog({ - summary, - onClose, -}: { - summary: CompactionDividerData | null; - onClose: () => void; -}) { - useEffect(() => { - if (!summary) return; - const onKey = (e: KeyboardEvent) => { - if (e.key === "Escape") onClose(); - }; - window.addEventListener("keydown", onKey); - return () => window.removeEventListener("keydown", onKey); - }, [summary, onClose]); - - if (!summary) return null; - return createPortal( -
- -
, - document.body, - ); -} diff --git a/tests/render-compaction-divider.test.tsx b/tests/render-compaction-divider.test.tsx index 633a280..ad237fb 100644 --- a/tests/render-compaction-divider.test.tsx +++ b/tests/render-compaction-divider.test.tsx @@ -1,15 +1,16 @@ /** - * THE COMPACTION DIVIDERS — SSR render pins (owner round 14). Two ruled - * surfaces from one marker: the MOMENT divider (minimal centered small - * text, the retrying-message register — no rule lines) where the user - * is when compaction lands, and the CUT divider (the longer one — - * phase-divider geometry, dotted rules + a chip carrying the summarized - * token count) at the kept boundary. Both open the same summary dialog - * (the marker's full model-facing content, verbatim, scrollable). + * THE COMPACTION DIVIDERS — SSR render pins (owner round 14; inert since + * release polish 2026-10-06). Two ruled surfaces from one marker: the + * MOMENT divider (minimal centered small text, the retrying-message + * register — no rule lines) where the user is when compaction lands, and + * the CUT divider (the longer one — phase-divider geometry, dotted rules + * + a chip carrying the summarized token count) at the kept boundary. * - * The dialog portals to document.body at z-50; SSR renders null when - * closed — the pins hold the open state through a jsdom-free server - * render (createPortal is a no-op passthrough on the server). + * NOT VIEWABLE (owner, 2026-10-06): the summary dialog is gone — the + * compaction content is model-facing plumbing; anyone who wants to + * inspect it reads their provider logs. The pins below hold the + * dividers INERT: plain markup, no button, no affordance, and the + * marker's summary text never reaches the DOM. */ import React from "react"; @@ -21,8 +22,6 @@ import { describe, it } from "node:test"; import { CompactionCutDivider, CompactionMomentDivider, - CompactionSummaryBody, - CompactionSummaryDialog, type CompactionDividerData, } from "../components/compaction-divider.tsx"; @@ -35,9 +34,11 @@ const data: CompactionDividerData = { }, }; +const INERT = ["no click affordance survives the 2026-10-06 ruling"]; + describe("CompactionMomentDivider", () => { it("renders the minimal centered note — no rule lines, retrying-message register", () => { - const html = renderToStaticMarkup( {}} />); + const html = renderToStaticMarkup(); assert.ok(html.includes('data-testid="compaction-divider"')); assert.ok(html.includes("Context compacted")); // NO horizontal rule (the owner ruling: minimal, unlike the cut) @@ -47,7 +48,7 @@ describe("CompactionMomentDivider", () => { describe("CompactionCutDivider", () => { it("renders the longer seam — dotted rules + chip with the token count", () => { - const html = renderToStaticMarkup( {}} />); + const html = renderToStaticMarkup(); assert.ok(html.includes('data-testid="compaction-cut-divider"')); assert.ok(html.includes("border-t-2 border-dotted")); assert.ok(html.includes("History compacted")); @@ -56,17 +57,24 @@ describe("CompactionCutDivider", () => { }); }); -describe("CompactionSummaryDialog", () => { - it("renders the marker content verbatim, scrollable (the pure body — portals need document)", () => { - const html = renderToStaticMarkup( - {}} />, - ); - assert.ok(html.includes('data-testid="compaction-summary-dialog"')); - assert.ok(html.includes("[SESSION HISTORY COMPACTED]")); - assert.ok(html.includes("overflow-y-auto")); - assert.ok(html.includes('data-testid="compaction-summary-close"')); +describe("not viewable (owner, release polish 2026-10-06)", () => { + it("neither divider is a button — plain markup, no click affordance", () => { + for (const html of [ + renderToStaticMarkup(), + renderToStaticMarkup(), + ]) { + assert.ok(!html.includes(" { - assert.equal(renderToStaticMarkup( {}} />), ""); + + it("the marker's summary text never reaches the DOM — provider logs are the inspection path", () => { + for (const html of [ + renderToStaticMarkup(), + renderToStaticMarkup(), + ]) { + assert.ok(!html.includes("[SESSION HISTORY COMPACTED]"), "the header stays out"); + assert.ok(!html.includes("THE LEARNER"), "the summary stays out"); + } }); });