Add the id-based reconcile tests to ReplaceProgramScheduleItemsReconcileTests:
reorder moves state with the logical item (the non-vacuous core — proven to fail
under forced-positional), insert-in-middle, delete-unreferenced, unknown-id→422,
duplicate-id→422, and stale-version+unknown-id→412 (412 precedes 422, §7c). The
GET→map→PUT lossless round-trip now round-trips r.Id so it exercises id-mode.
Threads the new int? Id through all command/wire construction sites in tests.
Docs: api-conventions §7c (stable child identity + the deliberate #2-#5 positional
asymmetry) and a decisions.md entry.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bug 1 (500 on watermark/graphics save): Replace/Add handlers projected the
freshly-built entity graph, whose ProgramScheduleItemWatermark / -GraphicsElement
join rows carry only foreign-key ids — the Watermark/GraphicsElement navs are null,
and Mapper.ProjectToViewModel dereferences them unguarded, throwing an NRE that the
controller surfaced as a 500 on PUT/POST. Both handlers now reload the persisted
item(s) through the read-side include chain before projecting. Extracted that chain
into ProgramScheduleItemQueryExtensions.IncludeScheduleItemDetails() so GET, Replace
and Add share one source of truth.
Masking: PersistItems returned a lazy LanguageExt Map, and the existing round-trip
test only checked .IsRight — never enumerating it, so the deferred NRE never fired.
The new ScheduleItemWriteProjectionTests force enumeration (as the controller's
.ToList()/serialization does) and seed watermark/graphics via a separate context so
the handler's fresh factory context has nothing pre-tracked.
Bug 2 (server side): GetProgramScheduleItemsHandler now .OrderBy(i => i.Index) —
it previously returned id order, which is not index order.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>