show compaction as it happens and open tool calls while they run

Compaction was the one thing the harness does that emitted nothing at all while it
ran, and it can run for minutes — silence that reads as a hung turn, which costs a
server restart to discover it wasn't. The sidecar now reports both ends: the start
from the PreCompact hook, the finish from the compact_boundary message with the
token count, both durable so a reload or a reconnect still sees them.

Tool rows open themselves while they run and hold for five seconds after their
result, so the inputs are on screen at the moment the call is made rather than
after the fact. The clock lives outside React, keyed by tool call id: rows are
virtualised, so unmounting is not the call ending, and a fast call can render its
start and its result together — a row that only opens when it catches the pending
state never opens for exactly the quickest calls. A click outranks the clock for
as long as the row lives. A failure behaves identically and differs only in colour,
so it stays findable by scanning and nameable in conversation.

Shell logs move off the green-on-black pre onto the shared code surface, which is
the one block that had no copy button and the one you most often want to hand to
someone else.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-07 05:10:22 +00:00
co-authored by Claude Opus 5
parent ce7968ac90
commit 969b2f3762
15 changed files with 694 additions and 147 deletions
+62 -12
View File
@@ -130,18 +130,6 @@
font-family: ui-monospace, monospace;
}
/* A fenced block is wrapped so its copy button has something to position against. The wrapper carries
the spacing, or the pre's own margin would collapse through it and defeat the first/last-child reset. */
.chat-md .chat-code {
margin: 0.75em 0;
}
.chat-md .chat-code pre {
margin: 0;
/* Room for the copy button, so a long first line scrolls up to it rather than under it. */
padding-right: 2.75em;
}
.chat-md pre {
margin: 0.75em 0;
padding: 0.75em 1em;
@@ -167,6 +155,68 @@
margin: 1.5em 0;
}
/* ── The shared code surface (`CodeSurface`) ──
Deliberately NOT scoped under `.chat-md`: a tool call renders code too, and it lives outside the prose
container. These rules come after the `.chat-md pre` block above so they win the specificity tie when a
surface does sit inside a chat reply. */
/* Vertical rhythm belongs to the prose that surrounds a block, not to the block. Inside a tool panel the
surface is already boxed and spaced by its container, and a leaked 0.75em reads as a gap nobody chose. */
.chat-md .code-surface {
margin: 0.75em 0;
}
.code-surface pre {
margin: 0;
padding: 0.75em 1em;
/* Room for the two copy buttons, so a long first line scrolls up to them rather than under them. */
padding-right: 5.5em;
border-radius: 0.5rem;
background: #0d1117;
/* Scroll, never wrap: wrapping folds `ls -la`, `git log --oneline` and any other columnar output into
mush, and it breaks the one-line-per-number contract the gutter makes. */
overflow-x: auto;
white-space: pre;
font-size: 0.8125rem;
line-height: 1.6;
}
/* A block that named a language also shows the name, which needs its own room. */
.code-surface[data-lang] pre {
padding-right: 10em;
}
.code-surface pre code {
padding: 0;
border-radius: 0;
background: none;
color: #e6edf3;
font-size: inherit;
font-family: ui-monospace, monospace;
}
/* Line numbers come from a counter rather than from markup, because generated content is not part of
`textContent` — so the copy buttons, and a hand-made selection, still yield the code and nothing else.
Both branches of the block emit `.line` spans, so the numbers survive the swap to highlighted output.
`--line-offset` seeds the counter: a `Read` of lines 400-460 has to say 400, not 1.
They scroll with a long line rather than sticking to the left edge; a sticky gutter would need an
opaque background, and that means hardcoding a colour the theme is entitled to change. */
.code-surface[data-numbered] code {
counter-reset: line var(--line-offset, 0);
}
.code-surface[data-numbered] .line::before {
counter-increment: line;
content: counter(line);
display: inline-block;
width: calc(var(--line-digits, 2) * 1ch);
margin-right: 1.25em;
text-align: right;
color: #6e7681;
user-select: none;
-webkit-user-select: none;
}
.chat-md table {
width: 100%;
border-collapse: collapse;