conduit · turn activity
A compaction is currently a system notice in the main thread. It should be a seam in the turn's ledger: visible without being loud, and impossible to miss once the log is open. Four options below; I recommend the first.
segmentTurns routes every system message into turn.notices,
and MessageList renders notices in the main thread between the activity and the reply.
The comment on that field states the reason: "Kept out of the activity log so a failure is
never hidden behind a collapsed panel."
That rule is right for an error and wrong for a finished compaction. The result is a full-width info panel wedged between the work and the reply, at the same visual weight as a crash:
Tests are green. The translator now folds the snapshot comparison into one pass.
The real cost isn't ugliness. The notice sits at error weight, so the eye reads it as "something went wrong" every single time. Meanwhile the fact that actually matters later, that the model's memory was cut at this point in the turn, is invisible once you scroll past it. It is loud in the moment and gone when you need it.
The wire carries three compaction states from claude-event-translator.ts.
They are not the same kind of event and should not share a renderer.
| State | Detail on the wire | Home | Why |
|---|---|---|---|
| started | Compacting conversation… |
Main thread | Live status. You are waiting on it, so it belongs where you are already looking. Unchanged. |
| completed | Context compacted (auto) · 142k → 38k |
Turn activity | History, not news. It is one more thing that happened during the turn, and its position in the sequence is the whole point. |
| failed | Compaction failed: … |
Main thread | Stays a notice. The existing rule holds: a failure is never hidden behind a collapsed panel. |
A completed compaction becomes a first-class activity part, and shows up in all three places the ledger already talks: the strip, the expanded log, and the summary line. Each one says a different amount, so the cost scales with how much you are looking.
A badge on the right of the summary row, the same slot and weight as the existing
N failed badge. Pink, because that is conduit's "this changed the state of
things" colour, and because the compaction is the one event in the ledger that explains a later
answer being worse.
The seam is fixed-width on purpose. Every other strip segment is weighted by duration, because width means "how long". A compaction takes 10 to 30 seconds of wall clock but it is not work, and weighting it would draw a fat bar that reads as progress. Three pixels of hatched pink reads as what it is: the timeline was cut here.
In the log it is a rule across the full width, not an indented tool row. Tool rows are things the model did; this is a boundary between two stretches of them. The shape should say so before you read the words.
What the rule buys you. Scrolling the log, the rows above the seam and the rows below it are different epochs. When the model re-reads a file it had already read, the seam three rows up is the answer. No other placement makes that legible.
Long turns get more than one. The pattern holds without a new idea, and the badge collapses to a count.
The seam appears in the expanded log and nowhere else. No strip segment, no badge. Simplest possible change and it satisfies the literal ask.
Why I would not ship this alone: a compaction you have to open a panel to discover is a compaction you find only when you already suspect it. The badge is the cheap part of option A and it is the part that earns its keep.
The inverse: a badge on the summary line, and the compaction never becomes an activity part at
all. Cheapest of the four, no changes to segmentTurns's part model.
Loses the one thing worth having: where in the turn it happened. A turn that compacted before any work and a turn that compacted after forty commands look identical here.
The purist reading: a compaction is a context boundary, so treat it exactly like a hand-back. Close the segment, render a divider in the thread, open a new ledger.
Why I reject it. A hand-back splits the ledger because control genuinely returned to you: there is a card to read and a decision to make between the halves. A compaction hands control to nobody. Splitting here fragments one continuous stretch of work into two panels you now have to expand separately, and it doubles the panel count on exactly the longest turns, the ones already hardest to scan. It also puts a divider back in the main thread, which is the thing we set out to remove.
Option A. Four changes, none of them structural.
types.ts — a CompactionMessage chat message
carrying preTokens, postTokens, trigger and
createdAt, rather than flattening the numbers into prose the UI then has to
re-parse. The wire already has them; only the frontend type throws them away.turns.ts — ActivityPart gains the new type, and
segmentTurns routes it into segment.activity when the compaction
completed and into turn.notices when it failed. That is the one-line change that
replaces "all system messages are notices" with a rule that distinguishes an event from a
failure. TurnStats gains a compactions count for the badge.stepWeights — a compaction part contributes a fixed weight so
the seam stays 3px. Everything else in the strip keeps its duration weighting.ActivityRow / TurnActivity — the seam renderer
and the badge. Both are presentational and neither touches the existing tool paths.One thing to confirm before building. The compaction's position in
segment.activity depends on its createdAt landing between the
surrounding parts. The live path appends in arrival order, which is right. The
message_parts table has a compaction type in its CHECK constraint but
zero rows in this project's store, so the persisted path is unexercised. That needs a real
compaction captured and reloaded before we call it done, otherwise history renders the seam in
the wrong place and nothing will have caught it.