Independent Codex review of the fix diff surfaced two real findings the fork pass missed:
- Medium: the #251 affected-playout QUERIES in ReplaceDecoTemplateItemsHandler and
UpdateDecoHandler still ran on the request `cancellationToken`, so a cancellation
landing after SaveChanges committed but before those queries executed would throw
before the CancellationToken.None enqueue — the edit committed but no playout Reset,
re-opening the stale-content bug in that window. Run the entire post-commit
invalidation (queries + enqueue) on CancellationToken.None so the side effect can't
be half-aborted once the data has changed.
- Low: UpdateDefaultDecoHandler enqueued a Reset for request.PlayoutId even when
ExecuteUpdateAsync matched 0 rows (nonexistent playout), creating a background build
request for an id that isn't there. Guard the enqueue on rows-updated > 0 so the
enqueued set equals the affected set. Added a regression test.
Also corrected the ReplaceProgramScheduleItemsHandler comments: the schedule-item
hierarchy is TPT (table-per-type), not TPH — the SetValues reconcile is safe either way
(same-runtime-type guard; no discriminator to corrupt), Codex confirmed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
#252: ReplaceProgramScheduleItems deleted and re-inserted every item on every
save (even a no-op PUT-back), and PlayoutScheduleItemFillGroupIndex.ProgramScheduleItemId
is OnDelete(Cascade) — so every schedule save silently wiped persisted
fill-group/shuffle enumerator progression for all playouts using the schedule.
Switch to a positional in-place reconcile: for a same-typed slot, copy scalars via
CurrentValues.SetValues (BuildItem stays the single source of item construction, so
no field is dropped) and rebuild the watermark/graphics join rows, keeping the item
id — and with it the fill-group index. Subtype change / surplus falls back to
delete+insert for that slot only. The request DTO carries no stable item id, so
position is the only key here; true content-aware stable identity is deferred to the
shared concurrency/round-trip contract in #253.
#251: deco / deco-template CONTENT edits (and default-deco assignment) only take
effect on a playout Reset build — deco/break/default-filler content is applied during
Reset, a Continue keeps the frozen filler items, and BlockKey change-detection has no
deco dimension to self-heal. The editors enqueued nothing (a commented-out TODO in
ReplaceDecoTemplateItemsHandler), so filler/break content stayed stale indefinitely
until a manual Reset. Enqueue BuildPlayout(Reset) for exactly the affected playouts:
- ReplaceDecoTemplateItemsHandler: playouts via PlayoutTemplate.DecoTemplateId
- UpdateDecoHandler: playouts via Playout.DecoId and via deco-template items
- UpdateDefaultDecoHandler: the reassigned playout (adjacent same-class fix)
Post-commit enqueues use CancellationToken.None (audit #22 policy).
Tests: DecoInvalidationTests + ReplaceProgramScheduleItemsReconcileTests, each proven
non-vacuous against a negative control (inverted the primitive, verified 0 CS errors so
the --no-build run used a fresh dll). Full ErsatzTV.Tests suite green (1067).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* add deco groups and decos; set default deco for block playout
* use block playout default deco for watermark
* add deco templates, groups and deco template editor
* associate deco template with playout template
* use deco template item watermark for playback
* update changelog for decos