diff --git a/frontend/packages/agent/src/app.js b/frontend/packages/agent/src/app.js index 09fa5d70..7075344d 100644 --- a/frontend/packages/agent/src/app.js +++ b/frontend/packages/agent/src/app.js @@ -1886,11 +1886,12 @@ window.marked = marked; return false; } // Coalescing target for the live `thinking_tokens` counter. - // 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. + // 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. 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. @@ -1930,15 +1931,11 @@ window.marked = marked; + (n != null ? '~' + Number(n).toLocaleString() + ' tokens' : ''); if (thinkingTokensRow && thinkingTokensRow.isConnected && thinkingTokensRow.nextElementSibling === null) { - // Update only the text portion after the icon glyph - // (textContent would clobber the .row-glyph span). - const glyph = thinkingTokensRow.querySelector('.row-glyph'); - [...thinkingTokensRow.childNodes].forEach((node) => { - if (node !== glyph) thinkingTokensRow.removeChild(node); - }); - thinkingTokensRow.appendChild(document.createTextNode(text)); + thinkingTokensText.nodeValue = text; } else { - thinkingTokensRow = api.row('note', text, '🧠'); + thinkingTokensRow = api.row('note', '', '🧠'); + thinkingTokensText = document.createTextNode(text); + thinkingTokensRow.appendChild(thinkingTokensText); } return; }