specs/009-open-file-at-comment/tasks.md
Ref: Size: 8.1 KiB
# Tasks: Open File at Inline Comment Location
**Input**: Design documents from `/specs/009-open-file-at-comment/`
**Prerequisites**: plan.md (required), spec.md (required for user stories)
**Tests**: Not explicitly requested in spec -- test tasks omitted.
**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)
## Phase 1: Setup (Shared Infrastructure)
**Purpose**: Add new fields and helpers to App struct to support comment tracking and status messages
- [ ] T001 Add `status_msg: Option<String>` field to `App` struct in `src/tui.rs` for transient user feedback messages
- [ ] T002 Add `comment_positions: Vec<(usize, String, u32)>` field to `App` struct in `src/tui.rs` to map rendered diff line indices to `(rendered_line_index, file_path, source_line)` tuples
- [ ] T003 Update `App::new()` to initialize `status_msg: None` and `comment_positions: Vec::new()` in `src/tui.rs`
**Checkpoint**: App compiles with new fields. Existing functionality unaffected.
---
## Phase 2: User Story 1 - Open File at Inline Comment Line (Priority: P1) MVP
**Goal**: Press 'e' in diff view to open the file referenced by the nearest inline comment at the exact line in $EDITOR/$VISUAL
**Independent Test**: Create a patch with an inline comment on a known file and line, press 'e' in diff view, verify the editor opens the correct file at the correct line.
### Implementation for User Story 1
- [ ] T004 [US1] Refactor `colorize_diff()` in `src/tui.rs` to return `(Text, Vec<(usize, String, u32)>)` instead of just `Text` -- track each inline comment's rendered line index alongside its `file` and `line` fields from `InlineComment`
- [ ] T005 [US1] Update the call site of `colorize_diff()` in `render_detail()` in `src/tui.rs` to store the returned comment positions into `app.comment_positions` (requires `app` to be `&mut App` or positions stored via a different mechanism)
- [ ] T006 [US1] Add helper function `find_comment_at_scroll(comment_positions: &[(usize, String, u32)], scroll: u16) -> Option<(String, u32)>` in `src/tui.rs` -- reverse linear scan to find the inline comment whose rendered line is closest to and at or above the current scroll position
- [ ] T007 [US1] Add helper function `resolve_editor() -> Option<Vec<String>>` in `src/tui.rs` -- check `$VISUAL` first, then `$EDITOR`, split on whitespace to handle editors like `code --wait`, return `None` if neither is set
- [ ] T008 [US1] Add function `open_editor_at(file: &str, line: u32, terminal: &mut Terminal<CrosstermBackend<io::Stdout>>) -> Result<(), String>` in `src/tui.rs` -- check file exists on disk with `std::path::Path::new(file).exists()`, suspend TUI (`disable_raw_mode` + `LeaveAlternateScreen`), build and execute `std::process::Command` with `+<line>` and `<file>` args, wait for exit, restore TUI (`enable_raw_mode` + `EnterAlternateScreen`), force redraw by clearing terminal
- [ ] T009 [US1] Add `KeyCode::Char('e')` handler in `run_loop()` in `src/tui.rs` -- guard: only active when `app.tab == Tab::Patches && app.mode == ViewMode::Diff`; call `find_comment_at_scroll()` to get file and line; if found, call `resolve_editor()` then `open_editor_at()`; clear `status_msg` on success
- [ ] T010 [US1] Update `render_footer()` in `src/tui.rs` to include `e:edit` hint when in diff view mode (`app.tab == Tab::Patches && app.mode == ViewMode::Diff`)
**Checkpoint**: Core open-file-at-comment functionality works end-to-end. User can press 'e' on a diff with inline comments and the editor opens at the correct location.
---
## Phase 3: User Story 2 - Handle Missing Files and Editor Gracefully (Priority: P2)
**Goal**: Show clear status messages for error cases instead of crashing
**Independent Test**: Unset $EDITOR/$VISUAL and press 'e', or press 'e' on a comment referencing a deleted file -- verify status message appears in footer.
### Implementation for User Story 2
- [ ] T011 [US2] In `KeyCode::Char('e')` handler in `src/tui.rs`: when `resolve_editor()` returns `None`, set `app.status_msg = Some("No editor configured. Set $EDITOR or $VISUAL.".to_string())` and return early
- [ ] T012 [US2] In `open_editor_at()` in `src/tui.rs`: when `Path::new(file).exists()` is false, return `Err(format!("File not found: {}", file))` and in the caller set `app.status_msg` to that error message
- [ ] T013 [US2] In `open_editor_at()` in `src/tui.rs`: when the editor command exits with a non-zero status, return `Err(format!("Editor exited with status: {}", code))` and in the caller set `app.status_msg` accordingly
- [ ] T014 [US2] In `KeyCode::Char('e')` handler in `src/tui.rs`: when `find_comment_at_scroll()` returns `None` (no comment near scroll position), set `app.status_msg = Some("No inline comment at current position".to_string())`
- [ ] T015 [US2] Render `status_msg` in `render_footer()` in `src/tui.rs`: when `app.status_msg.is_some()`, display the message in the footer area with a distinct style (e.g., yellow on dark background) instead of normal key hints
- [ ] T016 [US2] Clear `status_msg` on any keypress in `run_loop()` in `src/tui.rs`: at the top of the key event handler block, set `app.status_msg = None` before processing the key
**Checkpoint**: All error cases handled gracefully with user-visible status messages. No panics.
---
## Phase 4: Polish & Cross-Cutting Concerns
**Purpose**: Edge cases, cleanup, and final validation
- [ ] T017 Ensure 'e' keypress is a no-op when `app.tab != Tab::Patches` or `app.mode != ViewMode::Diff` in `src/tui.rs` (verify guard condition in T009 is correct)
- [ ] T018 Ensure 'e' keypress is a no-op when the patch has no inline comments in `src/tui.rs` (comment_positions will be empty, find_comment_at_scroll returns None, handled by T014)
- [ ] T019 Ensure `comment_positions` is cleared when switching away from diff view mode or when switching patches in `src/tui.rs`
- [ ] T020 Run `cargo clippy` and fix any warnings introduced by the new code
- [ ] T021 Run `cargo test` and verify all existing tests still pass (no regressions)
---
## Dependencies & Execution Order
### Phase Dependencies
- **Setup (Phase 1)**: No dependencies -- T001-T003 must complete first
- **User Story 1 (Phase 2)**: Depends on Phase 1 (new App fields exist)
- **User Story 2 (Phase 3)**: Depends on Phase 2 (editor launch and comment lookup exist)
- **Polish (Phase 4)**: Depends on Phase 2 and Phase 3
### Within Each Phase
- Phase 1: T001-T003 are sequential (same file, dependent struct changes)
- Phase 2: T004 before T005 (colorize_diff refactor before call site update); T006-T007 independent helpers; T008 depends on T007; T009 depends on T006+T008; T010 independent
- Phase 3: T011-T016 are all sequential (same file, build on each other)
- Phase 4: T017-T019 independent verifications; T020-T021 final checks
### Parallel Opportunities
- All tasks modify `src/tui.rs` -- no parallel execution possible
- T006 and T007 could theoretically be written in parallel (independent helper functions) but since they are in the same file, sequential is safer
---
## Implementation Strategy
### MVP First (User Story 1 Only)
1. Complete Phase 1: Setup (T001-T003)
2. Complete Phase 2: User Story 1 (T004-T010)
3. **STOP and VALIDATE**: Test that 'e' opens the editor at the correct file+line
4. Proceed to Phase 3-4
### Incremental Delivery
1. Setup -- App struct extended with new fields
2. Add US1 -- Editor launch from diff view works (MVP!)
3. Add US2 -- Error handling and status messages
4. Polish -- Edge cases, clippy, test validation
---
## Notes
- All 21 tasks modify a single file: `src/tui.rs` -- no parallelism possible
- The `colorize_diff()` refactor (T004) is the most complex task, touching rendering logic
- The `open_editor_at()` function (T008) handles terminal suspend/resume, which must match the pattern used in the existing `run()` function
- `InlineComment` struct in `src/state.rs` already has `file: String` and `line: u32` fields -- no changes needed there
- No `status_msg` or `InputMode` exists in current `tui.rs` -- T001 introduces the status message field
- Total tasks: 21 (Setup=3, US1=7, US2=6, Polish=5)