<!-- STATUS: COMPLETE -->

# Phase 18 Summary: Film Noir Theme and Narrative

## At a Glance

**What this phase accomplished:** Made the theme system fully plugin-compatible by templatizing LLM prompts with theme variables and threading vocabulary/aliases through all TUI user-facing strings. Also addressed deferred items from Phase 17 UA testing (command panel redesign, in-progress missions in next list).

### Files Changed

| File | Description |
|------|-------------|
| `tui/themes/__init__.py` | Added NarrativeConfig dataclass and vocab()/alias() helper methods to ThemeConfig |
| `tui/themes/film_noir/theme.py` | Added _NARRATIVE constant with Film Noir-specific narrative data |
| `llm/missions/prompts.py` | Templatized prompts with theme variables, added build_theme_prompt_vars() helper |
| `llm/missions/generator.py` | Added theme_config parameter to both generation functions |
| `llm/missions/pipeline.py` | Added theme_config parameter to run_mission_pipeline() |
| `campaign/orchestrator.py` | Loads theme and passes to pipeline |
| `tui/widgets/content.py` | Added theme parameter, all show_* methods use self.theme.vocab() |
| `tui/widgets/command_ref.py` | Added theme parameter, shows alias + description format |
| `tui/app.py` | Passes theme_config to widgets, all user-facing strings use themed aliases |
| `tui/queries.py` | Added load_actionable_missions() for IN_PROGRESS + AVAILABLE missions |

### Key Functions

| Function | Description |
|----------|-------------|
| `NarrativeConfig` | Frozen dataclass with tone, title_examples, intro_style, outro_style |
| `ThemeConfig.vocab(key)` | Returns themed vocabulary term with fallback to key |
| `ThemeConfig.alias(key)` | Returns themed command alias with fallback to key |
| `build_theme_prompt_vars(theme_config)` | Builds template variable dict for mission prompts with fallback defaults |
| `load_actionable_missions(db, campaign_id)` | Returns IN_PROGRESS + AVAILABLE missions (in-progress first) |

### Behavior Changes

- **LLM prompts:** Mission generation now uses templatized theme variables instead of hardcoded "Film Noir" language
- **Theme plugin-ready:** New themes require only data files (NarrativeConfig, vocabulary, aliases, TCSS) — zero code changes
- **Command reference panel:** Shows themed alias + description (e.g., "investigate" + "Pick next mission"), not canonical names
- **Help command:** Man-page style reference showing both themed aliases and canonical names (in parentheses)
- **Next command:** Shows both IN_PROGRESS and AVAILABLE missions; in-progress missions marked with status indicator
- **Mission resume:** Selecting an already-in-progress mission no longer throws MissionStatusError

---

## What Was Built

### 1. NarrativeConfig Dataclass

A structured configuration for LLM prompt injection:

```python
@dataclass(frozen=True)
class NarrativeConfig:
    tone: str              # General tone/genre description
    title_examples: list[str]  # 3-5 example themed titles
    intro_style: str       # How to write narrative_intro fields
    outro_style: str       # How to write narrative_outro fields
```

### 2. ThemeConfig Helper Methods

Safe fallback methods that never raise:

```python
def vocab(self, key: str) -> str:
    return self.vocabulary.get(key, key)

def alias(self, key: str) -> str:
    return self.aliases.get(key, key)
```

### 3. Templatized Mission Prompts

The `MISSION_SYSTEM_PROMPT` and `MISSION_HUMAN_PROMPT` now use template variables:

- `{theme_name}` — e.g., "Film Noir"
- `{theme_title_examples}` — e.g., "The Password Vault", "Shadows in the Login Flow"
- `{theme_intro_style}` — How to write narrative intros
- `{theme_outro_style}` — How to write narrative outros

### 4. Theme Pipeline Threading

Theme data flows through the entire call chain:

```
orchestrator.create_campaign()
  → get_theme(config.theme)
  → run_mission_pipeline(..., theme_config=theme_config)
    → generate_category_missions_multi(..., theme_config=theme_config)
      → build_theme_prompt_vars(theme_config)
      → chain.invoke(input_data)
```

### 5. Actionable Missions Query

New query function returns missions the user can act on:

```python
def load_actionable_missions(db, campaign_id) -> list[MissionSummary]:
    in_progress = db.mission_repo.get_by_status(campaign_id, MissionStatus.IN_PROGRESS)
    available = db.mission_repo.get_by_status(campaign_id, MissionStatus.AVAILABLE)
    summaries = [_mission_to_summary(m) for m in in_progress]
    summaries.extend(_mission_to_summary(m) for m in available)
    return summaries
```

---

## Key Decisions

1. **Structured NarrativeConfig over string blob:** A dataclass provides clear guidance for theme authors and enables validation. Fields are self-documenting.

2. **Fallback strategy:** `build_theme_prompt_vars()` returns generic "Investigation" language when theme_config is None or fields are empty. This ensures existing callers and tests work unchanged.

3. **IN_PROGRESS resume handling:** When a mission is already in progress, `_start_mission()` skips the status change call and just sets it as current. This prevents `MissionStatusError` on resume.

4. **Presentation-layer theming:** ThemeConfig is passed to widgets, not stored globally. This preserves separation of concerns — theme is presentation config, not data.

---

## Major Logic Flows

### Campaign Creation with Themed Narrative

```
1. orchestrator.create_campaign(repo_path, config)
2. get_theme(config.theme) → ThemeConfig with NarrativeConfig
3. run_mission_pipeline(..., theme_config=theme_config)
4. For each category:
   a. generate_category_missions_multi(..., theme_config=theme_config)
   b. build_theme_prompt_vars(theme_config) → template vars
   c. chain.invoke(input_data with theme vars)
5. LLM produces themed missions (Film Noir titles, narrative intros/outros)
```

### TUI Text Theming

```
1. UnfamiliarApp loads theme_config in __init__
2. compose() passes theme_config to ContentArea and CommandReference
3. ContentArea.show_* methods call self.theme.vocab() for all terms
4. CommandReference._update_children() shows alias + description
5. app.py command handlers use self.theme_config.alias() for hints
```

---

## Connection to Previous Phases

- **Phase 7 (Mission Generation):** Prompts now templatized with theme variables
- **Phase 16 (TUI Layout):** Theme infrastructure now has NarrativeConfig and helper methods
- **Phase 17 (TUI Screens):** Command handlers now use themed aliases in all user-facing strings

This phase completes the theme system for v1. A new theme requires only:
1. A new directory under `tui/themes/`
2. A theme.py with ThemeConfig (NarrativeConfig, aliases, vocabulary)
3. A TCSS stylesheet
4. Registration via `register_theme()`

No code changes needed in prompts.py, widgets, or app.py.
