diff --git a/docs/blazor-route-parity.md b/docs/blazor-route-parity.md index 1bec75b4b..8437516cc 100644 --- a/docs/blazor-route-parity.md +++ b/docs/blazor-route-parity.md @@ -146,7 +146,7 @@ SPA-ready are preserved below for the record. > | Filler presets / Trakt / FFmpeg profiles | PARITY-OK | — | > | Watermarks | PARITY-OK (copy via `/add?from=` prefill, 2026-07-09) | — | > | Playout creation + alternate-schedules | PARITY-OK | — | -> | Playout kind-editors + list actions | PARITY-OK (delete/reset/erase/scheduling-context wired + templates preview calendar, 2026-07-09; EntityLocker build-lock gating enforced server-side via 409 + mirrored in SPA `IsLocked`, 2026-07-10 #215) | — | +> | Playout kind-editors + list actions | PARITY-OK (delete/reset/erase/scheduling-context wired + templates preview calendar, 2026-07-09; EntityLocker build-lock gating enforced server-side via 409 + mirrored in SPA `IsLocked`, 2026-07-10 #215; per-playout **Reshuffle** action added alongside Reset/Erase/Delete — `POST /api/v1/playouts/{id}/reshuffle` reseeds + rebuilds via `PlayoutBuildMode.Reset`, 2026-07-16 #71) | — | > | Multi/rerun collections, playlists, trash | PARITY-OK (trash select-all/clear + per-kind "see all" paging past 100, 2026-07-11) | — | > | Collections | PARITY-OK (custom-order endpoint + reorder UI, all-10-kind add picker, 2026-07-09) | — | > | Channel editor | PARITY-OK (external logo URL mutual-exclusion, bare-create defaults, enumerated pickers — 2026-07-11) | #212 | diff --git a/docs/decisions.md b/docs/decisions.md index 447e5dfe0..f92f2fefd 100644 --- a/docs/decisions.md +++ b/docs/decisions.md @@ -82,6 +82,7 @@ in-file entries. - [2026-07-16 — Functional-E2E CI harness: advisory curl-contract job over an app booted from source (#299)](#2026-07-16--functional-e2e-ci-harness-advisory-curl-contract-job-over-an-app-booted-from-source-299) - [2026-07-16 — Optional advertised IPTV base URL (`iptv.base_url`) resolved centrally in the two generators (#340)](#2026-07-16--optional-advertised-iptv-base-url-iptvbase_url-resolved-centrally-in-the-two-generators-340) - [2026-07-16 — Auto-tuning enumerates via EF, persists via SmartCollection; additive coexistence (#69)](#2026-07-16--auto-tuning-enumerates-via-ef-persists-via-smartcollection-additive-coexistence-69) +- [2026-07-16 — Per-playout reshuffle = scoped Reset build; seed surfaced (#71)](#2026-07-16--per-playout-reshuffle--scoped-reset-build-seed-surfaced-71) --- @@ -1040,3 +1041,17 @@ name already exists is flagged and de-selected by default, and existing channels Bulk create loops the #63 `CreateChannelFromLineup` primitive via `ISender` and returns a per-channel Created/Skipped/Failed outcome. Known MVP limitation: the generated SmartCollection is named after the channel; a name collision with an existing SmartCollection surfaces as a per-channel Failed outcome. + +## 2026-07-16 — Per-playout reshuffle = scoped Reset build; seed surfaced (#71) + +- The shuffle seed (`Playout.Seed`) + per-collection `CollectionEnumeratorState` already persist a stable + shuffled order across rebuilds. #71's real gap was a **user-triggered per-playout reshuffle** (a Classic + playout's seed is otherwise only reseeded by a full Reset, and `reset-all` uses `Refresh` for Classic, so + it never reseeds Classic) plus visibility. +- `POST /api/v1/playouts/{id}/reshuffle` enqueues `BuildPlayout(id, PlayoutBuildMode.Reset)` — Reset already + reseeds + clears anchors/rerun-history + rebuilds. Named `/reshuffle` (not `/reset`) to (a) match user + intent and (b) avoid the "reset-one reseeds Classic while reset-all refreshes Classic" naming clash. It is + deliberately more aggressive than `reset-all` for Classic: an explicit single-channel action rolls a new + order; the bulk action stays non-disruptive. +- `Playout.Seed` is surfaced on the playout list + detail DTOs so the SPA can show it — its purpose is + **visible confirmation** (the seed changes after a reshuffle). diff --git a/docs/domain-model.md b/docs/domain-model.md index 9c51427fb..c23a4ca84 100644 --- a/docs/domain-model.md +++ b/docs/domain-model.md @@ -59,6 +59,7 @@ Channel (1) ──< Playout (0..N per channel; ChannelPlayoutSource distinguishe | **Sequential playout** | Driven by a YAML file (`Playout.ScheduleFile`); validated via the Schedule Validator screen. | `Playout.ScheduleFile` | `/app/troubleshooting/yaml` (validate only; file itself is server-side) | | **Scripted playout** | `PlayoutScheduleKind.Scripted`; backed by `ScriptedScheduleController`. | — | — | | **External-JSON playout** | `PlayoutScheduleKind.ExternalJson = 20`; JSON-driven, same shape idea as Sequential but JSON instead of YAML. | `Playout.ScheduleFile` | — | +| **Reshuffle** | `Playout.Seed` (the master shuffle seed) is surfaced on the playout list + detail API so the SPA can show it; a per-playout **Reshuffle** action reseeds and rebuilds via `POST /api/v1/playouts/{id}/reshuffle` (enqueues `BuildPlayout(id, PlayoutBuildMode.Reset)`), distinct from `reset-all`'s `Refresh` mode for Classic. | `Playout.Seed` | `/app/playouts` | | **Deco** | Per-playout "decoration": one of 4 independently-modal sections — watermark, graphics elements, default filler, dead-air fallback — plus break content. Each mode section is `Inherit`/`Disable`/`Override`/`Merge` (`DecoMode`). Can attach directly to a Block playout via `Playout.DecoId`. | `Deco`, `DecoGroup`, `DecoBreakContent` | `/app/decos` | | **DecoTemplate** | Time-of-day (`DecoTemplateItem.StartTime`/`EndTime`) calendar of `Deco`s, assigned to a playout via `PlayoutTemplate.DecoTemplateId` (same row as the Block-template assignment — one `PlayoutTemplate` entry carries both a `Template` and an optional `DecoTemplate`). | `DecoTemplate`, `DecoTemplateItem`, `DecoTemplateGroup` | `/app/deco-templates` | | **Default deco vs deco templates** | `Playout.DecoId` = one static deco for the whole playout; `PlayoutTemplate.DecoTemplateId` = a time-varying deco schedule. Both are optional and independent. | `Playout`, `PlayoutTemplate` | `/app/playouts/{id}/templates` |