feat(tui): command history, explorer/evidence navigation, header stats - #3
feat(tui): command history, explorer/evidence navigation, header stats#3Jacobcdsmith wants to merge 8 commits into
Conversation
Phase 1 of the v0.2 improvement plan (UX polish), plus integration tests: - Command history: persisted to .kairos/.tui_history (JSONL), cycled with up/down, prefix autocomplete via a ghost-text Suggester, a hint line below the command input, and `:history --clear`. - Explorer pane: item-count title, scroll-position indicators, a line-number gutter every 5th row, search/trace-term highlighting, and Ctrl+G "go to item". - Evidence pane: real keyboard scrolling (up/down/Page Up/Page Down/ Home/End) by wrapping its content in a VerticalScroll instead of a bare Static, which Textual never treats as scrollable. - Header/status line: active well, workspace stats (artifact count, size, well count), last-command runtime, and green/red status coloring plus a transient "running" indicator while a command is in flight. - Five Pilot-driven integration workflows covering ingest/search/show/ note, well switching mid-session, history surviving a restart, explorer navigation + evidence scrolling, and error recovery. Phases 2-6 (pagination, bookmarks, export, diff, themes, layouts, benchmarks, docs) are left for follow-up work. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JoRVBGw6Ea7wd8d1ZDFKAx
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JoRVBGw6Ea7wd8d1ZDFKAx
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
main was already failing CI's ruff-format and ruff-check steps (ruff 0.16.2 flags formatting/import-order/style issues in demo.py, tool.py, and workspace_pane.py that predate this branch). Fixing these mechanically here — no behavior changes — so this PR's CI can go green; none of it is TUI feature work. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JoRVBGw6Ea7wd8d1ZDFKAx
pyrightconfig's include covers all of src/ and tests/ unconditionally, but the workflow installed the optional `tui`/`tui-test` extras only after the Pyright step ran. Every file under src/kairos/tui and tests/tui was therefore unable to resolve `textual`/`textual.*` imports, cascading into 800+ reportUnknown* errors regardless of whether the code itself was correct — confirmed this reproduces on unmodified main under the same conditions, predating this branch. Moved "Install TUI extras" to right before Pyright, and moved the base "Pytest" step (which intentionally runs *without* the tui extra, to prove no accidental hard dependency on it) to run before that install so its behavior is unchanged. "Pytest (TUI)" already ran after the extras were installed and is unaffected. Also renamed two now-tested helpers (`_highlighted` -> `highlighted` in explorer_pane.py, `_format_size` -> `format_size` in header_line.py) that pyright flagged as reportPrivateUsage once it could actually see them, since my new unit tests import them directly. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JoRVBGw6Ea7wd8d1ZDFKAx
|
Heads up on CI status, since I found some things unrelated to this PR's actual content while getting it to run cleanly:
Everything actually in this PR's diff (the TUI Phase 1 work) is ruff/pyright-clean and fully tested. Generated by Claude Code |
test_home_lists_recent_activity predates the dashboard-home-screen feature (commit 8efe062) and still expected :home's last_result to be a bare list[ActivityEvent] — it's been a DashboardResult ever since, which also carries recent_activity. This was failing on main already; fixing it here so this branch's CI can actually reach the TUI-specific checks instead of dying on an unrelated pre-existing test bug. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JoRVBGw6Ea7wd8d1ZDFKAx
|
One more pre-existing issue surfaced once the ordering fix let CI reach the base With that, local Generated by Claude Code |
There was a problem hiding this comment.
Pull request overview
Implements Phase 1 (“UX Polish”) improvements for the KAIROS TUI v0.2 plan: persistent command history with hints/completion, improved Explorer/Evidence navigation (including goto-line and scrolling), and richer header/status feedback, plus new integration tests to validate end-to-end workflows.
Changes:
- Added persisted command history (JSONL) with ↑/↓ cycling, command-name ghost completion, and inline command hints; added
:history --clear. - Improved Explorer/Evidence navigation: Explorer title/count + scroll indicators + highlighting + Ctrl+G goto-item; Evidence pane now scrolls with standard keys.
- Added/updated TUI test suite (Pilot-driven workflows + focused unit tests) and adjusted CI ordering so Pyright runs after installing TUI extras.
Reviewed changes
Copilot reviewed 23 out of 23 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/tui/test_workflows.py | New Pilot-driven end-to-end workflow coverage across search/show/note, wells, history persistence, navigation, and error recovery. |
| tests/tui/test_header_updates.py | New tests validating header workspace stats/runtime and status line styling/“running” indicator. |
| tests/tui/test_explorer_navigation.py | New tests for Explorer title/count, gutter, highlighting, and Ctrl+G behavior. |
| tests/tui/test_evidence_pane_scrolling.py | New tests ensuring EvidencePane keyboard scrolling and scroll reset on selection. |
| tests/tui/test_controller.py | Updates home-related assertions to match dashboard result schema. |
| tests/tui/test_command_history.py | New unit/controller-level tests for history persistence, hints, clear behavior, and timing fields. |
| tests/tui/test_app.py | Adds Pilot tests for history cycling and command hint rendering. |
| src/kairos/tui/widgets/workspace_pane.py | Formatting-only adjustments; dashboard rendering alignment with dashboard result. |
| src/kairos/tui/widgets/status_line.py | Adds transient “running…” indicator and explicit success/error coloring. |
| src/kairos/tui/widgets/header_line.py | Adds workspace stats and last-command runtime display; introduces format_size. |
| src/kairos/tui/widgets/explorer_pane.py | Adds mode/count title, gutter numbering, query-term highlighting, and scroll indicators. |
| src/kairos/tui/widgets/evidence_pane.py | Converts to scrollable container with key bindings; proxies renderable via inner Static. |
| src/kairos/tui/widgets/command_line.py | Adds command suggester, history cursor with ↑/↓ bindings, and live hint updates. |
| src/kairos/tui/styles/kairos.tcss | Styles for evidence content, command hint line, and goto-line modal. |
| src/kairos/tui/state.py | Adds command history, last-command timing fields, and cached workspace stats. |
| src/kairos/tui/screens/main.py | Adds command hint widget to main screen layout. |
| src/kairos/tui/screens/goto_line.py | New modal screen for Ctrl+G “go to item”. |
| src/kairos/tui/controller.py | Records command timing, appends history to state/disk, and updates workspace stats in handlers. |
| src/kairos/tui/commands.py | Adds command hint text, exports known command names, and JSONL history persistence helpers. |
| src/kairos/tui/app.py | Loads persisted history on startup; adds Ctrl+G binding; shows “running…” immediately on dispatch. |
| src/kairos/tool.py | Minor refactors/formatting; small control-flow simplifications. |
| src/kairos/cli/commands/demo.py | Import/format cleanup; improves long strings/readability. |
| .github/workflows/ci.yml | Reorders Pyright to run after installing TUI extras; keeps dedicated TUI pytest step. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| stripped = text.strip() | ||
| if not stripped: | ||
| return state | ||
| append_history(runtime_ctx.workspace.root, stripped, success=success) | ||
| return dataclasses.replace(state, command_history=(*state.command_history, stripped)) |
There was a problem hiding this comment.
Good catch — fixed in 7b72273: append_history/clear_history now wrap their filesystem calls in contextlib.suppress(OSError). History is a convenience layer, so a read-only workspace or any other I/O error there no longer propagates into _track_history/dispatch; the in-memory command_history update (what the command line's ↑/↓ actually reads) still happens either way. Added tests exercising both the direct helpers and dispatch through a real filesystem failure.
Generated by Claude Code
| def load_history(workspace_root: Path) -> list[HistoryRecord]: | ||
| path = history_file_path(workspace_root) | ||
| if not path.exists(): | ||
| return [] | ||
| records: list[HistoryRecord] = [] | ||
| for line in path.read_text(encoding="utf-8").splitlines(): | ||
| line = line.strip() | ||
| if not line: | ||
| continue | ||
| try: | ||
| data = json.loads(line) | ||
| records.append( | ||
| HistoryRecord( | ||
| timestamp=datetime.fromisoformat(data["timestamp"]), | ||
| command=data["command"], | ||
| success=bool(data["success"]), | ||
| ) | ||
| ) | ||
| except (json.JSONDecodeError, KeyError, ValueError): | ||
| continue | ||
| return records |
There was a problem hiding this comment.
Fixed in 7b72273 — load_history now iterates the file object directly instead of read_text().splitlines(), so it no longer holds the whole file as both a raw string and a list of lines at once.
Generated by Claude Code
Addresses Copilot review feedback on PR #3: - append_history/clear_history now suppress OSError instead of propagating it. History is a convenience feature — a read-only workspace or other filesystem error must not break command dispatch, which calls append_history after every single command. The in-memory TuiState.command_history update (what actually backs the command line's up/down cycling) is unaffected either way. - load_history now iterates the history file line-by-line instead of read_text().splitlines(), avoiding holding the whole file as both a raw string and a list of lines at once for a file that's append-only and can grow without bound. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JoRVBGw6Ea7wd8d1ZDFKAx
Types out kairos/tool.py's dict returns (dict -> dict[str, Any], plus explicit return-type annotations and list[dict[str, Any]] locals on the inner _run() closures) and workspace_pane.py's dashboard renderable list (list[object] -> list[RenderableType]), clearing the ~88 pyright errors flagged in PR #3's review discussion. No behavior changes, except one real bug this surfaced: kairos_init() read a nonexistent Workspace.name attribute (Workspace only has root/kairos_dir/db_path/ etc., see infrastructure/filesystem/workspace.py) — would have raised AttributeError on every call. Fixed to mirror init_workspace()'s own `name or root.name` fallback. Untested/unused code path, so no regression risk, but worth calling out. pyright now reports 0 errors repo-wide. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JoRVBGw6Ea7wd8d1ZDFKAx
|
Went ahead and cleared the remaining pre-existing pyright debt too, so CI should be fully green now:
Generated by Claude Code |
The test blanket-blocked socket.socket() for any address family. But asyncio's own SelectorEventLoop uses an AF_UNIX socketpair internally for self-pipe wakeups — this is local IPC, not network access — and CPython can defer that loop's __del__/cleanup to run during this test's own teardown window (while the monkeypatch is still active), tripping the assertion for something the test was never meant to catch. Confirmed via CI logs: the blocked call was literally socket.socket(AF_UNIX, SOCK_STREAM, 0, ...). Narrowed the guard to only block AF_INET/AF_INET6 socket() calls (create_connection/getaddrinfo stay fully blocked, since those are unconditionally about real network access). Verified stable across repeated runs, both in isolation and as part of the full suite. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JoRVBGw6Ea7wd8d1ZDFKAx
|
Found the actual root cause of that "flaky" teardown error I'd been treating as unavoidable — it wasn't flaky, it was a real over-broad assertion. Narrowed the guard to only block Generated by Claude Code |
|
CI is fully green on 6a7ed25 — every step passed (ruff format, ruff check, pyright with 0 errors, pytest base + TUI, build, and the demo smoke test). Ready for review whenever you'd like to take it out of draft. Generated by Claude Code |
What does this change?
Implements Phase 1 ("UX Polish") of the KAIROS TUI v0.2 improvement plan — the MUST-HAVE deliverables — plus integration tests:
src/kairos/tui/commands.py,widgets/command_line.py): every submitted command line is persisted append-only as JSONL to.kairos/.tui_historyand loaded back intoTuiState.command_historyon startup. ↑/↓ cycle through it (including across restarts). ASuggestergives ghost-text prefix completion (:sear→:search), a hint line below the input shows what a partial/full command name does, and:history --clearwipes both the in-memory and on-disk history.widgets/explorer_pane.py,screens/goto_line.py): the pane's border shows<Mode> (<count>), plus scroll-position indicators (▲/▼) viaborder_subtitle. Every 5th row gets a line-number gutter.:trace/:searchquery terms are highlighted (reverse-video) in row labels.Ctrl+Gopens a small modal to jump straight to item N.widgets/evidence_pane.py): ↑/↓/Page Up/Page Down/Home/End now actually scroll long citations. This required wrapping the pane's content in aVerticalScrollinstead of a bareStatic— Textual only treats container widgets asis_scrollable, so a leafStatic's scroll actions silently no-op regardless of overflow.renderableis proxied through so existing callers/tests keep working unchanged.widgets/header_line.py,widgets/status_line.py): the header now shows workspace stats (artifact count, size, well count) alongside the active well and a<command> took <ms>msruntime readout. The status line color-codes success (green) vs. error (red), and shows a transient yellow "running…" indicator the instant a command is dispatched, before its worker thread returns.tests/tui/test_workflows.py): five Pilot-driven end-to-end workflows — search→select→show→note, well switching mid-session, command history surviving an app restart, Explorer goto-line + Evidence scrolling, and error-recovery (bad command → good command, both landing correctly in history).Phases 2–6 of the plan (result pagination, workspace-stats caching,
:bookmark/:export/:diffcommands, theme/layout/glyph customization, perf benchmarking, and the user/architecture docs) are intentionally left for follow-up work — this PR is scoped to the "MUST HAVE" Phase 1 items plus their test coverage, done thoroughly rather than touching every phase shallowly.No new dependencies; no changes to the
:-command grammar's existing commands (:history --clearis an additive subcommand) or to provenance/layer logic.Checklist
ruff format --check src testspasses (for all touched files)ruff check src testspasses (for all touched files; two pre-existing unused imports elsewhere and one pre-existing long line are untouched and out of scope)pyrightpasses with zero errors on all touched filespytestpasses, and I added/updated tests for the behavior changedocs/cli.md/docs/architecture.md— not updated; this PR only touches TUI-internal behavior (no CLI command, schema, or provenance changes)How was this tested?
pytest tests/tui -q— 73 passed (plus one pre-existing unrelated failure intest_home_lists_recent_activityand one pre-existing flaky teardown error intest_tui_makes_no_network_access, both present onmainbefore this branch and untouched by this PR).ruff check/ruff format --check/pyrighton every file this PR touches.test_command_history.py,test_header_updates.py.test_explorer_navigation.py,test_evidence_pane_scrolling.py,test_workflows.py.Generated by Claude Code