# Track: Prior-Session Sepia Tint

**Status:** Active (planning)
**Initialized:** 2026-06-10
**Owner:** Tier 2 Tech Lead
**Priority:** C (UI polish; no functional blocker; complements `multi_themes_20260604` and the 2026-06-05 prior-session work)
**Parent track (none).** Sibling: `multi_themes_20260604` (added TOML theme loading + per-theme syntax palette mapping).
**Design doc:** [docs/superpowers/specs/2026-06-10-prior-session-sepia-design.md](../../docs/superpowers/specs/2026-06-10-prior-session-sepia-design.md)

---

## 1. Problem Statement

When a user enters Historical Replay mode (a.k.a. "prior session" view), the current implementation
uses `theme.get_color("bubble_vendor")` as the background tint at three call sites
(`src/gui_2.py:1028`, `:3960`, plus the "HISTORICAL VIEW" banners at `:5142` and `:5440`).
This is **not a dedicated prior-session color slot** — it is a semantic overload of the
"Vendor API" role's bubble color. Authors of new themes cannot tune the prior-session
feel without changing the Vendor API bubble.

Beyond the missing dedicated slot, there is no **content-level color grading**: text, markdown,
and code blocks inside prior-session views render at full saturation in the active palette.
The "I'm looking at a past session" cue is carried only by the bg tint, not by the content.

**Pre-existing related bug** (surfaced by the user during brainstorming): code blocks rendered
by `imgui_color_text_edit` (`src/markdown_helper.py:328-340`, `src/gui_2.py:4773-4780`) are **not
tonemap-aware**. Their colors are loaded from the library's hardcoded palettes (`dark`, `light`,
`mariana`, `retro_blue`) and never passed through `theme._tone_map()`. The user has called this
"disappointing" because it defeats the primary purpose of the tonemapper: allowing a light
theme to be usable on a bright monitor without searing the user's retinas.

### 1.1 Goals (in scope)

- **G1** — Add three new theme slots, per-palette: `prior_session_bg`, `prior_session_tint`, and
  a per-palette float `prior_session_amount`. Defaults baked into `ThemePalette`
  (`src/theme_models.py`) and the hardcoded fallback dict (`src/theme_2.py:184-201`).
- **G2** — Add a runtime state dict `_prior_session_amount: dict[str, float]` in `src/theme_2.py`
  mirroring `_brightness/_contrast/_gamma` exactly. Expose
  `get_prior_session_amount(palette) / set_prior_session_amount(palette, val) /
  reset_prior_session(palette)`.
- **G3** — Add the pure-function helper `apply_prior_tint(rgba, palette_name)` in
  `src/theme_2.py`. All math uses **float** (not int) end-to-end per user requirement.
- **G4** — Replace the 2 hardcoded `bubble_vendor` tint sites in `src/gui_2.py` (lines 1028, 3960)
  with the new `prior_session_bg` slot, and wrap content-rendering color calls with
  `apply_prior_tint(...)` at 4 prior-session sites (the 2 "HISTORICAL VIEW" banners plus
  the comms/tool-log render functions that consume prior-session caches). Audit list in §4.1.
- **G5** — Add a new "Prior Session Sepia (Per-Palette)" section in the Theme Settings panel
  (`src/gui_2.py:5007+`) with a single `slider_float` 0.0–1.0 and a "Reset" button. Mirrors
  the existing Tone Mapping section's behavior: per-palette state, persisted to config, snaps
  to per-palette default on theme switch.
- **G6** — Add TOML keys to all 8 existing `themes/*.toml` files with sensible per-theme defaults.

### 1.1.1 Honest constraint: code-block tonemap-awareness is NOT in scope

During spec self-review, the user and I verified the upstream `imgui_bundle 1.92.5` API:
`TextEditor.get_palette() -> PaletteId` and `TextEditor.set_palette(PaletteId) -> None` where
`PaletteId` is an enum with 4 hardcoded values (`dark`, `light`, `mariana`, `retro_blue`).
**There is no `Palette` struct with mutable per-color slots** in this API surface.

The user had hoped this track could fix the pre-existing "code blocks are not tonemap-aware"
bug. The honest answer is: **the upstream library does not expose a per-instance color
override API**, so we cannot apply `_tone_map` or `apply_prior_tint` to code-block syntax
token colors. The same constraint already forced the `multi_themes_20260604` track to ship
a `syntax_palette` field (one of 4 enums) rather than custom token colors.

