Files
gitlore/plans/tui-prd-v2-frankentui.feedback-8.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.4 KiB
Raw Permalink Blame History

Below are the strongest revisions Id make. I intentionally avoided anything in your ## Rejected Recommendations.

  1. Unify commands/keybindings/help/palette into one registry Rationale: your plan currently duplicates action definitions across execute_palette_action, ShowCliEquivalent, help overlay text, and status hints. That will drift quickly and create correctness bugs. A single CommandRegistry makes behavior consistent and testable.
diff --git a/PRD.md b/PRD.md
@@ 4.1 Module Structure
+    commands.rs                   # Single source of truth for actions, keybindings, CLI equivalents

@@ 4.4 App — Implementing the Model Trait
-    fn execute_palette_action(&self, action_id: &str) -> Cmd<Msg> { ... big match ... }
+    fn execute_palette_action(&self, action_id: &str) -> Cmd<Msg> {
+        if let Some(spec) = self.commands.get(action_id) {
+            return self.update(spec.to_msg(self.navigation.current()));
+        }
+        Cmd::none()
+    }

@@ 8. Keybinding Reference
+All keybinding/help/status/palette definitions are generated from `commands.rs`.
+No hardcoded duplicate maps in view/state modules.
  1. Replace ad-hoc key flags with explicit input state machine Rationale: pending_go + go_prefix_instant is fragile and already inconsistent with documented behavior. A typed InputMode removes edge-case bugs and makes prefix timeout deterministic.
diff --git a/PRD.md b/PRD.md
@@ 4.4 LoreApp struct
-    pending_go: bool,
-    go_prefix_instant: Option<std::time::Instant>,
+    input_mode: InputMode, // Normal | Text | Palette | GoPrefix { started_at }

@@ 8.2 List Screens
-| `g` `g` | Jump to top |
+| `g` `g` | Jump to top (current list screen) |

@@ 4.4 interpret_key
- KeyCode::Char('g') => Msg::IssueListScrollToTop
+ KeyCode::Char('g') => Msg::ScrollToTopCurrentScreen
  1. Fix TaskSupervisor contract and message schema drift Rationale: the plan mixes request_id and generation, and TaskKey::Search { generation } defeats dedup by making every key unique. This can silently reintroduce stale-result races.
diff --git a/PRD.md b/PRD.md
@@ 4.3 Core Types (Msg)
-    SearchRequestStarted { request_id: u64, query: String },
-    SearchExecuted { request_id: u64, results: SearchResults },
+    SearchRequestStarted { generation: u64, query: String },
+    SearchExecuted { generation: u64, results: SearchResults },

@@ 4.5.1 Task Supervisor
-    Search { generation: u64 },
+    Search,
+    struct TaskStamp { key: TaskKey, generation: u64 }

@@ 10.9.1 Non-Snapshot Tests
-    Msg::SearchExecuted { request_id: 3, ... }
+    Msg::SearchExecuted { generation: 3, ... }
  1. Add a Clock boundary everywhere time is computed Rationale: you call SystemTime::now() in many query/render paths, causing inconsistent relative-time labels inside one frame and flaky tests. Injected clock gives deterministic rendering and lower per-frame overhead.
diff --git a/PRD.md b/PRD.md
@@ 4.1 Module Structure
+    clock.rs                      # Clock trait: SystemClock/FakeClock

@@ 4.4 LoreApp struct
+    clock: Arc<dyn Clock>,

@@ 10.11 action.rs
- let now_ms = std::time::SystemTime::now()...
+ let now_ms = clock.now_ms();

@@ 9.3 Phase 0 success criteria
+19. Relative-time rendering deterministic under FakeClock across snapshot runs.
  1. Upgrade text truncation to grapheme-safe width handling Rationale: unicode-width alone is not enough for safe truncation; it can split grapheme clusters (emoji ZWJ sequences, skin tones, flags). You need width + grapheme segmentation together.
diff --git a/PRD.md b/PRD.md
@@ 10.1 New Files
-crates/lore-tui/src/text_width.rs      # ... using unicode-width crate
+crates/lore-tui/src/text_width.rs      # Grapheme-safe width/truncation using unicode-width + unicode-segmentation

@@ 10.1 New Files
+Cargo.toml (lore-tui): unicode-segmentation = "1"

@@ 9.3 Phase 0 success criteria
+20. Unicode rendering tests pass for CJK, emoji ZWJ, combining marks, RTL text.
  1. Redact sensitive values in logs and crash dumps Rationale: current crash/log strategy risks storing tokens/credentials in plain text. This is a serious operational/security gap for local tooling too.
