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

8.2 KiB
Raw Permalink Blame History

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 --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 --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 --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 --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 --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 --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 --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 --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 --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.