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>
This commit is contained in:
203
plans/tui-prd-v2-frankentui.feedback-2.md
Normal file
203
plans/tui-prd-v2-frankentui.feedback-2.md
Normal file
@@ -0,0 +1,203 @@
|
||||
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.
|
||||
Reference in New Issue
Block a user