Context Compaction Viz
An interactive visualisation of how production LLM systems manage conversation memory. As turns accumulate, the token count climbs against a 16K context budget; when the user fires Compact (or the auto-threshold trips at 70%), the older messages collapse into a six-bullet summary block and the bar drops back into the safe zone. A What was lost? toggle reveals the original messages side-by-side with the summary so the trade-off is concrete.
The insight (JetBrains, NeurIPS 2025): observation masking — dropping tool outputs while keeping reasoning — performs as well as LLM-based summarisation, and proactive compaction at 50–60% beats waiting for auto-compaction at 70%.
Each API call packs the full conversation into the context window. Click "Add Turn" to watch the token count grow.
Installation
npx shadcn@latest add https://craftbits.dev/r/context-compaction-viz.jsonUsage
import { ContextCompactionViz } from "@craft-bits/viz/context-compaction-viz";
<ContextCompactionViz />Bring your own transcript:
<ContextCompactionViz
messages={[
{ id: 1, role: "user", text: "Why is my deploy failing?", tokens: 40 },
{ id: 2, role: "assistant", text: "Let me check the logs.", tokens: 220 },
{ id: 3, role: "tool", text: '{"build_id": "b-9214", "error": "OOM"}', tokens: 980 },
/* … */
]}
summaryFacts={["Build b-9214 OOM at link step", "Memory peak: 8.2GB / 8GB limit"]}
/>Subscribe to the compaction event:
<ContextCompactionViz
onCompact={({ tokensBefore, summaryTokens, tokensSaved }) => {
/* lift the new totals into an external chart */
}}
/>Understanding the component
- The timeline. A scrollable column of role-coloured message bubbles (
user,assistant,tool). Each bubble shows the role, the token cost, and a 40-character preview of the body. The full text stays in the data — visual truncation is purely for layout. - The budget bar. A vertical fill on the right tracks
totalTokens / tokenBudget. The fill colour shifts on three thresholds — green under 50%, warning betweencompactThresholdandautoCompactThreshold, error above the auto threshold. Two dashed lines mark the proactive (compactThreshold) and reactive (autoCompactThreshold) trigger points. - Compaction. When the user clicks
Compact(or the auto-threshold is crossed without intervention), the firstcompactSplitmessages animate out and a summary block animates in. The recent messages remain untouched. The bar drops and the savings indicator surfaces underneath. - What was lost. The toggle drops a side-by-side panel: every original message in the left column, the summary plus a
Droppedcallout in the right. - Reduced motion. Under
prefers-reduced-motion: reduce, every entrance, the compaction sequence, the bar-fill spring, and the savings indicator collapse to instant transitions.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
messages | readonly ContextCompactionVizMessage[] | 20-turn customer transcript | Pre-loaded conversation. Each entry needs id, role, text, tokens. |
summaryFacts | readonly string[] | 6 facts | Bullet facts rendered inside the post-compaction summary block. |
tokenBudget | number | 16000 | Upper bound for the token-budget bar. |
compactThreshold | number | 0.6 | Fraction of tokenBudget at which the Compact button appears. |
autoCompactThreshold | number | 0.7 | Fraction of tokenBudget at which compaction auto-fires. |
compactSplit | number | 12 | Number of leading messages folded into the summary block. |
summaryTokens | number | 800 | Token cost of the summary block. |
initialVisible | number | 2 | Messages visible on first render. |
autoPlayIntervalMs | number | 800 | Milliseconds between auto-play turns. |
transition | Transition | SPRINGS.smooth | Override bubble / bar / savings spring. |
onCompact | (summary) => void | — | Fires when compaction completes with { messagesCompacted, tokensBefore, summaryTokens, tokensSaved }. |
onVisibleCountChange | (visibleCount: number) => void | — | Fires every time the visible-message count changes. |
className | string | — | Merged onto the root via cn(). |
Accessibility
- The visualization root is
role="figure"with anaria-labelsummarising current token usage and turn count so screen-reader users get the headline without needing the visual. - The budget bar is
role="progressbar"witharia-valuemin/aria-valuemax/aria-valuenowreflecting live token usage, and anaria-labeldescribing the bar's purpose. - A polite live region announces the running token / turn state on every change, and again after compaction with the new totals.
- All four control buttons have visible focus rings, hit-target ≥ 32×32px, and pressed states (
aria-pressed) on the toggles. - Colour is never the only signal — every role pill carries its label (
User/Assistant/Tool Result), every threshold line has a percentage label, and the savings indicator includes the wordsaved. - Motion respects
prefers-reduced-motion: reduce— every entrance and the compaction sequence collapse to instant transitions, and the bar fill snaps withduration: 0.
Credits
- Extracted from:
craftingattention(app/src/lessons/primitives/systems/ContextCompactionViz.tsx). The source was a lesson component using per-track palette tokens (--color-accent-400,--color-warn-400,--color-success-400,--color-fail-400,--color-ink-*, hard-codedoklch()role colours) and CA-specific spring aliases (SPRINGS.snappy,SPRINGS.gentle,STAGGER.tight). The craft-bits extract re-keys every colour to the semantic--cb-*palette (var(--cb-accent)/var(--cb-warning)/var(--cb-success)/var(--cb-error)/var(--cb-fg-*)) so consumer themes repaint freely, routes every transition through the canonicalSPRINGS.snap/SPRINGS.smoothfrom@craft-bits/core/motion, and replacesSTAGGER.tightwith the library-wideSTAGGERconstant. The hard-coded transcript and summary facts are now props (messages,summaryFacts) with the original 20-turn customer-success story as the default, so the component teaches the same scene out of the box but accepts any conversation. The reduced-motion fallback was tightened to disable the bar's fill spring as well as the bubble entrances, thesetTimeout-in-render auto-compaction was moved to an effect, andforwardRef+cn()+ spread...propswere added to match the library's interactive-component API.