Comprehensive product requirements document for the gitlore TUI built on
FrankenTUI's Elm architecture (Msg -> update -> view). The PRD (7800+
lines) covers:
Architecture: Separate binary crate (lore-tui) with runtime delegation,
Elm-style Model/Cmd/Msg, DbManager with closure-based read pool + WAL,
TaskSupervisor for dedup/cancellation, EntityKey system for type-safe
entity references, CommandRegistry as single source of truth for
keybindings/palette/help.
Screens: Dashboard, IssueList, IssueDetail, MrList, MrDetail, Search
(lexical/hybrid/semantic with facets), Timeline (5-stage pipeline),
Who (expert/workload/reviews/active/overlap), Sync (live progress),
CommandPalette, Help overlay.
Infrastructure: InputMode state machine, Clock trait for deterministic
rendering, crash_context ring buffer with redaction, instance lock,
progressive hydration, session restore, grapheme-safe text truncation
(unicode-width + unicode-segmentation), terminal sanitization (ANSI/bidi/
C1 controls), entity LRU cache.
Testing: Snapshot tests via insta, event-fuzz, CLI/TUI parity, tiered
benchmark fixtures (S/M/L), query-plan CI enforcement, Phase 2.5
vertical slice gate.
9 plan-refine iterations (ChatGPT review -> Claude integration):
Iter 1-3: Connection pool, debounce, EntityKey, TaskSupervisor,
keyset pagination, capability-adaptive rendering
Iter 4-6: Separate binary crate, ANSI hardening, session restore,
read tx isolation, progressive hydration, unicode-width
Iter 7-9: Per-screen LoadState, CommandRegistry, InputMode, Clock,
log redaction, entity cache, search cancel SLO, crash diagnostics
Also includes the original tui-prd.md (ratatui-based, superseded by v2).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
203 lines
8.2 KiB
Markdown
203 lines
8.2 KiB
Markdown
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::<Msg>();
|
||
+ let (tx, rx) = std::sync::mpsc::sync_channel::<Msg>(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<String>, // inline parse/validation errors
|
||
+ pub warnings: Vec<String>, // 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<Screen>,
|
||
+ back_stack: Vec<Screen>,
|
||
+ current: Screen,
|
||
+ forward_stack: Vec<Screen>,
|
||
+ jump_list: Vec<Screen>, // 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. |