| 2026-07-13 |

tui: theme repaцвет chat + bounded history + tail purge (Etap 4)
...
- apply_theme now re-renders already-drawn chat bubbles in the new palette:
ChatPanel.refresh_content() rebuilds each visible item's rich renderable (the
item renderers read get_active_theme(), already switched) and drops the
per-widget Content/height caches so the next paint rebuilds instead of
returning the old-theme cached Content. Theme switch / live preview no longer
leaves the conversation in the previous palette (5.B1).
- theme_picker live preview is debounced (~100 ms coalesce via set_timer):
apply_theme now re-renders every bubble, so applying it on every highlight
while scrolling the theme list would stutter — rapid moves collapse into one
apply, with the freshest theme winning. Escape/cancel dismiss the pending
timer so it cannot fire after a restore and re-apply the highlighted theme
over the restored one (4.P2).
- ChatModel.items is now bounded: a cap (visible window * 3) trims the oldest
items off the front via a single _append/_trim path, so a long autonomous
session no longer grows items without limit. The visible window is much
smaller than the cap, so trimming only drops already-off-screen items and the
truncation hint still reads correctly (3.P3).
- stream_end purges empty assistant/thinking bubbles off the tail (while-pop)
instead of a full-history list-comprehension copy — empty bubbles are created
at the end of the turn, so O(tail) suffices (1.P1).
Tests: apply_theme re-renders bubbles; theme picker debounces + escape cancels;
chat_model cap trims front / cap=None keeps everything; stream_end keeps
non-empty assistant + purges trailing empty thinking. Full suite: 946 passed, 1 skipped.
Eugene Sukhodolskiy
committed
9 hours ago
|

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
9 hours ago
|

tui: code-review Etap 1 — crash/injection/reconnect fixes + drop artifact renderer
...
Point fixes from the full TUI/terminal-client code review (docs/code_review_tui.md),
each with a covering test:
- command_palette: guard on_list_view_selected against IndexError when the
filter is empty and the placeholder row is selected.
- cli /profile: read session_id (not the absent "id") from the server — the
handler no longer KeyErrors.
- todo_list: escape task text/validation/index/milestone before interpolating
into rich markup so a task like "fix [bug]" no longer breaks the line styling.
- builtin /help: drop bold markup tags from status content — StatusRenderer
builds a literal Text() that would show the tags as literal square brackets.
- tui_app on_user_submitted: always enqueue when a bridge exists (the input loop
buffers across reconnect) instead of dropping the message on connected=False.
- chat_model stream_start: also reset _current_thinking so a dropped turn (no
thinking_end) does not glue the next turn's reasoning onto the old block.
- todo renderer update: coerce validation to str before .strip() so a non-string
JSON value does not AttributeError.
- renderers: remove the unused ArtifactRenderer (no production path emits
type=artifact — TUI shows files via the OS); keep the diff renderer test.
Full suite: 937 passed, 1 skipped.
Eugene Sukhodolskiy
committed
10 hours ago
|
| 2026-07-12 |

