Backend for #69auto-tuning — "install it and it builds a lineup for you." This is PR1 (backend only); the SPA wizard + live-E2E land in PR2, which is when #69 closes. Refs #69 (not fixes).
Three new endpoints under the frozen /api/v1, orchestrating existing primitives (#63 composite create, Smart Collections, #64 Channel Templates):
POST /api/v1/channels/auto-tune/preview — enumerates library metadata for the selected axes (TV Show, TV Genre, Movie Genre) via EF distinct+count queries and returns proposed channels (name, allocated number, item count, alreadyExists). No writes.
POST /api/v1/channels/auto-tune — per selected proposal: creates a SmartCollection (server-generated Lucene query from axis+value) then calls the existing CreateChannelFromLineup (#63) handler via ISender; returns a per-channel Created/Skipped/Failed result.
Key design decisions (see docs/decisions.md)
Enumerate via EF, persist via SmartCollection — exact counts for the preview; each channel is a live Lucene query so it keeps tracking the library as it grows (PseudoTV's regenerative intent, but non-destructive).
Additive & non-destructive — reserved starting channel number (skips taken), name-based dedup (alreadyExists de-selected by default), existing channels never mutated. Fixes PseudoTV's wipe-and-rebuild.
Server owns all query generation — the client passes {axis, value}, never a Lucene string.
Scope
Deferred to follow-ups (not this MVP): TV Network, Movie Studio, Mixed Genre, Music Genre, Smart-Collection→channel, Mixed Content; per-axis templates; even-show-distribution.
Testing / review
16 new NUnit tests (axis map, number allocator, preview EF enumeration, bulk orchestration incl. rollback + name-length filtering) — all green; solution builds clean.
Built subagent-driven with per-task spec+quality review, then a cold-context whole-branch review (MERGEABLE-WITH-NITS). Two Important findings fixed + re-reviewed: (a) a null-channels 500 guard, (b) rollback of the just-created SmartCollection on a failed/skipped channel so re-runs stay clean. Plus a best-effort try/catch on that rollback.
OpenAPI (v1.json), endpoint-index.md, and the typed web client regenerated; check:api clean. docs/{api-conventions,domain-model,decisions}.md updated.
Known limitation (MVP)
A generated channel whose name collides with a pre-existing user SmartCollection of the same name reports Failed for that channel (documented in decisions.md). Residual: genre SelectMany/GroupBy enumeration is proven on SQLite (default); worth one smoke check before a MySQL deploy.
Backend for #69 **auto-tuning** — "install it and it builds a lineup for you." This is **PR1 (backend only)**; the SPA wizard + live-E2E land in **PR2**, which is when #69 closes. `Refs #69` (not `fixes`).
Design/plan: `docs/superpowers/specs/2026-07-16-auto-tuning-design.md` + `docs/superpowers/plans/2026-07-16-auto-tuning-pr1-backend.md`.
## What it adds (3-axis MVP)
Three new endpoints under the frozen `/api/v1`, orchestrating existing primitives (#63 composite create, Smart Collections, #64 Channel Templates):
- `POST /api/v1/channels/auto-tune/preview` — enumerates library metadata for the selected axes (**TV Show**, **TV Genre**, **Movie Genre**) via EF distinct+count queries and returns proposed channels (name, allocated number, item count, `alreadyExists`). **No writes.**
- `POST /api/v1/channels/auto-tune` — per selected proposal: creates a **SmartCollection** (server-generated Lucene query from `axis`+`value`) then calls the existing `CreateChannelFromLineup` (#63) handler via `ISender`; returns a per-channel **Created/Skipped/Failed** result.
## Key design decisions (see `docs/decisions.md`)
- **Enumerate via EF, persist via SmartCollection** — exact counts for the preview; each channel is a live Lucene query so it keeps tracking the library as it grows (PseudoTV's regenerative intent, but non-destructive).
- **Additive & non-destructive** — reserved starting channel number (skips taken), name-based dedup (`alreadyExists` de-selected by default), existing channels never mutated. Fixes PseudoTV's wipe-and-rebuild.
- **Server owns all query generation** — the client passes `{axis, value}`, never a Lucene string.
## Scope
Deferred to follow-ups (not this MVP): TV Network, Movie Studio, Mixed Genre, Music Genre, Smart-Collection→channel, Mixed Content; per-axis templates; even-show-distribution.
## Testing / review
- **16 new NUnit tests** (axis map, number allocator, preview EF enumeration, bulk orchestration incl. rollback + name-length filtering) — all green; solution builds clean.
- Built subagent-driven with per-task spec+quality review, then a **cold-context whole-branch review** (MERGEABLE-WITH-NITS). Two Important findings fixed + re-reviewed: (a) a null-`channels` 500 guard, (b) rollback of the just-created SmartCollection on a failed/skipped channel so re-runs stay clean. Plus a best-effort try/catch on that rollback.
- OpenAPI (`v1.json`), `endpoint-index.md`, and the typed web client regenerated; `check:api` clean. `docs/{api-conventions,domain-model,decisions}.md` updated.
## Known limitation (MVP)
A generated channel whose name collides with a **pre-existing user** SmartCollection of the same name reports `Failed` for that channel (documented in decisions.md). Residual: genre `SelectMany/GroupBy` enumeration is proven on SQLite (default); worth one smoke check before a MySQL deploy.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Task-by-task TDD plan for the auto-tuning backend: axis map, number
allocator, preview query + EF enumeration, bulk-create orchestration,
REST endpoints, OpenAPI + docs.
Refs #69
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- CreateAutoTunedChannelsRequest.ToCommand(): guard null Channels (was NREing on
a request body that omits "channels", causing HTTP 500).
- CreateAutoTunedChannelsHandler.CreateOne: when CreateChannelFromLineup returns
Left (Skipped/Failed), roll back the just-created SmartCollection via
DeleteSmartCollection so retries don't fail on SmartCollection-name uniqueness.
Best-effort; the delete result does not change the outcome.
- PreviewAutoTuneChannelsHandler: filter out proposals whose generated name
exceeds the 50-char Channel.Name limit before number allocation, so numbers
aren't wasted on proposals that can never be created.
- docs/superpowers/specs/2026-07-16-auto-tuning-design.md: fix field-name drift
in JSON examples (proposedNumber -> number, error -> reason) to match the
actual AutoTuneProposal/AutoTuneChannelOutcome DTOs.
Refs #69
DeleteSmartCollection rollback in CreateAutoTunedChannelsHandler.CreateOne
was called without exception handling; a transient infra exception during
the best-effort rollback would propagate and abort the whole batch,
contradicting the comment's stated intent. Wrap the send in try/catch so
an orphaned SmartCollection is the accepted degraded outcome instead.
Review trail: subagent-driven build with per-task spec+quality reviews (all clean), then a cold-context whole-branch review of c8c8ee96 → MERGEABLE-WITH-NITS. Two Important findings fixed in fa445bbe (null-channels 500 guard; SmartCollection rollback on failed/skipped channel) + a best-effort try/catch in 5098c048; the fix commit was independently re-reviewed → MERGEABLE. The current head 938e4e0f adds only a rebase onto origin/main (hand-resolved append-only decisions.md, regenerated OpenAPI/endpoint-index.md) on top of the reviewed 5098c048 — no logic change; 16/16 tests green post-rebase, check:api clean.
Note: this PR is Refs #69 (backend PR1); #69 does not close until PR2 (SPA wizard + live-E2E). Merge is not gate-auto-granted here — it's a normal reviewed feature-branch merge once CI is green.
Review-verdict: MERGEABLE @ 938e4e0f505610a6c4def8e699dbd039cb6ed1a7
Review trail: subagent-driven build with per-task spec+quality reviews (all clean), then a cold-context whole-branch review of `c8c8ee96` → MERGEABLE-WITH-NITS. Two Important findings fixed in `fa445bbe` (null-`channels` 500 guard; SmartCollection rollback on failed/skipped channel) + a best-effort try/catch in `5098c048`; the fix commit was independently re-reviewed → MERGEABLE. The current head `938e4e0f` adds only a rebase onto origin/main (hand-resolved append-only `decisions.md`, regenerated OpenAPI/`endpoint-index.md`) on top of the reviewed `5098c048` — no logic change; 16/16 tests green post-rebase, `check:api` clean.
Note: this PR is `Refs #69` (backend PR1); #69 does not close until PR2 (SPA wizard + live-E2E). Merge is not gate-auto-granted here — it's a normal reviewed feature-branch merge once CI is green.
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.
Backend for #69 auto-tuning — "install it and it builds a lineup for you." This is PR1 (backend only); the SPA wizard + live-E2E land in PR2, which is when #69 closes.
Refs #69(notfixes).Design/plan:
docs/superpowers/specs/2026-07-16-auto-tuning-design.md+docs/superpowers/plans/2026-07-16-auto-tuning-pr1-backend.md.What it adds (3-axis MVP)
Three new endpoints under the frozen
/api/v1, orchestrating existing primitives (#63 composite create, Smart Collections, #64 Channel Templates):POST /api/v1/channels/auto-tune/preview— enumerates library metadata for the selected axes (TV Show, TV Genre, Movie Genre) via EF distinct+count queries and returns proposed channels (name, allocated number, item count,alreadyExists). No writes.POST /api/v1/channels/auto-tune— per selected proposal: creates a SmartCollection (server-generated Lucene query fromaxis+value) then calls the existingCreateChannelFromLineup(#63) handler viaISender; returns a per-channel Created/Skipped/Failed result.Key design decisions (see
docs/decisions.md)alreadyExistsde-selected by default), existing channels never mutated. Fixes PseudoTV's wipe-and-rebuild.{axis, value}, never a Lucene string.Scope
Deferred to follow-ups (not this MVP): TV Network, Movie Studio, Mixed Genre, Music Genre, Smart-Collection→channel, Mixed Content; per-axis templates; even-show-distribution.
Testing / review
channels500 guard, (b) rollback of the just-created SmartCollection on a failed/skipped channel so re-runs stay clean. Plus a best-effort try/catch on that rollback.v1.json),endpoint-index.md, and the typed web client regenerated;check:apiclean.docs/{api-conventions,domain-model,decisions}.mdupdated.Known limitation (MVP)
A generated channel whose name collides with a pre-existing user SmartCollection of the same name reports
Failedfor that channel (documented in decisions.md). Residual: genreSelectMany/GroupByenumeration is proven on SQLite (default); worth one smoke check before a MySQL deploy.🤖 Generated with Claude Code
Review-verdict: MERGEABLE @
938e4e0f50Review trail: subagent-driven build with per-task spec+quality reviews (all clean), then a cold-context whole-branch review of
c8c8ee96→ MERGEABLE-WITH-NITS. Two Important findings fixed infa445bbe(null-channels500 guard; SmartCollection rollback on failed/skipped channel) + a best-effort try/catch in5098c048; the fix commit was independently re-reviewed → MERGEABLE. The current head938e4e0fadds only a rebase onto origin/main (hand-resolved append-onlydecisions.md, regenerated OpenAPI/endpoint-index.md) on top of the reviewed5098c048— no logic change; 16/16 tests green post-rebase,check:apiclean.Note: this PR is
Refs #69(backend PR1); #69 does not close until PR2 (SPA wizard + live-E2E). Merge is not gate-auto-granted here — it's a normal reviewed feature-branch merge once CI is green.