specs/004-dashboard-filtering/tasks.md
Ref: Size: 10.4 KiB
# Tasks: Dashboard Filtering
**Input**: Design documents from `/specs/004-dashboard-filtering/`
**Prerequisites**: plan.md (required), spec.md (required), research.md, data-model.md
**Tests**: Included per TDD preference (user memory). Tests written first, must fail before implementation.
**Organization**: Tasks grouped by user story. All changes in `src/tui.rs` (single file).
## Format: `[ID] [P?] [Story] Description`
- **[P]**: Can run in parallel (different files, no dependencies)
- **[Story]**: Which user story this task belongs to (e.g., US1, US2, US3)
- Include exact file paths in descriptions
## Phase 1: Setup (Shared Infrastructure)
**Purpose**: Add new types and modify App struct to support filtering
- [ ] T001 Add `StatusFilter` enum (Open, Closed, All) with a `next()` cycling method in `src/tui.rs`
- [ ] T002 Add `search_query: String`, `search_active: bool`, and `status_filter: StatusFilter` fields to `App` struct, replacing `show_all: bool` in `src/tui.rs`
- [ ] T003 Update `App::new()` to initialize new fields (`status_filter: StatusFilter::Open`, `search_query: String::new()`, `search_active: false`) in `src/tui.rs`
**Checkpoint**: App struct compiles with new fields. Existing functionality temporarily broken (show_all references).
---
## Phase 2: Foundational (Blocking Prerequisites)
**Purpose**: Update all existing code that references `show_all` to use `status_filter`, restoring compilation
**⚠️ CRITICAL**: No user story work can begin until this phase is complete
- [ ] T004 Update `visible_issues()` to filter by `status_filter` instead of `show_all` in `src/tui.rs` — match `StatusFilter::Open` to `IssueStatus::Open`, `StatusFilter::Closed` to `IssueStatus::Closed`, `StatusFilter::All` to no filter
- [ ] T005 Update `visible_patches()` to filter by `status_filter` instead of `show_all` in `src/tui.rs` — match `StatusFilter::Open` to `PatchStatus::Open`, `StatusFilter::Closed` to BOTH `PatchStatus::Closed` AND `PatchStatus::Merged` (merged patches are treated as closed for filtering), `StatusFilter::All` to no filter
- [ ] T006 Update `visible_issue_count()` to use `status_filter` instead of `show_all` in `src/tui.rs`
- [ ] T007 Update `follow_link()` to set `self.status_filter = StatusFilter::All` instead of `self.show_all = true` in `src/tui.rs`
- [ ] T008 Update `render_list()` to derive list title from `status_filter` (e.g., "Issues (open)", "Issues (closed)", "Issues (all)") instead of `show_all` in `src/tui.rs`
- [ ] T009 Update the `'a'` key handler in `run_loop()` to call `status_filter.next()` cycle instead of toggling `show_all` in `src/tui.rs`
**Checkpoint**: Project compiles and passes existing tests. `a` key now cycles Open → All → Closed → Open. All existing behavior preserved.
---
## Phase 3: User Story 1 - Text Search Filtering (Priority: P1) 🎯 MVP
**Goal**: Users press `/` to enter search mode, type to filter items by title in real-time, Escape clears and exits.
**Independent Test**: Launch dashboard with multiple items, press `/`, type partial title, verify only matching items appear.
### Tests for User Story 1 ⚠️
> **NOTE: Write these tests FIRST, ensure they FAIL before implementation**
- [ ] T010 [US1] Write unit test in `src/tui.rs` (mod tests): verify `visible_issues()` filters by `search_query` with case-insensitive substring match when `search_active` is true and `search_query` is non-empty
- [ ] T011 [US1] Write unit test in `src/tui.rs` (mod tests): verify `visible_patches()` filters by `search_query` with case-insensitive substring match when `search_active` is true and `search_query` is non-empty
- [ ] T012 [US1] Write unit test in `src/tui.rs` (mod tests): verify `visible_issues()` returns all status-matching items when `search_query` is empty
### Implementation for User Story 1
- [ ] T013 [US1] Add text filter to `visible_issues()`: when `search_query` is non-empty, additionally filter items where `title.to_lowercase().contains(&search_query.to_lowercase())` in `src/tui.rs`
- [ ] T014 [US1] Add text filter to `visible_patches()`: same case-insensitive substring match logic in `src/tui.rs`
- [ ] T015 [US1] Add text filter to `visible_issue_count()`: apply same text filter logic for consistency in `src/tui.rs`
- [ ] T016 [US1] Add `/` key handler in `run_loop()`: when not in search mode, set `search_active = true` and `search_query = ""` in `src/tui.rs`
- [ ] T017 [US1] Add search mode key handling in `run_loop()`: when `search_active` is true, intercept key events BEFORE the normal match block (critical: `Escape` must clear query and exit search mode, NOT quit the TUI — the normal `KeyCode::Esc => return Ok(())` arm must not fire while searching). Handle `Backspace` to pop last char, printable `Char(c)` to append (including `/`), and reset selection after each change in `src/tui.rs`
- [ ] T018 [US1] Add selection reset helper: after any filter change (typing, backspace, escape), check if current selection index >= `visible_count()` and reset to 0 or None in `src/tui.rs`
**Checkpoint**: Text search fully functional. Press `/`, type, list filters. Escape clears. Tests pass.
---
## Phase 4: User Story 2 - Status Filter Cycling (Priority: P2)
**Goal**: The `a` key cycles between open/closed/all status filters, applied consistently across tabs.
**Independent Test**: Press `a` multiple times, verify list shows only items matching the selected status.
### Tests for User Story 2 ⚠️
- [ ] T019 [US2] Write unit test in `src/tui.rs` (mod tests): verify `StatusFilter::next()` cycles Open → All → Closed → Open
- [ ] T020 [US2] Write unit test in `src/tui.rs` (mod tests): verify `visible_issues()` returns only closed issues when `status_filter` is `Closed`
- [ ] T021 [US2] Write unit test in `src/tui.rs` (mod tests): verify `visible_patches()` returns closed and merged patches when `status_filter` is `Closed`
### Implementation for User Story 2
- [ ] T022 [US2] Write unit test in `src/tui.rs` (mod tests): verify `a` key handler cycles `status_filter` through Open → All → Closed → Open and resets selection to 0 or None after each cycle
- [ ] T023 [US2] Ensure status filter persists across tab switches — verify `switch_tab()` does NOT reset `status_filter` in `src/tui.rs`
- [ ] T024 [US2] Add selection reset in `a` key handler: after cycling status filter, reset selection to 0 or None based on new `visible_count()` in `src/tui.rs`
**Checkpoint**: Status filter cycling works. `a` cycles through all three states. Tests pass.
---
## Phase 5: User Story 3 - Combined Filtering with Status Bar Feedback (Priority: P3)
**Goal**: Text and status filters compose, and the footer shows active filter state at all times.
**Independent Test**: Set both a text filter and status filter, verify only doubly-matching items show. Check footer reflects active state.
### Tests for User Story 3 ⚠️
- [ ] T025 [US3] Write unit test in `src/tui.rs` (mod tests): verify `visible_issues()` applies both `status_filter` and `search_query` simultaneously — only items matching both criteria are returned
- [ ] T026 [US3] Write unit test in `src/tui.rs` (mod tests): verify Escape clears text filter but preserves `status_filter`
### Implementation for User Story 3
- [ ] T027 [US3] Update `render_footer()` in `src/tui.rs`: when `search_active` is true, display `Search: {query}_` with cursor indicator instead of normal key hints
- [ ] T028 [US3] Update `render_footer()` in `src/tui.rs`: when `search_active` is false, show status filter state in the `filter_hint` area (replace "a:open only"/"a:show all" with three-state hint based on `status_filter`)
- [ ] T029 [US3] Update `render_footer()` in `src/tui.rs`: add `/:search` to the normal mode key hints
**Checkpoint**: Combined filtering works. Footer shows active filters. All tests pass.
---
## Phase 6: Polish & Cross-Cutting Concerns
**Purpose**: Edge cases, refresh behavior, and final cleanup
- [ ] T030 Ensure `reload()` in `src/tui.rs` preserves `search_query`, `search_active`, and `status_filter` after refresh (verify fields are not reset)
- [ ] T031 Handle empty filtered list in `render_detail()` in `src/tui.rs` — when no items match current filters, show "No matches for current filter" instead of the generic "No issues/patches to display"
- [ ] T032 Handle long search query display in `render_footer()` in `src/tui.rs` — truncate `search_query` to fit available footer width
- [ ] T033 Run `cargo clippy` and fix any warnings introduced by the new code in `src/tui.rs`
- [ ] T034 Run `cargo test` and verify all existing and new tests pass
---
## Dependencies & Execution Order
### Phase Dependencies
- **Setup (Phase 1)**: No dependencies — can start immediately
- **Foundational (Phase 2)**: Depends on Phase 1 — BLOCKS all user stories
- **User Story 1 (Phase 3)**: Depends on Phase 2
- **User Story 2 (Phase 4)**: Depends on Phase 2 (independent of US1)
- **User Story 3 (Phase 5)**: Depends on Phase 3 AND Phase 4 (combines both filters)
- **Polish (Phase 6)**: Depends on all user stories
### Within Each User Story
- Tests MUST be written and FAIL before implementation
- Implementation tasks are sequential (same file: `src/tui.rs`)
### Parallel Opportunities
- **Phase 1**: T001, T002, T003 are sequential (same file, dependent)
- **Phase 2**: T004-T009 are sequential (same file)
- **Phase 3 & 4**: US1 and US2 could theoretically run in parallel after Phase 2, but since all changes are in the same file (`src/tui.rs`), they MUST be sequential
- **No [P] markers**: All tasks modify `src/tui.rs` — no parallel execution possible
---
## Implementation Strategy
### MVP First (User Story 1 Only)
1. Complete Phase 1: Setup (T001-T003)
2. Complete Phase 2: Foundational (T004-T009)
3. Complete Phase 3: User Story 1 (T010-T018)
4. **STOP and VALIDATE**: Test text search independently
5. Deploy/demo if ready
### Incremental Delivery
1. Setup + Foundational → Status filter enum replaces show_all (existing behavior preserved)
2. Add User Story 1 → Text search works → Validate
3. Add User Story 2 → Status cycling verified → Validate
4. Add User Story 3 → Combined filtering + footer → Validate
5. Polish → Edge cases, cleanup → Final validation
---
## Notes
- All 34 tasks modify a single file: `src/tui.rs` — no parallelism possible
- TDD approach: 8 test tasks + 26 implementation tasks
- Each user story builds on the foundational `StatusFilter` refactor
- US3 is the only story with cross-story dependency (requires both text and status filters)