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.
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).
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).
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 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.
- 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>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Closes the first #91 phase (b) feature gates from the #203 mutation-depth sweep.
#210 — playouts
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-sideErasePlayoutItems/ErasePlayoutHistoryMediatR commands; 404 pre-check, 204 on success.GET /api/playouts/items/{id}/scheduling-context— decodes a PlayoutItem's stored scheduling-context JSON server-side via the existingProcessSchedulingContexthandler.PlayoutItemResponseModelgainsid(null for synthesized gap rows) +hasSchedulingContext;PlayoutListItemResponseModelgainsplayoutMode.POST /api/channels/{n}/playout/reset, server-default mode = Blazor semantics; stale "no endpoint" comment removed), Delete (existingDELETE /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 replicatingAlternateScheduleSelector.GetScheduleForDateexactly, incl. year-wrap ranges + day-overflow rules), and OnDemand-channel gating of the alternate-schedules button.GET /api/playoutscustom-order note: n/a.#211 — collections
PUT /api/collections/{id}/custom-order{ mediaItemIds: [] }, Index derived from array order (api-conventions §7); 404 pre-check + 204.GET /api/collections/{id}/itemsnow orders byCustomIndex(nulls last, title tiebreak) whenuseCustomPlaybackOrderis on — array order carries the order, DTO unchanged.#213 — nits (partial)
/add?from={id}prefill (FFmpeg-profiles pattern; no new endpoint).Docs
blazor-route-parity.mdverdict rows updated (playouts/collections/blocks/watermarks/trash clusters → PARITY-OK),decisions.md+6 entries, OpenAPIv1.json+v1.d.ts+endpoint-index.mdregenerated.Verification
dotnet test ErsatzTV.Tests967 passed;ErsatzTV.Core.Tests493 passed / 1 skipped.web/: 428 tests / 50 files passed, lint clean, build clean,check:apiclean./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
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 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.