# Handoff: ChicoryTV — Settings

## Overview
The **Settings** screen replaces ErsatzTV's seven legacy Blazor settings pages (FFmpeg, HDHR,
Playout, XMLTV, Scanner, Logging, UI) with **one screen**: a left sub-nav rail selects a settings
group, the right pane shows that group as dense "system settings" rows, and a **floating save bar**
appears whenever there are unsaved changes. It ships inside the ChicoryTV admin shell.

## About the design files
`Settings.jsx` here is a **design reference** (same file as `templates/chicorytv-admin/Settings.jsx`,
running on the UI-kit's React primitives + CSS custom-property tokens) — recreate it in the target
codebase (`web/` React SPA) using its established components and patterns, not by shipping this file.

## Backend contract (agreed scope, issue #93)
Each pane maps 1:1 to a thin REST wrapper over an existing MediatR handler pair:

| Pane | Endpoint | Fields |
|---|---|---|
| General | `GET/PUT /api/settings/ui` | theme (Dark/Light), language (culture) |
| Streaming | `GET/PUT /api/settings/ffmpeg` | ffmpeg/ffprobe paths, default profile id, preferred audio language, 4 bool flags, global watermark id, global fallback filler id, HLS idle timeout / work-ahead limit / initial segments, HLS-Direct output format, default MPEG-TS script |
| Streaming → Custom resolutions | `GET /api/settings/resolutions` + `POST`/`DELETE` custom resolutions | width × height list, custom-only deletable |
| Streaming → FFmpeg profiles | existing `GET /api/ffmpeg/profiles` | read-only list + Default badge; editing stays in legacy UI (callout) |
| Playout | `GET/PUT /api/settings/playout` | days to build, skip missing items, scripted schedule timeout |
| Guide (XMLTV) | `GET/PUT /api/settings/xmltv` | days to build, time zone (Local/UTC), block behavior (SplitTimeEvenly/UseActualTimes) |
| Scanner | `GET/PUT /api/settings/scanner` | library refresh interval hours (0 = disabled → warning callout) |
| Logging | `GET/PUT /api/settings/logging` | 6 Serilog min levels: Default, Scanning, Scheduling, Searching, Streaming, HTTP |
| System → HDHR | `GET/PUT /api/settings/hdhr` | tuner count (writable), device UUID (read-only + copy) |
| System → Media sources | existing `GET /api/media-sources` | read-only list + "Legacy UI" callout |
| System → About | existing `GET /api/version`, `GET /api/health` | version, health summary + link to Dashboard |

## Fidelity
**High-fidelity.** Colors, type, spacing, radii and interactions are resolved; all values are token
references (`var(--…)`) from the design system. Recreate with the codebase's real component library.

## Layout
Full-height flex row under the admin shell TopBar (title "Settings"):

- **Sub-nav rail** — 224px, `flex: 0 0 auto`, hairline right border, `padding: 12px 8px`,
  scrolls independently. One button per section: icon (16px, accent when active) + label
  (`--text-sm` medium; primary when active, else secondary) + one-line hint (`--text-2xs`,
  disabled color, ellipsized). Active = `--ctv-accent-soft` background, radius `--radius-sm`;
  hover = `--ctv-surface-2`. Uses `.ctv-press`. Sections (id · label · lucide icon · hint):
  1. `general` · General · SlidersHorizontal · "Theme & language"
  2. `streaming` · Streaming · Clapperboard · "FFmpeg & transcoding"
  3. `playout` · Playout · ListVideo · "Build defaults"
  4. `xmltv` · Guide (XMLTV) · CalendarDays · "EPG output"
  5. `scanner` · Scanner · Radar · "Library refresh"
  6. `logging` · Logging · ScrollText · "Log levels"
  7. `system` · System · Server · "HDHR, sources, about"

- **Pane** — fills the rest; scroll container padded `20px 24px 96px` (bottom room for the save
  bar). Content is a `max-width: 760px` column, `gap: 16`, entering with `ctv-fade-in` 240ms.
  Pane header: title (`--text-lg` semibold) + subtitle (`--text-sm`, secondary, 1.45 line-height).

## Row primitive (the core pattern)
Settings are flush rows inside `Card padded={false}`; each row:
`display:flex; align-items:center; gap:20; padding:13px 16px;` hairline top border between rows.
Left: label (`--text-sm` medium, primary) + optional help line (`--text-xs`, disabled, 1.45).
Right: fixed-width control slot (`flex: 0 0 260px` default; 320px for path inputs, 180px for
log-level selects, 340px for the UUID row), right-justified. Controls are `size="sm"`:
`Select fullWidth`, `Switch`, `Input`.

Specialized controls:
- **Path input** — mono font, full width of slot, trailing validity icon: `CircleCheck` in
  `--status-ok` when valid, `CircleAlert` in `--status-error` with a "File not found" tooltip when not.
- **Number input** — `type="number"`, 120px, mono, trailing unit hint (`sec`, `days`, `hours`,
  `tuners`, `sessions`, `segments`) in `--text-2xs` disabled color.

## Floating save bar (screen-level dirty state)
One draft state for the whole screen (all panes share it — switching sections keeps edits).
When ≥1 field differs from saved values, a pill bar floats bottom-center of the pane
(absolute, `padding-bottom: 18px`): `--surface-raised` bg, `--border-control` border,
`--radius-pill`, `--shadow-pop`, `ctv-fade-in` 200ms, `.ctv-lift`. Contents:
"N unsaved change(s)" (`--text-sm` medium) · 1px hairline divider · **Discard** (ghost, resets
draft) · **Save changes** (primary, Check icon). After save, the bar swaps to a transient
confirmation — `CircleCheck` in `--status-ok` + "Settings saved" — for ~1.8s, then disappears.

## Pane details
- **General**: one card — Theme select (Dark/Light), Language select. Subtitle notes these apply
  to the *legacy* web UI (ChicoryTV theming is the shell's theme switcher, not a server setting).
- **Streaming**: five cards —
  1. *FFmpeg*: both path inputs, default profile select, preferred audio language select, four
     switches (use embedded subtitles, extract embedded subtitles, probe interlaced, save reports).
  2. *Global defaults*: watermark select, fallback filler select (both with "(none)" option).
  3. *HLS sessions*: idle timeout, work-ahead limit, initial segments (number inputs) + HLS-Direct
     output format select (MPEG-TS/MP4/MKV) + default MPEG-TS script select.
  4. *Custom resolutions* (flush list): rows of `W × H` (mono) + `custom` Tag + ghost trash
     IconButton; footer add-row on `--ctv-bg-sunken`: width/height number inputs + "Add resolution"
     secondary button (disabled until both filled).
  5. *FFmpeg profiles* (flush list, read-only): Cpu icon (accent) + name + **Default** accent Badge on
     the default profile; card action shows a count Badge; footer = Legacy-UI callout (see below).
- **Playout**: one card — days to build, skip missing (switch), scripted timeout.
- **Guide (XMLTV)**: one card — days to build, time zone select, block behavior select.
- **Scanner**: one card — refresh interval hours. When value is 0, a warning callout appears under
  the card: `--status-warn-soft` bg, TriangleAlert icon, "Automatic scanning is disabled…".
- **Logging**: one card, six rows (Default/Scanning/Scheduling/Searching/Streaming/HTTP), each a
  level select (Verbose…Fatal, 180px slot) with a per-category help line.
- **System**: three cards —
  1. *HDHomeRun*: tuner count (writable number input), device UUID (read-only mono + Copy IconButton).
  2. *Media sources* (flush, read-only): kind icon (HardDrive for Local, Cast otherwise) + name +
     mono host detail + StatusDot (ok/warn) + last-scan text; footer Legacy-UI callout.
  3. *About*: version (mono), health checks summary + "Open Dashboard" secondary button (ArrowRight).

## Legacy-UI callout (reusable)
For anything visible-but-not-editable here: a row with `--ctv-surface-2` bg, **dashed**
`--border-control` border, `--radius-sm`; ExternalLink icon (disabled color) + explanation
(`--text-xs`, secondary) + a neutral **Legacy UI** Badge pinned right.

## Interaction notes
- Section switching is instant (no route change needed in the prototype; the SPA should use its
  routing so sections are linkable, e.g. `/app/settings/streaming`).
- Draft edits survive section switches; Discard restores all panes at once.
- Number inputs keep string state in the prototype; the SPA should validate (ints ≥ 0) before save.
- Reduced motion: all animation comes from `.ctv-lift`/`.ctv-press`/`ctv-fade-in`, already gated by
  `prefers-reduced-motion` in the kit.
