Skip to content

fix(llm): chat panel UX — divider repaint on cycle + drop 3-row truncation#176

Merged
fentas merged 3 commits into
masterfrom
fix/chat-divider-repaint-on-cycle
May 21, 2026
Merged

fix(llm): chat panel UX — divider repaint on cycle + drop 3-row truncation#176
fentas merged 3 commits into
masterfrom
fix/chat-divider-repaint-on-cycle

Conversation

@fentas
Copy link
Copy Markdown
Owner

@fentas fentas commented May 21, 2026

Bug

User reported: cycling providers with Alt+M updates the statusbar hint ("provider: lo-qwen (2/3)") but the chat panel header still shows the OLD provider name (e.g. ✨ atty chat · lo-qwen ──────).

Root cause

hooks.zig cycle handler bumps rt.current_provider_idx and calls latchHint, but never arms chat_inline_paint_pending / chat_overlay_paint_pending. The divider's label is computed by resolveProviderForMode(.chat, …) on every paint, so the stale name persists until some unrelated event triggers a repaint.

Fix

Arm whichever chat surface's paint latch is active after a successful cycle. One-line per surface.

Test plan

  • New regression test hooks_tests.zigAlt+M cycle arms chat panel repaint so divider shows the new provider. Verified fails without fix (794 pass, 1 fail), passes with fix (795/795).
  • zig build test — 795/795 unit tests green
  • zig fmt --check src/ — clean

🤖 Generated with Claude Code

fentas and others added 2 commits May 21, 2026 10:45
The chat panel divider renders the active provider name via
`resolveProviderForMode(.chat, …)` on every paint, but the cycle
action only nudged `current_provider_idx` and emitted a statusbar
hint — neither chat surface's paint latch was armed, so the
divider's `lo-qwen` (or whatever the previous provider) stayed
visible until some other event triggered a repaint. The user saw
the cycle reflected in the statusbar but not in the panel chrome.

Now sets `chat_inline_paint_pending` / `chat_overlay_paint_pending`
on every successful cycle so the next term-bytes tick re-emits
the divider with the new label.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…istory

The hardcoded `per_turn_max_rows = 3` truncated long replies with a
dim `[…]` marker even when the scrollback budget had plenty of
room. We already shipped PageUp/PageDown scrollback in #175 —
truncation on top of it is just a wart that hides content the user
came to see.

Removed: each turn now renders its full wrap-chunk count, capped
only by the scrollback budget. The back-walk's `oldest_turn_cap`
path still handles the natural panel boundary when total demand
exceeds budget — the OLDEST visible turn gets clipped (with the
`[…]` marker), the newest always renders in full.

`countTurnRows` now receives `scrollback_budget` as the upper
bound instead of a fixed 3. The marker logic in `renderWrappedRaw`
stays exactly the same — it just fires far less often.

Regression test asserts the LAST sentinel of a 5-chunk turn lands
in the paint output. Verified fail-without/pass-with (had the
hardcoded 3-row cap, marker `EEEE-LAST-CHUNK-MARKER` was dropped).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@fentas fentas changed the title fix(llm): chat panel divider repaints when Alt+M cycles provider fix(llm): chat panel UX — divider repaint on cycle + drop 3-row truncation May 21, 2026
@fentas
Copy link
Copy Markdown
Owner Author

fentas commented May 21, 2026

Folded a second fix in: the "no wrap, it cuts of [...]" report. Removed the hardcoded per_turn_max_rows = 3 that truncated long replies even when scrollback had room. Turns now render in full up to the scrollback budget; older turns scroll off naturally, PageUp/PageDown walks history. 796/796 tests green.

…on bytes

Em-dash `—` (U+2014 = E2 80 94) and other 3- or 4-byte UTF-8
sequences with continuation bytes in 0x80..0x9F were getting
corrupted: the C1-control filter dropped those continuation
bytes as if they were bare C1 controls, leaving an orphan
leading byte that the terminal rendered as `�`. The 2-byte
special case for `C2 + 80..9F` covered NBSP-region pairs but
nothing wider.

Rewrote `writeSanitized` to walk codepoints via `pw.utf8Iter`
and check the C1 range against the DECODED codepoint, not the
raw bytes. Multi-byte sequences re-emit verbatim; only actual
C0/C1 controls + DEL get dropped (TAB still passes through,
CR/LF still collapse to a single space).

Regression test pins em-dash, bullet (U+2022), sparkle emoji,
and a CJK glyph through the inline-panel paint path and asserts
each survives intact with no U+FFFD replacement char in the
output. Verified fail-without/pass-with.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@fentas fentas merged commit 6e1bd79 into master May 21, 2026
4 checks passed
@fentas fentas deleted the fix/chat-divider-repaint-on-cycle branch May 21, 2026 09:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant