Closes the SPA half of #69 (auto-tuning). PR1 (#379) shipped the backend; this is PR2: the wizard screen, so fixes#69.
What
A new /app/auto-tune screen (sidebar Auto-Tune, after New Channel) — the automatic-first channel-creation mode. Three steps on the existing PR1 endpoints, additive/non-destructive:
Configure — selectable axis cards (TV Shows / TV Genres / Movie Genres) + defaults (min items, starting number, channel group, channel template preselected from …/channel-templates/default).
Create (POST /api/v1/channels/auto-tune) — Created/Skipped/Failed Stat tiles + per-channel result list.
Design was iterated in Claude Design and synced to design-system/; visuals follow the approved prototype (design_handoff_auto_tune/).
Scope note — DetailPanel deferred
The design iteration added a per-channel DetailPanel (Configure-per-row editor: image, shuffle/always, template + Advanced overrides, weighted content sources). It needs backend PR1 doesn't have, so per design-first it's deferred to #383 (children #384/#385/#386). This PR is the 3-step wizard only — no per-row Configure button.
Implementation notes
Follows spa-conventions.md: discriminated-union state, activeRef + per-call seqRef guards, no synchronous set-state in effect bodies (§3); reuses channelTemplates API (§4); no primaryAction — wizard drives its own in-body buttons (§10). number is echoed as a string, never parsed.
New web/src/api/autoTune.ts (+test), web/src/screens/AutoTuneScreen.tsx (+test), route/nav + App nav-smoke test.
Docs (same PR)
docs/domain-model.md — /app/auto-tune added to channel routes + nav.
docs/blazor-route-parity.md — recorded as a net-new SPA screen (no Blazor ancestor).
No OpenAPI change (no new endpoints) — check:api green.
Verification
npm test (790 pass) · npm run lint clean · npm run build ok · npm run check:api green (independently re-run).
Live-E2E + cold independent review: in progress; results to follow before merge.
Closes the SPA half of **#69** (auto-tuning). PR1 (#379) shipped the backend; this is **PR2: the wizard screen**, so **fixes #69**.
## What
A new `/app/auto-tune` screen (sidebar **Auto-Tune**, after New Channel) — the automatic-first channel-creation mode. Three steps on the existing PR1 endpoints, additive/non-destructive:
1. **Configure** — selectable axis cards (TV Shows / TV Genres / Movie Genres) + defaults (min items, starting number, channel group, channel template preselected from `…/channel-templates/default`).
2. **Preview** (`POST /api/v1/channels/auto-tune/preview`) — proposals grouped by axis, per-row + tri-state per-group checkboxes, mono allocated number, item counts; already-existing names greyed + disabled + deselected (dedup).
3. **Create** (`POST /api/v1/channels/auto-tune`) — Created/Skipped/Failed `Stat` tiles + per-channel result list.
Design was iterated in Claude Design and synced to `design-system/`; visuals follow the approved prototype (`design_handoff_auto_tune/`).
## Scope note — DetailPanel deferred
The design iteration added a per-channel **DetailPanel** (Configure-per-row editor: image, shuffle/always, template + Advanced overrides, weighted content sources). It needs backend PR1 doesn't have, so per design-first it's **deferred to #383** (children #384/#385/#386). **This PR is the 3-step wizard only** — no per-row Configure button.
## Implementation notes
- Follows `spa-conventions.md`: discriminated-union state, `activeRef` + per-call `seqRef` guards, no synchronous set-state in effect bodies (§3); reuses `channelTemplates` API (§4); no `primaryAction` — wizard drives its own in-body buttons (§10). `number` is echoed as a string, never parsed.
- New `web/src/api/autoTune.ts` (+test), `web/src/screens/AutoTuneScreen.tsx` (+test), route/nav + App nav-smoke test.
## Docs (same PR)
- `docs/domain-model.md` — `/app/auto-tune` added to channel routes + nav.
- `docs/blazor-route-parity.md` — recorded as a net-new SPA screen (no Blazor ancestor).
- No OpenAPI change (no new endpoints) — `check:api` green.
## Verification
- `npm test` (790 pass) · `npm run lint` clean · `npm run build` ok · `npm run check:api` green (independently re-run).
- Live-E2E + cold independent review: in progress; results to follow before merge.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
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>
New `/app/auto-tune` screen driving the PR1 endpoints: pick metadata axes
(TV Shows / TV Genres / Movie Genres) + defaults, preview proposed channels
grouped by axis with per-row/per-group selection and already-exists dedup,
then bulk-create with a Created/Skipped/Failed summary. Additive/non-destructive.
The per-channel DetailPanel from the design iteration is deferred to #383
(new endpoints backlogged) — this ships the 3-step wizard only.
- web/src/api/autoTune.ts (+test), web/src/screens/AutoTuneScreen.tsx (+test)
- route/nav (routes.tsx, ScreenContent.tsx), api barrel, App nav-smoke test
- docs: domain-model (route), blazor-route-parity (net-new SPA screen)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Addresses the cold-review findings on PR #389:
- Medium: selection was keyed by proposal name, coupling two same-named
proposals (e.g. a show titled "Comedy" and the "Comedy" genre) so toggling
one flipped both. Now keyed by axis+value (unique); adds a regression test.
- Low: zero channel templates now shows a hint on Configure and the Create
tooltip explains the missing template instead of a misleading positive label.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fixes (commit 8e82673e): selection now keyed by axis+value (proposalKey), migrated at every site, + a non-vacuous regression test; zero-templates shows a Configure hint and an accurate Create tooltip.
Fix re-review (cold, scoped to the fix diff) confirmed: all selection sites migrated (no .name-keyed logic survives), separator safe, the regression test would fail against the old code, and the zero-templates gate can't POST a bad templateId. One theoretical Low outside scope (backend returning a default while the list is empty — unrealistic).
Pure-SPA leaf (no server-state change — the endpoints shipped in PR1). Live-E2E (real library → preview → create → M3U/XMLTV) running; will report before merge.
## Independent review — both passes clean
**Base review** (cold, review-only agent) → MERGEABLE, no Blocker/High. Two non-blocking findings:
- *Medium* — selection keyed by proposal `name` coupled two same-named proposals (a show titled "Comedy" vs the "Comedy" genre).
- *Low* — zero-templates UX (misleading Create tooltip + empty Select).
**Fixes** (commit `8e82673e`): selection now keyed by `axis+value` (`proposalKey`), migrated at every site, + a non-vacuous regression test; zero-templates shows a Configure hint and an accurate Create tooltip.
**Fix re-review** (cold, scoped to the fix diff) confirmed: all selection sites migrated (no `.name`-keyed logic survives), separator safe, the regression test would fail against the old code, and the zero-templates gate can't POST a bad `templateId`. One theoretical Low outside scope (backend returning a default while the list is empty — unrealistic).
Pure-SPA leaf (no server-state change — the endpoints shipped in PR1). Live-E2E (real library → preview → create → M3U/XMLTV) running; will report before merge.
Review-verdict: MERGEABLE @ 8e82673e6be8ab2d13bf1dfb639142833bae32df
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Closes the SPA half of #69 (auto-tuning). PR1 (#379) shipped the backend; this is PR2: the wizard screen, so fixes #69.
What
A new
/app/auto-tunescreen (sidebar Auto-Tune, after New Channel) — the automatic-first channel-creation mode. Three steps on the existing PR1 endpoints, additive/non-destructive:…/channel-templates/default).POST /api/v1/channels/auto-tune/preview) — proposals grouped by axis, per-row + tri-state per-group checkboxes, mono allocated number, item counts; already-existing names greyed + disabled + deselected (dedup).POST /api/v1/channels/auto-tune) — Created/Skipped/FailedStattiles + per-channel result list.Design was iterated in Claude Design and synced to
design-system/; visuals follow the approved prototype (design_handoff_auto_tune/).Scope note — DetailPanel deferred
The design iteration added a per-channel DetailPanel (Configure-per-row editor: image, shuffle/always, template + Advanced overrides, weighted content sources). It needs backend PR1 doesn't have, so per design-first it's deferred to #383 (children #384/#385/#386). This PR is the 3-step wizard only — no per-row Configure button.
Implementation notes
spa-conventions.md: discriminated-union state,activeRef+ per-callseqRefguards, no synchronous set-state in effect bodies (§3); reuseschannelTemplatesAPI (§4); noprimaryAction— wizard drives its own in-body buttons (§10).numberis echoed as a string, never parsed.web/src/api/autoTune.ts(+test),web/src/screens/AutoTuneScreen.tsx(+test), route/nav + App nav-smoke test.Docs (same PR)
docs/domain-model.md—/app/auto-tuneadded to channel routes + nav.docs/blazor-route-parity.md— recorded as a net-new SPA screen (no Blazor ancestor).check:apigreen.Verification
npm test(790 pass) ·npm run lintclean ·npm run buildok ·npm run check:apigreen (independently re-run).🤖 Generated with Claude Code
Independent review — both passes clean
Base review (cold, review-only agent) → MERGEABLE, no Blocker/High. Two non-blocking findings:
namecoupled two same-named proposals (a show titled "Comedy" vs the "Comedy" genre).Fixes (commit
8e82673e): selection now keyed byaxis+value(proposalKey), migrated at every site, + a non-vacuous regression test; zero-templates shows a Configure hint and an accurate Create tooltip.Fix re-review (cold, scoped to the fix diff) confirmed: all selection sites migrated (no
.name-keyed logic survives), separator safe, the regression test would fail against the old code, and the zero-templates gate can't POST a badtemplateId. One theoretical Low outside scope (backend returning a default while the list is empty — unrealistic).Pure-SPA leaf (no server-state change — the endpoints shipped in PR1). Live-E2E (real library → preview → create → M3U/XMLTV) running; will report before merge.
Review-verdict: MERGEABLE @
8e82673e6b