Files
ersatztv/design-system/design_handoff_auto_tune/README.md
T
timothyandClaude Opus 4.8 11d3ba3823 design(69): sync Auto-Tune prototype + handoff (incl. deferred DetailPanel → #383)
Iterated Auto-Tune prototype pulled back from the Claude Design working copy
(project 4eb64b16) into design-system/. The 3-step wizard (Configure → Preview
→ Create) is PR2; the per-channel DetailPanel it also contains is deferred to
the #383 arc (#384/#385/#386) per design-first — new endpoints backlogged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 23:46:46 +02:00

11 KiB
Raw Blame History

Handoff: ChicoryTV — Auto-Tune (generate channels from library metadata, #69)

Overview

Auto-Tune is the automatic-first channel-creation mode — the counterpart to the manual Channel Builder ("New Channel"). Instead of building one channel by hand, it enumerates your library's metadata along one or more axes (TV Shows, TV Genres, Movie Genres), previews the whole set of proposed channels so you can pick which to keep, then bulk-creates the selected ones. It is additive and non-destructive: it never edits or deletes an existing channel; name and number collisions are skipped, never overwritten. Each generated channel is backed by a live SmartCollection query, so a "Comedy" channel keeps picking up new comedies as the library grows.

Concept borrowed from PseudoTV Live's signature Auto-Tuning, deliberately fixing its two weaknesses: PseudoTV is all-or-nothing per category with no preview, and it wipes+rebuilds the whole lineup on every run. Ours adds a preview/select step and is non-destructive.

The backend already shipped (PR1, ersatztv#379) — two endpoints under the frozen /api/v1. This handoff is PR2: the SPA screen that drives them.

Scope note (2026-07-16). The prototype (AutoTune.jsx) was iterated to add a per-channel DetailPanel slide-over (opened from a "Configure" button on each Preview row — full per-channel editor: identity/image, shuffle/always-playing, template + Advanced overrides, weighted content sources, exclude/add-untagged, example schedule). That panel needs backend the PR1 endpoints don't have, so it is DEFERRED to a follow-up arc — ersatztv#383 (children #384 enumerate members, #385 per-channel overrides + rotation weights in create, #386 the SPA panel). PR2 (this handoff) implements the 3-step wizard only — Configure → Preview → Create — on the existing PR1 endpoints; no per-row Configure button. The DetailPanel sections below the wizard spec are recorded for the #383 arc, not PR2.

About the design files

The files in this bundle are design references built in HTML/React (a prototype on the ChicoryTV UI kit) — not production code. Recreate the design in the target codebase (the ChicoryTV React SPA, web/) using its real components (web/src/components/) and CSS-custom-property tokens. The prototype (AutoTune.jsx) is the source of truth for layout, spacing, motion, and interaction; its mock data and mock outcome logic are illustrative only — the real screen calls the API.

Fidelity

High-fidelity. Colors, typography, spacing, radii, and interactions are resolved and use tokens throughout, so it is fully theme-aware (verified in the warm + cool accent themes). All values below are exact.


The screen — a 3-step wizard

One full-height column: a fixed toolbar (12px 20px padding, 1px solid var(--border-hairline) bottom border) over a scrolling <main>. The toolbar is constant across steps; its content and primary action change per step.

Toolbar (all steps):

  • Left: a 34×34 accent icon tile (var(--radius-sm), background: var(--ctv-accent-soft), color: var(--ctv-accent)) with the Sparkles icon; then title "Auto-Tune" (--text-md, semibold) + subtitle "Generate channels from your library" (--text-xs, --text-secondary).
  • Center: a step rail — three steps (Configure · Preview · Create) joined by 26px hairline connectors. Each step is a 20px round chip + label. The active step: chip background var(--ctv-accent) / color var(--text-on-accent), label accent + semibold. A completed step: chip var(--ctv-accent-soft) bg + accent Check icon, label --text-secondary. A future step: chip var(--ctv-surface-2) + --text-disabled.
  • Right: the contextual primary action (below).

Step 1 — Configure

Centered column, max-width: 860px, padding: 26px 24px 40px.

  1. Intro paragraph (--text-sm, --text-secondary, max-width: 620px): what Auto-Tune does + the non-destructive promise.
  2. "Generate from" section (eyebrow label) — a 3-column grid of selectable axis cards (one per axis). Each card is a <button> (ctv-press for the tactile scale-on-press):
    • Selected: 1px solid var(--ctv-accent) border, var(--ctv-accent-soft) bg, var(--shadow-sm); a filled accent CheckCircle2 top-right; the icon tile is var(--ctv-accent) / var(--text-on-accent).
    • Unselected: 1px solid var(--border-control), var(--surface-card) bg; a Circle outline top-right (--text-disabled); icon tile var(--ctv-surface-2) / --text-secondary.
    • Content: axis icon (Tv / Clapperboard / Film), title, one-line tagline, and a bottom order chip (ListOrdered "Episode order" for TV Shows; Shuffle "Shuffled" for the genres).
  3. "Defaults" section — a 2-column grid of four labelled fields, each with a helper line (Field component: label --text-xs medium, hint --text-2xs --text-disabled):
    • Minimum items (Input type=number, Hash leading icon) — skip channels below this count.
    • Starting channel number (Input type=number, Tv icon) — numbers count up from here.
    • Channel group (Input, FolderTree icon) — the group every generated channel lands in.
    • Channel template (Select) — the batch's streaming/playout/filler defaults.

Primary action: Preview channels (primary, Eye icon). Disabled with a tooltip until ≥1 axis is selected.

Step 2 — Preview

Centered column, max-width: 920px.

  • Summary bar: N of M selected (mono N), an info Tag "{k} already exist — deselected" when any proposal's name collides, a spacer, then ghost Select all / Clear buttons (they only touch selectable — non-existing — rows).
  • One section per axis that produced rows (var(--surface-card), hairline border, var(--radius-md)), in axis order (TV Shows → TV Genres → Movie Genres). Section header (var(--ctv-bg-sunken)): a group checkbox (tri-state: checked / indeterminate when partial), the axis icon + title, a neutral Badge with the row count, and a right-aligned order eyebrow.
  • Rows (hairline-separated): per-row Checkbox, the allocated number (mono, min-width 42), the channel name (semibold, ellipsis) over a from "{value}" sub-line, a neutral Badge {itemCount} items, and — for an already-existing name — an Exists Tag. Existing rows render at opacity: 0.55 with a disabled, unchecked checkbox (dedup: you can't re-create a channel that already exists by that name).
  • Empty state (no proposals matched): centered SearchX + "try lowering the minimum items".

Primary action: Create {N} channels (primary, Check icon; label pluralizes; disabled until ≥1 selected) preceded by a ghost Back (ArrowLeft) that returns to Configure.

Step 3 — Create (results)

Centered column, max-width: 860px.

  • Three Stat tiles across the top: Created / Skipped / Failed counts (icons CheckCircle2 / MinusCircle / XCircle).
  • A "Added to group {group}" line (FolderTree icon).
  • A results list (card, hairline rows): per channel a status glyph in the status color (CheckCircle2 live-green / MinusCircle secondary / XCircle danger), the name, an optional reason sub-line (skip/fail explanation), the new #{channelId} (mono) when created, and a status Badge (positive / neutral / danger).

Primary action: Start over (primary, RotateCcw) — resets the wizard to Configure.


API mapping (the real screen)

Both endpoints already exist (PR1). The client sends only axis + value back — never a Lucene query; the server regenerates it (query authorship is server-side only).

UI element Endpoint / field
Preview channels POST /api/v1/channels/auto-tune/preview
— axis cards request axes: AutoTuneAxis[] ("TvShow" | "TvGenre" | "MovieGenre")
— Minimum items request minItems: number
— Starting channel number request startingNumber: number
Preview rows response AutoTuneProposalResponseModel[]: { axis, value, name, number (string), itemCount, alreadyExists }
— number chip number (string — channel numbers can be "500.1"; render as-is)
{itemCount} items badge itemCount
— greyed + Exists alreadyExists === true (deselect + disable)
Create {N} channels POST /api/v1/channels/auto-tune
— Channel template field request templateId: number (from GET /api/v1/channel-templates / …/default)
— Channel group field request group: string
— selected rows request channels: { axis, value, name, number }[] (echo the selected proposals)
Results tiles + list response AutoTuneResultResponseModel: { results: { name, status ("Created"|"Skipped"|"Failed"), channelId, reason }[], createdCount, skippedCount, failedCount }

Notes:

  • Numbers are advisory. The preview allocates them (skipping taken numbers); the create handler re-validates at create time — a number taken in between yields a per-channel Skipped, not a batch failure. So a Skipped outcome with "number … already taken" is normal, not an error.
  • Default selection = every proposal whose alreadyExists is false.
  • Grouping/ordering is fixed: axis order (TvShow, TvGenre, MovieGenre) then by value; mirror the server's ordering rather than re-sorting client-side.
  • Template default: preselect GET /api/v1/channel-templates/default (fall back to the first template) so the field is never empty; the batch requires a templateId.

Real-SPA implementation notes (target = web/)

  • New screen web/src/screens/AutoTuneScreen.tsx; new route id autoTune, path /app/auto-tune, label "Auto-Tune", icon Sparkles, placed right after builder in the first sidebar nav group (the two channel-creation modes sit together). No primaryAction label — the wizard's action lives in-body (per spa-conventions.md §10: the "+" banner is for single unambiguous create list screens; a multi-step wizard drives its own buttons), so the shell shows no banner button for this screen.
  • New API module web/src/api/autoTune.ts (re-export the generated DTOs; previewAutoTune(body) + createAutoTunedChannels(body) over the shared request helper; a messageFrom…Error narrower), re-exported from web/src/api/index.ts.
  • Reuse getChannelTemplates / getDefaultChannelTemplate from web/src/api/channelTemplates.ts for the template Select.
  • Follow spa-conventions.md §3 for the two async calls (discriminated-union state, seqRef + activeRef, no synchronous set-state in an effect body). The wizard is transient (a create flow, not an editor of persisted data), so it does not register the §8 unsaved-changes guard.
  • Map prototype primitives → real components: Button, Input, Select, Checkbox, Badge, Tag, Stat, Tooltip, Spinner from web/src/components/. The axis cards and step rail are small screen-local components built from ctv-* utility classes + tokens (no new shared primitive).
  • Docs to update in the same PR: docs/domain-model.md (add /app/auto-tune to the channel routes), docs/blazor-route-parity.md (net-new SPA screen, no Blazor ancestor), docs/spa-conventions.md (if the wizard/step-rail pattern is worth recording), and design-system/ committed alongside.