<!-- STATUS: COMPLETE -->

# Phase 20 Testing: Polish — Deferrals, Accessibility, and Noir Design

## At a Glance

**One sentence:** Verify all 9 deferred items are addressed, keyboard accessibility works, themed colors/decorations/icons render correctly, and reopen/overwrite flows function properly.

### What To Verify
- [x] Keyboard shortcuts: `/` re-focuses input, arrow keys navigate modals, Tab works
- [x] Campaign overwrite: Prompts confirmation when overwriting ACTIVE campaign with progress
- [x] Reopen command: Reopens a CLOSED campaign
- [x] Themed colors: All UI elements use Film Noir palette (no hardcoded hex visible)
- [x] Decorative elements: Dividers, bullets, progress bars appear in TUI
- [x] Title deduplication: Duplicate thematic titles get (II), (III) suffixes
- [x] Silent failure handling: Shows error message when database unavailable

---

## Automated Tests

**How to run:** `pytest tests/ -q --tb=short`

**New tests:** 15 tests in `tests/test_phase20_functions.py`
- `TestReopenCampaign`: 4 tests for reopen_campaign()
- `TestCampaignExistsError`: 2 tests for exception attributes
- `TestThemeConfigIcons`: 3 tests for icon() method
- `TestThemeConfigDecoration`: 3 tests for decoration() method
- `TestMissionCountMethods`: 3 tests for count methods

**Full suite:** 1059 tests passing

---

## Manual Testing Steps

### Quick Smoke Test (2-5 min)

1. Launch TUI with a seeded campaign
2. Press `Escape` to defocus command input
3. Press `/` to re-focus command input
4. Type `help` and verify keyboard shortcuts section shows `/`, `Tab`, `←/→`
5. Verify decorative dividers appear in help and status views

- **Expected:** All keyboard shortcuts work, decorative elements visible

### Feature 1 — Keyboard Accessibility (5 min)

1. Launch TUI
2. Type `close the case` (end campaign command) to trigger confirmation modal
3. Press `←` and `→` arrow keys
4. Press `Tab`
5. Press `Escape`
6. Press `/` then type `status`

- **Expected:**
  - Arrow keys cycle focus between Cancel and Confirm buttons
  - Tab moves focus between elements
  - Escape dismisses modal or defocuses input
  - `/` re-focuses command input
- **If fails:** Check noir.tcss for `:focus` styles, confirm.py for `on_key` handler

### Feature 2 — Campaign Overwrite Guard (5-10 min)

1. From command line: `unfamiliar investigate /path/to/repo` (use a repo with existing campaign)
2. Complete at least 1 mission in that campaign
3. Run `unfamiliar investigate /path/to/repo` again
4. When prompted, decline the overwrite
5. Run `unfamiliar investigate /path/to/repo --force`

- **Expected:**
  - Step 3: Prompts "Campaign exists with X/Y missions completed. Overwrite?"
  - Step 4: Exits without modifying campaign
  - Step 5: Overwrites without prompting
- **If fails:** Check CLI catches CampaignExistsError, orchestrator.py force parameter

### Feature 3 — Reopen Command (5 min)

1. Launch TUI with a campaign
2. Type `close the case` and confirm to close it
3. Campaign status should show CLOSED in header
4. Type `reopen` (or `cold case` if alias added)
5. Verify campaign is now ACTIVE

- **Expected:**
  - Step 2: Campaign closes, shows completion summary
  - Step 4: Notification "Case reopened. Continue your investigation."
  - Step 5: Header shows campaign as ACTIVE
- **If fails:** Check missions.py reopen_campaign(), app.py _handle_reopen()

### Feature 4 — Themed Colors and Decorations (2-3 min)

1. Launch TUI
2. Type `status` to see case board
3. Check for:
   - Gold accent color (#c9a227) on headers
   - Cream text (#e8e4d9) on content
   - Progress bars with █ and ░ characters
   - Decorative divider `━━━✦━━━` in command reference

- **Expected:** All colors match Film Noir palette, progress bars visible, dividers decorative
- **If fails:** Check ThemeConfig.color(), decoration(), _c() shorthand in widgets

### Feature 5 — Silent Failure Handling (2 min)

1. This is hard to test manually (requires DB to be None)
2. Verify via code review that `_require_db()` is used in all command handlers

---

## Edge Cases to Check

### Priority 1 (Test First)

- [x] **Reopen on ACTIVE campaign**: Type `reopen` on an already ACTIVE campaign
  - **Expected:** Shows error "Campaign X is not CLOSED (status: ACTIVE)"
- [x] **Overwrite INITIALIZING campaign**: Run investigate on a campaign stuck in INITIALIZING
  - **Expected:** Silently overwrites without prompting (only ACTIVE with progress triggers prompt)
- [x] **Title deduplication**: Generate missions, check logs for dedup warnings
  - **Expected:** If LLM generates duplicate titles, (II), (III) suffixes applied

### Priority 2

- [x] **No missions completed**: Run investigate on repo with 0 completed missions
  - **Expected:** No overwrite prompt (0 completed doesn't count as "progress")
- [x] **Focus on modal open**: Open modal, verify Cancel button has initial focus
- [x] **Escape during modal**: Open modal, press Escape, verify modal dismisses without action

---

## Possibly Obsolete Tests

None identified. All existing tests continue to pass.

---

## Issues Discovered During Build

1. **Missing "reopen" alias**: Plan specified `"reopen": "cold case"` in Film Noir aliases — not implemented. The `reopen` command works but uses canonical name.
2. **Missing tests for color()**: No unit tests for `ThemeConfig.color()` method (though it follows same pattern as icon/decoration which are tested).
3. **Missing tests for _deduplicate_titles()**: No unit tests for title deduplication function.

---

## Phase Completion Checklist

### Must Pass
- [ ] All manual tests pass
- [ ] Edge cases checked
- [ ] No regressions in existing features (1059 tests passing)
- [ ] Keyboard accessibility verified

### Nice to Have
- [ ] Add "cold case" alias for reopen command
- [ ] Add tests for ThemeConfig.color()
- [ ] Add tests for _deduplicate_titles()

### Known Limitations
- "stay" vocabulary term not in vocabulary dict (falls back to "stay")
- Title deduplication only triggers on exact duplicate thematic titles

---

## User Testing Notes

*Fill in this section after completing the testing steps above. Then run `/plan:review` to record your results and close the phase.*

### Issues Found

1. **Missing "cold case" alias** — Fixed: added to `_COMMAND_ALIASES` in film_noir/theme.py
2. **Missing CLI reopen command** — Fixed: added `unfamiliar reopen <repo>` to cli.py
3. **Ruff errors (8)** — Fixed: missing imports, line length, exception chaining

### Error Messages / Logs

```
[none]
```

### Questions

1. [none]

### General Feedback

[none]

### Result

- [x] All good — ready to proceed
- [ ] Issues found — see above
