<!-- STATUS: DRAFT -->

# Phase 17 Summary: TUI Screens and Interaction Flows

## At a Glance

Phase 17 wires the TUI command handlers into live interaction flows — missions can be selected, started, completed, and resumed; sessions can end with confirmation modals; and all state changes produce toast notifications or friendly errors.

### Files Changed

| File | Change |
|------|--------|
| `src/unfamiliar/campaign/missions.py` | Fixed `complete_mission()` return type from raw tuple to `CompletionResult` dataclass |
| `src/unfamiliar/tui/app.py` | Full implementation of all 8 interaction flows: next/start, complete, mission view, session resume, end campaign, notifications, error handling, quit confirmation |
| `src/unfamiliar/tui/commands.py` | Added `accepts_args=True` to `mission` command definition |
| `src/unfamiliar/tui/widgets/content.py` | Updated hint text to "Type 'next <N>' to start a lead, or 'mission <N>' to peek." |
| `src/unfamiliar/tui/screens/__init__.py` | Created — exports `ConfirmModal` |
| `src/unfamiliar/tui/screens/confirm.py` | Created — `ConfirmModal(ModalScreen[bool])` generic reusable confirmation dialog |

### Key Functions

| Function | Behavior |
|----------|----------|
| `complete_mission()` in `missions.py` | Now returns `CompletionResult(mission_id, unlocked_ids)` instead of a raw tuple |
| `UnfamiliarApp.on_mount()` | Detects in-progress missions on startup and shows contextual welcome message |
| `UnfamiliarApp._handle_next(args)` | No args → show available missions and cache them; with number → start that mission |
| `UnfamiliarApp._start_mission(mission_id)` | Calls `start_mission()`, sets current, loads detail, notifies, refreshes header, invalidates cache |
| `UnfamiliarApp._handle_complete(args)` | Completes current mission, builds `CompletionData`, shows completion view, notifies on unlocks |
| `UnfamiliarApp._handle_mission(args)` | No args → current mission; with number → view from cache without starting |
| `UnfamiliarApp._handle_end_campaign(args)` | Pushes `ConfirmModal`, on confirm calls `_close_campaign()` |
| `UnfamiliarApp._close_campaign()` | Updates DB status to CLOSED, shows summary, schedules exit |
| `UnfamiliarApp._confirm_quit()` | Exits immediately if no active mission; otherwise pushes quit confirmation modal |
| `ConfirmModal.__init__(message, confirm_label)` | Generic confirmation dialog; dismisses with True/False |
| `ConfirmModal.on_button_pressed(event)` | Dismisses with True on confirm, False on cancel |
| `ConfirmModal.on_key(event)` | Dismisses with False on escape |

### Behavior Changes (user-visible)

- **`next` with no args**: Shows available mission list (numbered), stores in cache
- **`next <N>`**: Starts mission N from the last displayed list, transitions to IN_PROGRESS
- **`mission <N>`**: Views mission N from the last list without starting it
- **Session resume**: On launch, TUI detects in-progress missions and sets contextual welcome message
- **`end campaign`**: Triggers confirmation modal; on confirm, marks campaign CLOSED and exits
- **`q` / `quit` with active mission**: Triggers confirmation modal; no active mission exits immediately
- **State-change toasts**: "On the case", "Lock picked" notifications on start/complete/unlock
- **`complete_mission()` callers**: Function now returns `CompletionResult` dataclass instead of `tuple[int, list[int]]`

---

## What Was Built

Phase 17 transformed the Phase 16 TUI skeleton into a functional investigation tool. The phase implemented all eight planned interaction tasks:

1. **Bug fix**: `complete_mission()` was returning a raw tuple but app code expected a `CompletionResult` dataclass — fixed the return type and added the import.

2. **Mission selection cache**: `_mission_list_cache` maps displayed numbers (1-indexed) to `MissionSummary` objects. Populated by `next` with no args, invalidated on any status-changing command (start, complete, unlock).

3. **`next` and `mission` with arguments**: Both commands now parse a number arg, validate it against the cache, and either start or preview the selected mission.

4. **Session resume flow**: `on_mount` queries for IN_PROGRESS missions and shows one of three contextual welcome messages based on campaign state (has in-progress, has available, all locked/closed).

5. **Confirmation modals**: `ConfirmModal(ModalScreen[bool])` is a generic reusable dialog. Used for both end-campaign and quit-with-active-mission confirmations.

6. **Toast notifications**: `self.notify()` used for transient confirmations (mission started, leads unlocked, unlock escape hatch). Content area reserved for scrollable reference views.

7. **Error handling audit**: All status-changing handlers catch `MissionStatusError` and show friendly messages. Header refresh and cache invalidation consistent across all handlers.

8. **Quit confirmation**: `_confirm_quit()` shared by both `action_quit` (q key) and `_handle_quit` command. Exits immediately if no active mission.

## Key Decisions

- **Cache for mission selection**: Instead of looking up missions by number live, the `_mission_list_cache` stores the last `next` result. This keeps `next <N>` consistent with what was displayed and avoids surprise if the list changes between commands. Cache is cleared on any status change.

- **Multiple IN_PROGRESS allowed**: No constraint to one active mission. `current_mission_id` is UI tracking only. This supports ADHD-friendly free switching between leads.

- **`ConfirmModal` as generic widget**: Reused for both end-campaign and quit confirmations. The `confirm_label` parameter differentiates the buttons.

- **Toast vs. content area**: State-change confirmations (started, unlocked) go to toasts. Reference views (mission list, detail, status, briefing) go to the content area.

## Major Logic Flows

### Starting a mission (`next 2`)
1. User types `next 2`
2. `_handle_next(["2"])` — converts to index 1, validates cache bounds
3. Gets `_mission_list_cache[1].id` → calls `_start_mission(mission_id)`
4. `_start_mission` calls `start_mission(db, mission_id)` — DB update AVAILABLE → IN_PROGRESS
5. Sets `self.current_mission_id`
6. Loads `load_mission_detail` → shows in content area
7. `self.notify("Lead started: ...", title="On the case")`
8. Refreshes header, invalidates cache

### Completing a mission (`complete`)
1. User types `complete`
2. `_handle_complete([])` — checks `current_mission_id` is set
3. Calls `complete_mission(db, current_mission_id)` → returns `CompletionResult`
4. Builds `CompletionData` with unlocked titles and progress stats
5. Shows completion view, notifies on unlocks
6. Clears `current_mission_id`, invalidates cache, refreshes header

### End campaign flow
1. User types `end campaign` (or Film Noir alias)
2. `_handle_end_campaign` pushes `ConfirmModal`
3. User presses Confirm → `on_confirm(True)` → `_close_campaign()`
4. DB updates campaign status to CLOSED
5. Shows summary text, schedules exit in 3 seconds

## Connection to Previous Phases

- **Phase 13** (`campaign/missions.py`): `start_mission`, `complete_mission`, `unlock_mission` — all used directly in app.py handlers.
- **Phase 16** (`tui/` base): Widgets, queries, command routing — all unchanged; Phase 17 fills in the command handlers only.
- **Phase 15** (pipeline output quality): No direct dependency, but clean mission data in DB is what the TUI now navigates.

<!-- STATUS: COMPLETE -->
