• v0.8.1 11ef6830ab

    fix(tui,sse): inline Text streaming + empty-id keepalive skip (v0.8.1)

    vh released this 2026-05-24 21:39:02 -07:00 | 248 commits to main since this release

    Two related fixes for the same user-reported bug pattern from a
    running session against ratatoskr:sindra (qwen3.6-35-a3b-heretic):

    1. Streaming text overlapping the transcript

    Operator: "new text comes at the bottom and overwrites the existing
    pane information instead of pushing it up naturally."

    Root cause: the v0.6.0 #current-text Static was dock: bottom
    with height: auto, sitting between the transcript RichLog (1fr)
    and the prompt Input (dock: bottom). As text streamed, the Static
    grew UPWARD but Textual didn't dynamically resize the 1fr transcript
    to accommodate — the growing Static visually OVERLAPPED the
    transcript's bottom rows. On Done, current_text.update("") snapped
    it to height 0 and the transcript re-laid-out — "boom, everything
    updates."

    Fix: remove #current-text Static entirely. Apply the same
    coalesce-on-newline pattern v0.7.1 used for thinking — Text deltas
    accumulate in TuiPresenterState.text_chunk_buffer, flushing whole
    lines (each \n boundary) directly to log (transcript). On Done:
    flush remaining tail, then [done] label + Rule + Markdown body.

    Trade-off accepted: streamed lines + post-Done Markdown body are
    both in the transcript (some content duplication). The Markdown
    body re-renders the same content with proper formatting (lists,
    bold, code blocks). Acceptable — operator gets both the live-progress
    streaming AND the canonical rendered version.

    2. MalformedSseId raw='' crashing every turn

    Operator: "current session is erroring on every turn with
    [malformed_sse_id] raw=''"

    Worldtree's qwen3.6-35-a3b-heretic provider emits some events
    without id: lines (observed 2026-05-25 mid-stream). When the FIRST
    such event arrives before any prior id has been seen, httpx_sse's
    ServerSentEvent.id is "". _parse_sse_id('') raised ValueError
    → MalformedSseId → turn worker bailed → operator saw the label
    every turn.

    Per SSE RFC, events without id: are legitimate (they just don't
    update Last-Event-ID). Issue #7 already covered the empty-DATA
    keepalive case with skip-silently semantics. Empty-id is the same
    shape of wire weirdness; same fix shape:

    if sse.id == "":
    continue # treat as keepalive

    Ordered AFTER the empty-data branch so an empty-data + empty-id
    event still gets skipped on the data check.

    Tests + smoke

    287/287 GREEN (was 286, +1 for empty-id skip; +1 net Text-flow test
    adjustments). Ruff clean.

    Verified Worldtree alive when the user hit the empty-id bug
    (/healthz returned ok in 18ms) — not a server-down issue, just
    wire-format mid-stream.

    Caveats

    The fix doesn't recover content from the dropped empty-id event.
    If the event happened to carry meaningful data (not a true
    keepalive), we silently lose it. Acceptable trade-off: pre-v0.8.1
    EVERY turn died on the offending agent; post-v0.8.1 the turn
    continues and any single dropped frame is recoverable from logs if
    debugging. Worldtree-side fix (always emit ids) is the right
    upstream answer; ratatoskr just stops panicking on wire weirdness.

    Patch bump (v0.8.0 → v0.8.1) — both fixes are bug fixes; no public
    API change. The TuiPresenterState.render signature loses the
    current_text parameter (was added v0.6.0), but presenter is an
    internal contract; no external callers.

    Downloads