Skip to content

feat(feed): render Compacting placeholder for /compact turns - #74

Merged
Juliusolsson05 merged 1 commit into
mainfrom
fix/compaction-synthesis
May 11, 2026
Merged

Juliusolsson05 merged 1 commit into
mainfrom
fix/compaction-synthesis

Conversation

@Juliusolsson05

Copy link
Copy Markdown
Owner

Summary

  • When Claude Code's /compact runs (manual or auto), the synthesis turn streams <analysis>...</analysis><summary>...</summary> XML to the model. Native TUI hides it; cc-shell rendered it raw in the feed for the entire ~10–90s the model spends summarising.
  • This PR detects the turn at the proxy layer (sniffing the fixed compact prompt on the last user message — feat(proxy): tag compaction-synthesis turns from request body sniff claude-code-headless#8) and renders a "Compacting conversation…" placeholder in the streaming feed instead.
  • The user-facing artefacts (compact_boundary + isCompactSummary: true user entry) already had working renderers (CompactBoundaryRow, CompactSummaryRow). This PR only covers the streaming window where the XML leaked.

Architecture

mitm addon (sniff body)
  └── ProxyTransportEvent.request_shape.compaction_synthesis
       └── ClaudeProxyAdapter.ParsedRequestShape.isCompactionSynthesis
            └── channel.startTurn({ isCompactionSynthesis: true })
                 └── SemanticTurnStartedEvent.isCompactionSynthesis
                      └── foldEvent → SemanticLiveTurn.isCompactionSynthesis
                           └── StreamingTurn.tsx → "Compacting…" placeholder

All hops carry the flag as an optional, truthy-only field — non-Claude sources and older proxy adapters keep their event payloads byte-identical, and the renderer fails closed (legacy XML-leak behaviour) on a missing flag rather than over-hiding turns.

Files touched

Layer File Purpose
Submodule mitmAddon.py, ClaudeProxyAdapter.ts, SemanticChannel.ts, channels/types.ts (in claude-code-headless#8)
Renderer src/renderer/src/workspace/workspaceState.ts Add isCompactionSynthesis?: boolean to SemanticLiveTurn
Renderer src/renderer/src/workspace/semantic/foldEvent.ts Set flag on turn at turn_started
Renderer src/renderer/src/features/feed/ui/semantic/StreamingTurn.tsx Render placeholder when flag set

Why detect at the proxy and not in the renderer

A renderer-side heuristic (e.g. "if textSoFar starts with <analysis>") would render a flash of raw XML before the heuristic engages. The request body is the earliest layer where the signal is unambiguous — Claude Code's compaction prompt is a fixed preamble in vendor/claude-code-src/full/services/compact/prompt.ts (BASE_COMPACT_PROMPT, PARTIAL_COMPACT_PROMPT, PARTIAL_COMPACT_UP_TO_PROMPT all open with "Your task is to create a detailed summary"). The mitm addon already parses these bodies for sidecar detection, so the cost is one new regex + one new bool field.

Test plan

  • Submodule build (tsc) passes ✅ (verified locally).
  • cc-shell build (npm run build) passes ✅ (verified locally).
  • Manual: trigger /compact (or wait for auto-compaction in a 600k+ context session). Expect "Compacting conversation…" placeholder in feed during the synthesis, then the compact_boundary divider + Conversation Summary card from CompactSummaryRow once JSONL catches up.
  • Manual: normal turn renders text exactly as before (placeholder MUST NOT appear).
  • Verify a per-paste debug bundle captured during compaction shows turn_started events with isCompactionSynthesis: true (and only on compaction turns).

Risk

  • False positive (placeholder hides a real turn): only fires when the LAST user message begins with the literal phrase "Your task is to create a detailed summary". User typing that phrase would trigger it, but the response is also XML-shaped so the placeholder would be the correct UI anyway. Practical risk: negligible.
  • False negative (XML leaks as before): possible if Claude Code changes the compact prompt preamble upstream. Detection lives in two places (mitm addon for new bodies, ClaudeProxyAdapter.parseRequestBody for legacy body_b64) — both anchor on the same phrase, so they'd fail together and fall back to the pre-fix behaviour. Recoverable, not breaking.

Dependencies

🤖 Generated with Claude Code

Previously, when Claude Code's `/compact` ran (manual or auto), the
streaming assistant turn that synthesises the summary leaked its raw
output into the feed for as long as it took to run — typically tens
of seconds, sometimes >90s on large contexts. The output is
`<analysis>...</analysis><summary>...</summary>` XML that's meant to
be consumed by Claude Code itself, not rendered to the user. Native
Claude Code TUI hides this entirely; cc-shell rendered it raw.

The cleanest place to detect this turn is the proxy layer where the
request body is already in memory — that's claude-code-headless#8
(submodule bump in this PR). The renderer just consumes the new
`isCompactionSynthesis` bit on `turn_started`:

* `SemanticLiveTurn.isCompactionSynthesis`: new optional flag,
  populated once at `turn_started` and preserved across same-turn
  refresh events. Defaults missing/false everywhere — codex, screen
  fallback, and pre-2026-05-11 proxy adapters never set it.
* `StreamingTurn.tsx`: early-return a "Compacting conversation…"
  MarkerRow placeholder when the flag is set, mirroring the existing
  `CompactBoundaryRow` styling so live and committed states share
  visual language. Placed AFTER existing hooks to keep Rules of
  Hooks happy.

The post-compaction user-facing artefacts (`compact_boundary` system
entry, `isCompactSummary: true` user entry) land via JSONL after the
synthesis turn ends and already have working renderers
(`CompactBoundaryRow`, `CompactSummaryRow`). The placeholder only
covers the streaming window where the bug was visible.

Bumps claude-code-headless to b237c88 (PR #8) for the proxy-side
flag plumbing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@Juliusolsson05
Juliusolsson05 merged commit d91f198 into main May 11, 2026
Juliusolsson05 added a commit that referenced this pull request May 11, 2026
chore: address review follow-ups from #73 / #74 / #8
@Juliusolsson05
Juliusolsson05 deleted the fix/compaction-synthesis branch July 7, 2026 21:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant