Two O(n^2) costs on the per-token/per-frame streaming path, both over the
growing answer:
WS2.2 — snapshotLiveTurn() serialized the whole live turn via turn.outerHTML on
every render frame. That snapshot only backs mid-stream session-switch restore,
and the switch path (sessions.js) plus tool/done event boundaries already
capture synchronously, so the per-frame call is throttled to a coarse trailing
snapshot instead of serializing the entire growing DOM each frame.
WS2.3 — the token handler ran _parseStreamState() (strip tool-call XML + extract
inline thinking over the FULL accumulated text) on every token, but _doRender
re-parses once per throttled frame anyway and the only per-token use is the
assistant-row creation gate. Skip the parse once the row exists (the common
case); keep it only until the row is created. Also drop the full-string
toLowerCase() allocation in _stripXmlToolCalls for a non-allocating regex test.
Output-neutral: same rendered DOM; the row-creation gate and snapshot semantics
are preserved (row appears within one 66ms frame; switch/settle snapshots stay
synchronous). node --check + ESLint runtime guard clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>