Fix errored stream bleeding into the next reply's leading chars

ober

d8719d10dd2ea11768cd8996116abc40eec47187

diff --git a/src/jcode/ui/tui.ss b/src/jcode/ui/tui.ss
index ce07515..1d970a8 100644
--- a/src/jcode/ui/tui.ss
+++ b/src/jcode/ui/tui.ss
@@ -1521,21 +1521,29 @@
 
 (def (tui-stream-token! state token)
   "Handle a streaming token from the LLM — called from agent thread."
-  (let ((buf (app-state-stream-buf state)))
-    (tui-log "tui-stream-token: buf-len=~a token-len=~a" (string-length buf) (string-length token))
-    ;; buf="" marks the start of an assistant turn (initial round, or a round
-    ;; resumed after tool calls). Reuse the trailing assistant block only
-    ;; when it is still the empty placeholder; otherwise append a fresh one
-    ;; so we don't overwrite the previous round's reply.
-    (when (and (string-empty? buf)
-               (let ((msgs (app-state-messages state)))
-                 (or (null? msgs)
-                     (let ((last (car (reverse msgs))))
-                       (or (not (streamed-reply-role? (msg-block-role last)))
-                           (not (string-empty? (msg-block-content last))))))))
-      (tui-log "tui-stream-token: adding new assistant block")
+  ;; Reuse the trailing block ONLY when it is the live streaming target: a
+  ;; streamed-reply block (assistant/expert) whose content still equals
+  ;; stream-buf. Otherwise — after an error/system/tool block, or once the
+  ;; buffer was reset between rounds — start a FRESH assistant block and
+  ;; clear the buffer. The old logic only opened a new block when stream-buf
+  ;; was empty, so a reply that arrived after an aborted stream appended onto
+  ;; whatever stale content was left behind. That is the "error bleeds into
+  ;; the next output" bug: a partial byte from the aborted stream prefixed
+  ;; the next reply and corrupted its leading chars (e.g. "Based on" shown as
+  ;; "<U+FFFD>ased on"). Keying reuse on content==stream-buf makes streaming
+  ;; robust to any preceding block.
+  (let* ((msgs  (app-state-messages state))
+         (last  (and (pair? msgs) (car (reverse msgs))))
+         (reuse? (and last
+                      (streamed-reply-role? (msg-block-role last))
+                      (equal? (msg-block-content last)
+                              (app-state-stream-buf state)))))
+    (tui-log "tui-stream-token: reuse?=~a token-len=~a" reuse? (string-length token))
+    (unless reuse?
+      (tui-log "tui-stream-token: opening fresh assistant block")
+      (app-state-stream-buf-set! state "")
       (add-message! state (msg-block-assistant "")))
-    (let ((new-buf (string-append buf token)))
+    (let ((new-buf (string-append (app-state-stream-buf state) token)))
       (app-state-stream-buf-set! state new-buf)
       (update-last-assistant! state new-buf)
       (app-state-scroll-offset-set! state 0)