a73x

specs/004-dashboard-filtering/plan.md

Ref:   Size: 4.2 KiB

# Implementation Plan: Dashboard Filtering

**Branch**: `004-dashboard-filtering` | **Date**: 2026-03-21 | **Spec**: [spec.md](spec.md)
**Input**: Feature specification from `/specs/004-dashboard-filtering/spec.md`

## Summary

Add text search and status filter cycling to the TUI dashboard. Users press `/` to enter search mode with real-time case-insensitive title filtering, cycle status filters (open/closed/all) with the existing `a` key, and see active filter state in the footer. All changes are confined to `src/tui.rs` — no new dependencies or modules required.

## Technical Context

**Language/Version**: Rust 2021 edition
**Primary Dependencies**: ratatui 0.30, crossterm 0.29, git2 0.19
**Storage**: N/A (ephemeral filter state, no persistence)
**Testing**: cargo test (integration tests in `tests/`)
**Target Platform**: Terminal (Linux/macOS)
**Project Type**: CLI tool with TUI dashboard
**Performance Goals**: Immediate keystroke response (sub-frame, <16ms)
**Constraints**: Single-threaded event loop, no async
**Scale/Scope**: ~1000 lines in tui.rs, adding ~100-150 lines

## Constitution Check

*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*

Constitution is unpopulated (template only). No gates to enforce. User preference for TDD noted from memory — tests should be written before implementation.

## Project Structure

### Documentation (this feature)

```text
specs/004-dashboard-filtering/
├── plan.md              # This file
├── research.md          # Phase 0 output
├── data-model.md        # Phase 1 output
├── spec.md              # Feature specification
├── checklists/          # Quality checklists
└── tasks.md             # Phase 2 output (via /speckit.tasks)
```

### Source Code (repository root)

```text
src/
├── tui.rs               # PRIMARY: All filtering logic added here
└── state.rs             # READ ONLY: IssueStatus, PatchStatus enums used for filtering

tests/
├── common/              # Shared test helpers
└── [existing tests]     # No new test files needed; TUI filtering tested via unit tests in src/tui.rs
```

**Structure Decision**: Single-file change in `src/tui.rs`. The `App` struct gains new fields (`search_query: String`, `search_active: bool`, `status_filter: StatusFilter`). The existing `show_all: bool` is replaced by a `StatusFilter` enum (Open, Closed, All). Visible item methods (`visible_issues`, `visible_patches`) are extended to apply both text and status filters. The `render_footer` function is updated to show active filter state. A new `InputMode` enum or flag manages whether keystrokes go to search input vs normal navigation.

## Design Decisions

### 1. Search Mode State Machine

Add an `InputMode` concept to `App`:
- **Normal**: All existing keybindings work as before
- **Search**: Keystrokes append to `search_query`; only Escape, Backspace, and printable chars are handled

The `/` key transitions Normal → Search. Escape transitions Search → Normal and clears the query.

### 2. Status Filter Enum Replaces `show_all` Boolean

```
enum StatusFilter { Open, Closed, All }
```

The `a` key cycles: Open → All → Closed → Open. This replaces the current `show_all: bool` toggle. The list title shows the current filter (e.g., "Issues (closed)").

### 3. Filter Application in Visible Methods

`visible_issues()` and `visible_patches()` apply both filters:
1. Status filter (match on `IssueStatus`/`PatchStatus`)
2. Text filter (case-insensitive substring match on `title`)

Both filters compose — an item must pass both to be visible.

### 4. Footer Rendering

When search is active: footer shows `Search: {query}_` with a cursor indicator.
When search is inactive but a status filter other than the default (Open) is active: footer shows the active status filter in the hint area.
Normal mode with no special filter: existing key hints shown.

### 5. Selection Reset on Filter Change

Any filter change (typing, status cycle) checks if the current selection is still within bounds. If not, resets to index 0 or None if the list is empty.

### 6. Key Conflict Check

Verified: `/` is not bound to any existing action in `run_loop`. The `a` key is already used for `show_all` toggle and will be repurposed for the status filter cycle.