Files
gitlore/plans/tui-prd-v2-frankentui.feedback-2.md
Taylor Eernisse 1161edb212 docs: add TUI PRD v2 (FrankenTUI) with 9 plan-refine iterations
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>
2026-02-11 08:11:26 -05:00

203 lines
8.2 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
I excluded the two items in your `## Rejected Recommendations` and focused on net-new improvements.
These are the highest-impact revisions Id 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.