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
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)
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>
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 main2026-07-21 22:59:52 +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.
Closes #135.
What
POST /api/v1/channels/from-lineup(and Auto-Tune per-channeladvanced, which reuses the same DTO) resolved every advanced override asadvanced.X ?? template.X, sonullalways meant inherit — a channel could not drop a template-set watermark / filler / preferred language. Adds an optional typedclearenum list onadvanced:/api/v1is frozen-additive)clearis forced to none even when the template sets oneEnum
CreateChannelFromLineupClearFieldlives inErsatzTV.CoresoStartup.UseStringEnumSchemasrenders 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
CLEARoverrides sentinel thatapplyOverridesToRequestfolds intoadvanced.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
CreateChannelFromLineupHandlerTests(3 new: clear-forces-none, set+clear id conflict, string conflict + empty-redundant) + 136 Channels/AutoTune/OpenAPI-contract tests greenv1.json+ endpoint index + generated TS regeneratedDocs
api-conventions.md§2,spa-conventions.md§11,decisions.mdrecordapi.from-lineup-clear-to-none(catalog rebuilt).🤖 Generated with Claude Code
Review-verdict: MERGEABLE @
928784baIndependent cold-context review of the core change (589e0313) returned MERGEABLE — verified existing-client byte-stability (null-clear path unchanged), Auto-Tune
withpreservingClear, 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).