diff --git a/frontend/packages/agent/src/app.js b/frontend/packages/agent/src/app.js index 5769cccc..190e3cc1 100644 --- a/frontend/packages/agent/src/app.js +++ b/frontend/packages/agent/src/app.js @@ -1886,12 +1886,11 @@ window.marked = marked; return false; } // Coalescing target for the live `thinking_tokens` counter. - // Holds the row + its text node so consecutive ticks update in place - // instead of appending a note each. Reset implicitly: we only reuse - // it while it's still the last row rendered (nextElementSibling === - // null), so any other event starts fresh. + // Holds the row currently showing the running estimate so consecutive + // ticks update in place instead of appending a note each. Reset + // implicitly: we only reuse it while it's still the last row rendered + // (see the nextElementSibling check), so any other event starts fresh. let thinkingTokensRow = null; - let thinkingTokensText = null; function renderStream(v, api) { // Drop claude's result line and rate-limit — noise. TurnEnd // communicates pass/fail; rate-limit events are noisy status chatter. @@ -1927,13 +1926,13 @@ window.marked = marked; // tick starts a fresh row. if (v.subtype === 'thinking_tokens') { const n = v.estimated_tokens; - const text = 'thinking … ' + const label = '🧠 thinking … ' + (n != null ? '~' + Number(n).toLocaleString() + ' tokens' : ''); if (thinkingTokensRow && thinkingTokensRow.isConnected && thinkingTokensRow.nextElementSibling === null) { - thinkingTokensText.nodeValue = text; + thinkingTokensRow.textContent = label; } else { - [thinkingTokensRow, thinkingTokensText] = api.mutableRow('note', text, '🧠'); + thinkingTokensRow = api.row('note', label); } return; } diff --git a/frontend/packages/shared/src/terminal.css b/frontend/packages/shared/src/terminal.css index 2abc36f8..01d4a906 100644 --- a/frontend/packages/shared/src/terminal.css +++ b/frontend/packages/shared/src/terminal.css @@ -216,44 +216,11 @@ details.row { white-space: normal; } -/* Two-column layout for expandable summary rows. - Left col: fixed-width icon cell. Right col: disclosure chevron + - text, wrapping within itself so no continuation line bleeds under - the icon. Overrides the flat-row hanging-indent metrics (.live .row - sets padding-left: 1.9em; text-indent: -1.4em) with explicit flex - geometry. The negative margin-left on summary cancels the details - container's inherited padding-left so the icon lands at the same - horizontal position as flat-row icons. */ details.row > summary { cursor: pointer; list-style: none; white-space: pre-wrap; word-break: break-word; - display: flex; - align-items: baseline; - margin-left: -1.9em; - padding-left: 0.5em; - text-indent: 0; -} -/* Icon column: 2em accommodates wide emoji without ink bleeding into - the chevron column. Overrides the inline-block + width: 1.4em set - on .live .row-glyph for the shared flat-row context. */ -details.row > summary > .row-glyph { - flex: 0 0 2em; - width: auto; - text-align: center; -} -/* Content column: chevron (::before) + text, wraps within the cell. */ -details.row > summary > .summary-text { - flex: 1; - min-width: 0; -} -/* Icon-less summaries: the CSS-generated ▸/▾ acts as the hanging - marker. Hanging indent keeps wrapped lines under the text body, - not under the chevron. Mirrors the flat-row text-indent metric. */ -details.row > summary > .summary-text:only-child { - padding-left: 1.4em; - text-indent: -1.4em; } details.row > summary > .summary-text::before { content: '▸ '; diff --git a/frontend/packages/shared/src/terminal.js b/frontend/packages/shared/src/terminal.js index e00a55cd..ddc63c60 100644 --- a/frontend/packages/shared/src/terminal.js +++ b/frontend/packages/shared/src/terminal.js @@ -215,22 +215,6 @@ export function create(opts) { afterAppend(wasNearBottom); return e; } - // Like row(), but returns [element, textNode] so the caller can update - // the text in place via textNode.nodeValue. Use for rows whose content - // changes after initial render (e.g. live-updating counters). - // Text is stored as a plain text node — no linkify, no innerHTML. - function mutableRow(cls, text, icon) { - clearPlaceholder(); - const wasNearBottom = isNearBottom(); - const e = document.createElement('div'); - e.className = 'row ' + (cls || '') + (currentNoAnim ? ' no-anim' : ''); - if (icon != null && icon !== '') e.appendChild(glyphSpan(icon)); - const tn = document.createTextNode(text == null ? '' : String(text)); - e.appendChild(tn); - log.appendChild(e); - afterAppend(wasNearBottom); - return [e, tn]; - } function details(cls, summary, body, icon) { clearPlaceholder(); const wasNearBottom = isNearBottom(); @@ -269,7 +253,7 @@ export function create(opts) { function api(extra) { return Object.assign({ - row, mutableRow, details, detailsDiff, placeholder, linkify, + row, details, detailsDiff, placeholder, linkify, fromHistory: false, }, extra || {}); } @@ -502,7 +486,7 @@ export function create(opts) { } const ready = start(); - return { row, mutableRow, details, detailsDiff, placeholder, ready }; + return { row, details, detailsDiff, placeholder, ready }; } // Build a DocumentFragment from `text`, turning bare http(s) URLs into