feat(web+api): deco templates (#144 S4, #162) #175

Merged
timothy merged 4 commits from feat/144-s4-deco-templates into main 2026-07-07 20:06:36 +02:00
Owner

Slice S4 of the #162 scheduling REST plan (parity issue #144): Deco Templates + Deco Template Groups.

API (new DecoTemplateController, sibling of TemplateController)

  • GET/POST /api/deco-templates/groups, DELETE /api/deco-templates/groups/{id} (cascade verified in DB config)
  • GET /api/deco-templates (flat list, groups×by-group per the DecoController precedent), GET/POST/DELETE /api/deco-templates[/{id}]
  • GET /api/deco-templates/{id}/items (404-guarded, startTime-sorted, decoName included; endTime "00:00:00" = end-of-day round-trips exactly)
  • PUT /api/deco-templates/{id} — full replace
  • Handler hardening (deliberate deviation, documented in-code): CreateDecoTemplateHandler group-existence 422 (the S2-review latent FK-500); ReplaceDecoTemplateItemsHandler now REJECTS what it previously silently dropped — unknown decoId (422), start≥end (422, end 00:00 treated as 24:00), overlaps (422, strict <, adjacency allowed), and out-of-day-range TimeSpans (422 — were persisting raw while displaying .TimeOfDay).

SPA

  • /app/deco-templates list + editor (deco-group→deco cascade picker with reset-on-group-change, start time + duration hours/minutes, sorted items table with 24:00 end-of-day display, client-side validation mirroring the server exactly — verified identical math in review; Save disabled on violations).
  • decoTemplates.ts api module + tests; nav next to Decos.

Verification

  • ErsatzTV.Tests 748 green; Core.Tests 493 green; web 287 green; lint/tsc/build clean.
  • Live E2E: full API matrix (dup 422s, group-existence 422 not 500, unknown-decoId 422 not 500, adjacency+end-of-day 200 with exact round-trip, overlap/ordering 422s, cascade delete) + browser (cascade picker, add/save/reload persistence, overlap warning gating Save, deletes) + S3 regression spot-check.
  • Adversarial review: no must-fix; the day-bounds should-fix applied (c745d01a). Known follow-up families already tracked in #172 (duplicate-item value-equality skip, null-name NRE, exists-query, unreachable 404 docs).

Parity note: no deco-template copy exists upstream (Blazor nor CQRS) — nothing deferred. Part of #144 / #162 (slice S4 — do not auto-close).

🤖 Generated with Claude Code

Slice S4 of the #162 scheduling REST plan (parity issue #144): Deco Templates + Deco Template Groups. **API** (new `DecoTemplateController`, sibling of TemplateController) - `GET/POST /api/deco-templates/groups`, `DELETE /api/deco-templates/groups/{id}` (cascade verified in DB config) - `GET /api/deco-templates` (flat list, groups×by-group per the DecoController precedent), `GET/POST/DELETE /api/deco-templates[/{id}]` - `GET /api/deco-templates/{id}/items` (404-guarded, startTime-sorted, decoName included; `endTime "00:00:00"` = end-of-day round-trips exactly) - `PUT /api/deco-templates/{id}` — full replace - Handler hardening (deliberate deviation, documented in-code): `CreateDecoTemplateHandler` group-existence 422 (the S2-review latent FK-500); `ReplaceDecoTemplateItemsHandler` now REJECTS what it previously silently dropped — unknown decoId (422), start≥end (422, end 00:00 treated as 24:00), overlaps (422, strict `<`, adjacency allowed), and out-of-day-range TimeSpans (422 — were persisting raw while displaying `.TimeOfDay`). **SPA** - `/app/deco-templates` list + editor (deco-group→deco cascade picker with reset-on-group-change, start time + duration hours/minutes, sorted items table with 24:00 end-of-day display, client-side validation mirroring the server exactly — verified identical math in review; Save disabled on violations). - `decoTemplates.ts` api module + tests; nav next to Decos. **Verification** - ErsatzTV.Tests 748 green; Core.Tests 493 green; web 287 green; lint/tsc/build clean. - Live E2E: full API matrix (dup 422s, group-existence 422 not 500, unknown-decoId 422 not 500, adjacency+end-of-day 200 with exact round-trip, overlap/ordering 422s, cascade delete) + browser (cascade picker, add/save/reload persistence, overlap warning gating Save, deletes) + S3 regression spot-check. - Adversarial review: no must-fix; the day-bounds should-fix applied (c745d01a). Known follow-up families already tracked in #172 (duplicate-item value-equality skip, null-name NRE, exists-query, unreachable 404 docs). Parity note: no deco-template copy exists upstream (Blazor nor CQRS) — nothing deferred. Part of #144 / #162 (slice S4 — do not auto-close). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
timothy added 4 commits 2026-07-07 20:01:13 +02:00
Adds DecoTemplateController mirroring TemplateController: CRUD for deco
template groups, deco templates (flat list + by-id), item listing, and a
full-replace PUT for a deco template's item list.

Hardening (deviation from a literal port of the existing handlers, matching
the #144 S2 fix for ReplaceTemplateItemsHandler):
- CreateDecoTemplateHandler now validates DecoTemplateGroupId exists before
  insert (previously a bad id hit the FK constraint at SaveChanges and
  surfaced as a 500; now a 422).
- ReplaceDecoTemplateItemsHandler now rejects invalid items (unknown DecoId,
  StartTime >= EndTime unless EndTime is the end-of-day sentinel 00:00:00,
  or overlapping ranges) with a 422 instead of silently dropping/persisting
  them - the same silent-drop/silent-overlap bug class already fixed for
  templates.

Response DTOs serialize the raw item TimeSpans (via .TimeOfDay), so an
end-of-day item still round-trips as StartTime=22:00:00/EndTime=00:00:00
regardless of the ViewModel's day-wrapping DateTime representation.
Adds DecoTemplatesScreen mirroring TemplatesScreen: list mode groups deco
templates by deco template group with create/delete dialogs; editor mode
(/app/deco-templates/{id}) has an add-content row (deco group -> deco ->
start time -> duration hours/minutes pickers), a time-sorted items table
displaying end-of-day items as "24:00", and client-side validation mirroring
ReplaceDecoTemplateItemsHandler's hardened rules (start must precede its
effective end time; no overlapping ranges, with EndTime 00:00:00 treated as
end-of-day for both checks) that disables Save on violation.

No copy action: neither the Blazor DecoTemplateEditor nor the CQRS layer has
a deco-template copy command (unlike Templates, which has both Copy* and a
/copy route), so this isn't a parity gap - just matches upstream scope.

Registered at /app/deco-templates in the primary nav, next to Decos.
fix(app): bound deco template item times to a single day (#144 S4 review)
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 4m19s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 4m29s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
c745d01a2c
Out-of-range TimeSpans (25:00:00, 1.06:00:00, negatives) previously
persisted raw while the read side projected .TimeOfDay, so display and
playout behavior diverged silently.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
timothy merged commit 4ea5273770 into main 2026-07-07 20:06:36 +02:00
timothy deleted branch feat/144-s4-deco-templates 2026-07-07 20:06:37 +02:00
Sign in to join this conversation.