fix(135): from-lineup advanced overrides can express "clear to none" #550

Merged
timothy merged 1 commits from issue-135-clear-to-none into main 2026-07-21 22:59:52 +02:00
Owner

Closes #135.

What

POST /api/v1/channels/from-lineup (and Auto-Tune per-channel advanced, which reuses the same DTO) resolved every advanced override as advanced.X ?? template.X, so null always meant inherit — a channel could not drop a template-set watermark / filler / preferred language. Adds an optional typed clear enum list on advanced:

  • omitted/null still inherits (byte-stable for existing clients — /api/v1 is frozen-additive)
  • a field named in clear is forced to none even when the template sets one
  • set + clear of the same field is a 422

Enum CreateChannelFromLineupClearField lives in ErsatzTV.Core so Startup.UseStringEnumSchemas renders it as a string enum in the spec (matching every sibling advanced-options enum). Clearable set: watermark, the 4 fillers, preferred audio/subtitle language + audio title.

SPA: the shared advanced-options model re-adds a real "None" option to the 5 id selects (watermark + fillers) in both the Channel Builder and the Auto-Tune DetailPanel, routed through a CLEAR overrides sentinel that applyOverridesToRequest folds into advanced.clear (never leaking as a field value). String clear-fields are covered by the API for machine clients; SPA text inputs keep "empty = inherit" (tri-state deferred).

Verification

  • .NET: 29 CreateChannelFromLineupHandlerTests (3 new: clear-forces-none, set+clear id conflict, string conflict + empty-redundant) + 136 Channels/AutoTune/OpenAPI-contract tests green
  • Web: 895 tests green (5 new advancedOptions unit tests); typecheck + lint clean
  • OpenAPI v1.json + endpoint index + generated TS regenerated
  • Independent review MERGEABLE (core) + delta re-review MERGEABLE

Docs

api-conventions.md §2, spa-conventions.md §11, decisions.md record api.from-lineup-clear-to-none (catalog rebuilt).

🤖 Generated with Claude Code

Closes #135. ## What `POST /api/v1/channels/from-lineup` (and Auto-Tune per-channel `advanced`, which reuses the same DTO) resolved every advanced override as `advanced.X ?? template.X`, so `null` always meant **inherit** — a channel could not drop a template-set watermark / filler / preferred language. Adds an optional typed `clear` enum list on `advanced`: - omitted/null still inherits (byte-stable for existing clients — `/api/v1` is frozen-additive) - a field named in `clear` is forced to **none** even when the template sets one - set + clear of the same field is a 422 Enum `CreateChannelFromLineupClearField` lives in `ErsatzTV.Core` so `Startup.UseStringEnumSchemas` renders it as a **string enum** in the spec (matching every sibling advanced-options enum). Clearable set: watermark, the 4 fillers, preferred audio/subtitle language + audio title. **SPA:** the shared advanced-options model re-adds a real **"None"** option to the 5 id selects (watermark + fillers) in both the Channel Builder and the Auto-Tune DetailPanel, routed through a `CLEAR` overrides sentinel that `applyOverridesToRequest` folds into `advanced.clear` (never leaking as a field value). String clear-fields are covered by the API for machine clients; SPA text inputs keep "empty = inherit" (tri-state deferred). ## Verification - .NET: 29 `CreateChannelFromLineupHandlerTests` (3 new: clear-forces-none, set+clear id conflict, string conflict + empty-redundant) + 136 Channels/AutoTune/OpenAPI-contract tests green - Web: 895 tests green (5 new advancedOptions unit tests); typecheck + lint clean - OpenAPI `v1.json` + endpoint index + generated TS regenerated - Independent review MERGEABLE (core) + delta re-review MERGEABLE ## Docs `api-conventions.md` §2, `spa-conventions.md` §11, `decisions.md` record `api.from-lineup-clear-to-none` (catalog rebuilt). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
timothy added 1 commit 2026-07-21 22:38:28 +02:00
fix(135): from-lineup advanced overrides can express "clear to none"
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 12s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 17s
PR Gates / Docs update reminder (pull_request) Successful in 19s
PR Gates / decisions lifecycle (pull_request) Successful in 22s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 13m44s
Build ErsatzTV Image / Functional E2E (curl contracts) (pull_request) Successful in 17m8s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 20m34s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 20m41s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
928784ba48
CreateChannelFromLineupHandler resolved every advanced override with
advanced.X ?? template.X, so null always meant INHERIT and a channel could
not drop a template-set watermark / filler / preferred language. Add an
optional typed `clear` enum list to CreateChannelFromLineupAdvancedOptions:
omitted/null still inherits (byte-stable for existing clients), a field named
in `clear` is forced to none. Set+clear of the same field is a 422.

The enum (CreateChannelFromLineupClearField) lives in ErsatzTV.Core so the
OpenAPI string-enum scan renders it as a string enum, matching every sibling
advanced-options enum. Handler resolves clearable fields once via
ResolveClearable and validates set/clear conflicts via ValidateClear;
reference validation skips existence checks for cleared (null) refs.

SPA: the shared advancedOptions model re-adds a real "None" option to the five
id selects (watermark + fillers) in both the Channel Builder and the Auto-Tune
DetailPanel, routed through a CLEAR overrides sentinel that applyOverridesToRequest
folds into advanced.clear (never leaking onto the wire as a field value). The
backend enum also covers the preferred audio/subtitle language strings for
machine clients; the SPA text inputs keep "empty = inherit" (tri-state deferred).

Docs: api-conventions.md §2, spa-conventions.md §11, decisions.md record
api.from-lineup-clear-to-none; v1.json + generated TS regenerated.

fixes #135

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Author
Owner

Review-verdict: MERGEABLE @ 928784ba

Independent cold-context review of the core change (589e0313) returned MERGEABLE — verified existing-client byte-stability (null-clear path unchanged), Auto-Tune with preserving Clear, clear-forces-none on both Channel and flood item for all 8 fields, reference-existence-skip for cleared refs, set+clear conflict for ids and strings, string-enum schema, and no SPA sentinel leak. A focused re-review of the follow-up delta (applyOverridesToRequest narrowed to CLEARABLE_FIELDS so a non-clearable field can never silently drop; +string-conflict and non-clearable-passthrough tests) also returned MERGEABLE. All tests green (.NET 29 handler + 136 area; web 895).

Review-verdict: MERGEABLE @ 928784ba Independent cold-context review of the core change (589e0313) returned MERGEABLE — verified existing-client byte-stability (null-clear path unchanged), Auto-Tune `with` preserving `Clear`, clear-forces-none on both Channel and flood item for all 8 fields, reference-existence-skip for cleared refs, set+clear conflict for ids and strings, string-enum schema, and no SPA sentinel leak. A focused re-review of the follow-up delta (applyOverridesToRequest narrowed to CLEARABLE_FIELDS so a non-clearable field can never silently drop; +string-conflict and non-clearable-passthrough tests) also returned MERGEABLE. All tests green (.NET 29 handler + 136 area; web 895).
timothy merged commit 41f5701722 into main 2026-07-21 22:59:52 +02:00
Sign in to join this conversation.