What IS tonemap-aware in code blocks today: the bg, selection highlight, current-line fill,
and line number bg, because those use **ImGui style colors** which go through `get_color()`
and therefore through `_tone_map`. The four hardcoded syntax token colors
(`default`/`keyword`/`number`/`string`/`comment`/etc.) are NOT tonemap-aware and CANNOT
be made so without forking `imgui_bundle`.

This track does NOT fix that. **Out of scope** (see §1.2 N6 and §9 N1). The user should
not be promised a fix that the API doesn't support.

### 1.2 Non-Goals (out of scope)

- **N1** — Per-language syntax color customization beyond the existing 4 built-in palettes
  (`dark`, `light`, `mariana`, `retro_blue`). Upstream `imgui_color_text_edit` limitation;
  deferred per the `multi_themes_20260604` track.
- **N2** — A "film grain" or "vignette" post-effect to enhance the nostalgic feel. Pure
  color-grading is sufficient for the "obvious prior session" cue. If the user wants more
  later, that is a follow-up track.
- **N3** — Changing the prior-session default to per-theme *config* keys (e.g., users
  overriding the default per-theme via `config.toml`). The slider value is saved as
  per-palette state in the same way as brightness/contrast/gamma; the TOML key is the
  *factory default*, not a user-tweakable value.
- **N4** — Applying the sepia transform to the **whole window** at scope (iii) of the
  brainstorming. The user chose scope (ii) ("data + chrome inside prior-session views"),
  with fallback to (iii) only if (ii) looks bad in manual review. Phase 3 includes a
  manual smoke test that escalates to (iii) only if needed.
- **N5** — Moving `_prior_session_amount` into the same config dict as the existing
  brightness/contrast/gamma state. Use the same persistence mechanism (config flush +
  `app.save_config()`) but a separate dict so the "per-palette" semantics are explicit.

### 1.3 Design constraint (HARD — from user)

> "Make sure that all math you do is not integer based. I want to have as much accuracy
> as possible for smooth calculations."

Applied to:
- `apply_prior_tint(rgba, palette) -> tuple[float, float, float, float]` — float math
  throughout (desaturation, lerp, alpha pass-through).
- The slider is `imgui.slider_float`, not `slider_int`. Range 0.0–1.0 inclusive.
- The `_prior_session_amount` dict stores `float`, not `Decimal` or `int`. Default 0.3.
- TOML key `prior_session_amount` is a `float`, not an int. Round-trip through
  `tomllib`/`tomli_w` preserves the float type.
- `theme_2._tone_map()` is already float; the new transform composes on top of it.
- `apply_color_grades_to_editor_palette` iterates `Palette` color slots as float
  quadruples (RGBA in 0.0–1.0).

The only place integer 0-255 RGB appears is the TOML theme files (existing convention).
That is the input boundary; the math happens entirely in float.

---

## 2. Current State Audit (as of commit `9f895117`)

### 2.1 Already Implemented (DO NOT re-implement)

- **TOML theme loading**: `src/theme_2.py:load_themes_from_disk()` (line 339),
  `_TOML_PALETTES` / `_TOML_SEMANTIC_CACHE` / `_TOML_COLOUR_CACHE` (lines 64-67),
  `get_color()` (line 153) which resolves through TOML → dataclass → fallback dict.
- **ThemePalette dataclass**: `src/theme_models.py:ThemePalette` (line ~86) with
  `bubble_vendor: tuple[int, int, int] = (65, 55, 30)`. Schema validator at line 119.
- **Per-palette tonemap state**: `_brightness/_contrast/_gamma: dict[str, float]`
  (`src/theme_2.py:75-77`), `_get_tm()` accessor (line 84), `set_brightness/contrast/gamma`
  (lines 91-93), `reset_tone_mapping(palette)` (line 95).
- **Tonemap UI section**: `src/gui_2.py:5007-5023` — "Tone Mapping (Per-Palette)" header,
  3 sliders + reset button. Each slider calls `theme.set_X(curr_palette, val)` then
  `app._flush_to_config(); app.save_config()`.
- **Prior session state**: `AppController.is_viewing_prior_session: bool` (line 982),
  `prior_session_entries` (line 983), `cb_exit_prior_session()` (line 2105).
  `App.is_viewing_prior_session` is exposed via the App→Controller delegate.
