b1d5fbefcba02fdc6c19fef85cec1c4e82fc8dea
11
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
1a7f15fb27 |
fix(510): address independent review — Resource null guard, honest routing claim
Two independent reviews (cross-family Codex + cold Opus) both returned BLOCKED. Findings, all verified against source before acting: 1. Resource arm could throw ArgumentNullException (Codex, Medium). Making the channel/global Resource arm reachable exposed that CreateWatermarkHandler and UpdateWatermarkHandler write `Image = null` for EVERY non-Custom watermark, so an API-created Resource watermark reached Path.Combine(folder, null). Added the blank/null guard the arm never had. This was live at the playout-item level too, not just newly-reachable code. 2. "Routing is unaffected" was false (Codex, Low but sharp). The predicate is unchanged, but CanUseFFmpegNativeWatermark also tests Count == 1, and dropping an unresolvable watermark shortens the list. A deco with one valid and one missing permanent watermark now routes ffmpeg-native where it previously routed to the graphics engine. Intended, but observable -- so it is documented and pinned by a test rather than claimed away. 3. "Exactly one resolver" over-claimed (Opus, High). True of the selector, not the application: the song-progress overlay is built as a WatermarkOptions directly by the streaming and troubleshooting handlers, unchecked, and can still hand ffmpeg a nonexistent -i. Pre-existing; scoped the claim in the record and channels.md and filed #653. 4. Undeclared crash->degrade change (Opus, Medium). Channel/global Custom had no blank-image guard, so a cleared image hit ImageCache's fileName[..2] and threw out of stream startup. Now declared in the record and tested. 5. Contradictory rule text (Opus, Medium) -- the catalog one-liner said "always no bug" while the body documents the playout-item fall-through exception. Qualified; catalog regenerated. 6. History was wrong in both the record and the XML docs: the three precedence levels did NOT all check every source -- channel/global had no Resource arm and threw. Corrected. Tests: 30 in the fixture now (was 18). New coverage for the preserved blank-Custom fall-through (to channel AND to global), the complement case (missing-but-named must NOT fall through), null/blank Resource, and the valid+missing routing case. 17 of 24 failed against the pre-fix resolver before this round; the fixture stays mutation-sensitive. Also: hoisted the mock-filesystem Initialize() out of its loop so a multi-file case cannot silently seed only the last file, and marked the generated-URL test honestly as redundant-by-construction rather than claiming independent coverage. The decision record is now 81 prose lines, over the 60-line ceiling. Declared as a legitimate decline per docs.corpus-size-signal: the length is the review findings above, each a distinct fact, not redundancy. refs #510 #652 #653 |
||
|
|
9cbe70e486 |
fix(510): one watermark resolver for all four attachment points
WatermarkSelector resolved watermarks in two places with two policies. The three precedence levels (playout item, channel, global) existence-checked every image source and degraded to None; the deco path had its own copy of the same switch that returned whatever path it computed, unchecked. So one channel could disagree with itself about whether an on-screen bug rendered, based only on how the watermark was attached. #502 deferred this here but scoped it to ChannelLogo. It was never ChannelLogo-only: the deco path skipped the existence check for Custom and Resource too. Extract one ResolveWatermark used by all four sites. Severity is not cosmetic. A dead LOCAL path is not harmlessly skipped -- CanUseFFmpegNativeWatermark hands a single permanent watermark to ffmpeg as a bare -i argument and excludes only URLs, so the deco path could hand ffmpeg a nonexistent input file. The generated-initials nameplate was real: a live-E2E on a real transcoded frame confirmed it composited via the deco path (/iptv/logos/gen is on ArtworkController, which has no auth filter, so the container-internal self-fetch succeeded). The #502-era comment claiming "it has never rendered here" was wrong, and the new record says so. It is still removed: serving it means an HTTP fetch inside stream startup, which graphics.channel-logo-caching (#525) eliminated for logos, and it depends on #1's hardcoded localhost. Reviving it by caching the image instead is #652. Measured blast radius on prod: 0 Deco rows, 0 DecoWatermark rows, all 43 channels have logo artwork -- no rendered output changes. Preserved deliberately: a playout-item Custom watermark with a blank image still falls THROUGH to the channel/global watermark; unifying resolution must not change which watermark wins. Routing is untouched. Strict improvement: the channel and global arms previously threw NotSupportedException on a Resource watermark; they now resolve it. The default arm still throws so a new image source fails loudly. Tests: 18 new cases including a positive control and 8 deco-vs-channel parity cases. 12 of the 18 fail against the pre-fix resolver, which is what proves they are load-bearing rather than vacuous. fixes #510 |
||
|
|
b7d58bbf32 |
docs(74): tighten overlay suppression rule (Merge-during-filler also clears)
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 15s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 17s
PR Gates / Docs update reminder (pull_request) Successful in 20s
PR Gates / decisions lifecycle (pull_request) Successful in 22s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 14m25s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 16m45s
Build ErsatzTV Image / Functional E2E (curl contracts) (pull_request) Successful in 16m51s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 21m18s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Folds whole-branch review finding #2. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
c80839b338 | docs(74): channel-level graphics attachment + On Now/Next overlay | ||
|
|
dfed9a393b |
fix(68): rebuild on-demand channel guide (and mirrors) on thaw
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 10s
PR Gates / Docs update reminder (pull_request) Successful in 15s
PR Gates / decisions lifecycle (pull_request) Successful in 27s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 1m19s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 1m26s
Build ErsatzTV Image / Functional E2E (curl contracts) (pull_request) Successful in 14m38s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 17m52s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 7m25s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
An on-demand channel (`PlayoutMode.OnDemand`) already is the "resume where I left off" feature: `Playout.OnDemandCheckpoint` persists the viewer's position and `PlayoutTimeShifter.TimeShift` slides the materialized timeline forward on tune-in so the paused item is active again. Because it rewrites `GuideStart`/ `GuideFinish` alongside `Start`/`Finish`, guide and playback freeze together — structurally avoiding the free-running-wall-clock desync #68 was filed about. The one gap: `TimeShift` rewrote the stored `PlayoutItem` rows but the XMLTV guide is served from a cached fragment that only `RefreshChannelData` rebuilds, and the tune-in path never enqueued it. So an external EPG client polling after a thaw could see a stale timeline until the next incidental rebuild. Fix: `IPlayoutTimeShifter.TimeShift` now returns the channel numbers whose cached guide is stale — the shifted channel plus any channels that mirror it (the same fan-out `BuildPlayoutHandler` already does) — and `TimeShiftOnDemandPlayoutHandler` enqueues a `RefreshChannelData` for each on `CancellationToken.None` (post-commit side effect must not be abandoned if the session token cancels). Tests: handler enqueues a rebuild per stale channel (+ mirror + no-shift cases); `PlayoutTimeShifter` reports source+mirrors on a shift, empty on Continuous / zero-offset / active-unforced, and correctly seeds+shifts a never-watched playout. Non-vacuity of the enqueue proven by a compiling negative control. Docs: channels.md (On-demand resume section), domain-model.md, decisions.md (scheduling.ondemand-guide-refresh-on-thaw). Per-viewer resume is out of scope (single per-channel checkpoint; #68 says per-channel suffices). fixes #68 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
aa76994825 |
docs(525): correct logo-download rejection status to 422, not 400
Build ErsatzTV Image / CI image pin matches docker/ci (pull_request) Successful in 8s
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 12s
Build ErsatzTV Image / decisions lifecycle (pull_request) Successful in 24s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 1m21s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 1m21s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 7m34s
Build ErsatzTV Image / Functional E2E (curl contracts) (pull_request) Successful in 13m59s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 17m49s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Live-E2E of the write path (scripts/e2e-local.sh) confirmed the feature end to end — good URL -> cached /iptv/logos/<hash>, M3U emits the cached URL, cached logo serves back as PNG, rejected save leaves the prior logo intact — and caught that a bad/non-image/oversized logo rejects as 422 (BaseError -> ValidationProblemDetails via ToErrorResult), not 400. 400 remains model-binding-only. Corrected channels.md, api-conventions.md, and the graphics.channel-logo-caching decision Rule + catalog. |
||
|
|
56a18cd5dc |
docs(525): record download-on-save decision; update channels + api-conventions
New lifecycle record graphics.channel-logo-caching (supersedes: none — narrows for YAML image elements). Regenerated the active catalog. channels.md + api- conventions.md updated for the new save-time download + 400s. |
||
|
|
893d4a6398 |
test(67): de-vacuify the template-stamping assertion; cover adopt-then-delete
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 11s
Build ErsatzTV Image / CI image pin matches docker/ci (pull_request) Successful in 9s
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 10s
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 1m6s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 13m25s
Build ErsatzTV Image / Functional E2E (curl contracts) (pull_request) Successful in 16m1s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 20m26s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 21m19s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Whole-branch review found the stamping test was structurally vacuous: a bare foreach over ChannelTemplates.Where(IsSystem) passes with zero iterations, so the test would have stayed green if template seeding silently bailed out. Assert the collection is non-empty first. Proven non-vacuous by a negative control (forcing SeedChannelTemplates to bail makes exactly this test fail). Also cover the ACTUAL production sequence -- adopt an existing hand-made row, then delete it -- which the previous no-resurrect test did not exercise (it covered seed-then-delete). The marker is written on the adopt path too, so the deleted row must stay deleted. docs: note that a deleted preset degrades to no default rather than failing, and that the default applies to newly created channels, not retroactively. Refs #67 |
||
|
|
bb7f57d04d | docs(67): record the shared-preset decision, watermark seeding, and the additive DTO field | ||
|
|
62c15da490 |
docs(73): document seasonal / date-conditional scheduling (already implemented)
Build ErsatzTV Image / Docs update reminder (push) Has been skipped
Build ErsatzTV Image / decisions.md append-only (push) Has been skipped
Build ErsatzTV Image / Build & test (.NET) (push) Has been cancelled
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Has been cancelled
Build ErsatzTV Image / Functional E2E (curl contracts) (push) Has been cancelled
Build ErsatzTV Image / Build & push image (amd64) (push) Has been cancelled
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (push) Has been cancelled
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (push) Has been cancelled
fixes #73 #73's premise ("ErsatzTV has no native date-conditional scheduling today") is false. The predicate (IAlternateScheduleItem) is shipped on both engines, evaluated by AlternateScheduleSelector, reachable from the SPA, and already unit-tested — so the only real gap was discoverability, and it was a docs gap: the mechanism was documented as a mechanism, never as a task. - channels.md: task-shaped "Recipe: seasonal / holiday programming" for both engines, plus the gotchas — chiefly that blank StartYear/EndYear is what makes a range repeat every year (AlternateScheduleSelector.cs:32-40), that the override branch needs BOTH years, and that explicit years disable wrap-around detection. - domain-model.md: glossary row (mirrors the #77 "already exists" row). - decisions.md: entry recording the verdict and the rejected asks — notably that "prioritize collection X during a date range" is #70's weighting problem, not a second primitive built here while #70 is mid-flight. No production code changed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
f1e97b94a7 |
Add architecture docs and fork maintenance strategy (#6)
Document channel architecture, M3U/XMLTV integration with Jellyfin, and fork maintenance strategy for the archived upstream. Also includes CLAUDE.md updates for implementer workflow and project boundaries. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |