Auto-Tune DetailPanel backend (#385) — the additive per-channel overrides half.
What
POST /api/v1/channels/auto-tune's AutoTunedChannelRequest gains three optional, backward-compatible per-channel fields (omit = PR1 behavior exactly):
templateId — overrides the batch template per channel.
advanced — reuses the manual Channel Builder's CreateChannelFromLineupAdvancedOptionsRequest verbatim (same 24-field override set + ToCommand(), same advanced.X ?? template.X stamp-at-create contract). The axis default fills PlaybackOrder only when the caller left it null.
logo — uploaded channel image, Sanitized() at the request boundary (#283 stored-XSS defense), forwarded to CreateChannelFromLineup.Logo.
Resolved per channel in CreateAutoTunedChannelsHandler.CreateOne, so one channel's bad override still yields a per-channel Failed/Skipped without aborting the batch.
Deferred (not in this PR)
Per-source rotation weights + query corrections → #425.#70's WeightedShuffle reads weights only off MultiCollection join rows; an auto-tuned channel is one SmartCollection (fair-share only). Honoring per-source weights needs a MultiCollection-of-per-source-SmartCollections redesign that contradicts "one live query per channel" — a [PLAN-MODE] design call.
Bug initials + fallback colour generated logo — needs persisted Channel state + FFmpeg-pipeline wiring; the uploaded logo covers the image case.
Independent cold review (review-only agent, cross-model rotation unavailable this window → cold same-model, per the skip-rubric which permits a cold agent) + live-E2E complete.
Cold review verified all seven risk points on the write-path handler: axis-default merge (Advanced.PlaybackOrder ?? axisOrder — confirmed necessary, since CreateChannelFromLineupHandler defaults a null order to Chronological), logo Sanitized() at the boundary (#283), template fallback, batch independence, nullability/no-schema-drift, no shared-reference with mutation, and non-vacuous tests. No BLOCKER/HIGH/MEDIUM; two LOW nits — one addressed (added a per-channel override-failure isolation test), one accepted (redundant defensive ?? None).
Live-E2E (real instance, seeded TV library, scan): POST /api/v1/channels/auto-tune with per-channel overrides → 200/Created (no 500); read-back proved every override persisted and round-tripped distinct from the batch-default baseline — per-channel templateId won over batch, logo persisted, DB-confirmed Chronological + shuffleScheduleItems=true on the schedule; playout built, /iptv/channels.m3u + /iptv/xmltv.xml → 200 with the override logo. Baseline (no overrides) → Created (backward-compat intact).
Head delta since the review's base (3285a3d8 → e91f72c9) is test-only (the isolation test the review requested); the reviewed logic is unchanged and is the shipped logic.
Independent cold review (review-only agent, cross-model rotation unavailable this window → cold same-model, per the skip-rubric which permits a cold agent) + live-E2E complete.
**Cold review** verified all seven risk points on the write-path handler: axis-default merge (`Advanced.PlaybackOrder ?? axisOrder` — confirmed *necessary*, since `CreateChannelFromLineupHandler` defaults a null order to `Chronological`), logo `Sanitized()` at the boundary (#283), template fallback, batch independence, nullability/no-schema-drift, no shared-reference `with` mutation, and non-vacuous tests. No BLOCKER/HIGH/MEDIUM; two LOW nits — one addressed (added a per-channel override-failure isolation test), one accepted (redundant defensive `?? None`).
**Live-E2E** (real instance, seeded TV library, scan): `POST /api/v1/channels/auto-tune` with per-channel overrides → 200/Created (no 500); read-back proved every override persisted and round-tripped distinct from the batch-default baseline — per-channel `templateId` won over batch, logo persisted, DB-confirmed `Chronological` + `shuffleScheduleItems=true` on the schedule; playout built, `/iptv/channels.m3u` + `/iptv/xmltv.xml` → 200 with the override logo. Baseline (no overrides) → Created (backward-compat intact).
Head delta since the review's base (3285a3d8 → e91f72c9) is **test-only** (the isolation test the review requested); the reviewed logic is unchanged and is the shipped logic.
Review-verdict: MERGEABLE @ e91f72c99c8da0c119e717de9b2cff20a0ff9f09
Rebased onto origin/main (main advanced 13 commits; the only conflict was append-vs-append in docs/decisions.md, resolved by keeping both entries — no code change). The reviewed logic is byte-identical to the MERGEABLE review at e91f72c9; the head delta e91f72c9 → f2a60a46 is purely the rebase (decisions.md merge). Post-rebase locally: solution builds, no OpenAPI drift, format + BOM clean, 10 auto-tune tests green.
Rebased onto origin/main (main advanced 13 commits; the only conflict was append-vs-append in `docs/decisions.md`, resolved by keeping both entries — no code change). The reviewed logic is **byte-identical** to the MERGEABLE review at e91f72c9; the head delta e91f72c9 → f2a60a46 is purely the rebase (decisions.md merge). Post-rebase locally: solution builds, no OpenAPI drift, format + BOM clean, 10 auto-tune tests green.
Review-verdict: MERGEABLE @ f2a60a46605938db5445e0f5dd6d8ec09b6c1564
Rebased again onto origin/main (main advanced via #424 — same append-vs-append decisions.md conflict, resolved keeping both). Diff vs the previously CI-green head f2a60a46 is only main's inherited #424 files + the decisions.md merge — no code/API change (git diff f2a60a46 b9ab2d79 = .husky/pre-push, the new #424 hook, and docs/decisions.md). The reviewed logic is unchanged.
Rebased again onto origin/main (main advanced via #424 — same append-vs-append `decisions.md` conflict, resolved keeping both). Diff vs the previously CI-green head f2a60a46 is **only** main's inherited #424 files + the decisions.md merge — **no code/API change** (`git diff f2a60a46 b9ab2d79` = `.husky/pre-push`, the new #424 hook, and `docs/decisions.md`). The reviewed logic is unchanged.
Review-verdict: MERGEABLE @ b9ab2d79f3535bab174467092bc951e61997fda4
timothy
scheduled this pull request to auto merge when all checks succeed 2026-07-17 23:34:11 +02:00
timothy
merged commit 1abc5df18c into main2026-07-18 00:03:53 +02:00
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.
Auto-Tune DetailPanel backend (#385) — the additive per-channel overrides half.
What
POST /api/v1/channels/auto-tune'sAutoTunedChannelRequestgains three optional, backward-compatible per-channel fields (omit = PR1 behavior exactly):templateId— overrides the batch template per channel.advanced— reuses the manual Channel Builder'sCreateChannelFromLineupAdvancedOptionsRequestverbatim (same 24-field override set +ToCommand(), sameadvanced.X ?? template.Xstamp-at-create contract). The axis default fillsPlaybackOrderonly when the caller left it null.logo— uploaded channel image,Sanitized()at the request boundary (#283 stored-XSS defense), forwarded toCreateChannelFromLineup.Logo.Resolved per channel in
CreateAutoTunedChannelsHandler.CreateOne, so one channel's bad override still yields a per-channel Failed/Skipped without aborting the batch.Deferred (not in this PR)
[PLAN-MODE]design call.Channelstate + FFmpeg-pipeline wiring; the uploadedlogocovers the image case.Tests / docs
ToCommand()logo-sanitization test. 1105 controller/channel tests green.decisions.md(+ Index),api-conventions.md,domain-model.md.fixes #385
Independent cold review (review-only agent, cross-model rotation unavailable this window → cold same-model, per the skip-rubric which permits a cold agent) + live-E2E complete.
Cold review verified all seven risk points on the write-path handler: axis-default merge (
Advanced.PlaybackOrder ?? axisOrder— confirmed necessary, sinceCreateChannelFromLineupHandlerdefaults a null order toChronological), logoSanitized()at the boundary (#283), template fallback, batch independence, nullability/no-schema-drift, no shared-referencewithmutation, and non-vacuous tests. No BLOCKER/HIGH/MEDIUM; two LOW nits — one addressed (added a per-channel override-failure isolation test), one accepted (redundant defensive?? None).Live-E2E (real instance, seeded TV library, scan):
POST /api/v1/channels/auto-tunewith per-channel overrides → 200/Created (no 500); read-back proved every override persisted and round-tripped distinct from the batch-default baseline — per-channeltemplateIdwon over batch, logo persisted, DB-confirmedChronological+shuffleScheduleItems=trueon the schedule; playout built,/iptv/channels.m3u+/iptv/xmltv.xml→ 200 with the override logo. Baseline (no overrides) → Created (backward-compat intact).Head delta since the review's base (3285a3d8 → e91f72c9) is test-only (the isolation test the review requested); the reviewed logic is unchanged and is the shipped logic.
Review-verdict: MERGEABLE @ e91f72c99c8da0c119e717de9b2cff20a0ff9f09
e91f72c99ctof2a60a4660Rebased onto origin/main (main advanced 13 commits; the only conflict was append-vs-append in
docs/decisions.md, resolved by keeping both entries — no code change). The reviewed logic is byte-identical to the MERGEABLE review at e91f72c9; the head delta e91f72c9 → f2a60a46 is purely the rebase (decisions.md merge). Post-rebase locally: solution builds, no OpenAPI drift, format + BOM clean, 10 auto-tune tests green.Review-verdict: MERGEABLE @ f2a60a46605938db5445e0f5dd6d8ec09b6c1564
f2a60a4660tob9ab2d79f3Rebased again onto origin/main (main advanced via #424 — same append-vs-append
decisions.mdconflict, resolved keeping both). Diff vs the previously CI-green head f2a60a46 is only main's inherited #424 files + the decisions.md merge — no code/API change (git diff f2a60a46 b9ab2d79=.husky/pre-push, the new #424 hook, anddocs/decisions.md). The reviewed logic is unchanged.Review-verdict: MERGEABLE @
b9ab2d79f3