diff --git a/PRD.md b/PRD.md
@@ 4.1 Module Structure
     safety.rs                     # sanitize_for_terminal(), safe_url_policy()
+    redact.rs                     # redact_sensitive() for logs/crash reports

@@ 10.3 install_panic_hook_for_tui
- let _ = std::fs::write(&crash_path, format!("{panic_info:#?}"));
+ let report = redact_sensitive(format!("{panic_info:#?}"));
+ let _ = std::fs::write(&crash_path, report);

@@ 9.3 Phase 0 success criteria
+21. Redaction tests confirm tokens/Authorization headers never appear in persisted crash/log artifacts.
  1. Add search capability detection and mode fallback UX Rationale: semantic/hybrid mode should not silently degrade when embeddings are absent/stale. Explicit capability state increases trust and avoids “why are results weird?” confusion.
diff --git a/PRD.md b/PRD.md
@@ 5.6 Search
+Capability-aware modes:
+- If embeddings unavailable/stale, semantic mode is disabled with inline reason.
+- Hybrid mode auto-falls back to lexical and shows badge: "semantic unavailable".

@@ 4.3 Core Types
+    SearchCapabilitiesLoaded(SearchCapabilities)

@@ 9.3 Phase 0 success criteria
+22. Mode availability checks validated: lexical/hybrid/semantic correctly enabled/disabled by fixture capabilities.
  1. Define sync cancel latency SLO and enforce fine-grained checks Rationale: “check cancel between phases” is too coarse on big projects. Users need fast cancel acknowledgment and bounded stop time.
diff --git a/PRD.md b/PRD.md
@@ 5.9 Sync
-CANCELLATION: checked between sync phases
+CANCELLATION: checked at page boundaries, batch upsert boundaries, and before each network request.
+UX target: cancel acknowledged <250ms, sync stop p95 <2s after Esc.

@@ 9.3 Phase 0 success criteria
+23. Cancel latency test passes: p95 stop time <2s under M-tier fixtures.
  1. Add a “Hotspots” screen for risk/churn triage Rationale: this is high-value and uses existing data (events, unresolved discussions, stale items). It makes the TUI more compelling without needing new sync tables or rejected features.
diff --git a/PRD.md b/PRD.md
@@ 1. Executive Summary
+- **Hotspots** — file/path risk ranking by churn × unresolved discussion pressure × staleness

@@ 5. Screen Taxonomy
+### 5.12 Hotspots
+Shows top risky paths with drill-down to related issues/MRs/timeline.

@@ 8.1 Global
+| `gx` | Go to Hotspots |

@@ 10.1 New Files
+crates/lore-tui/src/state/hotspots.rs
+crates/lore-tui/src/view/hotspots.rs
  1. Add degraded startup mode when compat/schema checks fail Rationale: hard-exit on mismatch blocks users. A degraded mode that shells to lore --robot for read-only summary/doctor keeps the product usable and gives guided recovery.
diff --git a/PRD.md b/PRD.md
@@ 3.2 Nightly Rust Strategy
- On mismatch: actionable error and exit
+ On mismatch: actionable error with `--degraded` option.
+ `--degraded` launches limited TUI (Dashboard/Doctor/Stats via `lore --robot` subprocess calls).

@@ 10.3 TuiCli
+    /// Allow limited mode when schema/compat checks fail
+    #[arg(long)]
+    degraded: bool,
  1. Harden query-plan CI checks (dont rely on SCAN TABLE string matching) Rationale: SQLite planner text varies by version. Parse opcode structure and assert index usage semantically; otherwise CI will be flaky or miss regressions.
diff --git a/PRD.md b/PRD.md
@@ 9.3.1 Required Indexes (CI enforcement)
- asserts that none show `SCAN TABLE`
+ parses EXPLAIN QUERY PLAN rows and asserts:
+ - top-level loop uses expected index families
+ - no full scan on primary entity tables under default and top filter combos
+ - join order remains bounded (no accidental cartesian expansions)
  1. Enforce single-instance lock for session/state safety Rationale: assumption says no concurrent TUI sessions, but accidental double-launch will still happen. Locking prevents state corruption and confusing interleaved sync actions.
diff --git a/PRD.md b/PRD.md
@@ 10.1 New Files
+crates/lore-tui/src/instance_lock.rs   # lock file with stale-lock recovery

@@ 11. Assumptions
-21. No concurrent TUI sessions.
+21. Concurrent sessions unsupported and actively prevented by instance lock (with clear error message).

If you want, I can turn this into a consolidated patched PRD (single unified diff) next.