b1d5fbefcba02fdc6c19fef85cec1c4e82fc8dea
10
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
ba6a4b08aa |
feat(732): On Now / Next gets a background box, and is on by default (#843)
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (push) Skipped
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (push) Skipped
Build ErsatzTV Image / CI toolchain image resolves (push) Successful in 9s
Build ErsatzTV Image / Delimiter ban (release path) (push) Successful in 25s
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 8m40s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 6m18s
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (push) Successful in 6m12s
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 4m13s
probe742/combined-newest SECOND
Co-authored-by: Timothy <timothy@noreply.gitea.tblindustries.be> |
||
|
|
8b9a7ed541 |
feat(414): stamp immutable Channel.Origin (auto-tuned vs user-created) and surface it (#575)
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (push) Has been skipped
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (push) Has been skipped
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 6m23s
Build ErsatzTV Image / Functional E2E (curl contracts) (push) Successful in 14m21s
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 19m9s
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 12m1s
Co-authored-by: Timothy <timothy.look@gmail.com> Co-committed-by: Timothy <timothy.look@gmail.com> |
||
|
|
928784ba48 |
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
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> |
||
|
|
93188eeb5e |
feat(525): download external-url logo on channel create + create-from-lineup
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
0295a8a9f6 |
style(70): strip UTF-8 BOM from the .cs files this PR touches
CI's Formatting job failed: 19 touched files carried a BOM, which .editorconfig forbids (charset=utf-8). Pure encoding change — one byte per file, no semantic diff (verified: every hunk is `-namespace` -> `+namespace`). Self-inflicted. The patches that edited these legacy files wrote them back as utf-8-sig to "preserve the existing style", but the #311 fix-as-you-touch gate requires a file to be normalized when you touch it — that is the whole point of scoping the gate to changed files instead of reformatting the ~2500 legacy BOM files at once. dotnet format leaves the EF-generated Designer/snapshot files alone as generated code, and its verify skips them the same way, so they stay as ef emitted them. Two corrections to what I believed going in: - `dotnet format --include` does NOT no-op here. It reported `error CHARSET` for each file and exit 2, reproducing CI exactly, and fixed them in place. The note claiming otherwise is wrong for this invocation. - My first BOM check reported all files clean. The od pattern was wrong; reading the first three bytes directly found 19. A detector that can only say "ok" is worse than no detector. Core.Tests 565, ErsatzTV.Tests 1673, Architecture.Tests 5 — all passed. API artifacts still in sync. Refs #70 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
ccef0ca88a |
fix(70): test the weight gate; correct rationale my own clamp made false
Re-review of the fix commit returned MERGEABLE-WITH-NITS. It verified the gate is now complete by enumerating the writers itself (no fourth persisting writer) and proved B2's fix works by writing throwaway handler tests — which was also its point: the fix shipped with none. B2 was create and update silently DISAGREEING on the same input, and the fix re-established agreement with nothing pinning it. Both paths are now driven from one shared case list, plus an explicit test that create and update agree on every case — the per-path tests would both have passed while the two diverged, which is how the bug existed in the first place. Non-vacuity proven: inverting only the update path's validation fails 10 of 20 on a clean build (0 errors, so not a stale-dll pass), and the agreement test is among the failures. The rest is my own prose contradicting my own code. The commit that added EffectiveWeight removed the weight filter, then left four statements asserting a 0-weight source "is filtered out" — two of them authored by that same commit, including the stated justification for Minimum=1 in MultiCollectionItemWeight. A future agent could have read that and deleted the clamp or the floor as redundant; they are belt-and-braces and neither is. Corrected to describe what the code now does: the gate refuses input that means nothing on a share-of-airtime scale, the clamp protects rows predating the gate. Also corrected the writer count in the very bullet whose lesson is "grep every writer of the field": ReplaceBlockItems writes BlockItem.PlaybackOrder, not PlaylistItem.PlaybackOrder. There are TWO persisting writers of PlaylistItem's, and the correction itself had miscounted by conflating the two fields — so the lesson now says to grep each field separately. Core.Tests 565 passed, ErsatzTV.Tests 1673 passed, 0 failed. Refs #70 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
c0da414a4c |
fix(70): close the review blockers — third playlist writer, weight bounds, overflow
Adversarial review of PR #402 returned BLOCKED. It could not break the WRR math or the stateless-restore claim (it probed restore across wraps at indices 12/13/20/37 — all held, and the clamp preserves a 1000:1 ratio exactly). What it broke was the perimeter. B1 — the validation gate had a hole, so the silent-drop bug shipped. CreateChannelFromLineup is a THIRD writer of PlaylistItem.PlaybackOrder; its own guard only covered MultiCollection entries, so a 2+ entry lineup of plain collections persisted WeightedShuffle straight through to PlaylistEnumerator's null-drop. My decisions.md claim that "the silent sites never see it" was false as written — corrected in place, with the lesson recorded: grep every writer of the field, the non-obvious composite handler is the one that gets missed. The Add*ToPlaylist handlers are safe only because they hardcode their order. B2 — Weight had no validation at all, and create/update disagreed on the same input. EF's HasDefaultValue(1) substitutes 1 for a 0 on INSERT (0 reads as "not set") but an UPDATE writes the 0 through — and a 0-weight source was filtered out of the rotation, deleting it from the channel silently. Exactly the failure this order is careful to avoid everywhere else. Now bounded 1..1000 by a shared MultiCollectionItemWeight used by both paths so they cannot drift, and clamped again in the enumerator for rows that predate the gate. B3 — Sum(weights) is checked arithmetic, so two int.MaxValue weights threw OverflowException from inside a playout build. Reachable through the API precisely because of B2. The ceiling fixes both; the sum also widens to long. M1 the lineup mirror now allows WeightedShuffle for multi collections, matching the PlayoutModeMustBeValid change it claims to mirror. M3 ScheduleAsGroup is documented as deliberately unread by this order. L1 MinimumDuration is computed over every source instead of the current rotation — under the clamp a rotation is a strict subset and is rebuilt each wrap, so caching over it went stale. L2 the retry guard keys off the rotation, not the raw collection count. N1 the tautological default test is gone: it built entities in C#, so it asserted the property initializer, not the migration — it could not have failed. Replaced with clamp, overflow, and cross-wrap restore cases (the property the review proved but found unpinned). H1 the two follow-ups the PR body claimed were "filed" did not exist. Now filed: #403 (silent dispatch-fallback hardening) and #404 (SPA weight UI, blocked-by #388). Core.Tests 565 passed, ErsatzTV.Tests 1643 passed, 0 failed. Refs #70 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
a1bd303cce |
fix(app): post-commit side effects on CancellationToken.None + guide-xml/empty-list hardening (#254)
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 6s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Failing after 18m17s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Failing after 18m41s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been cancelled
Backend mutation-hardening cluster from the 2026-07-11 mutation-safety audit sweep (adversarial-reviewer #22/#23), the parallel-safe backend-isolated slice. audit#22 F4 — standardize post-commit enqueues on CancellationToken.None: 20 command handlers under MediaCollections/, ProgramSchedules/, Playouts/, Channels/ threaded the request cancellationToken into work that runs AFTER SaveChangesAsync commits (WriteAsync rebuild/refresh enqueues, mediator.Publish, reindex, cache Refresh, and post-commit lookups that gate an enqueue). A late client-disconnect then turns an already-durable commit into a thrown request AND drops the side effect. Generalizes the #251 deco-handler fix. Excludes BuildPlayoutHandler (worker/background token, not a client-disconnect token), the config/FFmpeg multi-upsert handlers (partial-commit case, separate follow-up), and response-projection reloads (correctly keep the request token). audit#22 F2 — DeleteChannelHandler/DeletePlayoutHandler now delete the channel guide {number}.xml through IFileSystem.File.Delete (observable under MockFileSystem) and BEFORE the commit (a post-commit delete orphans the xml on a crash; the xml is regenerable on demand, so pre-commit delete is the safe order). audit#23 F4 — ReplacePlayoutAlternateScheduleItemsHandler rejects an empty item list in the handler (not only the controller pre-guard) so a direct caller can't trip the Max()-on-empty crash. Docs: api-conventions.md §7a (post-commit token convention + boundaries), decisions.md entry (rationale, sweep scope, #253 PR2-4 coordination note). Tests: guide-cache-delete-through-FS for both delete handlers, empty-list guard. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
cf36c30997 |
fix(api): rework from-lineup to generated playlist design + review fixes (#63)
Adversarial review found the previous multi-flood design non-viable:
PlayoutModeSchedulerFlood never yields to a following Dynamic-start
schedule item, so only the first item ever played, and grouping
media items into a Collection silently dropped the requested order.
Redesign:
- Single-item lineup: one ProgramScheduleItemFlood referencing the
target directly (media item / collection / smart / multi / rerun /
playlist); no generated collection or playlist. Response PlaylistId
is null.
- Multi-item lineup (>= 2): one generated IsSystem Playlist in a
get-or-created IsSystem PlaylistGroup ("Channel Lineups"), one
PlaylistItem per entry in lineup order with PlayAll=true, referenced
by a single Flood schedule item. Rerun collections and playlists are
rejected (422) in multi lineups (PlaylistItem/CollectionKey lack the
fields to enumerate them).
Review fixes:
- Normalize + strict-validate MediaType<->CollectionType pairs once up
front (422 on mismatch / wrong id / not exactly one id).
- Reject MultiCollection with non-Shuffle order (mirrors
PlayoutModeMustBeValid), Mirror playout source, all via 422.
- OnDemand parity: queue TimeShiftOnDemandPlayout post-commit.
- De-collide generated ProgramSchedule and Playlist names against their
unique indexes instead of leaking a UNIQUE-constraint DbUpdateException.
- Generic 422 on save failure + ILogger; AnyAsync existence checks;
Either/Validation unwrap via Match; XML doc on request DTO + endpoint.
- Response model: ChannelId, PlaylistId (nullable), ProgramScheduleId,
PlayoutId (CollectionId removed). Regenerated OpenAPI v1.json + v1.d.ts.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
6857a0d191 | feat(api): composite create-channel endpoint (#63) |