feat: playouts delete/reset/erase + collections custom-order/all-kind adds + parity nits (#210, #211, #213) #214

Merged
timothy merged 8 commits from feat/210-211-playouts-collections into main 2026-07-09 23:17:30 +02:00
Owner

Closes the first #91 phase (b) feature gates from the #203 mutation-depth sweep.

#210 — playouts

  • New endpoints: POST /api/playouts/{id}/erase-items (Block/Sequential/Scripted; 422 otherwise), POST /api/playouts/{id}/erase-items-and-history (Classic/Block/Sequential/Scripted) — reuse the Blazor-side ErasePlayoutItems/ErasePlayoutHistory MediatR commands; 404 pre-check, 204 on success.
  • New decode endpoint (per the 2026-07-07 decode-by-row-id decision): GET /api/playouts/items/{id}/scheduling-context — decodes a PlayoutItem's stored scheduling-context JSON server-side via the existing ProcessSchedulingContext handler. PlayoutItemResponseModel gains id (null for synthesized gap rows) + hasSchedulingContext; PlayoutListItemResponseModel gains playoutMode.
  • SPA wiring: per-playout Reset (existing POST /api/channels/{n}/playout/reset, server-default mode = Blazor semantics; stale "no endpoint" comment removed), Delete (existing DELETE /api/playouts/{id}), erase buttons by kind (Block gets both variants, matching Blazor), per-item scheduling-context dialog, templates preview calendar (client-side month grid replicating AlternateScheduleSelector.GetScheduleForDate exactly, incl. year-wrap ranges + day-overflow rules), and OnDemand-channel gating of the alternate-schedules button.
  • Dropped the separate "Schedule reset" button (daily rebuild time already editable via Edit details) — decisions.md entry.
  • GET /api/playouts custom-order note: n/a.

#211 — collections

  • New endpoint: PUT /api/collections/{id}/custom-order { mediaItemIds: [] }, Index derived from array order (api-conventions §7); 404 pre-check + 204.
  • GET /api/collections/{id}/items now orders by CustomIndex (nulls last, title tiebreak) when useCustomPlaybackOrder is on — array order carries the order, DTO unchanged.
  • SPA: explicit reorder mode (loads ALL pages first — a partial-list PUT would scramble the rest — then move up/down + Save/Cancel); add-items picker widened from 4 to all 10 media kinds (backend already accepted them); "All" fan-out unchanged (Movie/Show/Artist), new kinds reachable via their filter chip. Not movies-only (deliberate deviation from Blazor — decisions.md entry).

#213 — nits (partial)

  • Block copy button + dialog (existing endpoint/client, was unwired).
  • Watermark copy via /add?from={id} prefill (FFmpeg-profiles pattern; no new endpoint).
  • Trash: Select all / Clear selection; pageSize 50→100 (API max; true paging deferred — decisions.md entry).
  • Trakt stale "Classic UI search page" note fixed.
  • 4 decisions.md entries (YAML paste, channel renumber, deco-templates table, trash cap) + 2 for the deviations above.
  • Deferred (no API support / read-only conveniences): logs sorting, page-size persistence, blocks/templates list filter — #213 stays open for those.

Docs

blazor-route-parity.md verdict rows updated (playouts/collections/blocks/watermarks/trash clusters → PARITY-OK), decisions.md +6 entries, OpenAPI v1.json + v1.d.ts + endpoint-index.md regenerated.

Verification

  • dotnet test ErsatzTV.Tests 967 passed; ErsatzTV.Core.Tests 493 passed / 1 skipped.
  • web/: 428 tests / 50 files passed, lint clean, build clean, check:api clean.
  • Live E2E (scripts/e2e-local.sh + Playwright): reset/erase/delete flows on real playouts (list 2→1 after delete), preview calendar renders, block copy created "E2E Block copy", watermark copy prefilled at /add?from=1, trash Select-all present, add-items dialog shows all 10 kind chips; new endpoints curl-smoked (204/404/200 as designed).

🤖 Generated with Claude Code

Closes the first #91 phase (b) feature gates from the #203 mutation-depth sweep. ## #210 — playouts - **New endpoints**: `POST /api/playouts/{id}/erase-items` (Block/Sequential/Scripted; 422 otherwise), `POST /api/playouts/{id}/erase-items-and-history` (Classic/Block/Sequential/Scripted) — reuse the Blazor-side `ErasePlayoutItems`/`ErasePlayoutHistory` MediatR commands; 404 pre-check, 204 on success. - **New decode endpoint** (per the 2026-07-07 decode-by-row-id decision): `GET /api/playouts/items/{id}/scheduling-context` — decodes a PlayoutItem's stored scheduling-context JSON server-side via the existing `ProcessSchedulingContext` handler. `PlayoutItemResponseModel` gains `id` (null for synthesized gap rows) + `hasSchedulingContext`; `PlayoutListItemResponseModel` gains `playoutMode`. - **SPA wiring**: per-playout **Reset** (existing `POST /api/channels/{n}/playout/reset`, server-default mode = Blazor semantics; stale "no endpoint" comment removed), **Delete** (existing `DELETE /api/playouts/{id}`), **erase buttons by kind** (Block gets both variants, matching Blazor), per-item **scheduling-context dialog**, **templates preview calendar** (client-side month grid replicating `AlternateScheduleSelector.GetScheduleForDate` exactly, incl. year-wrap ranges + day-overflow rules), and OnDemand-channel gating of the alternate-schedules button. - Dropped the separate "Schedule reset" button (daily rebuild time already editable via Edit details) — decisions.md entry. - `GET /api/playouts` custom-order note: n/a. ## #211 — collections - **New endpoint**: `PUT /api/collections/{id}/custom-order` `{ mediaItemIds: [] }`, Index derived from array order (api-conventions §7); 404 pre-check + 204. - `GET /api/collections/{id}/items` now orders by `CustomIndex` (nulls last, title tiebreak) when `useCustomPlaybackOrder` is on — array order carries the order, DTO unchanged. - **SPA**: explicit reorder mode (loads ALL pages first — a partial-list PUT would scramble the rest — then move up/down + Save/Cancel); add-items picker widened from 4 to all **10 media kinds** (backend already accepted them); "All" fan-out unchanged (Movie/Show/Artist), new kinds reachable via their filter chip. Not movies-only (deliberate deviation from Blazor — decisions.md entry). ## #213 — nits (partial) - Block **copy** button + dialog (existing endpoint/client, was unwired). - Watermark **copy** via `/add?from={id}` prefill (FFmpeg-profiles pattern; no new endpoint). - Trash: **Select all / Clear selection**; pageSize 50→100 (API max; true paging deferred — decisions.md entry). - Trakt stale "Classic UI search page" note fixed. - 4 decisions.md entries (YAML paste, channel renumber, deco-templates table, trash cap) + 2 for the deviations above. - Deferred (no API support / read-only conveniences): logs sorting, page-size persistence, blocks/templates list filter — #213 stays open for those. ## Docs `blazor-route-parity.md` verdict rows updated (playouts/collections/blocks/watermarks/trash clusters → PARITY-OK), `decisions.md` +6 entries, OpenAPI `v1.json` + `v1.d.ts` + `endpoint-index.md` regenerated. ## Verification - `dotnet test ErsatzTV.Tests` 967 passed; `ErsatzTV.Core.Tests` 493 passed / 1 skipped. - `web/`: 428 tests / 50 files passed, lint clean, build clean, `check:api` clean. - Live E2E (scripts/e2e-local.sh + Playwright): reset/erase/delete flows on real playouts (list 2→1 after delete), preview calendar renders, block copy created "E2E Block copy", watermark copy prefilled at `/add?from=1`, trash Select-all present, add-items dialog shows all 10 kind chips; new endpoints curl-smoked (204/404/200 as designed). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
timothy added 7 commits 2026-07-09 22:54:57 +02:00
Backend slice for the ChicoryTV playouts and collections screens.

PlayoutController:
- POST /api/playouts/{id}/erase-items (204; 404 pre-check; 422 unless
  Block/Sequential/Scripted) -> ErasePlayoutItems
- POST /api/playouts/{id}/erase-items-and-history (204; 404; 422 unless
  Classic/Block/Sequential/Scripted) -> ErasePlayoutHistory
- GET /api/playouts/items/{id}/scheduling-context (200/404) decodes a
  playout item's stored context by row id via a new
  GetPlayoutItemSchedulingContext query that reuses ProcessSchedulingContext
- PlayoutItemResponseModel gains HasSchedulingContext (no raw JSON in list)
- PlayoutListItemResponseModel gains PlayoutMode (ChannelNumber already present)

CollectionController:
- PUT /api/collections/{id}/custom-order (204; 404 pre-check; 422) with
  UpdateCollectionCustomOrderRequest deriving CustomIndex from array order
- GetCollectionItemsHandler orders by CustomIndex (nulls last) then title/id
  when the collection's UseCustomPlaybackOrder is set

Tests: controller route + behavior tests, OpenAPI ProblemDetails TestCases,
GetCollectionItems custom-order handler test. Regenerated v1.json, v1.d.ts,
endpoint-index.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The SPA needs the item's row id to call
GET /api/playouts/items/{id}/scheduling-context. Plumbed through
PlayoutItemViewModel -> PlayoutItemResponseModel as a nullable Id
(null for synthesized UNSCHEDULED gap rows, which are PlayoutGaps,
not PlayoutItems). Additive for existing consumers (Playouts.razor
reads the VM by property). Regenerated v1.json + v1.d.ts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds the SPA slice for the newly-merged playout endpoints:
- api/playouts: deletePlayout, resetChannelPlayout, erasePlayoutItems,
  erasePlayoutItemsAndHistory, getPlayoutItemSchedulingContext (+ types).
- PlayoutsScreen: enable per-playout Reset (channel playout reset), Delete
  (danger, confirm), and erase-by-kind buttons (Block = items + items&history;
  Classic/Sequential/Scripted = items&history; none for ExternalJson); replaces
  the stale "no per-playout reset endpoint" block. Disable Alternate schedules
  for OnDemand Classic playouts (Blazor parity).
- Per-item scheduling-context dialog (info button when hasSchedulingContext &&
  id != null), rendering the decoded JSON in a scrollable <pre>.
- Playout-templates editor: "Preview calendar" toggle with a Monday-first month
  grid; winning template per day computed client-side via a colocated
  appliesToDate/firstMatchingIndex helper that ports
  AlternateScheduleSelector.GetScheduleForDate / PlayoutTemplateEditViewModel
  .AppliesToDate exactly (inclusive range, year-wrap reverse, start-day rollover,
  end-day clamp, explicit-year override).
- Button gains a title prop (parity with IconButton) for the disabled-button hint.

Tests: api client URL/method assertions, PlayoutsScreen action flows
(delete/reset/erase per kind, OnDemand gating, scheduling-context dialog +
error), calendar-helper unit tests, and a preview-calendar render test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds PUT /api/collections/{id}/custom-order support (updateCollectionCustomOrder)
and a reorder mode in ManualItemsView: loads every page of a manual collection
(so the wholesale-replace PUT never drops items), lets the user move items with
up/down icon buttons, and saves/cancels. Reorder is offered for any manual
collection with useCustomPlaybackOrder on, not just movies-only (server/enumerator
already support any kind).

Widens the add-items picker (ADDABLE_TYPE_LIST, MEDIA_KIND_FILTERS,
toAddItemsRequest) from 4 to all 10 addable media kinds; the default "All" search
fan-out stays Movie/Show/Artist (seasons excluded per #180), with the new kinds
reachable via their specific filter, mirroring Blazor's per-kind list pages.
- BlocksScreen: add copy-to-group dialog (mirrors TemplatesScreen), wiring the
  already-existing copyBlock() API client
- WatermarksScreen: add copy via /add?from={id} prefill (mirrors
  FFmpegProfilesScreen), no new endpoint needed
- TrashScreen: add Select all / Clear selection to the action bar; bump
  PAGE_SIZE to the search API's max (100) — true paging needs a page param
  the API doesn't have yet
- TraktListsScreen: fix stale callout text — "View matched items" now opens
  the SPA's own search screen, not the Classic UI

docs/decisions.md: record the YAML-validator paste-textarea deviation, the
channel-number prompt-renumber deviation, extend the "table not calendar"
convention to the deco-templates editor, and document the trash 100-item cap.
docs: parity verdict rows for #210/#211/#213 + decision entries (schedule-reset drop, reorder buttons)
Build ErsatzTV Image / Build & test (.NET) (pull_request) Failing after 3m10s
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 11s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 9m15s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
f3d8ac559f
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
timothy added 1 commit 2026-07-09 23:06:35 +02:00
test(web): deflake builder collections fan-out test; fix(spa): watermark mode derives from pathname state (review)
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 8m6s
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 12s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 10m8s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
3528f689d6
- App.test.tsx: await the initial library fan-out being issued before
  mockClear — passive effects flush asynchronously, so on a slow machine the
  mount fan-out leaked past the clear and polluted the post-click assertion
  (CI-only failure on run 383).
- WatermarksScreen: modeFromPath parses the pathname state string (incl.
  search) instead of reading mutable window.location during render.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
timothy merged commit 4f8079986f into main 2026-07-09 23:17:30 +02:00
timothy deleted branch feat/210-211-playouts-collections 2026-07-09 23:17:30 +02:00
Sign in to join this conversation.