| 2026-07-13 |

tui: handle terminal lifecycle events + status-bar count (Etap 4)
...
terminal_opened/output/closed WS events used to fall through to ChatModel's
unknown-event path (raw dict dumped into chat). Now they feed a dedicated
terminals state and a status-bar count, with no chat bubble:
- ChatModel: terminals dict (name → {description, pid, background, status,
output_tail, closed}) updated by terminal_opened/output/closed in
handle_ws_event (return None — no chat item). open_terminal_count property;
seed_terminals(summaries) populates from a REST list (on attach). output_tail
capped at 200 lines.
- tui_app.on_ws_event: a terminal-events branch forwards to the model (no _sync
walk — background output can be frequent) and updates the StatusPanel count.
attach_session seeds via api.list_terminals and sets the count (graceful 0 on
failure).
- StatusPanel: the "Ctrl+P palette | /help commands" hint line is replaced with
a live "Terminals: N" count (set_terminals_count). Key combos still live in
the bottom StatusBar.
- Tests: mock_tui_api fixtures across 5 client test files gain an async
fake_list_terminals so attach_session never hits a real network in tests.
Tests: ChatModel opened tracks state (no bubble) / output appends tail / unknown
ignored / closed decrements count / seed from REST; tui_app terminal events
update the status count with no chat bubble. Full suite: 988 passed, 1 skipped.
Plan: docs/terminal_tool_plan.md — Etap 4 of 5.
Eugene Sukhodolskiy
committed
on 13 Jul
|

tui: bash-style message history (Up/Down recall, per-session, in-memory)
...
On an empty prompt with the hints list closed, Up recalls the previous
submitted message and Down moves toward the most recent; Down past the newest
restores the empty draft. Like a shell history:
- InputBox owns the per-session history (capped at the 10 most recent, with
consecutive-duplicate suppression) plus the browsing index and saved draft.
- _PromptInput._on_key enters history mode on Up only when the field is empty
and the hints list is not open; while hints are open Up/Down still navigate
command matches, and a non-empty field uses TextArea's multiline cursor.
- Only plain user messages are recorded — slash commands and !shell are not
remembered (they are not messages to the agent). The raw typed text is stored.
- on_user_submitted records a plain submit; attach_session resets the history
on session switch/resume (the history is in-memory per session, not synced
to the server, by design).
Tests: append cap/dedup/skip-commands, up/down navigation, reset, Up-recall /
Down-draft / Up-non-empty-no-recall over pilot, submit records plain (not
/commands). Full suite: 964 passed, 1 skipped.
Eugene Sukhodolskiy
committed
on 13 Jul
|

tui: minor correctness/perf fixes (Etap 5)
...
The last batch from the code review — real bugs and edge cases, not cosmetics:
- tui_app._resolve_session: read session_id with .get and return None when a
malformed server response omits it, instead of KeyError slipping past the
api-call except (5.B3).
- cli /switch: surface the original error cause (404 vs network vs server) on
a non-unique prefix match instead of swallowing it into a generic "not found"
(6.B2).
- input_box _complete_command: a bare "/" with no hint list available no longer
auto-picks the first command in the registry (3.B3).
- filesystem _render_grep: when the grep ran in regex mode, highlight with the
actual regex so marked spans match what the server found — a literal highlight
of a regex pattern would mark nothing (2.B2).
- file_refs _collect_files: filter sensitive entries out in the generator before
sorted() so a huge directory is not fully materialized only to be mostly
discarded (5.P1).
- renderers/tool ToolResultRenderer: cap a generic tool result at 200 lines
(tail + marker) so a non-filesystem tool with huge output does not flood the
chat bubble — filesystem has its own renderer (2.P3).
Tests: malformed-response /switch error surfacing, bare-slash no-auto-pick,
grep regex highlight, sensitive-subdir skip, tool-result truncation. Full
suite: 953 passed, 1 skipped.
Eugene Sukhodolskiy
committed
on 13 Jul
|

tui: async REST api client — stop blocking the event loop (Etap 3)
...
The terminal client's REST helpers were synchronous (httpx.Client); every
caller inside the Textual event loop (attach/switch/resume/profile/stop) blocked
the whole TUI for the duration of each network round-trip — the spinner froze,
input stopped responding, the screen did not repaint. Move the network layer to
async and a shared pooled client:
- api.py: all 7 helpers are now `async def` over a single lazily-created
httpx.AsyncClient (connection pooling) — awaits yield the loop while a request
is in flight, so the TUI never blocks on the network.
- tui_app.py + commands/builtin.py: every `api.*` call site is now `await`-ed
(all already ran in async workers / execute). _stop_stream_worker drops the
dead `iscoroutine` check (api.stop_session is async, the await is real).
- cli.py: _resolve_session_id and _handle_command await api.*; _run_raw wraps
the resolve + run in asyncio.run(_run_raw_async) so the sync click entrypoint
stays unchanged. Removed the now-unused _run_one_shot wrapper.
- sessions_picker.on_mount: list_sessions runs in a worker (run_worker) instead
of blocking on_mount — a slow/unreachable backend no longer hangs the modal;
the input is focusable immediately and the list populates when the request
returns.
Tests: every api mock across 6 client test files is now an `async def` fake
(test_tui_app, test_chat_panel, test_sessions_picker, test_terminal_client,
test_tui_export, test_input_box); the sessions picker tests use an async
_raise_not_found helper for the 404 path. Full suite: 939 passed, 1 skipped.
Eugene Sukhodolskiy
committed
on 13 Jul
|
| 2026-07-11 |
Navi Code TUI: interactive slash-command hints (Up/Down + Enter)
...
Make the inline command hints navigable:
- Up/Down move a highlight through the matching commands (wraps around).
- Enter while a hint is open runs the highlighted command (routed through
UserSubmitted as /<name>, so the app's _run_command handles it — not sent
to the agent). Enter with hints closed still submits the raw text.
- Tab completes the input to the highlighted command's canonical name and
stays in the field for typing args (was: always the first match).
CommandHints stays a non-focusable Static (renders the highlighted line with
reverse video); _PromptInput owns all key handling via a sibling reference.
Co-Authored-By: Claude <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 11 Jul
|
Navi Code TUI: inline slash-command hints + Tab completion
...
Show a non-interactive list of matching commands above the input box while
the user types a '/'-command (no whitespace yet). Tab completes the prefix to
the canonical name of the top match. Enter already routed '/...' through
_run_command, so submitting a typed command executes it instead of sending
it to the agent — unchanged.
- CommandRegistry.match(prefix): case-insensitive prefix match on name and
aliases, exact match sorted first; empty prefix returns all.
- CommandHints(Static): purely visual, never takes focus, hidden by default.
- InputBox composes hints above the prompt and refreshes them on
TextArea.Changed.
Co-Authored-By: Claude <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 11 Jul
|