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>
8.4 KiB
8.4 KiB
Below are the strongest revisions I’d make. I intentionally avoided anything in your ## Rejected Recommendations.
- 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 singleCommandRegistrymakes 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.
- Replace ad-hoc key flags with explicit input state machine
Rationale:
pending_go+go_prefix_instantis fragile and already inconsistent with documented behavior. A typedInputModeremoves 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
- Fix TaskSupervisor contract and message schema drift
Rationale: the plan mixes
request_idandgeneration, andTaskKey::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, ... }
- Add a
Clockboundary everywhere time is computed Rationale: you callSystemTime::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.
- Upgrade text truncation to grapheme-safe width handling
Rationale:
unicode-widthalone 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.
- 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.
- 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.
- 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.
- 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
- Add degraded startup mode when compat/schema checks fail
Rationale: hard-exit on mismatch blocks users. A degraded mode that shells to
lore --robotfor 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,
- Harden query-plan CI checks (don’t rely on
SCAN TABLEstring 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)
- 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.