- **Prior session render sites (6 audit-ready call sites)**:
  - `src/gui_2.py:1027-1028` — `App._gui_func` window_bg wrap.
  - `src/gui_2.py:3959-3961` — `render_prior_session_view` (dedicated prior view).
  - `src/gui_2.py:4087-4193` — `render_comms_history_panel` consumes
    `_comms_log_cache` which is replaced with `prior_session_entries` at line 1560
    when `is_viewing_prior_session` is True.
  - `src/gui_2.py:4591+` — `render_tool_calls_panel` consumes `_tool_log_cache`
    similarly replaced at line 1568.
  - `src/gui_2.py:5140-5145` — `render_mma_dashboard` "HISTORICAL VIEW" banner.
  - `src/gui_2.py:5438-5442` — `render_tier_stream_panel` "HISTORICAL VIEW" banner.
- **TextEditor render sites (2)**:
  - `src/markdown_helper.py:328-340` — per-code-block editor, `editor.set_palette(p_id)`.
  - `src/gui_2.py:4773-4780` — text viewer TextEditor.
- **Syntax palette mapping**: `src/theme_2.py:350-367` `get_syntax_palette_for_theme` and
  `apply_syntax_palette` use the upstream `ed.TextEditor.PaletteId` enum (4 values).
  `apply_syntax_palette` is called from `apply()` (line 220, line 278) and on
  `MarkdownRenderer.__init__` (line 90-91).
- **8 existing themes**: `themes/10x_dark.toml`, `themes/binks.toml`,
  `themes/gruvbox_dark.toml`, `themes/monokai.toml`, `themes/moss.toml`,
  `themes/nord_dark.toml`, `themes/solarized_dark.toml`, `themes/solarized_light.toml`.
  Each defines a `bubble_vendor` line; none has prior-session slots.
- **Test infrastructure**: `tests/conftest.py` `live_gui`, `isolate_workspace`,
  `reset_paths`, `reset_ai_client` fixtures. Puppeteer pattern documented in
  `docs/guide_simulations.md`.

### 2.2 Gaps to Fill (This Track's Scope)

- **Gap A** — No dedicated prior-session theme slots. The bg is a reuse of `bubble_vendor`.
- **Gap B** — No content-level color grading. Text/markdown in prior views stays at full
  palette saturation.
- **Gap C** — No per-palette runtime control over the prior-session amount. Authors can't
  tune it without recompiling.
- **Gap D** — Code blocks (`TextEditor`) are not tonemap-aware (pre-existing). The
  tonemap slider does not affect syntax-highlighted code. **NOT in scope for this track**
  — see §1.1.1: the upstream `imgui_bundle 1.92.5` API does not expose per-instance color
  override for `TextEditor.PaletteId` (the only API is the 4-value enum). Fixing this
  requires forking the library or writing a custom syntax highlighter, which is a
  separate track.
- **Gap E** — No `apply_prior_tint` helper exists. Any future "color grade any
  ThemeColor" feature would reimplement this from scratch.

---

## 3. Approach: Per-render explicit transform (A1)

The brainstorming design doc (`docs/superpowers/specs/2026-06-10-prior-session-sepia-design.md`)
records the full rationale. Summary:

- The view layer composes the color transform. `theme.get_color(name)` continues to return
  the tonemap-graded but non-prior-tinted color. A new `apply_prior_tint(rgba, palette)`
  helper applies the sepia blend on top. Prior-session render sites wrap their
  `theme.get_color(...)` calls with `apply_prior_tint(...)` (one-line wrap, ~6 sites).
- The TextEditor palette fix uses a parallel `apply_color_grades_to_editor_palette(editor,
  palette_name)` helper that **mutates the per-instance `Palette` struct in-place** and
  re-applies it via `editor.set_palette(palette_struct)`. This is the only known way to
  per-instance override the upstream library's hardcoded palette.
- The per-palette `_prior_session_amount` dict mirrors the existing tonemap dicts
  (`_brightness`, `_contrast`, `_gamma`) exactly. The same persistence mechanism
  (`app._flush_to_config(); app.save_config()`) applies.

### 3.1 Why not the alternatives

- **A2 (transparent via `get_color`)** — would auto-apply sepia when
  `is_viewing_prior_session` is True. Rejected: violates the data-oriented principle
  ("the view composes"); risks accidentally tinting status indicators that must stay
  saturated (e.g., the red error dot in `is_thinking`).
