compaction: not viewable — the dividers stay, inert (release polish 4)
Owner ruling (release polish round, 2026-10-06): the compaction summary dialog is gone. The content is model-facing plumbing — anyone who wants to inspect it reads their provider logs. Both dividers survive as INERT markers: the moment divider's 'Context compacted' note and the cut divider's 'HISTORY COMPACTED · 152k tok' seam chip render as plain text; no button, no dialog, no state. The marker parts and the server fold are untouched — this is a pure render-side removal. Pins: dividers render without <button> or cursor affordances, and the marker's summary text never reaches the DOM. 1427 tests, tsc, lint, build green.
This commit is contained in:
+3
-18
@@ -48,7 +48,6 @@ import { PhaseChip, PhaseDivider } from "@/components/phase-divider";
|
|||||||
import {
|
import {
|
||||||
CompactionCutDivider,
|
CompactionCutDivider,
|
||||||
CompactionMomentDivider,
|
CompactionMomentDivider,
|
||||||
CompactionSummaryDialog,
|
|
||||||
type CompactionDividerData,
|
type CompactionDividerData,
|
||||||
} from "@/components/compaction-divider";
|
} from "@/components/compaction-divider";
|
||||||
import { CoverageRing } from "@/components/extraction-coverage";
|
import { CoverageRing } from "@/components/extraction-coverage";
|
||||||
@@ -630,9 +629,8 @@ export function Chat({
|
|||||||
// Compaction (owner round 14): the RUNNING state — compaction-changed
|
// Compaction (owner round 14): the RUNNING state — compaction-changed
|
||||||
// flips it; the send paths, canSend, and the docks gate on it (the
|
// flips it; the send paths, canSend, and the docks gate on it (the
|
||||||
// server's registry mutex is the structural gate; this is 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 [compacting, setCompacting] = useState(initiallyCompacting);
|
||||||
const [compactionSummary, setCompactionSummary] = useState<CompactionDividerData | null>(null);
|
|
||||||
const canSend =
|
const canSend =
|
||||||
(text.trim().length > 0 || dictation.active) && !pending && !activeProblem && !compacting;
|
(text.trim().length > 0 || dictation.active) && !pending && !activeProblem && !compacting;
|
||||||
|
|
||||||
@@ -1290,21 +1288,9 @@ export function Chat({
|
|||||||
case "data-graph-context":
|
case "data-graph-context":
|
||||||
return <GraphContextCard key={pi} data={part.data as GraphContextData} />;
|
return <GraphContextCard key={pi} data={part.data as GraphContextData} />;
|
||||||
case "data-compaction":
|
case "data-compaction":
|
||||||
return (
|
return <CompactionMomentDivider key={pi} data={part.data as CompactionDividerData} />;
|
||||||
<CompactionMomentDivider
|
|
||||||
key={pi}
|
|
||||||
data={part.data as CompactionDividerData}
|
|
||||||
onOpen={() => setCompactionSummary(part.data as CompactionDividerData)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
case "data-compaction-cut":
|
case "data-compaction-cut":
|
||||||
return (
|
return <CompactionCutDivider key={pi} data={part.data as CompactionDividerData} />;
|
||||||
<CompactionCutDivider
|
|
||||||
key={pi}
|
|
||||||
data={part.data as CompactionDividerData}
|
|
||||||
onOpen={() => setCompactionSummary(part.data as CompactionDividerData)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
case "data-practice-thinking":
|
case "data-practice-thinking":
|
||||||
return <ThinkingCard key={pi} data={part.data as PracticeThinkingData} />;
|
return <ThinkingCard key={pi} data={part.data as PracticeThinkingData} />;
|
||||||
case "data-practice-next":
|
case "data-practice-next":
|
||||||
@@ -1600,7 +1586,6 @@ export function Chat({
|
|||||||
)}
|
)}
|
||||||
{planOpen && <PlanPopup messages={messages} onClose={() => setPlanOpen(false)} />}
|
{planOpen && <PlanPopup messages={messages} onClose={() => setPlanOpen(false)} />}
|
||||||
<AbortTurnDialog mode={abortAsk} onClose={() => setAbortAsk(null)} onConfirm={confirmAbort} busy={busy} />
|
<AbortTurnDialog mode={abortAsk} onClose={() => setAbortAsk(null)} onConfirm={confirmAbort} busy={busy} />
|
||||||
<CompactionSummaryDialog summary={compactionSummary} onClose={() => setCompactionSummary(null)} />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useEffect } from "react";
|
|
||||||
import { createPortal } from "react-dom";
|
|
||||||
import type { CompactionDetails } from "@/src/core";
|
import type { CompactionDetails } from "@/src/core";
|
||||||
|
|
||||||
/** What one divider carries: the marker's full model-facing content (the
|
/** What one divider carries: the marker's full model-facing content (the
|
||||||
* [SESSION HISTORY COMPACTED] header + the summary) and its details —
|
* [SESSION HISTORY COMPACTED] header + the summary) and its details.
|
||||||
* both dividers open the same dialog from the same payload. */
|
* 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 {
|
export interface CompactionDividerData {
|
||||||
content: string;
|
content: string;
|
||||||
details: CompactionDetails;
|
details: CompactionDetails;
|
||||||
@@ -15,18 +16,14 @@ export interface CompactionDividerData {
|
|||||||
/** The moment divider (owner round 14): minimal centered small text where
|
/** The moment divider (owner round 14): minimal centered small text where
|
||||||
* the user currently is — the retrying-message register (SystemLine's
|
* the user currently is — the retrying-message register (SystemLine's
|
||||||
* exact classes), NO horizontal rule. The transcript's quiet note that
|
* exact classes), NO horizontal rule. The transcript's quiet note that
|
||||||
* history was just summarized; the cut divider below carries the seam. */
|
* history was just summarized; the cut divider below carries the seam.
|
||||||
export function CompactionMomentDivider({ data, onOpen }: { data: CompactionDividerData; onOpen: () => void }) {
|
* Inert since release polish 2026-10-06 — plain text, no affordance. */
|
||||||
|
export function CompactionMomentDivider({ data: _data }: { data: CompactionDividerData }) {
|
||||||
return (
|
return (
|
||||||
<div className="flex w-full items-center justify-center py-1">
|
<div className="flex w-full items-center justify-center py-1">
|
||||||
<button
|
<span data-testid="compaction-divider" className="px-1.5 text-xs text-muted-foreground/70">
|
||||||
type="button"
|
|
||||||
data-testid="compaction-divider"
|
|
||||||
onClick={onOpen}
|
|
||||||
className="rounded px-1.5 text-xs text-muted-foreground/70 transition-colors hover:text-muted-foreground"
|
|
||||||
>
|
|
||||||
Context compacted
|
Context compacted
|
||||||
</button>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -34,80 +31,18 @@ export function CompactionMomentDivider({ data, onOpen }: { data: CompactionDivi
|
|||||||
/** The cut divider (owner round 14): the LONGER divider at the actual
|
/** The cut divider (owner round 14): the LONGER divider at the actual
|
||||||
* compaction cut — the seam where verbatim history ends and the kept
|
* compaction cut — the seam where verbatim history ends and the kept
|
||||||
* recent tail begins. Phase-divider's geometry (dotted rules + centered
|
* recent tail begins. Phase-divider's geometry (dotted rules + centered
|
||||||
* chip) so it reads as a transcript boundary, not a message. */
|
* chip) so it reads as a transcript boundary, not a message. Inert
|
||||||
export function CompactionCutDivider({ data, onOpen }: { data: CompactionDividerData; onOpen: () => void }) {
|
* 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` : "";
|
const kTok = data.details.tokensBefore > 0 ? ` · ${Math.round(data.details.tokensBefore / 1000)}k tok` : "";
|
||||||
return (
|
return (
|
||||||
<button
|
<div data-testid="compaction-cut-divider" className="my-4 flex w-full items-center gap-3">
|
||||||
type="button"
|
|
||||||
data-testid="compaction-cut-divider"
|
|
||||||
onClick={onOpen}
|
|
||||||
className="my-4 flex w-full items-center gap-3 text-left"
|
|
||||||
>
|
|
||||||
<span className="h-0 flex-1 border-t-2 border-dotted border-border/50" aria-hidden />
|
<span className="h-0 flex-1 border-t-2 border-dotted border-border/50" aria-hidden />
|
||||||
<span className="rounded-full border border-border/80 px-2.5 py-0.5 text-xs font-medium uppercase tracking-wide text-muted-foreground">
|
<span className="rounded-full border border-border/80 px-2.5 py-0.5 text-xs font-medium uppercase tracking-wide text-muted-foreground">
|
||||||
History compacted{kTok}
|
History compacted{kTok}
|
||||||
</span>
|
</span>
|
||||||
<span className="h-0 flex-1 border-t-2 border-dotted border-border/50" aria-hidden />
|
<span className="h-0 flex-1 border-t-2 border-dotted border-border/50" aria-hidden />
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 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 (
|
|
||||||
<div
|
|
||||||
data-testid="compaction-summary-dialog"
|
|
||||||
className="flex max-h-[75vh] w-full max-w-2xl flex-col rounded-2xl border border-border bg-card shadow-xl"
|
|
||||||
>
|
|
||||||
<div className="flex items-center justify-between gap-3 border-b border-border px-5 py-3">
|
|
||||||
<span className="font-title text-xl tracking-tight">Compacted context</span>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
data-testid="compaction-summary-close"
|
|
||||||
onClick={onClose}
|
|
||||||
className="rounded-md px-2 py-1 text-xs text-muted-foreground transition-colors hover:text-foreground"
|
|
||||||
>
|
|
||||||
Close
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div className="min-h-0 flex-1 overflow-y-auto px-5 py-4">
|
|
||||||
<pre className="whitespace-pre-wrap font-mono text-xs leading-relaxed text-foreground/90">
|
|
||||||
{summary.content}
|
|
||||||
</pre>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 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(
|
|
||||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-background/80 p-6">
|
|
||||||
<CompactionSummaryBody summary={summary} onClose={onClose} />
|
|
||||||
</div>,
|
|
||||||
document.body,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
/**
|
/**
|
||||||
* THE COMPACTION DIVIDERS — SSR render pins (owner round 14). Two ruled
|
* THE COMPACTION DIVIDERS — SSR render pins (owner round 14; inert since
|
||||||
* surfaces from one marker: the MOMENT divider (minimal centered small
|
* release polish 2026-10-06). Two ruled surfaces from one marker: the
|
||||||
* text, the retrying-message register — no rule lines) where the user
|
* MOMENT divider (minimal centered small text, the retrying-message
|
||||||
* is when compaction lands, and the CUT divider (the longer one —
|
* register — no rule lines) where the user is when compaction lands, and
|
||||||
* phase-divider geometry, dotted rules + a chip carrying the summarized
|
* the CUT divider (the longer one — phase-divider geometry, dotted rules
|
||||||
* token count) at the kept boundary. Both open the same summary dialog
|
* + a chip carrying the summarized token count) at the kept boundary.
|
||||||
* (the marker's full model-facing content, verbatim, scrollable).
|
|
||||||
*
|
*
|
||||||
* The dialog portals to document.body at z-50; SSR renders null when
|
* NOT VIEWABLE (owner, 2026-10-06): the summary dialog is gone — the
|
||||||
* closed — the pins hold the open state through a jsdom-free server
|
* compaction content is model-facing plumbing; anyone who wants to
|
||||||
* render (createPortal is a no-op passthrough on the server).
|
* 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";
|
import React from "react";
|
||||||
@@ -21,8 +22,6 @@ import { describe, it } from "node:test";
|
|||||||
import {
|
import {
|
||||||
CompactionCutDivider,
|
CompactionCutDivider,
|
||||||
CompactionMomentDivider,
|
CompactionMomentDivider,
|
||||||
CompactionSummaryBody,
|
|
||||||
CompactionSummaryDialog,
|
|
||||||
type CompactionDividerData,
|
type CompactionDividerData,
|
||||||
} from "../components/compaction-divider.tsx";
|
} 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", () => {
|
describe("CompactionMomentDivider", () => {
|
||||||
it("renders the minimal centered note — no rule lines, retrying-message register", () => {
|
it("renders the minimal centered note — no rule lines, retrying-message register", () => {
|
||||||
const html = renderToStaticMarkup(<CompactionMomentDivider data={data} onOpen={() => {}} />);
|
const html = renderToStaticMarkup(<CompactionMomentDivider data={data} />);
|
||||||
assert.ok(html.includes('data-testid="compaction-divider"'));
|
assert.ok(html.includes('data-testid="compaction-divider"'));
|
||||||
assert.ok(html.includes("Context compacted"));
|
assert.ok(html.includes("Context compacted"));
|
||||||
// NO horizontal rule (the owner ruling: minimal, unlike the cut)
|
// NO horizontal rule (the owner ruling: minimal, unlike the cut)
|
||||||
@@ -47,7 +48,7 @@ describe("CompactionMomentDivider", () => {
|
|||||||
|
|
||||||
describe("CompactionCutDivider", () => {
|
describe("CompactionCutDivider", () => {
|
||||||
it("renders the longer seam — dotted rules + chip with the token count", () => {
|
it("renders the longer seam — dotted rules + chip with the token count", () => {
|
||||||
const html = renderToStaticMarkup(<CompactionCutDivider data={data} onOpen={() => {}} />);
|
const html = renderToStaticMarkup(<CompactionCutDivider data={data} />);
|
||||||
assert.ok(html.includes('data-testid="compaction-cut-divider"'));
|
assert.ok(html.includes('data-testid="compaction-cut-divider"'));
|
||||||
assert.ok(html.includes("border-t-2 border-dotted"));
|
assert.ok(html.includes("border-t-2 border-dotted"));
|
||||||
assert.ok(html.includes("History compacted"));
|
assert.ok(html.includes("History compacted"));
|
||||||
@@ -56,17 +57,24 @@ describe("CompactionCutDivider", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("CompactionSummaryDialog", () => {
|
describe("not viewable (owner, release polish 2026-10-06)", () => {
|
||||||
it("renders the marker content verbatim, scrollable (the pure body — portals need document)", () => {
|
it("neither divider is a button — plain markup, no click affordance", () => {
|
||||||
const html = renderToStaticMarkup(
|
for (const html of [
|
||||||
<CompactionSummaryBody summary={data} onClose={() => {}} />,
|
renderToStaticMarkup(<CompactionMomentDivider data={data} />),
|
||||||
);
|
renderToStaticMarkup(<CompactionCutDivider data={data} />),
|
||||||
assert.ok(html.includes('data-testid="compaction-summary-dialog"'));
|
]) {
|
||||||
assert.ok(html.includes("[SESSION HISTORY COMPACTED]"));
|
assert.ok(!html.includes("<button"), INERT[0]);
|
||||||
assert.ok(html.includes("overflow-y-auto"));
|
assert.ok(!html.includes("cursor-pointer"), INERT[0]);
|
||||||
assert.ok(html.includes('data-testid="compaction-summary-close"'));
|
}
|
||||||
});
|
});
|
||||||
it("renders nothing when closed", () => {
|
|
||||||
assert.equal(renderToStaticMarkup(<CompactionSummaryDialog summary={null} onClose={() => {}} />), "");
|
it("the marker's summary text never reaches the DOM — provider logs are the inspection path", () => {
|
||||||
|
for (const html of [
|
||||||
|
renderToStaticMarkup(<CompactionMomentDivider data={data} />),
|
||||||
|
renderToStaticMarkup(<CompactionCutDivider data={data} />),
|
||||||
|
]) {
|
||||||
|
assert.ok(!html.includes("[SESSION HISTORY COMPACTED]"), "the header stays out");
|
||||||
|
assert.ok(!html.includes("THE LEARNER"), "the summary stays out");
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user