permissions: remove broken client-side gate; add backend design doc
...
The terminal-TUI permission gate was a race: _execute_tools_with_sink
emits ToolStarted and starts the tool on the backend immediately, so by
the time the client dialog appeared the destructive action had already
run -- 'deny' could only stop the session, not un-execute the tool. It
also existed only in the terminal TUI (webclient/android had nothing),
and the system-prompt 'Strict Confirmation' nudge was an unreliable
duplicate.
Remove the broken machinery for a clean slate and plan the real
replacement from zero:
- delete clients/terminal/tui/permissions.py, screens/permission_dialog.py
and their tests
- strip tui_app.py (engine, _deny_tool, _show_permission_dialog,
_confirm_shell_command, _stop_session_worker, on_permission_request,
tool_started gate) and the PermissionRequest TUI event; !cmd now runs
directly (user-typed, no agent gate)
- drop the 'Strict Confirmation' prompt rule from navi_code/developer/
tool_developer
- add docs/permissions.md: authoritative backend gate design (engine +
registry + event + endpoint + agent gate + postgres policy + global
rules config, sub-agents gated) -- design only, not yet implemented
- update docs/index.md, navi_code_cli.md, profiles.md, testing.md
Until Phase 1 lands there is no destructive-action confirmation -- a
conscious period without the false security of the old race-gate.
Eugene Sukhodolskiy
committed
1 day ago
|
| 2026-07-11 |
Navi Code TUI: unify session commands on shared workers; /switch hybrid
...
Route /new, /sessions, and /switch through shared app-side logic instead of
each command reimplementing the switch path:
- _open_sessions_picker(): pushes SessionsPickerScreen and wires the select
callback to _switch_session_worker / _create_new_session_worker. Both
/sessions and /switch (no/ambiguous arg) use it.
- /new -> _create_new_session_worker (was: inline create + attach + clear).
- /switch <prefix>: resolve to a single navi_code session (exact id, then
prefix match) and switch directly via _switch_session_worker; no/ambiguous/
unknown arg opens the picker pre-filtered to what was typed. This removes
the latent clear()-after-attach bug from the old SwitchCommand (clear erased
the history that attach_session had just replayed).
- SessionsPickerScreen gains initial_query to open pre-filtered.
Co-Authored-By: Claude <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
2 days ago
|
Navi Code TUI: sessions picker for /sessions (navi_code, full switch)
...
Replace the chat-printed session list with a modal picker:
- SessionsPickerScreen lists only navi_code-profile sessions (filtered by
settings.default_profile_id), with a filter input, a top "✚ New session"
row, and the current session marked. Up/Down + Enter to pick, Esc cancels.
- /sessions pushes the screen. Selecting an existing session runs
_switch_session_worker (persist via StateManager + attach_session, which
replays history and restarts the WS bridge — no chat_panel.clear() after,
since load_history already replaces the chat and clearing would erase the
replayed history). The "New session" row runs _create_new_session_worker.
Co-Authored-By: Claude <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
2 days ago
|
| 2026-06-26 |
Navi Code TUI: fix input box layout, command palette duplicate IDs, status renderer, and WS input loop
...
- clients/terminal/tui/widgets/input_box.py: switch Horizontal to Vertical with width: 100% for Input so it renders and accepts input in real terminals; add refresh on Input.Changed.
- clients/terminal/tui/screens/command_palette.py: remove fixed ListItem IDs to avoid DuplicateIds on fast filter.
- clients/terminal/tui/chat_model.py + renderers/status.py + widgets/chat_panel.py: render backend status events as dim system messages instead of raw dicts.
- clients/terminal/tui/ws_bridge.py: start NaviWebSocketClient.input_loop so enqueued user messages are actually sent to the backend.
- clients/terminal/tui/tui_app.py: focus InputBox synchronously in on_mount so typing works immediately.
- tests/clients/test_tui_app.py: regression test for visible input text.
Co-Authored-By: Claude <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
18 days ago
|
| 2026-06-24 |
Navi Code TUI: review fixes for Phase 5
...
- Fix raw CLI session API fields (session_id/name/preview)
- Add --mouse/--no-mouse flags and persistent TUI settings coercion
- Make attach_session/apply_theme public, add ChatPanel.clear()
- Deduplicate session selection handlers and editor error handling
- Update docs and tests
Eugene Sukhodolskiy
committed
20 days ago
|
| 2026-06-23 |
Navi Code TUI: Phase 5.1 — config, mouse, themes, status
...
- Add TuiSettings persisted in ~/.navi_code/tui.json (theme, mouse, scroll_speed, diff_style, keybinds).
- TUI applies saved theme/mouse on startup; CLI gets --theme override.
- Add /themes picker with live preview and /mouse toggle command.
- Extend StatusPanel with backend URL, current theme, tokens/iter placeholders.
- Fix TuiContext.app() to use Textual active_app ContextVar.
- Add unit tests for settings, themes, and status panel.
513 passed, 1 skipped. Ruff clean.
Signed-off-by: Eugene Sukhodolskiy <eugene.sukhodolskiy@gmail.com>
Eugene Sukhodolskiy
committed
20 days ago
|
Navi Code TUI: fix Phase 4 review critical/medium issues
...
- PermissionEngine: always-deny returns None via check(); is_always_deny() exposed for explicit rejection; default rules for code_exec/ssh_exec/shell; extract_target() made public.
- ShellRunner: set ShellResult.truncated when output is actually truncated.
- File refs: restrict paths to base_dir/home, block sensitive files/dirs, skip binary, support glob brackets, shared guess_language.
- Permission dialog: deny now renders synthetic tool_call before stopping; shell ! commands require permission with always-allow/deny persistence.
- Tests: add permission tests, fix file_refs/shell tests, add __init__.py to fix pytest name collisions, update websocket integration for AgentSessionOrchestrator.
504 passed, 1 skipped. Ruff clean.
Signed-off-by: Eugene Sukhodolskiy <eugene.sukhodolskiy@gmail.com>
Eugene Sukhodolskiy
committed
20 days ago
|
Navi Code TUI: complete Phase 4
...
- @ file references with glob/dir support and size limits
- ! shell commands with timeout and output truncation
- inline permission dialog for destructive tool calls
- diff and artifact renderers with theme-aware highlighting
Tests: 40 client tests passing
Eugene Sukhodolskiy
committed
20 days ago
|
Navi Code TUI: command palette and themed markdown
...
- Add CommandPaletteScreen modal with fuzzy filtering and keyboard navigation.
- Bind Ctrl+P to push the palette; selected commands execute via registry.
- Make MarkdownRenderer theme-aware: headings, inline code, links, and code blocks use gnexus palette.
- Pick dracula/github-light code theme based on active theme brightness.
Co-Authored-By: Claude <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
21 days ago
|