- **A3 (context manager scope)** — would require a `with prior_session_view():`
  wrapper at every prior-session site. Rejected: more boilerplate than A1 with no
  benefit.

### 3.2 Float-only math contract

The transform is `result = lerp(desaturate(input), tint_color, amount)`.

- `desaturate(rgba)` — float math: `luma = 0.2126*r + 0.7152*g + 0.0722*b` (BT.709),
  `return (luma, luma, luma, a)`. All in 0.0–1.0.
- `lerp(a, b, t)` — float: `a + (b - a) * t` for each channel. The result is in 0.0–1.0.
- `apply_prior_tint(rgba, palette)` — `return lerp(desaturate(rgba), get_prior_tint(palette),
  get_prior_session_amount(palette))`. Alpha passed through unchanged.
- `apply_color_grades_to_editor_palette(editor, palette_name)` — iterates the
  `Palette` struct's color slots (RGBA float quadruples), calls
  `_tone_map(slot, palette_name)` first, then conditionally `apply_prior_tint(slot,
  palette_name)`. Sets the modified `Palette` back via `editor.set_palette(palette)`.

The user can confirm the per-palette amount via `imgui.slider_float("##prior_amt",
theme.get_prior_session_amount(curr_p), 0.0, 1.0, "%.2f")` with `0.0 <= amount <= 1.0`.
Default 0.3 (subtle, per user choice).

---

## 4. Functional Requirements

### 4.1 Theme model additions

`src/theme_models.py:ThemePalette` gains 3 fields:

```python
prior_session_bg:      tuple[int, int, int] = (60, 50, 35)    # dark default
prior_session_tint:    tuple[int, int, int] = (112, 66, 20)   # classic sepia
prior_session_amount:  float                = 0.3              # subtle
```

`src/theme_models.py:ThemeFile.from_dict()` and `to_dict()` (lines 137-157) round-trip
the new fields. The validator at line 119 enforces `0.0 <= prior_session_amount <= 1.0`
and 3-tuple RGB bounds.

`src/theme_2.py:184-201` fallback dict gains the 3 keys with the same defaults.

### 4.2 Theme module additions

`src/theme_2.py` gains, in order, after the existing tonemap state (~line 95):

```python
_prior_session_amount: dict[str, float] = {}

def _get_psa(palette: str, default: float) -> float:
    return _prior_session_amount.get(palette, default)

def get_prior_session_amount(palette: str) -> float:
    return _get_psa(palette, 0.3)

def set_prior_session_amount(palette: str, val: float) -> None:
    val = max(0.0, min(1.0, float(val)))  # clamp + cast
    _prior_session_amount[palette] = val

def reset_prior_session(palette: str) -> None:
    _prior_session_amount.pop(palette, None)
```

### 4.3 Transform helpers

`src/theme_2.py` gains, after the tonemap helpers (~line 110):

