Add ResultsGrouping {Agent, Conversation, Workspace, Flat} and bind
Alt+F plus a command-palette action so a chronological flat list is
reachable without losing agent/workspace panes. Grouping is persisted
with ranking and filters in saved views.
README and the keymap RFC document Alt+F, the newest+flat combination,
and that Ctrl+1..9 stores grouping. Tests cover the key reaching
uncapped flat results, preserving text/agent filters, and palette
plus saved-view restore.
39 KiB
TUI Interaction Model & Keymap RFC (cass)
Status: draft
Owner: RedHill
Related issues: coding_agent_session_search-2noh9.1.4, coding_agent_session_search-2noh9.1.5, coding_agent_session_search-2noh9.2.2
Principles
- Keyboard-first; mouse optional, never required.
- Consistency: same chord does the same thing across panes; avoid mode confusion.
- Discoverability: contextual help strip + command palette make actions findable.
- Safety: no destructive actions without confirmation; ESC always exits modals/search.
- Terminal resilience: graceful degradation on limited key support (esp. Ctrl+arrows, function keys).
Global Keymap
Ctrl+P— Command palette (fuzzy actions)?— Quick tour / help overlayF1— Toggle help strip pin/unpin (if available)Esc— Close modal/overlay; clear inline searches; cancel promptsTab/Shift+Tab— Cycle focus panes (search → results → detail → footer/help)Ctrl+S— Save view (see Saved Views)Ctrl+R— Reload index/view state (non-destructive)F12— Cycle ranking mode (recent / balanced / relevance / quality)Ctrl+B— Toggle rounded/plain bordersD— Cycle density (Compact/Cozy/Spacious)g/G— Jump to top/bottom of listPageUp/PageDownorCtrl+U/Ctrl+D— Scroll results pane by page
Search Bar
- Direct typing — live query
Up/Down— Navigate query historyCtrl+L— Clear search queryCtrl+W— Delete last tokenEnter— Run search immediately (forces query even during debounce)Ctrl+F— Toggle wildcard fallback indicator (UI only; does not change query)
Filters & Pills
F3— Agent filter pickerF4— Workspace filter pickerF5— Time filter cycle (today/week/30d/all)F6— Custom date range promptF10— Ranking mode cycle (alias of F12 if function keys limited)- In pills:
Enterto edit,Backspaceto remove,Left/Rightto move between pills - Mouse: click pill to edit/remove
Results Pane
Up/Down— Move selectionEnter— Open drill-in modal (thread view)m— Toggle multi-selectSpace— Peek XL context (tap again to restore)A— Bulk actions menu (open all, copy paths, export JSON, tag)/— In-pane quick filter;Escclearsy— Copy current snippet to clipboardo— Open source file in $EDITORv— View raw source (non-interactive)r— Refresh results (re-run query)
Detail Pane (Drill-In Modal)
Left/Right— Switch tabs (Messages / Snippets / Raw)c— Copy pathy— Copy selected snippeto— Open in $EDITORf— Toggle wrap in detail viewEsc— Close modal, return focus to results
Saved Views
Ctrl+1..9— Save current filters/ranking to slotShift+1..9— Recall slot- Toast confirms save/load; errors surface in footer
Density & Theme
D— Cycle density presetsCtrl+T— Toggle theme (dark/light)F2— Theme toggle (legacy alias)
Update Assistant
- When banner shown:
Uupgrade,sskip this version,dview notes,Escdismiss
Minimal / Robot Mode Behavior
- When
TUI_HEADLESS=1or stdout non-TTY: disable command palette, animation, icons; only allowsearch/stats/viewvia CLI. - Shortcut hints hidden; actions reachable via flags.
Fallbacks for Limited Key Support
- If function keys unavailable: map F3/F4/F5/F6 to
Ctrl+3/4/5/6. - If Ctrl+P conflicts: palette alias
Alt+P. - If clipboard unsupported:
ywrites to temporary file path displayed in footer.
Mouse (optional)
- Click focus between panes.
- Scroll wheel scrolls results/detail.
- Click pill to edit/remove; click breadcrumb to change scope.
- Drag not required anywhere.
Safety / Destructive Actions
- Bulk operations that open files only; no delete actions exist.
- Any future destructive command must confirm via y/N prompt; default No.
Acceptance (coding_agent_session_search-002)
- Keymap is conflict-free, discoverable (help strip + palette), and defined for degraded terminals.
- ESC always backs out safely; no orphaned modal states.
- Saved view, density, and theme toggles have keybindings and documented fallbacks.
- Update assistant keys defined.
FrankenTUI Architecture Mapping (coding_agent_session_search-2noh9.1.4)
Core Mapping Table
| Concern | Status | cass (ftui) | Implementation Notes |
|---|---|---|---|
| Terminal lifecycle | Done | ftui_core::terminal_session::TerminalSession + SessionOptions |
Centralized startup/shutdown in one session owner; guarantees cleanup on panic/exit paths. |
| Screen mode | Done | ftui_runtime::ScreenMode::{Fullscreen, Inline { ui_height }} + UiAnchor |
Inline mode via --inline flag; fullscreen remains default. |
| Render pipeline | Done | ftui_render::Frame -> BufferDiff -> Presenter |
Deterministic diff-based rendering with Bayesian strategy selection. |
| Event model | Done | ftui_core::event::Event consumed by Program update loop |
All feature logic receives normalized ftui events via CassMsg. |
| Runtime orchestration | Done | ftui_runtime::{Program, Model, Cmd, Subscription} |
Side effects are explicit (Cmd) and composable; cancellation/debounce via runtime. |
| Layout system | Done | ftui_layout::{Flex, Grid, Constraint, LayoutSizeHint} |
Responsive layout with intrinsic sizing and explicit breakpoints. |
| Widget primitives | Done | ftui_widgets::{Widget, StatefulWidget} + targeted built-ins |
Built-ins used where available; cass-specific wrappers where needed. |
| Command palette | Done | ftui_widgets::command_palette |
Keybinding contract preserved, internals use standard widget. |
| Help system | Done | ftui_widgets::{help, help_registry, hint_ranker} |
Discoverability preserved with context ranking. |
| Results virtualization | Done | ftui_widgets::VirtualizedList |
Scales to large result sets with bounded render cost. |
| Modal/toast stack | Done | ftui_widgets::{modal, toast, notification_queue} |
ESC/back semantics preserved with standardized stack behavior. |
| Focus traversal | Done | ftui_widgets::focus::{FocusGraph, FocusManager} |
One focus graph for panes, modals, and command palette. |
| Testing harness | Done | ftui-harness snapshots + ProgramSimulator + render traces |
Snapshot and state-transition testing is primary path. |
| Debug traceability | Done | ftui_runtime::{render_trace, input_macro, AsciicastRecorder} |
Replayable traces for keyflow and rendering debugging. |
Widget Adoption Plan (Use Built-ins First)
| Existing cass surface | Preferred ftui widget/module | Fallback if gap remains |
|---|---|---|
| Query/filter top bar | input, group, status_line, help_registry |
Local wrapper in src/ui/components |
| Results pane | VirtualizedList, scrollbar, list |
Keep temporary local renderer until feature parity |
| Detail pane | paragraph, tree, json_view, log_viewer |
Local detail renderer while migrating tabs incrementally |
| Bulk actions / menus | command_palette, modal |
Local modal wrapper with same action contract |
| Notifications | toast + notification_queue |
Existing toast manager (temporary) |
Design Decisions (Locked for Migration)
- Use a single top-level
Modelwith explicit sub-state enums rather than multiple independent runtime programs. - Use
FocusGraphas the authoritative focus-routing mechanism forTab/Shift+Taband modal focus restore. - Keep current keybinding contract stable during migration; behavior changes must be explicit RFC updates.
- Use built-in ftui widgets for palette/help/modal/virtualized list whenever available; do not re-implement equivalents.
- Start with explicit state updates in
update()and only adopt ftui reactive bindings in targeted areas where it reduces complexity.
Gap-Handling Policy
- If a missing capability is generally reusable, upstream to FrankenTUI and consume via pinned git revision.
- If the behavior is cass-specific, implement a thin local adapter/wrapper in cass.
- Never block migration on "perfect widget parity" if a temporary local wrapper can preserve user behavior.
- Record each gap as a bead linked to the owning migration epic, with explicit exit criteria.
Migration Execution Order
- Foundation (
2noh9.2.x): dependency + runtime skeleton + terminal/session wiring. Done. - Parity (
2noh9.3.x): search/filter/results/detail/modals reimplemented on ftui. Done. - Enhancements (
2noh9.4.x): inline mode, traces, advanced UX, dashboards. In progress. - QA and removal (
2noh9.5.x,2noh9.6.x): test hardening, ratatui removed. Ratatui fully removed (2noh9.6.1).
Acceptance for This Mapping Bead
- Mapping covers terminal lifecycle, screen modes, render pipeline, event model, layout, widgets, runtime, and testing hooks.
- Includes explicit decisions for model structure, focus strategy, reactive usage, and built-in widget adoption.
- Includes a concrete gap policy so implementation beads can proceed without reopening architecture debates.
Analytics Dashboard IA & Interaction Spec (coding_agent_session_search-2noh9.4.18.1)
Status: finalized draft
Owner: CoralLantern
Related analytics beads: z9fse.12, z9fse.6, z9fse.10, z9fse.11
Time semantics: UTC-only for v1
Scope and Constraints
- Defines a single interaction contract for ftui analytics surfaces so CLI + TUI semantics do not drift.
- Uses rollup-first queries by default; deep/fact scans are explicit opt-in paths.
- Requires full keyboard operation; mouse is an enhancement only.
- Keeps existing search workflows central: analytics drilldown must jump into the main Search surface with pre-applied filters.
Information Architecture
Top-level analytics entry points:
- Command palette entries:
Analytics: DashboardAnalytics: ExplorerAnalytics: HeatmapAnalytics: BreakdownsAnalytics: ToolsAnalytics: Cost ModelsAnalytics: Coverage
- Global hotkey:
Alt+A(fallbackg achord)
Analytics subviews:
| View | Primary Goal | Core Widgets | Primary Data Source |
|---|---|---|---|
| Dashboard | At-a-glance health and trend deltas | KPI tiles + sparklines + top movers | usage_daily, token_daily_stats, cost rollups |
| Explorer | Time-series comparison and overlays | Line/area chart + overlay legend + cursor tooltip | rollups by bucket (hour/day/week/month) |
| Heatmap | Calendar activity scan | Calendar heatmap + legend + day cursor | daily rollups |
| Breakdowns | Ranked dimensions and trends | Virtualized sortable table + per-row sparkline | grouped rollups |
| Tools | Tool usage efficiency | per-tool table + trend strip + jump action | tool_usage_hourly/daily, tool_calls_detail |
| Cost Models | Model/provider spend behavior | stacked token/cost bars + model table | pricing + model rollups |
| Coverage | Data quality visibility | coverage matrix + warning list | API coverage counters + connector health |
Layout and Minimum Terminal Sizes
- Recommended:
>= 160x44for full analytics layout with side panels. - Supported baseline:
>= 120x36with compressed sidebars. - Narrow mode (
< 120 cols): single-primary-pane with drawer overlays for filters and legend. - Hard minimum for reliable operation:
100x30; below this, show inline "expand terminal" guidance.
Global Filters (Persisted While in Analytics)
Filter ribbon (top) plus collapsible filter drawer (narrow mode):
- Time range:
- presets: today, 7d, 30d, 90d, YTD, all
- custom: since/until
- Agent: multi-select with fuzzy match
- Workspace: multi-select with fuzzy match
- Source: all/local/remote/source-id
- Optional advanced: role filter for message role segmentation
Persistence rules:
- Analytics filters persist separately from general search ad hoc filters.
- On leaving analytics and returning in the same session, previous analytics filter state is restored.
- On app restart, last analytics filter state is restored from
tui_state.json-compatible persistence.
Drilldown Semantics (Contract)
Enteron a chart point, heatmap day, or KPI delta opens Search view with inherited filters:- time bucket mapped to
created_from/created_to - analytics agent/workspace/source selections applied
- query left empty by default
- time bucket mapped to
Enteron a breakdown row opens Search with inherited filters plus row dimension:- examples:
agent=codex,workspace=<id>,tool_name=Read
- examples:
- Drilldown always pushes a view-stack entry.
Escreturns to prior analytics view without losing filter context.
Keyboard Model
Global within analytics:
Tab/Shift+Tab: cycle analytics subviewsg: open analytics-local "go to view" selector/: open filter/search within the active analytics viewEnter: drilldown to SearchEsc: back (pop view stack)?: analytics-context help overlay
Explorer-specific:
Left/Right: move bucket cursorUp/Down: cycle overlay dimension (agent/workspace/source/model)[/]: previous/next metric
Breakdowns-specific:
s: cycle sort columnr: reverse sort directionSpace: pin/unpin selected row for comparison
Command Palette Contract
Required command entries:
Analytics: DashboardAnalytics: ExplorerAnalytics: HeatmapAnalytics: BreakdownsAnalytics: ToolsAnalytics: Cost ModelsAnalytics: CoverageAnalytics: Reset FiltersAnalytics: Jump to Search (Current Scope)
Data Semantics and Definitions
- All bucketing in v1 is UTC.
- Week definition: ISO week (Mon-Sun).
- Month definition: calendar month UTC.
- Costs are marked as:
- measured (API cost data present)
- estimated (derived from pricing table)
- unavailable (insufficient metadata)
- Coverage panel must disclose when metrics are partially estimated or stale.
Dependency Mapping to Analytics Beads
| UI Capability | Dependency Bead | Why |
|---|---|---|
| Shared analytics query semantics across CLI/TUI | z9fse.12 |
prevents drift and duplicated logic |
| Tool breakdown and trends | z9fse.6 |
provides per-tool rollups and fact linkage |
| USD cost estimations and labels | z9fse.10 |
powers cost/cost-model surfaces |
| Stable model attribution dimensions | z9fse.11 |
enables model/provider grouping consistency |
Snapshot and PTY Test Targets
Snapshot targets (ftui-harness):
- Dashboard default state with 30d preset
- Explorer with agent overlay active
- Heatmap with legend + selected day
- Breakdowns sorted by descending tokens
- Tools view with selected tool row and trend sparkline
- Coverage matrix with at least one warning badge
PTY e2e flow targets:
- Launch TUI -> open analytics (
Alt+A) -> switch Dashboard -> Explorer -> Heatmap. - Apply filters (time + agent + source) and verify ribbon state.
- Perform drilldown with
Enterand verify Search is scoped correctly. - Use
Escback-stack unwind and confirm prior analytics state restored. - Exit cleanly with no terminal corruption.
Acceptance Criteria
- IA is explicit enough for implementation without reopening interaction design.
- Keymap + command palette entries are specified and non-conflicting.
- Drilldown behavior is deterministic and reversible (
Esc). - Dependencies on
z9fse.12,.6,.10,.11are explicit and actionable. - Test targets are concrete for both snapshot and PTY coverage.
Finalized Interaction Contract (coding_agent_session_search-2noh9.1.5)
Status: finalized Audited against:
src/ui/tui.rs,src/ui/shortcuts.rs,src/ui/components/export_modal.rsDate: 2026-02-06
1. Complete Context Keymap Matrix
Every binding below is conflict-free within its context scope. Contexts are listed from broadest (Global) to narrowest (sub-modal). A key listed in a narrower scope shadows the same key in a broader scope while that context is active.
1.1 Global (always active unless a modal captures input)
| Key | Action | Notes |
|---|---|---|
Ctrl+C |
Force quit | Overrides ALL modes, never intercepted |
Esc |
Context-sensitive unwind (see ESC contract below) | |
F10 |
Quit alias (same as Esc when no pending state) | |
F1 / ? / Ctrl+? |
Toggle help overlay | |
F2 |
Toggle theme (dark/light) | Alias: Ctrl+T |
Ctrl+T |
Toggle theme | Primary after migration; F2 kept for compat |
F3 |
Agent filter picker | Enter InputMode::Agent |
Shift+F3 |
Quick-scope to current result's agent | |
F4 |
Workspace filter picker | Enter InputMode::Workspace |
Shift+F4 |
Clear agent scope (show all) | |
F5 |
Date-from filter prompt | Enter InputMode::CreatedFrom |
Shift+F5 |
Cycle time presets (24h / 7d / 30d / all) | |
F6 |
Date-to filter prompt | Enter InputMode::CreatedTo |
F7 |
Cycle context window (Small / Medium / Large / XLarge) | |
F8 |
Open selected result in $EDITOR | No-op if nothing selected |
F9 |
Cycle match mode (Standard / Prefix) | |
F11 |
Cycle source filter (All / Local / Remote / per-host) | |
Shift+F11 |
Open source filter popup menu | |
F12 |
Cycle ranking mode (Recent / Balanced / Relevance / Quality / Date Newest / Date Oldest) | |
Alt+S |
Cycle search mode (Lexical / Semantic / Hybrid) | |
Ctrl+P / Alt+P |
Open command palette | Alt+P fallback for Ctrl+P conflicts |
Ctrl+R |
Cycle through query history | When history exists |
Ctrl+Shift+R |
Refresh search index (trigger re-index) | |
Ctrl+Del |
Clear all active filters | |
Ctrl+Shift+Del |
Reset entire UI state (delete tui_state.json) | |
Ctrl+B |
Toggle fancy/plain borders | |
Ctrl+D |
Cycle density (Compact / Cozy / Spacious) | |
Tab / Shift+Tab |
Cycle focus (SearchInput -> Results -> Detail) | |
Alt+h/j/k/l |
Vim-style directional pane nav | |
Alt+G / Alt+Shift+G |
Open / clear the agent filter | Aliases of F3 / Shift+F3 |
Alt+F |
Cycle result grouping (Agent / Conversation / Workspace / Flat) | Also in the command palette |
Alt+1..9 |
Quick-switch to pane N | |
Shift+= / + |
Increase pane size (+2 items, max 50) | |
Alt+- |
Decrease pane size (-2 items, min 4) | |
PageUp / PageDown |
Page-level scroll in focused pane | |
Ctrl+1..9 |
Save current view to slot N | Persists filters + ranking + grouping |
Shift+1..9 |
Load view from slot N | Toast confirms |
1.2 Search Input (when query bar has focus)
| Key | Action | Notes |
|---|---|---|
| Printable chars | Append to query, trigger live search | |
Backspace |
Delete char; if empty, clear last filter (time -> workspace -> agent) | |
Enter |
Force search now (skip debounce); if empty + history, load most recent | |
Up / Down |
Navigate query suggestions / history | |
Ctrl+n / Ctrl+p |
History next / previous | |
Ctrl+L |
Clear search query text | |
Ctrl+W |
Delete last token (word) | |
Ctrl+F |
Toggle wildcard fallback indicator | UI indicator only |
/ |
Enter PaneFilter mode (local results filter) | Context-sensitive: in query bar = pane filter |
1.3 Results Pane (when results list has focus)
| Key | Action | Notes |
|---|---|---|
Up / Down / j / k |
Move selection | |
Home / End / g / G |
Jump to first / last result | |
Enter |
Open detail modal for selected result | |
Space / Ctrl+Space |
Peek XL context (toggle; tap again to restore) | |
y / Ctrl+Y |
Copy current snippet to clipboard | |
o |
Open source file in $EDITOR | |
v |
View raw source (non-interactive) | |
r |
Refresh results (re-run current query) | |
/ |
Enter in-pane quick filter; Esc clears | |
Ctrl+X |
Toggle multi-select on current item | |
Ctrl+A |
Select / deselect all in pane | |
Ctrl+Enter |
Enqueue item (select + advance to next) | |
Ctrl+O |
Open all enqueued items in $EDITOR | |
Ctrl+E |
Quick export with defaults | |
A |
Open bulk actions menu | Only when items selected |
Left / h |
Move focus to previous pane or exit to results | |
Right / l |
Move focus to detail pane |
1.4 Detail Pane (when detail view has focus)
| Key | Action | Notes |
|---|---|---|
Up / Down / j / k |
Scroll content | |
PageUp / PageDown |
Page scroll | |
Home / g |
Jump to top | |
End / G |
Jump to bottom | |
[ / ] or Left / Right |
Cycle tabs (Messages / Snippets / Raw) | |
c |
Copy rendered content to clipboard | |
p |
Copy source path to clipboard | |
s |
Copy snippet to clipboard | |
y |
Copy selected snippet (alias of s) |
|
o |
Open source file in $EDITOR | |
n |
Open content in nano editor | |
e |
Open export modal | |
Ctrl+E |
Quick export with defaults | |
f |
Toggle text wrap in detail view | |
/ |
Enter detail-local find mode | |
Esc |
Close detail modal, restore focus to results |
1.5 Detail Find Mode (sub-mode within Detail)
| Key | Action | Notes |
|---|---|---|
| Printable chars | Build find query, highlight matches | |
Backspace |
Delete char from find query | |
Enter |
Apply find, jump to first match | |
n |
Next match | |
N (Shift+n) |
Previous match | |
Esc |
Exit find mode, return to detail navigation |
1.6 In-Pane Filter Mode (sub-mode within Results)
| Key | Action | Notes |
|---|---|---|
| Printable chars | Build filter text, narrow results live | |
Backspace |
Delete char from filter | |
Enter |
Apply pane filter (persist filtering) | |
Esc |
Cancel filter, restore unfiltered results |
1.7 Filter Input Modes (Agent, Workspace, DateFrom, DateTo)
| Key | Action | Notes |
|---|---|---|
| Printable chars | Build filter value | Agent/workspace show suggestions |
Tab |
Auto-complete to first matching suggestion | |
Backspace |
Delete char | |
Enter |
Apply filter, return to Query mode | |
Esc |
Cancel, return to Query mode |
1.8 Command Palette (Ctrl+P / Alt+P)
| Key | Action | Notes |
|---|---|---|
| Printable chars | Search/filter palette actions | |
Backspace |
Delete char from palette search | |
Up / Down |
Move selection (-1 / +1) | |
PageUp / PageDown |
Move selection (-5 / +5) | |
Enter |
Execute selected action | |
Esc |
Close palette, clear search |
1.9 Help Overlay (F1 / ?)
| Key | Action | Notes |
|---|---|---|
Up / Down / j / k |
Scroll help content | |
PageUp / PageDown |
Page scroll | |
Home / End |
Jump to top / bottom | |
Esc / F1 / ? |
Close help overlay |
1.10 Bulk Actions Modal (A key)
| Key | Action | Notes |
|---|---|---|
Up / Down / j / k |
Navigate action list | |
Enter |
Execute selected bulk action | |
Esc |
Cancel, keep selections intact |
Actions: Copy selected paths, Copy selected content, Open selected in editor, Export selected.
1.11 Export Modal (e key in detail)
| Key | Action | Notes |
|---|---|---|
Tab / Shift+Tab |
Cycle focus between form fields | |
Space |
Toggle checkbox (encryption, timestamps, etc.) | |
Backspace / chars |
Edit text fields (password, filename) | |
Ctrl+H |
Toggle password visibility | |
Enter |
Execute export | |
Esc |
Cancel export |
1.12 Consent Dialog (semantic model download)
| Key | Action | Notes |
|---|---|---|
d / D |
Start model download (~23MB) | |
h / H |
Show help/info about model | |
Esc |
Cancel dialog (or cancel active download) |
1.13 Update Assistant (when version banner shown)
| Key | Action | Notes |
|---|---|---|
U |
Open release page in browser | |
s / S |
Skip this version (persisted) | |
d |
View release notes | |
Esc |
Dismiss for session only |
1.14 Source Filter Menu (Shift+F11)
| Key | Action | Notes |
|---|---|---|
Up / Down / j / k |
Navigate source options | |
Enter |
Select source filter | |
Esc |
Close menu |
1.15 Did-You-Mean Suggestions
| Key | Action | Notes |
|---|---|---|
1 / 2 / 3 |
Apply suggestion N | Only shown when results empty + misspelling detected |
2. Reconciliation with src/ui/shortcuts.rs
| Constant | Current Value | Final Contract | Status |
|---|---|---|---|
HELP |
F1 |
F1 / ? / Ctrl+? |
keep (extend aliases) |
THEME |
F2 |
F2 + Ctrl+T |
keep (add alias) |
FILTER_AGENT |
F3 |
F3 |
keep |
FILTER_WORKSPACE |
F4 |
F4 |
keep |
FILTER_DATE_FROM |
F5 |
F5 |
keep |
FILTER_DATE_TO |
F6 |
F6 |
keep |
CONTEXT_WINDOW |
F7 |
F7 |
keep |
EDITOR |
F8 |
F8 |
keep |
MATCH_MODE |
F9 |
F9 |
keep |
SEARCH_MODE |
Alt+S |
Alt+S |
keep |
QUIT |
Esc/F10 |
Esc/F10 |
keep |
CLEAR_FILTERS |
Ctrl+Del |
Ctrl+Del |
keep |
RESET_STATE |
Ctrl+Shift+Del |
Ctrl+Shift+Del |
keep |
RANKING |
F12 |
F12 |
keep |
REFRESH |
Ctrl+Shift+R |
Ctrl+Shift+R |
keep |
DETAIL_OPEN |
Enter |
Enter |
keep |
DETAIL_CLOSE |
Esc |
Esc |
keep |
FOCUS_QUERY |
/ |
/ (context-sensitive) |
keep (clarified) |
HISTORY_NEXT |
Ctrl+n |
Ctrl+n |
keep |
HISTORY_PREV |
Ctrl+p |
Ctrl+p |
keep |
HISTORY_CYCLE |
Ctrl+R |
Ctrl+R |
keep |
SCOPE_AGENT |
Shift+F3 |
Shift+F3 |
keep |
SCOPE_WORKSPACE |
Shift+F4 |
Shift+F4 |
keep |
CYCLE_TIME_PRESETS |
Shift+F5 |
Shift+F5 |
keep |
COPY |
y |
y / Ctrl+Y |
keep (add modifier alias) |
BULK_MENU |
A |
A |
keep |
TOGGLE_SELECT |
Ctrl+X |
Ctrl+X |
keep (reverted from draft Ctrl+M) |
PANE_FILTER |
/ |
/ (context-sensitive) |
keep (clarified) |
TAB_FOCUS |
Tab |
Tab / Shift+Tab |
keep |
VIM_NAV |
Alt+h/j/k/l |
Alt+h/j/k/l |
keep |
JUMP_TOP |
Home |
Home / g |
keep (extend alias) |
JUMP_BOTTOM |
End |
End / G |
keep (extend alias) |
New constants to add to shortcuts.rs post-migration:
| Constant | Value | Purpose |
|---|---|---|
SOURCE_FILTER |
F11 |
Cycle source filter |
SOURCE_FILTER_MENU |
Shift+F11 |
Source filter popup |
PALETTE |
Ctrl+P |
Command palette |
PALETTE_ALT |
Alt+P |
Palette fallback |
SELECT_ALL |
Ctrl+A |
Select/deselect all |
ENQUEUE |
Ctrl+Enter |
Multi-select enqueue |
OPEN_QUEUED |
Ctrl+O |
Open all enqueued |
QUICK_EXPORT |
Ctrl+E |
Quick export |
PEEK_XL |
Ctrl+Space |
Peek XL context |
BORDERS |
Ctrl+B |
Toggle border style |
DENSITY |
Ctrl+D |
Cycle density |
THEME_ALT |
Ctrl+T |
Theme toggle |
PANE_GROW |
Shift+= |
Increase pane size |
PANE_SHRINK |
Alt+- |
Decrease pane size |
3. Focus Model (ftui FocusGraph Integration)
3.1 Focus Nodes
Each focusable UI region maps to a FocusNode with a stable FocusId:
| FocusId | Region | tab_index | group_id |
|---|---|---|---|
FOCUS_SEARCH |
Search/query input bar | 0 | GROUP_MAIN |
FOCUS_RESULTS |
Results list pane | 1 | GROUP_MAIN |
FOCUS_DETAIL |
Detail/preview pane | 2 | GROUP_MAIN |
Modal focus nodes (created on push, destroyed on pop):
| FocusId | Region | group_id |
|---|---|---|
FOCUS_PALETTE_INPUT |
Command palette search | GROUP_PALETTE |
FOCUS_PALETTE_LIST |
Command palette results | GROUP_PALETTE |
FOCUS_HELP_CONTENT |
Help overlay scroll area | GROUP_HELP |
FOCUS_BULK_LIST |
Bulk actions list | GROUP_BULK |
FOCUS_EXPORT_* |
Export modal form fields | GROUP_EXPORT |
FOCUS_DETAIL_MODAL |
Detail drill-in modal | GROUP_DETAIL_MODAL |
FOCUS_CONSENT |
Consent dialog | GROUP_CONSENT |
FOCUS_SOURCE_MENU |
Source filter menu | GROUP_SOURCE |
3.2 Focus Graph Edges (directional navigation)
Tab order (Next/Prev):
FOCUS_SEARCH <-> FOCUS_RESULTS <-> FOCUS_DETAIL (wraps)
Spatial edges (Left/Right/Up/Down):
FOCUS_SEARCH --Down--> FOCUS_RESULTS
FOCUS_RESULTS --Up--> FOCUS_SEARCH
FOCUS_RESULTS --Right--> FOCUS_DETAIL
FOCUS_DETAIL --Left--> FOCUS_RESULTS
3.3 Focus Groups and Trapping
// Main application group - Tab cycles through these
FocusGroup { id: GROUP_MAIN, members: [SEARCH, RESULTS, DETAIL], wrap: true }
// Modal groups - focus trapped within until popped
FocusGroup { id: GROUP_PALETTE, members: [PALETTE_INPUT, PALETTE_LIST], wrap: true, exit_key: Escape }
FocusGroup { id: GROUP_EXPORT, members: [EXPORT_FIELDS...], wrap: true, exit_key: Escape }
FocusGroup { id: GROUP_DETAIL_MODAL, members: [DETAIL_MODAL], wrap: false, exit_key: Escape }
FocusGroup { id: GROUP_HELP, members: [HELP_CONTENT], wrap: false, exit_key: Escape }
FocusGroup { id: GROUP_BULK, members: [BULK_LIST], wrap: false, exit_key: Escape }
FocusGroup { id: GROUP_CONSENT, members: [CONSENT], wrap: false, exit_key: Escape }
FocusGroup { id: GROUP_SOURCE, members: [SOURCE_MENU], wrap: false, exit_key: Escape }
3.4 Focus Trap Stack (Modal Lifecycle)
When a modal opens:
focus_manager.push_trap(GROUP_<modal>)— savesreturn_focusto current node- Focus moves to first member of the modal's group
- Tab/Shift+Tab cycle within group only (trap)
When a modal closes:
focus_manager.pop_trap()— restoresreturn_focus- Modal group nodes are removed from graph
Stack ordering (innermost = highest priority):
[bottom] GROUP_MAIN
GROUP_HELP (F1/?)
GROUP_PALETTE (Ctrl+P)
GROUP_DETAIL_MODAL (Enter on result)
GROUP_EXPORT (e in detail)
GROUP_CONSENT (semantic model prompt)
GROUP_BULK (A key)
GROUP_SOURCE (Shift+F11)
[top] (active prompt)
Only the top group receives keyboard input. Background groups render but do not interact.
3.5 Focus Transitions (Concrete Examples)
Opening detail modal:
State: focus = FOCUS_RESULTS, trap_stack = [GROUP_MAIN]
User presses Enter
-> push_trap(GROUP_DETAIL_MODAL)
-> focus = FOCUS_DETAIL_MODAL
-> trap_stack = [GROUP_MAIN, GROUP_DETAIL_MODAL]
-> Tab cycles within: [FOCUS_DETAIL_MODAL] only
Opening export from within detail:
State: focus = FOCUS_DETAIL_MODAL, trap_stack = [GROUP_MAIN, GROUP_DETAIL_MODAL]
User presses e
-> push_trap(GROUP_EXPORT)
-> focus = FOCUS_EXPORT_FORMAT (first field)
-> trap_stack = [GROUP_MAIN, GROUP_DETAIL_MODAL, GROUP_EXPORT]
Closing export (Esc):
-> pop_trap() -> return_focus = FOCUS_DETAIL_MODAL
-> trap_stack = [GROUP_MAIN, GROUP_DETAIL_MODAL]
Closing detail (Esc again):
-> pop_trap() -> return_focus = FOCUS_RESULTS
-> trap_stack = [GROUP_MAIN]
3.6 Focus Flash Indicator
On focus change: render a 220ms highlight on the newly focused pane border.
Implementation: Cmd::tick(Duration::from_millis(220)) -> message clears flash state.
4. Modal and Overlay Semantics
4.1 Modal Stack Priority (rendering order, back-to-front)
- Main panes (SearchInput, Results, Detail) — always rendered
- Help overlay — renders over main, dims background
- Command palette — renders over everything below
- Detail drill-in modal — replaces detail pane area
- Export modal — renders over detail modal
- Source filter menu — dropdown overlay
- Bulk actions menu — centered modal
- Consent dialog — centered modal, highest non-prompt priority
- Active text prompt — inline within its parent modal
4.2 Input Routing
Only the top-most overlay receives keyboard input:
- If consent dialog is open -> consent keys only
- If export modal is open -> export keys only
- If detail modal is open -> detail keys only (unless export is stacked)
- If help overlay is open -> help scroll keys only
- If command palette is open -> palette keys only
- Otherwise -> main pane keys based on current focus
4.3 Background Rendering
Panes below the active overlay continue rendering (results may update from async search). They are visually dimmed to indicate non-interactivity.
5. ESC Unwind Contract
On Esc, unwind in this exact priority order until one action succeeds:
- Cancel active text input (filter input, palette search, export field, find query).
- Close top-most modal (pop_trap in FocusManager, restore prior focus).
- Exit pane-local find/filter mode (ResultsList or DetailPane).
- Clear multi-select if any items are selected.
- Close detail pane if open (return focus to Results).
- Quit TUI session if no pending state remains.
Ctrl+C bypasses this chain entirely and force-quits immediately.
6. State Persistence (tui_state.json)
Location: ~/.local/share/coding-agent-search/tui_state.json
Persisted on change:
| Field | Type | Default |
|---|---|---|
search_mode |
"lexical" / "semantic" / "hybrid" |
"lexical" |
match_mode |
"standard" / "prefix" |
"standard" |
ranking_mode |
"recent" / "balanced" / "relevance" / "quality" / ... |
"balanced" |
context_window |
"small" / "medium" / "large" / "xlarge" |
"medium" |
theme_dark |
bool | true |
density_mode |
"compact" / "cozy" / "spacious" |
"cozy" |
per_pane_limit |
usize (4..50) | 10 |
query_history |
Vec (max 50, deduplicated) | [] |
saved_views |
Vec (slots 1-9) | [] |
fancy_borders |
bool | true |
has_seen_help |
bool | false |
Reset: Ctrl+Shift+Del deletes tui_state.json and resets all fields to defaults.
ftui integration: Use Cmd::SaveState / Cmd::RestoreState for persistence lifecycle.
7. Headless / Robot Mode Constraints
When TUI_HEADLESS=1 or stdout is non-TTY:
| Feature | Behavior |
|---|---|
| Terminal raw mode | Disabled — no raw mode setup |
| Mouse capture | Disabled |
| Alt-screen | Disabled |
| Rendering / event loop | Disabled — exits after non-interactive operation |
| Command palette | Disabled |
| Modal animations | Disabled |
| Decorative effects | Disabled |
| Focus management | Disabled |
| All interactive modals | Disabled |
| State persistence | Enabled (--reset-state supported) |
| CLI flag operations | Enabled (search, stats, view via flags) |
For ftui snapshot/smoke tests:
- Use
ProgramSimulatorfrom ftui-harness - Keep deterministic render path
- Overlay-only actions are no-ops unless explicitly invoked by test harness API
8. Degraded Terminal Fallbacks
| Missing Feature | Fallback | Notes |
|---|---|---|
| Function keys (F1-F12) | Ctrl+3/4/5/6 for F3-F6 filters; Ctrl+T for F2 theme; ? for F1 help |
|
Ctrl+P conflict (tmux) |
Alt+P palette alias |
|
| Clipboard (no OSC 52) | y writes to temp file; path shown in footer toast |
|
| Narrow terminal (<80 cols) | Collapse visual affordances; hide help strip; preserve all keybindings | |
| No Unicode support | Plain ASCII borders (+, -, ` |
`); no emoji in pills |
| No color support | Monochrome mode; bold/underline for emphasis |
9. Mouse Behavior (Optional, Never Required)
| Event | Region | Action |
|---|---|---|
| Left click | Pane header | Focus that pane |
| Left click | Result item | Select item + show detail |
| Left click | Detail area | Focus detail pane |
| Left click | Filter pill | Edit pill value |
| Left click | Breadcrumb | Change scope |
| Scroll up/down | Results pane | Scroll result selection |
| Scroll up/down | Detail pane | Scroll detail content |
Ignored when any modal is open (help, palette, export, bulk, consent, source menu).
Drag: Not required anywhere. No drag interactions.
10. Action System Contract
Every user action maps to a named action for palette registration and keybinding docs:
| Action Name | Keys | Requires |
|---|---|---|
help.toggle |
F1, ?, Ctrl+? |
— |
theme.toggle |
F2, Ctrl+T |
— |
filter.agent |
F3 |
— |
filter.agent.scope |
Shift+F3 |
Selected result |
filter.workspace |
F4 |
— |
filter.agent.clear_scope |
Shift+F4 |
— |
filter.date_from |
F5 |
— |
filter.time_presets |
Shift+F5 |
— |
filter.date_to |
F6 |
— |
filter.clear_all |
Ctrl+Del |
— |
context_window.cycle |
F7 |
— |
editor.open |
F8, o |
Selected result |
match_mode.cycle |
F9 |
— |
source_filter.cycle |
F11 |
— |
source_filter.menu |
Shift+F11 |
— |
ranking.cycle |
F12 |
— |
search_mode.cycle |
Alt+S |
— |
palette.open |
Ctrl+P, Alt+P |
— |
history.cycle |
Ctrl+R |
History non-empty |
history.next |
Ctrl+n |
History non-empty |
history.prev |
Ctrl+p |
History non-empty |
index.refresh |
Ctrl+Shift+R |
— |
state.reset |
Ctrl+Shift+Del |
— |
borders.toggle |
Ctrl+B |
— |
density.cycle |
Ctrl+D |
— |
view.save |
Ctrl+1..9 |
— |
view.load |
Shift+1..9 |
Slot non-empty |
result.select |
Enter |
Result focused |
result.peek |
Space, Ctrl+Space |
Result focused |
result.copy |
y, Ctrl+Y |
Result focused |
result.open |
o |
Result focused |
result.view_raw |
v |
Result focused |
result.refresh |
r |
— |
select.toggle |
Ctrl+X |
Result focused |
select.all |
Ctrl+A |
— |
select.enqueue |
Ctrl+Enter |
Result focused |
select.open_all |
Ctrl+O |
Selection non-empty |
bulk.menu |
A |
Selection non-empty |
export.quick |
Ctrl+E |
Result focused |
export.modal |
e |
Detail open |
detail.copy_content |
c |
Detail open |
detail.copy_path |
p |
Detail open |
detail.copy_snippet |
s |
Detail open |
detail.open_nano |
n |
Detail open |
detail.wrap |
f |
Detail open |
detail.tab_cycle |
[, ] |
Detail open |
detail.find |
/ |
Detail focused |
pane.filter |
/ |
Results focused |
pane.grow |
Shift+= |
— |
pane.shrink |
Alt+- |
— |
nav.focus_next |
Tab |
— |
nav.focus_prev |
Shift+Tab |
— |
nav.top |
Home, g |
— |
nav.bottom |
End, G |
— |
nav.page_up |
PageUp |
— |
nav.page_down |
PageDown |
— |
app.quit |
Esc, F10 |
No pending state |
app.force_quit |
Ctrl+C |
Always |
11. Conflict Audit
Verified conflict-free within each context scope. Key observations:
/is context-sensitive: in search bar focus = pane filter, in detail focus = find-in-detail. No conflict because they are different focus contexts.yin results = copy snippet,yin detail = copy snippet. Same semantic, no conflict.g/Gfor jump: only active when results/detail focused, not during text input. No conflict with filter char input.Dfor density: only active in query mode, not consumed during filter text input modes.nin detail = open nano,nin detail-find = next match. Different sub-contexts (find active vs not). No conflict.Ctrl+R: history cycle in search, index refresh isCtrl+Shift+R. Different modifiers, no conflict.
Acceptance Criteria for This Bead
- Every shortcut in
src/ui/shortcuts.rsis accounted for with keep/change rationale. - All shortcuts discovered in
src/ui/tui.rsimplementation are documented (100+ bindings). - No duplicate meaning exists for a shortcut within the same interaction context.
- Focus traversal mapped to ftui
FocusGraph/FocusManager/FocusGroupwith concrete IDs. - Modal stack lifecycle (push_trap/pop_trap) is deterministic and documented.
- ESC unwind contract has explicit priority ordering.
- State persistence fields enumerated with defaults.
- Headless and degraded-terminal behaviors are explicit and testable.
- Action system named for palette registration and future keybinding customization.
- Conflict audit performed and no conflicts found.