feat(llm): chat surface defaults to dialog + Alt+T auto toggle + chrome polish#180
Merged
Conversation
…me polish
Live testing surfaced UX gaps in the chat surface. Four changes:
- **Default to dialog mode when chat is open.** `currentDispatchMode`
used to return `.chat` whenever a chat panel/overlay was visible,
which routed through the prose-only `prompt_chat`. The user wants
the richer exec/question/done protocol available in chat too. Now
returns `.dialog` (or `.auto` if the auto flag is set) for any
chat-open state. The standalone `.chat` mode stays in the type
system for users who configure it explicitly.
- **Alt+T toggles auto-exec within chat.** New action
`llm_chat_toggle_auto` flips `rt.auto_mode_active`. Bound to
Alt+T (dual-encoded kitty kbd CSI-u + legacy `\x1ba`). Only
acts while a chat surface is open; outside chat the persistent
Alt+S / Alt+Shift+S bindings own the dialog/auto distinction.
- **Chrome shows live state + shortcuts.** Inline panel divider
trailing hint expanded from `Alt+C close · Enter send` (25 cols)
to `Alt+T auto · Alt+M model · Alt+C close · Enter send` (51
cols). When auto is on, mode word becomes `atty chat (auto)` /
`atty chat (auto, incognito)`. Overlay title + footer carry the
same shortcut hints.
- **Statusbar suppresses LLM hint while chat is open.** The
panel's own chrome already shows the active shortcuts; the
statusbar's `Alt+C chat` discovery hint becomes redundant + AND
misleading ("Alt+C opens the panel" is wrong when it's open).
`statusText` returns null in that state; other modules still
contribute.
`.done` chat-bypass updated to check `chat_inline_open or
chat_overlay_open` directly (was via `currentDispatchMode == .chat`
which no longer fires).
817/817 unit tests (+2 chrome regression tests, +1 Alt+T toggle
test) + fmt clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR updates the LLM chat UI/behavior so chat surfaces behave like full dialog/auto flows by default, adds an in-chat auto toggle, and adjusts chat chrome to make key shortcuts more discoverable while reducing duplicated hints in the status bar.
Changes:
- Default chat-surface dispatch now uses dialog/auto semantics (instead of a prose-only
.chatmode) and updates.donehandling accordingly. - Adds
llm_chat_toggle_auto(Alt+T) to toggle auto-exec while a chat surface is open. - Updates inline/overlay chrome + tests to surface Alt+T / Alt+M / Alt+C / Enter shortcuts and suppress the LLM statusbar hint while chat is open.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src/proxy.zig | Routes the new llm_chat_toggle_auto action through the LLM module dispatch path. |
| src/modules/llm/paint.zig | Updates overlay title/footer and inline divider chrome (mode word + shortcut hints). |
| src/modules/llm/paint_tests.zig | Adds/updates regression tests for the updated chrome and auto mode word behavior. |
| src/modules/llm/hooks.zig | Implements Alt+T toggle handler; changes dispatch mode selection for chat surfaces; adjusts .done chat-bypass logic. |
| src/modules/llm/hooks_tests.zig | Adds a regression test for Alt+T toggling behavior and repaint latching. |
| src/modules/llm.zig | Adds default keybindings for Alt+T (dual-encoded) and suppresses LLM status text while chat is open. |
| src/keymap.zig | Adds the new llm_chat_toggle_auto action with documentation. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…rome + in-flight indicator Three subagent blockers addressed: - **Dead `prompt_chat` + `effective_chat_system_prompt` deleted.** `currentDispatchMode` doesn't return `.chat` for chat-open state any more (returns `.dialog`/`.auto`), so the chat-specific system prompt and its `effective_*` wrapper became unreachable. Per Suckless ethos: delete rather than ship dead paths. `Mode.chat` stays in the enum because `for_modes` provider masks still reference it; `selectPrompt(.chat)` and the request-build `live_mode` switch arm now fall through to `prompt_dialog` / `effective_dialog_system_prompt`. Sibling tests updated. - **In-flight indicator kept while chat is open.** Round-1 fix suppressed all LLM statusbar text when a chat surface was open — which also hid the `thinking…` indicator during a request. Real UX regression (chat panel doesn't paint its own spinner). Now `statusText` returns `thinking_hint` when `rt.in_flight` AND chat is open; only the idle/discovery hint is suppressed. - **Trailing-chrome hint shrinks progressively.** Round-1 fix hardcoded `trail_min_clearance = 51`. On an 80-col terminal with the `(auto, incognito)` mode word, the label alone hit 35 cols, leaving 45 < 51 — chrome overran into the panel body. Now three hint sizes: full (51), medium (38, drops `Enter send`), short (22, drops labels but keeps the keys). Picked dynamically from `cols - label_visible`. Narrow-terminal test pins behaviour at 80 cols + auto + incognito. 817/817 unit tests + fmt clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…shows incognito Two Copilot findings on ec9fb72: - **Inline chrome provider resolution used `.chat`** but `currentDispatchMode` now returns `.dialog`/`.auto` for chat surfaces. A user with a chat-only entry in `cfg.providers[]` would see the chrome label show one provider while the request actually fired through a different one (the dialog match). Now resolves with the live mode (`.dialog` or `.auto` based on `rt.auto_mode_active`). - **Overlay title only reflected auto-mode** — incognito state was ignored, unlike the inline divider that shows the 🕶 glyph + `(incognito)` annotation. Threaded `ctx` into `paintChatOverlay`; title now mirrors the inline divider's icon + parenthetical-state logic so the two surfaces stay visually consistent. Off-by-one nit on the `trail_min_clearance = 51` doc comment vs actual byte count is cosmetic — the runtime math uses the same constant throughout, so a 52-col reality is internally consistent. Skipping that one. Dead-`Mode.chat` from finding #4 is partially addressed (provider resolution no longer relies on `.chat`); full deletion of `Mode.chat` + `for_modes.chat` would be a bigger cleanup landing in a follow-up. 817/817 unit tests + fmt clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Owner
Author
|
Review loop: 3 rounds. 12 findings addressed across rounds:
Mode.chat enum value + for_modes.chat mask survive but no longer reach a live dispatch — full deletion deferred to follow-up. 817/817 unit + 6/6 itest + fmt clean. Merging. |
Comment on lines
+838
to
+842
| // Provider resolution uses the LIVE dispatch mode so the | ||
| // chrome label matches the provider that will actually | ||
| // serve the next request. Chat surfaces now dispatch as | ||
| // `.dialog`/`.auto` (per `currentDispatchMode` in | ||
| // hooks.zig); hardcoding `.chat` here was a relic of the |
Comment on lines
+908
to
+922
| const min_dashes: usize = 4; | ||
| const hint: []const u8 = if (cols_usize >= label_visible + 51 + min_dashes) | ||
| hint_full | ||
| else if (cols_usize >= label_visible + 38 + min_dashes) | ||
| hint_med | ||
| else if (cols_usize >= label_visible + 22 + min_dashes) | ||
| hint_short | ||
| else | ||
| "\x1B[0m"; // No room for any hint — leave the row blank past the label. | ||
| const hint_cols: usize = if (hint.ptr == hint_full.ptr) | ||
| 51 | ||
| else if (hint.ptr == hint_med.ptr) | ||
| 38 | ||
| else if (hint.ptr == hint_short.ptr) | ||
| 22 |
Comment on lines
+909
to
+924
| const hint: []const u8 = if (cols_usize >= label_visible + 51 + min_dashes) | ||
| hint_full | ||
| else if (cols_usize >= label_visible + 38 + min_dashes) | ||
| hint_med | ||
| else if (cols_usize >= label_visible + 22 + min_dashes) | ||
| hint_short | ||
| else | ||
| "\x1B[0m"; // No room for any hint — leave the row blank past the label. | ||
| const hint_cols: usize = if (hint.ptr == hint_full.ptr) | ||
| 51 | ||
| else if (hint.ptr == hint_med.ptr) | ||
| 38 | ||
| else if (hint.ptr == hint_short.ptr) | ||
| 22 | ||
| else | ||
| 0; |
Comment on lines
1020
to
1024
| .chat_scroll_page_down, | ||
| .llm_chat_inline_grow, | ||
| .llm_chat_inline_shrink, | ||
| .llm_chat_toggle_auto, | ||
| => { |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Requests addressed
From live testing of the chat panel:
Changes
Chat surface defaults to dialog mode.
currentDispatchMode(rt)returns.dialog(or.autoifrt.auto_mode_active) when a chat panel/overlay is open, instead of the prose-only.chat. The LLM now uses the full exec/question/done protocol in chat surfaces — the panel itself is the richer surface.New action
llm_chat_toggle_autobound toAlt+T. Togglesrt.auto_mode_active. Only acts while a chat surface is open. Dual-encoded (kitty kbd CSI-u + legacy).Chat panel chrome surfaces the shortcuts. Trailing hint now
Alt+T auto · Alt+M model · Alt+C close · Enter send(51 cols). Mode word shows live auto state:atty chat (auto),atty chat (auto, incognito). Overlay title + footer mirror.Statusbar drops LLM hint while chat is open.
statusTextreturns null whenchat_inline_open or chat_overlay_open— the panel's own chrome owns the shortcut discovery surface; doubling them in the statusbar would advertiseAlt+C chatas an open-hint when the panel is already open..donechat-bypass updated to checkchat_inline_open or chat_overlay_opendirectly (was viacurrentDispatchMode == .chatwhich no longer fires).Test plan
(auto); press again → flips back🤖 Generated with Claude Code