Files
ersatztv/design-system/design_handoff_settings
timothyandClaude Fable 5 5f1d3e4d96 design: Settings screen prototype + handoff bundle (#93)
templates/chicorytv-admin/Settings.jsx wired into app.html;
design_handoff_settings/ carries the implementation spec + API mapping.
Synced to the Claude Design project via DesignSync.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 09:22:50 +02:00
..

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 inputtype="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.