```python
def _desaturate(rgba: tuple[float, float, float, float]) -> tuple[float, float, float, float]:
    """Convert to grayscale using BT.709 luma. All float math."""
    r, g, b, a = rgba
    luma = 0.2126 * r + 0.7152 * g + 0.0722 * b
    return (luma, luma, luma, a)

def _lerp_rgba(
    a: tuple[float, float, float, float],
    b: tuple[float, float, float, float],
    t: float,
) -> tuple[float, float, float, float]:
    """Linear interpolation. All float math; t in [0.0, 1.0]."""
    t = max(0.0, min(1.0, t))
    return (
        a[0] + (b[0] - a[0]) * t,
        a[1] + (b[1] - a[1]) * t,
        a[2] + (b[2] - a[2]) * t,
        a[3] + (b[3] - a[3]) * t,
    )

def apply_prior_tint(
    rgba: tuple[float, float, float, float],
    palette_name: str,
) -> tuple[float, float, float, float]:
    """Apply the per-palette prior-session sepia blend to a color.

    result = lerp(desaturate(input), get_prior_tint(palette), get_prior_session_amount(palette))
    No-op at amount=0.0; pure tint at amount=1.0.
    """
    amount = get_prior_session_amount(palette_name)
    if amount <= 0.0:
        return rgba
    tint_rgb = get_color("prior_session_tint")
    tint = (tint_rgb.x, tint_rgb.y, tint_rgb.z, rgba[3])
    return _lerp_rgba(_desaturate(rgba), tint, amount)

### 4.4 Call-site wraps in `src/gui_2.py`

The 6 prior-session-affected sites get the following surgical edits:

| Line | Current | New |
|---|---|---|
| 1027-1028 | `with imscope.style_color(imgui.Col_.window_bg, theme.get_color("bubble_vendor")):` | `with imscope.style_color(imgui.Col_.window_bg, theme.get_color("prior_session_bg")):` |
| 3960-3961 | `with imscope.style_color(imgui.Col_.child_bg, theme.get_color("bubble_vendor")):` | `with imscope.style_color(imgui.Col_.child_bg, theme.get_color("prior_session_bg")):` |
| 5142 | `c = theme.get_color("status_warning") if theme.is_nerv_active() else theme.get_color("status_warning")`<br>`imgui.text_colored(c, "HISTORICAL VIEW - READ ONLY")` | wrap with `apply_prior_tint` per the call-site pattern in §4.3 |
| 5440 | Same pattern as 5142 | Same |
| `_render_comms_history_panel` (`4087-4193`) | Comms entries that iterate `log_to_render` (line 4115) | When `app.is_viewing_prior_session` is True, wrap each `theme.get_color()` call (for row text, role badge, etc.) with `apply_prior_tint` |
| `render_tool_calls_panel` (`4591+`) | Same | Same |

A small helper pair is added to `src/theme_2.py` (next to `apply_prior_tint`) to convert
`imgui.ImVec4` ↔ float tuple, so the wrap is one expression per call site:

```python
def _imvec4_to_rgba(v: imgui.ImVec4) -> tuple[float, float, float, float]:
    return (float(v.x), float(v.y), float(v.z), float(v.w))

def _rgba_to_imvec4(rgba: tuple[float, float, float, float]) -> imgui.ImVec4:
    return imgui.ImVec4(*rgba)
```

Call-site pattern (e.g., the line 5142 banner):

```python
c_raw = theme.get_color("status_warning")
if app.is_viewing_prior_session:
    c = _rgba_to_imvec4(apply_prior_tint(_imvec4_to_rgba(c_raw), theme.get_current_palette()))
else:
    c = c_raw
imgui.text_colored(c, "HISTORICAL VIEW - READ ONLY")
```

### 4.5 Theme Settings panel UI

`src/gui_2.py:5007+` gains a new section after the "Reset Tone Mapping" button:

```python
imgui.separator()
imgui.text("Prior Session Sepia (Per-Palette)")
curr_p = theme.get_current_palette()
ch_p, p = imgui.slider_float("##ps_amount", theme.get_prior_session_amount(curr_p), 0.0, 1.0, "%.2f")
if ch_p:
    theme.set_prior_session_amount(curr_p, p)
    app._flush_to_config()
    app.save_config()
if imgui.button("Reset Prior Session Sepia"):
    theme.reset_prior_session(curr_p)
    app._flush_to_config()
    app.save_config()
