I excluded the two items in your `## Rejected Recommendations` and focused on net-new improvements. These are the highest-impact revisions I’d make. ### 1. Fix the package graph now (avoid a hard Cargo cycle) Your current plan has `root -> optional lore-tui` and `lore-tui -> lore (root)`, which creates a cyclic dependency risk. Split shared logic into a dedicated core crate so CLI and TUI both depend downward. ```diff diff --git a/PRD.md b/PRD.md @@ ## 9.1 Dependency Changes -[workspace] -members = [".", "crates/lore-tui"] +[workspace] +members = [".", "crates/lore-core", "crates/lore-tui"] @@ -[dependencies] -lore-tui = { path = "crates/lore-tui", optional = true } +[dependencies] +lore-core = { path = "crates/lore-core" } +lore-tui = { path = "crates/lore-tui", optional = true } @@ # crates/lore-tui/Cargo.toml -lore = { path = "../.." } # Core lore library +lore-core = { path = "../lore-core" } # Shared domain/query crate (acyclic graph) ``` ### 2. Stop coupling TUI to `cli/commands/*` internals Calling CLI command modules from TUI is brittle and will drift. Introduce a shared query/service layer with DTOs owned by core. ```diff diff --git a/PRD.md b/PRD.md @@ ## 4.1 Module Structure - action.rs # Async action runners (DB queries, GitLab calls) + action.rs # Task dispatch only + service/ + mod.rs + query.rs # Shared read services (CLI + TUI) + sync.rs # Shared sync orchestration facade + dto.rs # UI-agnostic data contracts @@ ## 10.2 Modified Files -src/cli/commands/list.rs # Extract query_issues(), query_mrs() as pub fns -src/cli/commands/show.rs # Extract query_issue_detail(), query_mr_detail() as pub fns -src/cli/commands/who.rs # Extract query_experts(), etc. as pub fns -src/cli/commands/search.rs # Extract run_search_query() as pub fn +crates/lore-core/src/query/issues.rs # Canonical issue queries +crates/lore-core/src/query/mrs.rs # Canonical MR queries +crates/lore-core/src/query/show.rs # Canonical detail queries +crates/lore-core/src/query/who.rs # Canonical people queries +crates/lore-core/src/query/search.rs # Canonical search queries +src/cli/commands/*.rs # Consume lore-core query services +crates/lore-tui/src/action.rs # Consume lore-core query services ``` ### 3. Add a real task supervisor (dedupe + cancellation + priority) Right now tasks are ad hoc and can overrun each other. Add a scheduler keyed by screen+intent. ```diff diff --git a/PRD.md b/PRD.md @@ ## 4.5 Async Action System -The `Cmd::task(|| { ... })` pattern runs a blocking closure on a background thread pool. +The TUI uses a `TaskSupervisor`: +- Keyed tasks (`TaskKey`) to dedupe redundant requests +- Priority lanes (`Input`, `Navigation`, `Background`) +- Cooperative cancellation tokens per task +- Late-result drop via generation IDs (not just search) @@ ## 4.3 Core Types +pub enum TaskKey { + LoadScreen(Screen), + Search { generation: u64 }, + SyncStream, +} ``` ### 4. Correct sync streaming architecture (current sketch loses streamed events) The sample creates `tx/rx` then drops `rx`; events never reach update loop. Define an explicit stream subscription with bounded queue and backpressure policy. ```diff diff --git a/PRD.md b/PRD.md @@ ## 4.4 App — Implementing the Model Trait - let (tx, _rx) = std::sync::mpsc::channel::(); + let (tx, rx) = std::sync::mpsc::sync_channel::(1024); + // rx is registered via Subscription::from_receiver("sync-stream", rx) @@ - let result = crate::ingestion::orchestrator::run_sync( + let result = crate::ingestion::orchestrator::run_sync( &config, &conn, |event| { @@ - let _ = tx.send(Msg::SyncProgress(event.clone())); - let _ = tx.send(Msg::SyncLogLine(format!("{event:?}"))); + if tx.try_send(Msg::SyncProgress(event.clone())).is_err() { + let _ = tx.try_send(Msg::SyncBackpressureDrop); + } + let _ = tx.try_send(Msg::SyncLogLine(format!("{event:?}"))); }, ); ``` ### 5. Upgrade data-plane performance plan (keyset pagination + index contracts) Virtualized list without keyset paging still forces expensive scans. Add explicit keyset pagination and query-plan CI checks. ```diff diff --git a/PRD.md b/PRD.md @@ ## 9.3 Phase 0 — Toolchain Gate -7. p95 list query latency < 75ms on synthetic fixture (10k issues, 5k MRs) +7. p95 list page fetch latency < 75ms using keyset pagination (10k issues, 5k MRs) +8. EXPLAIN QUERY PLAN must show index usage for top 10 TUI queries +9. No full table scan on issues/MRs/discussions under default filters @@ -8. p95 search latency < 200ms on synthetic fixture (50k documents, lexical mode) +10. p95 search latency < 200ms on synthetic fixture (50k documents, lexical mode) +## 9.4 Required Indexes (GA blocker) +- `issues(project_id, state, updated_at DESC, iid DESC)` +- `merge_requests(project_id, state, updated_at DESC, iid DESC)` +- `discussions(project_id, entity_type, entity_iid, created_at DESC)` +- `notes(discussion_id, created_at ASC)` ``` ### 6. Enforce `EntityKey` everywhere (remove bare IID paths) You correctly identified multi-project IID collisions, but many message/state signatures still use `i64`. Make `EntityKey` mandatory in all navigation and detail loaders. ```diff diff --git a/PRD.md b/PRD.md @@ ## 4.3 Core Types - IssueSelected(i64), + IssueSelected(EntityKey), @@ - MrSelected(i64), + MrSelected(EntityKey), @@ - IssueDetailLoaded(IssueDetail), + IssueDetailLoaded { key: EntityKey, detail: IssueDetail }, @@ - MrDetailLoaded(MrDetail), + MrDetailLoaded { key: EntityKey, detail: MrDetail }, @@ ## 10.10 State Module — Complete - Cmd::msg(Msg::NavigateTo(Screen::IssueDetail(iid))) + Cmd::msg(Msg::NavigateTo(Screen::IssueDetail(entity_key))) ``` ### 7. Harden filter/search semantics (strict parser + inline diagnostics + explain scores) Current filter parser silently ignores unknown fields; that causes hidden mistakes. Add strict parse diagnostics and search score explainability. ```diff diff --git a/PRD.md b/PRD.md @@ ## 10.12.1 Filter Bar Widget - _ => {} // Unknown fields silently ignored + _ => self.errors.push(format!("Unknown filter field: {}", token.field)) + pub errors: Vec, // inline parse/validation errors + pub warnings: Vec, // non-fatal coercions @@ ## 5.6 Search -- **Live preview:** Selected result shows snippet + metadata in right pane +- **Live preview:** Selected result shows snippet + metadata in right pane +- **Explain score:** Optional breakdown (lexical, semantic, recency, boosts) for trust/debug ``` ### 8. Add operational resilience: safe mode + panic report + startup fallback TUI failures should degrade gracefully, not block usage. ```diff diff --git a/PRD.md b/PRD.md @@ ## 3.1 Risk Matrix +| Runtime panic leaves user blocked | High | Medium | Panic hook writes crash report, restores terminal, offers fallback CLI command | @@ ## 10.3 Entry Point +pub fn launch_tui(config: Config, db_path: &Path) -> Result<(), LoreError> { + install_panic_hook_for_tui(); // terminal restore + crash dump path + ... +} @@ ## 8.1 Global (Available Everywhere) +| `:` | Show fallback equivalent CLI command for current screen/action | ``` ### 9. Add a “jump list” (forward/back navigation, not only stack pop) Current model has only push/pop and reset. Add browser-like history for investigation workflows. ```diff diff --git a/PRD.md b/PRD.md @@ ## 4.7 Navigation Stack Implementation pub struct NavigationStack { - stack: Vec, + back_stack: Vec, + current: Screen, + forward_stack: Vec, + jump_list: Vec, // recent entity/detail hops } @@ ## 8.1 Global (Available Everywhere) +| `Ctrl+o` | Jump backward in jump list | +| `Ctrl+i` | Jump forward in jump list | ``` If you want, I can produce a single consolidated “PRD v2.1” patch that applies all nine revisions coherently section-by-section.