Conversation
Co-located Rust workspace under tui/, sibling to the Python library. sqlx with compile-time-checked queries against the live prospecta schema — honors P19 (durable observability) by treating the schema as the contract. This commit ships only the scaffold: - Cargo.toml with locked stack (ratatui 0.30, crossterm 0.29, sqlx 0.8, tokio 1, clap 4) - main.rs with --smoke flag that connects via DATABASE_URL and counts banks - .env.example, .gitignore, README pointing at the handoff doc The compile-time SELECT against banks verifies the toolchain end-to-end: build only succeeds when DATABASE_URL points at a migrated prospecta DB. Smoke verified against local docker compose substrate (schema v3, 1 bank). Next: Ratatui event loop + bank-list view, then event-stream view, then fan out across manage/use/search/observability axes. See HANDOFF in ~/forge/notes/ for the v0.1 plan and locked decisions.
The skeleton everything else hangs off: - src/lib.rs exposes the modules so integration tests and the binary share one source of truth; main.rs imports app+config from the lib. - src/theme.rs centralizes styles + honors NO_COLOR (accessibility convention). - src/views/common.rs renders the chrome (header bar with redacted DATABASE_URL, footer hints, ?-overlay help popup) and a chrome_layout helper every view uses. - src/views/manage.rs implements the bank-list Table widget with selection, empty-state copy, and an error-state panel. - src/db/banks.rs runs the canonical 'bank stats one-liner' from docs/observability.md (correlated subqueries for documents/items/retains/recalls count rollups) via sqlx::query! — compile-time-checked against the live schema. - src/app.rs wires the event loop: 250ms poll cadence (a free hook for the upcoming event-stream view), j/k/↑/↓/g/G navigation, r to refresh, ? to help, q/Esc/Ctrl-C to quit. Help overlay swallows the next key to close — keeps the UX consistent (no per-key special-case). - src/config.rs has redact_database_url + validate; 5 unit tests cover redaction and scheme validation. - tests/render.rs covers the bank-list view headless against TestBackend: populated render, empty state, error state, and selection wrap. 9 tests passing (5 unit + 4 integration). Cargo build clean. Smoke verified against the live substrate: 3 banks visible.
Axis 4 'review observability' entry point — tail view of the append-only
event tables, interleaved by created_at DESC, color-coded by kind.
db/events.rs:
- One sqlx::query! per table (retain/recall/formulate/llm_call/sweep_pass)
so compile-time schema checking stays intact — UNION ALL across
heterogeneous tables defeats type inference.
- Five queries run concurrently via tokio::try_join!; wall-clock is the
slowest, not the sum.
- Per-kind row → Event with normalized {kind, id, bank_id, created_at,
duration_ms, has_error, summary}, then sorted DESC and truncated to
EVENTS_TOTAL_CAP (200).
- Optional bank filter is wired through (None = all banks); v0.1 leaves
the bank-filter keybinding for a future commit.
views/observability.rs:
- Ratatui Table with kind color-coding (LightGreen retain, LightBlue
recall, LightCyan formulate, LightYellow llm_call, Magenta sweep);
NO_COLOR honored.
- Auto-scroll mode pins selection to row 0 across refreshes; manual
navigation (j/k/Up/Down) turns it off, 'g'/Home/'f' toggles it back.
Title shows ▼ marker while auto-scroll is on.
- Duration humanized: <1000ms shown as 'NNNms', ≥1000ms as 'N.Ns'.
- Error marker '!' surfaces parse_fallback, llm_call.error, and
sweep_pass.errors_count > 0.
app.rs:
- Two-tab navigation (banks ↔ events) via Tab/Shift-Tab/1/2.
- Per-tab refresh: events tab polls every 750ms (only when visible);
banks tab refreshes on 'r'. The 100ms key-poll cadence stays
responsive while the 750ms refresh tick fires on its own schedule.
- Help overlay enumerates the new keybindings (Tab, 1/2, f).
main.rs:
- New --dump-events flag dumps the recent-events query to stdout
(parallel to --smoke). Sanity check without launching the TUI.
tests/render_events.rs adds 4 integration tests: all-kinds render,
empty state, error state, auto-scroll state machine.
Test suite: 13 passing (5 unit + 8 integration). dump-events verified
against the live substrate (14 fixture events across all five kinds).
Adds the manage-axis depth that pairs with banks rollup and event stream. Three tabs at v0.1: banks, docs, events. Same chrome shape; the drill-down state machine carries a level field so docs renders documents-or-items. db/documents.rs: - list_for_bank: documents in a bank with rolled-up item_count via correlated subquery, ordered by created_at DESC, paginated by limit+offset. - list_for_document: memory_items for one document, ordered for stable display. Both via sqlx::query!, schema-verified at compile time. views/docs.rs: - DocsState carries (level, bank_id, documents, focused_document, items) plus independent TableState per level — selection on items doesn't clobber selection on documents. - Documents table: short uuid prefix, items count, source, tags, created_at. - Items table: short uuid, src marker (llm/caller), tags, content. - Empty-state panels distinguish 'no bank picked' from 'bank has no docs'. - short_uuid() helper exported for the CLI dump path. app.rs: - Three-tab cycle via Tab/⇧Tab/1/2/3; descent via Enter/→/l from banks (auto-binds the selected bank and loads its documents) and from docs-level (loads items for the selected document); ascent via Esc/←/h. Esc at the top level still quits (preserves v0.1 contract from c3877ea). - Per-tab refresh on 'r' dispatches to the right loader. - Help overlay enumerates the new keybindings. main.rs: - New --dump-docs <bank> CLI flag for live verification without launching the TUI; --doc-id <uuid> descends into one document's memory_items. - SIGPIPE handler set to SIG_DFL so 'prospecta-tui --dump-docs ... | head' exits cleanly instead of panicking. tests/render_docs.rs: 5 integration tests (no-bank hint, documents render, items render, navigation wraps independently per level, empty-bank hint). Live-verified against the substrate: 3 documents in default bank, 6/3/3 items respectively. 18 tests passing total (5 unit + 13 integration).
…vent
The load-bearing observability surface per docs/observability.md
'Reading a recall event end-to-end'. Pressing Enter on a recall row in
the event stream opens a one-pane thread view showing:
• the preceding formulate_event (verbatim user message + raw LLM JSON
expansion + parse_fallback diagnostics including error_kind)
• the recall body (queries list, per-chunk results with rank/source/
document_id/per-channel scores/200-char preview, synthesis text)
• the paired llm_calls in the ±10s window (prompt_name, verbatim
prompt_text and response_text, duration, error markers)
db/recall_thread.rs:
- Three sqlx::query! calls (recall + LATERAL-style most-recent-formulate-
before-recall + ±10s llm_calls window), with formulate+llm_calls
fetched concurrently via tokio::try_join!.
- Schema-as-contract preserved end-to-end; all compile-time-checked.
views/recall_thread.rs:
- Single scrollable Paragraph rendering — multi-section layout via
push_section helpers, ratatui Wrap{trim:false} handles long content.
- chunk_lines() soft-wraps long verbatim payloads at ~90 cols on word
boundaries while preserving the raw newlines in JSON / prompts.
- Honest about missing data: explicit hints when results JSONB is null
(pre-0003 row), when synthesis is null (plain recall, not recall_synth),
when llm_calls window is empty, when persist_llm_text=false stripped
the verbatim prompt/response.
app.rs:
- recall_thread overlay swallows keys on the events tab while open;
navigation = scroll (j/k/↑/↓, Space/PgDn, g/Home), Esc/← closes.
- Enter on a non-recall event row prints a status hint rather than
silently doing nothing.
- Footer hints + help overlay updated for both events-tab modes.
main.rs: --dump-recall-thread <RECALL_ID> CLI verifier, same family as
--smoke / --dump-events / --dump-docs.
tests/render_recall_thread.rs: 6 integration tests covering empty state,
load-error state, full multi-section render (all four section headers,
queries list, per-chunk results with scores, synthesis, llm_call
prompt/response sections), missing-formulate, missing-results-and-
synthesis, scroll state machine.
Live-verified: dump_recall_thread on recall id=1 surfaces the preceding
formulate, 3 llm_calls in the ±10s window, and the synthesis. 24 tests
green (5 unit + 19 integration).
…vent
Sibling to the recall thread view per docs/observability.md. From the
event stream, Enter on a retain row opens a one-pane thread showing:
• the retain_event metadata (items_count, caller_supplied flag,
duration, error)
• the source document (id, source, content_hash, tags, full
original_text — P5 honored, no truncation)
• every memory_item the retain produced (id, llm/caller marker, tags,
content — the question-shaped index_text strings)
• the paired index_text llm_call (verbatim prompt_text and
response_text), plus the verbatim index_text_generated array from
the retain row when LLM-authored
db/retain_thread.rs:
- Four sqlx::query! calls: retain row, document by id, items by
document_id, and the nearest index_text llm_call within ±10s. The
llm_call query orders by ABS(EXTRACT EPOCH ...) so the closest call
to the retain timestamp wins.
- Document/items/index_text fetched concurrently via tokio::try_join!.
- Schema-as-contract preserved end-to-end.
views/retain_thread.rs:
- Single scrollable Paragraph rendering, same shape as recall_thread.
- Honest about boundaries: distinct hints for 'no document linked'
(retain pre-dated documents column or doc deleted), 'no items
linked', 'caller-supplied → no LLM call expected', 'no index_text
call in window' (with raw_llm_response fallback when present), and
'no prompt/response captured' (persist_llm_text=false or pre-0003).
- chunk_lines() soft-wraps long original_text and verbatim payloads
on word boundaries.
app.rs:
- retain_thread_open state + open/close helpers.
- Enter on Events tab routes by event kind: Recall → recall thread,
Retain → retain thread, anything else → status hint.
- Retain-thread overlay swallows the same scroll keys as the recall
thread overlay (j/k, Space/PgDn, g/Home, Esc/← closes).
- Footer hints union both threads under one Events-overlay shape.
main.rs:
- --dump-retain-thread <RETAIN_ID> CLI verifier, same family as
--dump-recall-thread.
tests/render_retain_thread.rs: 6 integration tests covering empty,
load-error, full multi-section render, caller-supplied skip path,
missing-document hint, scroll state.
Live-verified via --dump-retain-thread against retain id=1: retain
metadata + index_text llm_call surface correctly, honest 'none linked'
report when document_id is null on the seed row. 30 tests passing
(5 unit + 25 integration).
Fourth tab, workhorse register. Five concurrent compile-time-checked queries condensed into a 2x2 card grid + per-prompt llm_call table. db/dashboard.rs: - One sqlx::query! per axis (bank meta, doc counts, retain+recall with mean durations, formulate fallback rate, per-prompt llm_calls + total llm_ms). Five fetches in parallel via tokio::try_join!. - 24h window matches the cheat-sheet 'bank stats one-liner' and 'volume + timing distribution' recipes from docs/observability.md. views/dashboard.rs: - 2x2 card grid: substrate (counts), activity (24h volumes), latency (24h means + Σ llm time), health (parse_fallback rate + llm error rate). Bottom strip: per-prompt llm_calls table. - parse_fallback rate is the load-bearing P18 health signal — color graduates green/yellow/red at 0% / >0% / ≥10% thresholds with formatted percentage + raw fraction (e.g. '15.0% (3/20)'). - Latency formatting humanized: <1s as 'NNNms', ≥1s as 'N.Ns'; total llm time over a minute renders as 'N.Nm'. app.rs: - Fourth tab plumbed through Tab/⇧Tab cycle, 1/2/3/4 direct jumps, Tab::Dashboard navigation arms (no-op on j/k/g/G — dashboard is a read-only surface), r refresh, render dispatch, footer hints. - 5s background poll while the dashboard tab is visible; tighter interval than the events stream because health signals settle slower but matter as live indicators. Pinned to currently-selected bank; falls through to bank-list selection when the user tabs in cold. main.rs: - --dump-dashboard <bank> CLI flag — fifth verifier in the family. Live-verified against the real substrate: 3 docs, 12 items, 50% parse_fallback rate (1/2 formulates), per-prompt breakdown shows formulate/index_text/synthesize all in the window. tests/render_dashboard.rs: 4 integration tests cover empty state, load error, full multi-card render (substrate/activity/latency/health cards + llm table + P18 percentage + bank id), and no-activity state (hint text when 24h window is empty). 34 tests green (5 unit + 29 integration). cargo build + cargo fmt clean. Schema-as-contract preserved end-to-end via sqlx::query!.
The "is the sweeper running? did it stop? is it erroring?" signal, sitting on the dashboard tab beside the llm_calls table — same workhorse register, same bank scope, same is-this-healthy question. Started as a card per the last handoff; it earned a table once the per-corpus shape was clear. db/dashboard.rs: - SweepStat struct + fetch_sweeps(), sixth concurrent query folded into the existing tokio::try_join!. Derives from the append-only sweep_passes history via DISTINCT ON (corpus_path) ORDER BY started_at DESC — the history is the durable truth and survives a sweeper_state snapshot that was never written (which is exactly the live state: 2 passes, 0 snapshot rows). Schema-as-contract preserved via sqlx::query!. views/dashboard.rs: - Bottom region now splits 55/45: llm_calls table | sweep table. Sweep table is one row per corpus showing the latest pass: corpus tail, status (ok/error/running), files indexed/seen, error count, and age-since-start. - Status colors graduate: green 'ok', red 'error' (fatal error OR any per-file errors_count > 0), accent 'running' (started, never ended). errors_count cell reddens when nonzero. Age column is the load-bearing "did it stop?" signal — coarse 12s/5m/3h/4d so a stale last-pass reads loud. Empty state: "(this bank has never been swept)". - shorten_path tails long corpus paths (/home/dt/notes -> notes); format_age is the coarse relative-time helper. main.rs: - --dump-dashboard now prints the sweep section too (sixth signal in the family verifier). Live-verified against the real substrate: /home/dt/notes reads 'error' (errs=1) — DISTINCT ON correctly picked the most recent degraded pass over the earlier healthy one. tests/render_dashboard.rs: dummy_stats() carries two sweeps (one healthy, one errored). Full-render test asserts the panel title, headers, corpus tail, and both ok/error statuses paint. Two new tests: never-swept hint and in-flight 'running' status. 36 tests green (5 unit + 31 integration). cargo build + cargo fmt clean, zero warnings.
The fourth and final feature axis (manage/use/search/observability). Fifth tab `search` with a query box + results table + preview pane. Searches both tsvector channels the library indexes and reports which one(s) surfaced each hit — making the P14 honest-safety-net visible at the operator surface. db/search.rs: - SearchHit + search(): one websearch_to_tsquery (same parser the library's lexical legs use, so operator queries behave like agent-path retrieval — quoted phrases, OR, -negation) CROSS JOINed against memory_items. Reports ts_rank and @@ match for BOTH content_tsv (the index_text questions) and body_tsv (the source body, migration 0002). ORDER BY GREATEST(rank) DESC. Empty queries short-circuit to no results. Schema-as-contract via query!. - channel_label() collapses (content_hit, body_hit) to both/question/body — the load-bearing signal: did the anticipated question match, or did the body channel rescue a doc whose index_text drifted from query language? views/search.rs: - Two input modes (Editing / Browsing) so the app loop knows whether keys edit the query or navigate results. Three zones: query box (top, cursor when editing), results table (channel/rank/gen/matched-index_text/source), preview pane (full index_text + body chunk + per-channel rank breakdown for the selected hit). Channel labels color-graduate: both=green, question=accent, body=blue. app.rs: - Fifth tab plumbed through: `5` jump, Tab/⇧Tab cycle (now 5-way), r runs the query, j/k/g/G navigate results. Editing mode intercepts text input ahead of the main match (mirrors the thread-overlay pattern): chars append, Backspace deletes, Enter runs + lands in Browsing, Esc toggles back to the query box (or quits when the list is empty). bind_search_bank() falls through to the banks-tab selection, mirroring how dashboard auto-binds. Footer hints + `?` help overlay updated for both modes. main.rs: - --dump-search "BANK:QUERY" verifier (sixth in the family). Live-verified against the real substrate: "default:bilateral synthesis" → 6 hits with correct both/body attribution; "compile-time checking" → 1 question-channel hit at rank 0.461 (denser match in index_text than diffuse body); a nonsense term → 0 hits. All four channel states confirmed. tests/render_search.rs: 6 tests — pre-search prompt, no-results hint, the three channel labels + ranks painting, preview pane content, editing-mode query echo, and select_next/prev clamping. 42 tests green (5 unit + 37 integration). cargo build + cargo fmt clean, zero warnings.
Closes the manual-ops milestone (#1): the last write-path surface. Read paths go direct via sqlx; the retain write path shells out to the Python `prospecta retain` so the LLM-in-the-loop work and the spine stay Python-owned — the schema is the contract, the CLI is the writer (handoff decision 3). shell.rs: - Async shell-out via tokio::process. RetainArgs → `prospecta retain` with --source / --tags / --index-text (repeatable). Connection + embedder env (DATABASE_URL, PROSPECTA_EMBEDDER, PROSPECTA_EMBED_MODEL) inherit from the TUI; only PROSPECTA_BANK is pinned per-form. Invocation is configurable so the same binary works in dev and deployed: PROSPECTA_CLI — command prefix (default `prospecta`; e.g. "uv run prospecta") PROSPECTA_CLI_CWD — child working dir (lets `uv run` resolve the project) - RetainOutcome is the honest contract: ok flag, parsed document_id, verbatim stdout/stderr, exit code. Never fabricates success. views/retain.rs: modal form — content (multiline, required) / source / tags / index_text, field focus with Tab/arrows, a result banner that graduates idle→running→ok(document_id)→failed(reason). Centered overlay, NO_COLOR-safe. app.rs: - Retain modal opened with `R` from the search tab (the "use" axis pairs with search). Modal intercepts all input while open: text edits the focused field, Tab/↑↓ move fields, Enter is newline in content / next-field elsewhere, Ctrl-S submits, Esc cancels. Drawn last over the full frame. - submit_retain() records the honest outcome — document UUID on success, or the CLI's real stderr via stderr_tail() which prefers the `error:` line and filters litellm/provider warnings + progress-bar noise. - Footer + `?` help updated; tokio gains the `process` feature. main.rs: --dump-retain "BANK:CONTENT" verifier (seventh in the --dump-* family). Auto-derives index_text so it runs offline. Verified end-to-end against the live tui-st (dim-384) bank, fully offline via `PROSPECTA_CLI="uv run prospecta" PROSPECTA_EMBEDDER=sentence-transformers`: - success: retained document ead4453b-… (exit 0), real UUID parsed - failure: nonexistent bank surfaces the real FK-constraint error, no fake success — the honest outcome contract holds both ways. tests: render_retain.rs (9 — fields, typed content, ok/running/failed banners, field cycling, can_submit gating, focused_mut, reset) + app unit tests (5 — opt + stderr_tail error-line preference / noise filtering / fallbacks). 56 tests green (10 unit + 46 integration). cargo build + cargo fmt clean, zero warnings.
Closes the remaining issue #1 ship-readiness boxes (README, cargo install) and adds a requirable CI status check. Offline build (.sqlx/ cache): - sqlx query! macros previously needed a live DATABASE_URL (or .env) at compile time, so a clean `cargo install` by anyone else failed without a running DB. Committed the .sqlx/ offline query cache (24 queries) generated via `cargo sqlx prepare`. The crate now builds with SQLX_OFFLINE — no DB needed at compile time. - Verified: `cargo clean -p prospecta-tui && SQLX_OFFLINE=true cargo build` with DATABASE_URL unset builds clean; `cargo install --path tui` produces a working `prospecta-tui` on PATH; `--help` runs. tui/README.md: real install/run/keybindings doc replacing the scaffold stub — tabs table, full keybinding reference, the manual-retain shell-out env (PROSPECTA_CLI / PROSPECTA_CLI_CWD + the offline sentence-transformers path), the --dump-* headless verifier family, and the offline-build / cargo sqlx prepare note. Top-level README: a "prospecta-tui — the interactive inspection surface" pointer under Observability, framing the TUI as the legible shape of the durable trace. CI (.github/workflows/tui-ci.yml): fmt --check + build + test on push/PR touching tui/. Tests are hermetic — ratatui TestBackend + dummy fixtures + the .sqlx offline cache, no Postgres needed — so this runs anywhere and gives the branch-protection ruleset a status check it can require. Verified the exact CI commands pass locally offline + --locked (56 tests green). Note on testcontainers (still open on #1): the existing 56 tests are already CI-hermetic but they exercise render/format logic with in-memory fixtures, not the live sqlx query path. True integration tests against a throwaway Postgres are a distinct, focused piece of work — deliberately NOT half-built into this ship-readiness PR. Tracked separately.
There was a problem hiding this comment.
Pull request overview
Lands prospecta-tui v0.1 — a new co-located Rust + Ratatui workspace under tui/ that provides an interactive terminal inspection surface for the prospecta memory substrate. Read paths use sqlx direct against Postgres with compile-time schema checking (via a committed .sqlx/ offline query cache), and the retain write path shells out to the Python prospecta CLI. Five tabs cover banks, docs, events (with recall/retain thread drill-downs), a per-bank dashboard (24h health + sweep status), and cross-channel search with a retain form.
Changes:
- New
tui/Rust crate: binary + library, modularviews/(banks, docs, events, recall/retain threads, dashboard, search, retain form),db/query modules,shell.rsfor CLI shell-out,theme.rs,config.rs, plus README,.env.example, and 24 committed.sqlx/query cache files for offline builds. - 56 hermetic tests across 7 integration files using
ratatui::TestBackend+ in-memory fixtures (no Postgres required). - New
.github/workflows/tui-ci.ymlrunningfmt --check,build, andtestwithSQLX_OFFLINE=true; top-level README pointer added under Observability.
Reviewed changes
Copilot reviewed 38 out of 63 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
tui/Cargo.toml, tui/.gitignore, tui/.env.example |
Crate manifest, ignores, local-dev env scaffold. |
tui/src/main.rs |
CLI entrypoint: --smoke + --dump-* verifiers, TUI bootstrap with raw-mode/alt-screen restore. |
tui/src/lib.rs, tui/src/views/mod.rs, tui/src/db/mod.rs |
Module wiring. |
tui/src/config.rs |
DATABASE_URL validation + password redaction (with unit tests). |
tui/src/shell.rs |
Async shell-out to prospecta retain honoring PROSPECTA_CLI/PROSPECTA_CLI_CWD. |
tui/src/theme.rs |
Centralized styles + NO_COLOR honoring. |
tui/src/db/{banks,documents,events,dashboard,search,recall_thread,retain_thread}.rs |
Per-topic schema-checked sqlx queries; events fan out to 5 tables in parallel via try_join!. |
tui/src/views/common.rs |
Shared chrome (header/footer/help overlay). |
tui/src/views/manage.rs |
Bank-list table with row selection (contains dead-code workaround flagged). |
tui/src/views/docs.rs |
Two-level documents → memory_items drill-down. |
tui/src/views/observability.rs |
Event-stream tail with kind color-coding + auto-scroll. |
tui/src/views/recall_thread.rs, tui/src/views/retain_thread.rs |
End-to-end thread inspection panes. |
tui/src/views/dashboard.rs |
2x2 health cards + per-prompt llm_calls + sweep-status table. |
tui/src/views/search.rs |
Dual-channel search with input/results/preview split and channel labels. |
tui/src/views/retain.rs |
Modal retain form with field focus, status banner, submit gating. |
tui/tests/render*.rs (7 files) |
Hermetic render + state tests via TestBackend. |
tui/.sqlx/query-*.json (×24) |
Offline query cache for sqlx::query! compile-time checks. |
tui/README.md, README.md |
TUI user-facing docs + top-level pointer. |
.github/workflows/tui-ci.yml |
CI: fmt + build + test, hermetic via SQLX_OFFLINE. |
Files not reviewed (24)
- tui/.sqlx/query-0e31ead873bf598cc23b7148b4f6f6115885be16c1b6e03a754bc253935b5ec7.json: Language not supported
- tui/.sqlx/query-19b9647d6d8bbf688376a2ddee0c58f282ffef1de489f1c32f953191b16d1839.json: Language not supported
- tui/.sqlx/query-1ac0645e1f1bb6bf04be8fa1af7896903ac09374a68aa54804d961b1430c36af.json: Language not supported
- tui/.sqlx/query-1c3cde708f73858657bdf1720958ae1d7274ce70603f675e2348e7d691955183.json: Language not supported
- tui/.sqlx/query-226afdbe4a64c0b0c5895e5ed75f1bec5681efa89e69dff50e618506b1d67465.json: Language not supported
- tui/.sqlx/query-2526e9744ef464aa4fa49f3278c833c105fdbca566580b373ef7ca09f56fb600.json: Language not supported
- tui/.sqlx/query-37e41ab1a55228c51e3ba8686b18218c33c90fd2758cef54bc75c62e72b71b18.json: Language not supported
- tui/.sqlx/query-38d74f1e800e5016e6294a869b6c822ac8906e2072513e859ca68093f1dd2587.json: Language not supported
- tui/.sqlx/query-413de3d76a0cca0b6842befe82f485c7370d4746b22bcb6b0287f2b0c0e2242c.json: Language not supported
- tui/.sqlx/query-4dc3f08975fae225e5a0cb397d5e7c01d9c24e9f296d10efeb9d71fe2686be8c.json: Language not supported
- tui/.sqlx/query-509a7057b0ef3f0a05fcb31fd790e29186ad267636f177a658d9621538ed99b4.json: Language not supported
- tui/.sqlx/query-5edb89eb6a86f53cee05322b23e15d7dbf20143d4fe675c13bf991153c6e7100.json: Language not supported
- tui/.sqlx/query-76f4a0664fb71fb84ede551a9359145520ddbbf9e558d7cc94ee96dabab0c774.json: Language not supported
- tui/.sqlx/query-78ea78e125e1136a3b3ede153577e3d6ba7ac1d234c0a7d3bbe413df71d7cd9e.json: Language not supported
- tui/.sqlx/query-93bba2459ec72364def23751398693acf2398a3cd6c4da1f27d4814e589aa5eb.json: Language not supported
- tui/.sqlx/query-a79f07ab1088ea7f5e2af520ff12db9bf3607632c0a60e7bed841ef077614174.json: Language not supported
- tui/.sqlx/query-ad4394062829cec6b5b2e39c1f32cbf076c3ea3a2f477b1cd45a56a98a8b0930.json: Language not supported
- tui/.sqlx/query-af1615c7e7c64fc5f68da920f7aecfe7d8c236f35dd373bc5a0cfcaee60304f2.json: Language not supported
- tui/.sqlx/query-b3c4ee505669a6bef7a2c51733aec004982e3999b531a22fdaaf96c46dbca7b5.json: Language not supported
- tui/.sqlx/query-bbac0eb64d127f5ab4c5f2153e277e50cab3ee738ea2fb01636583722dee9d4f.json: Language not supported
- tui/.sqlx/query-be142a62e92a4222c91b3c850eb61082f81836aa192395a82a8a0d164beef5ef.json: Language not supported
- tui/.sqlx/query-c5dcc0c422ea6870142e0248efa529a2f43b7898237650e8b9ea339175f26531.json: Language not supported
- tui/.sqlx/query-d7322dd02c8a52308b661f1a8f0e5092a2a5209bf88c08c211e270e88caef151.json: Language not supported
- tui/.sqlx/query-efeffb6d175ad90a9fe69a5c2141b494f6da304ad7b2c3441a23b17855c2142e.json: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Addresses Copilot review on #4. Lines 124-134 were leftover scaffolding from the bank-list commit: an `if let Some(b) = banks.get(i) { let _ = b; }` dance plus `let _ = Style::default;` whose only job was silencing the unused-Style import warning. Removed both no-op blocks and the unused import — the build is genuinely warning-free without the workaround. If the selected-bank detail strip lands in v0.2 it'll come with real rendering, not a placeholder.
Lands prospecta-tui v0.1 — the interactive terminal UI for the prospecta memory substrate. Co-located Rust + Ratatui workspace under
tui/, sibling to the Python library.The schema is the contract: read paths go direct via
sqlx(compile-time-checked against the live schema), the retain write path shells out to the PythonprospectaCLI so the LLM-in-the-loop work and the spine stay library-owned. The TUI never mutates the schema.Five tabs, four feature axes
Enterdrills into documentsmemory_itemsdrill-down (the question-shaped index_text)Enteron a recall/retain row opens the full thread (formulate → recall → synthesis → llm_calls, or retain → document → items → index_text call)content_tsv= anticipated questions,body_tsv= source body); each hit labelledboth/question/bodyso the P14 honest-safety-net is visible.Ropens the retain formManual ops (the write path)
The retain form shells out to
prospecta retain. Connection + embedder env inherit from the TUI; only the bank is pinned per-form. Invocation is configurable (PROSPECTA_CLI/PROSPECTA_CLI_CWD) so the same binary works in dev (uv run prospecta) and deployed. The form reports the honest outcome — the document UUID on success, the verbatim CLI error on failure. It never fabricates success.Verified end-to-end against a live dim-384 bank, fully offline (
PROSPECTA_EMBEDDER=sentence-transformers, no API key): success retains a real document with the UUID parsed from stdout; a bad bank surfaces the real FK-constraint error. (The offline embedder path itself shipped in #3.)Ship-readiness
.sqlx/query cache (24 queries) means the crate builds withSQLX_OFFLINEand no database at compile time.cargo install --path tuiproduces a workingprospecta-tuion PATH; verified from a cleancargo cleanwithDATABASE_URLunset..github/workflows/tui-ci.ymlrunsfmt --check+build+teston PRs touchingtui/. Tests are hermetic (ratatuiTestBackend+ in-memory fixtures + the.sqlxcache, no Postgres), so this runs anywhere and gives the branch-protection ruleset a status check it can require.Tests
56 green (10 unit + 46 integration), all hermetic —
fmt,build, andtestpass offline with--locked. Coverage spans every view's render paths, the dashboard health/sweep signals, the search channel attribution, and the retain form (fields, status banners, submit gating, thestderr_tailerror-line preference that filters provider noise).Out of scope (tracked, deliberately not half-built)
prospecta recall-synth --jsonsubcommand on the library side; tracked separately.Commits
Scaffold → bank list → event stream → docs drill-down → recall/retain threads → dashboard → sweep panel → search axis → manual retain → ship-readiness. Tracking checklist on #1.
⚒️ Forge — built bench-beside-bench with @witt3rd.