```

Persistence: `_prior_session_amount` is saved to `[theme] prior_session_amount` in
`config.toml` keyed by palette, mirroring the existing brightness/contrast/gamma keys.
`load_from_config()` (`src/theme_2.py:319-335`) reads it back on startup.

### 4.7 Theme TOML files

All 8 `themes/*.toml` files gain the 3 new keys. Defaults:

| Theme | prior_session_bg | prior_session_tint | prior_session_amount |
|---|---:|---:|---:|
| 10x_dark | (60, 50, 35) | (112, 66, 20) | 0.3 |
| binks | (235, 220, 190) | (140, 80, 30) | 0.3 |
| gruvbox_dark | (60, 50, 35) | (112, 66, 20) | 0.3 |
| monokai | (60, 50, 35) | (112, 66, 20) | 0.3 |
| moss | (60, 50, 35) | (112, 66, 20) | 0.3 |
| nord_dark | (60, 50, 35) | (112, 66, 20) | 0.3 |
| solarized_dark | (60, 50, 35) | (112, 66, 20) | 0.3 |
| solarized_light | (235, 220, 190) | (140, 80, 30) | 0.3 |

The light themes use a cream bg (235, 220, 190) so the bg is distinct from the
text-bg and from the Vendor API bubble. The sepia tint on light themes is slightly
warmer (140, 80, 30) to maintain visible contrast on a light background.

---

## 5. Non-Functional Requirements

- **Performance** — `apply_prior_tint` is a pure function: 3 multiplies + 3 lerps + alpha
  pass-through per call, all float. At ~6 prior-session call sites × ~10 calls per frame
  in a 60Hz render, total CPU cost is <50µs/frame. Negligible. (`apply_color_grades_to_editor_palette`
  is NOT in this track — see §1.1.1.)
- **Thread safety** — all new module-level state in `theme_2.py` is keyed by `palette: str`
  and accessed in the render thread (the only thread that calls these helpers). No new
  threads introduced.
- **Float precision** — All math is float. No integer truncation, no `int(0.5 * 255)`.
  Output is `tuple[float, float, float, float]` consumed by `imgui.ImVec4` (which is
  already float-based). The TOML round-trip for `prior_session_amount: float` is
  tested in `tests/test_prior_session_toml.py`.
- **Backward compatibility** — Themes missing the new keys fall back to the defaults in
  `src/theme_2.py:184-201`. Existing user themes (if any) continue to work; the new
  keys are optional with sensible defaults.
- **Hot reload** — Adding a new theme key is not a hot-reload concern (themes are
  loaded at startup). The new `_prior_session_amount` runtime state is not hot-reloaded
  (same as the existing tonemap state).

---

## 6. Architecture Reference

- `docs/guide_architecture.md` — Threading model (the helpers run on the render thread).
- `docs/guide_themes.md` — Theme TOML schema, fallback dict, palette resolution.
- `docs/guide_testing.md` — `live_gui` fixture, Puppeteer pattern, structural testing
  contract, audit-script policy.
- `docs/guide_app_controller.md` — `AppController._settable_fields` /
  `_gettable_fields` registries (we add `prior_session_amount` to `_settable_fields`
  if the user wants to inspect/override via the Hook API; otherwise it lives in
  `theme_2` only).
- `docs/superpowers/specs/2026-06-10-prior-session-sepia-design.md` — Brainstorm
  design doc with full rationale for the A1 approach.
- `conductor/tracks/multi_themes_20260604/spec.md` — Sibling track that established
  the TOML theme loading and per-theme syntax palette mapping pattern.
- `conductor/tracks/prior_session_test_harden_20260605/` — Predecessor that
  refactored `test_prior_session_no_pop_imbalance` to call the narrow
  `render_prior_session_view` (now a more testable function thanks to that work).

---

## 7. Phases

The track is small enough to ship in 3-4 phases over ~2 days. The TDD Red→Green→Refactor
discipline applies per the project workflow. Atomic per-task commits. Git notes attached
to each commit.

### Phase 1: Theme model + state + helpers (Day 1, ~3 hours)

- [ ] T1.1 (Red): `tests/test_prior_session_amount.py` — `get/set/reset_prior_session_amount`
  with per-palette dict semantics; default 0.3 when key absent; reset removes the key;
  set clamps to [0.0, 1.0].
- [ ] T1.2 (Green): add `_prior_session_amount` dict + accessors to `src/theme_2.py`.
- [ ] T1.3 (Red): `tests/test_prior_session_tint.py` — `apply_prior_tint` math:
  - At amount=0.0, returns input unchanged.
  - At amount=1.0, returns pure sepia tint (modulated by input alpha).
  - Monotonic: amount=0.5 is between amount=0.0 and amount=1.0 outputs (in luma).
  - All values in [0.0, 1.0] for any input.
  - Alpha is preserved exactly.
- [ ] T1.4 (Green): add `_desaturate`, `_lerp_rgba`, `apply_prior_tint` to `src/theme_2.py`.
  All math is float.
- [ ] T1.5 (Red): `tests/test_prior_session_toml.py` — round-trip a theme TOML with the
  3 new keys; verify defaults when keys are missing; verify validation rejects
  `prior_session_amount < 0.0` or `> 1.0` or non-3-tuple RGB.
- [ ] T1.6 (Green): add the 3 fields to `ThemePalette` (`src/theme_models.py`); update
  `from_dict` / `to_dict` / `validator`; add the 3 keys to the fallback dict
  (`src/theme_2.py:184-201`).
- [ ] T1.7: Add the 3 keys to all 8 `themes/*.toml` files.
- [ ] T1.8: Run `scripts/audit_weak_types.py` and `scripts/check_test_toml_paths.py`;
  confirm no regressions.
- [ ] T1.9: Commit + git note.

### Phase 2: Call-site wraps (Day 1-2, ~4 hours)

- [ ] T2.1 (Red): `tests/test_prior_session_render.py` — unit test for the helper
  `_imvec4_to_rgba` / `_rgba_to_imvec4` round-trip.
- [ ] T2.2 (Green): add the 2 helpers to `src/theme_2.py` (next to `apply_prior_tint`).
- [ ] T2.3: Refactor `src/gui_2.py:1027-1028` and `:3960-3961` to use
  `theme.get_color("prior_session_bg")` (replacing `bubble_vendor`).
- [ ] T2.4: Wrap the 4 "HISTORICAL VIEW" + `_render_comms_history_panel` + `_render_tool_calls_panel`
  call sites with `apply_prior_tint`. All edits are 1-3 lines per site.
- [ ] T2.5 (DROPPED): `tests/test_code_block_tonemap.py` — **not applicable.** The helper
  was dropped per §1.1.1. The test is also dropped.
- [ ] T2.6 (DROPPED): `apply_color_grades_to_editor_palette` — **not implemented.**
  See §1.1.1.
- [ ] T2.7 (DROPPED): TextEditor wrap into markdown_helper.py and gui_2.py — **not done.**
  See §1.1.1.
- [ ] T2.8: Commit + git note.

### Phase 3: Theme panel UI + persistence (Day 2, ~2 hours)

- [ ] T3.1: Add the "Prior Session Sepia (Per-Palette)" section to
  `src/gui_2.py:5007+` per §4.6. Mirrors the existing Tone Mapping section.
- [ ] T3.2: Wire `app._flush_to_config(); app.save_config()` on change.
- [ ] T3.3: Update `src/theme_2.py:save_to_config()` (line 301-317) to persist
  `_prior_session_amount` under `[theme.prior_session_amount.<palette>]` (a nested
  table mirroring the existing `[theme.tone_mapping.<palette>]` structure on line 312).
  Example resulting TOML:

  ```toml
  [theme]
  palette = "10x Dark"

  [theme.prior_session_amount]
  "10x Dark" = 0.3
  ```

  `load_from_config()` (line 319-335) reads it back the same way `_brightness` is
  read on line 333.
- [ ] T3.4: Update `src/theme_2.py:load_from_config()` (line 319-335) to read it back.
- [ ] T3.5 (Red): `tests/test_prior_session_persistence.py` — slider change →
  `app._flush_to_config(); app.save_config()` → restart (or call `load_from_config`)
  → state restored.
- [ ] T3.6 (Green): implement persistence in save/load.
- [ ] T3.7: Commit + git note.

### Phase 4: Verify + checkpoint (Day 2, ~2 hours)

- [ ] T4.1: Run the full `live_gui` test batch (per the existing
  `live_gui_test_hardening_20260605` guidance — batch, not isolation). Confirm
  no regressions in the 273+ existing tests.
- [ ] T4.2: Run the new tests in `tests/test_prior_session_*.py` and
  `tests/test_code_block_tonemap.py`. All pass.
- [ ] T4.3: Manual smoke:
  - Launch `uv run sloppy.py --enable-test-hooks`.
  - Open Theme Settings. Find the new "Prior Session Sepia" section. Verify slider
    snaps to the per-palette default (0.3).
  - Enter a prior session (`Session Analysis → Open Prior Session`). Verify the
    "HISTORICAL VIEW" banner is tinted, the prior discussion entries are tinted
    (subtle sepia), and code blocks within them are also tinted.
  - Adjust the slider to 0.0 → 1.0. Verify the tint amount scales smoothly (no
    integer stepping artifacts).
  - Switch themes (e.g., from `10x Dark` to `Solarized Light`). Verify the slider
    snaps to the new palette's default.
  - Quit and restart. Verify the slider value persists.
  - If the prior-session chrome (banners, button labels) does NOT look "obviously
    old" at amount=0.3, escalate to scope (iii) per brainstorming Q3 (b) — wrap
    the entire `_gui_func` window bg with the prior-session tint instead of just
    the prior-session views. Capture before/after screenshots in
    `docs/reports/prior_session_sepia_<date>/`.
- [ ] T4.4: Phase checkpoint commit + git note with full verification report.
- [ ] T4.5: Update `conductor/tracks.md` with the new track entry (or remove
  the entry if archival). Add `docs/superpowers/specs/2026-06-10-prior-session-sepia-design.md`
  to the planning-digest index (no digest exists yet for 2026-06-10; if one is
  authored later, this spec should be referenced).

---

## 8. Verification Criteria

The track is complete when:

- [ ] All 3 new theme slots (`prior_session_bg`, `prior_session_tint`,
      `prior_session_amount`) are present in `ThemePalette`, the fallback dict,
      and all 8 `themes/*.toml` files.
- [ ] `_prior_session_amount` per-palette dict is implemented with get/set/reset
      accessors, persists to `config.toml`, restores on next launch.
- [ ] `apply_prior_tint` passes the math contract: identity at 0.0, pure tint at 1.0,
      monotonic, alpha-preserved, all-float output, all values in [0.0, 1.0].
- [ ] All 6 prior-session render sites in `src/gui_2.py` use `prior_session_bg` (not
      `bubble_vendor`) and content is sepia-tinted via `apply_prior_tint`.
- [ ] Theme Settings panel has a working "Prior Session Sepia (Per-Palette)" section
      with a 0.0–1.0 slider and a Reset button.
- [ ] All new tests pass: `tests/test_prior_session_amount.py`,
      `tests/test_prior_session_tint.py`, `tests/test_prior_session_toml.py`,
      `tests/test_prior_session_render.py`,
      `tests/test_prior_session_persistence.py`. (`tests/test_code_block_tonemap.py`
      is NOT created — the code-block fix is out of scope per §1.1.1.)
- [ ] No regressions in the 273+ existing `live_gui` tests (batch-verified per
      `workflow.md` "Isolated-Pass Verification Fallacy" rule).
- [ ] Manual smoke confirms the prior-session look is "obviously old" at the
      default 0.3 amount.
- [ ] **HONEST DISCLOSURE in the final track report**: the pre-existing "code
      blocks are not tonemap-aware" bug is NOT fixed by this track. The
      reason (upstream API constraint) is documented in §1.1.1 and §9.
- [ ] No diagnostic noise (`sys.stderr.write("[XYZ_DIAG] ...")`) in production
      code. All instrumentation goes to `tests/artifacts/` log files.
- [ ] `git restore` / `git checkout --` / `git reset` are NOT used without
      explicit user permission (HARD BAN per AGENTS.md).

---

## 9. Out of Scope (Definitively)

- **N1** — Per-language syntax color customization beyond the 4 built-in palettes.
  Upstream limitation; deferred.
- **N2** — Film grain / vignette / scanline post-effect. Pure color-grading only.
- **N3** — Per-theme user overrides via `config.toml` (the TOML key is the factory
  default; the slider is the user-tweakable value).
- **N4** — Process-isolation of the `apply_prior_tint` helper (it's pure; no need).
- **N5** — Reorganizing the existing `_brightness/_contrast/_gamma` state into a
  single dict (out of scope; follow the existing pattern).
- **N6** — **Code-block tonemap-awareness.** The `imgui_bundle` 1.92.5 API
  exposes `TextEditor.get_palette() -> PaletteId` (the 4-value enum: `dark`,
  `light`, `mariana`, `retro_blue`) and `set_palette(PaletteId)`. There is
  **no `Palette` struct with mutable per-color slots**. Per-instance
  tonemap/sepia overrides on code-block syntax tokens are NOT possible
  without forking the library. The pre-existing "code blocks not
  tonemap-aware" behavior persists. Same constraint as the
  `multi_themes_20260604` track, which shipped a per-theme `syntax_palette`
  enum field rather than custom token colors.
- **N7** — Adding a `prior_session_indicator` color slot to the existing
  `app_controller.py:1142` Hook API registry. The indicator is binary (on/off);
  the new color is consumed by render code, not the Hook API.

---

## 10. Cross-References

- `conductor/tracks/multi_themes_20260604/spec.md` — sibling track (TOML theme
  loading pattern this track extends).
- `conductor/tracks/prior_session_test_harden_20260605/` — predecessor that
  refactored the prior-session test to call `render_prior_session_view` narrowly.
- `docs/superpowers/specs/2026-06-10-prior-session-sepia-design.md` — full
  brainstorming design rationale.
- `docs/guide_themes.md` — theme system reference.
- `docs/guide_testing.md` — `live_gui` batch-verification rule and structural
  testing contract.
- `conductor/workflow.md` — TDD Red→Green→Refactor + atomic per-task commits +
  Phase Completion Verification Protocol.
- `conductor/product-guidelines.md` §"Phase 5: Heavy Curation & Structural
  Integrity" — float-only math, no shortcuts, no integer truncation.
