From ff5346fd0cca5b171463b7e7c87cc077df19dc01 Mon Sep 17 00:00:00 2001 From: NeilClack Date: Tue, 29 Sep 2026 10:50:18 -0500 Subject: [PATCH] docs: finalize Phase 4 schedule copy in the contract --- docs/schedule-api-contract.md | 42 ++++++++++++++++++++++++++++------- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/docs/schedule-api-contract.md b/docs/schedule-api-contract.md index 91ec87b..a1b8976 100644 --- a/docs/schedule-api-contract.md +++ b/docs/schedule-api-contract.md @@ -339,16 +339,25 @@ populate `email` and `blocks` exactly as before. Old Kiosks ignore the new fields; new Kiosks may read either endpoint. This endpoint does **not** emit an ETag and does **not** return 304. -### (e) Portal-internal endpoints (names & purpose only) +### (e) Portal-internal endpoints (not Kiosk-facing) -Finalized in Phases 3 and 4. They are **not** Kiosk-facing. +These are session-authenticated Portal actions; they are **not** part of the +Kiosk API and are not served by `/api/v2/...`. -- `POST /api/v2/...` / server actions for Schedule CRUD (create/update/delete - Block, set `type`, assign Proclamations) — Phase 3. -- `POST .../copy` to copy a Schedule between License Keys on the same account — - Phase 4. - -Exact paths and payloads are deferred; they will be recorded here when added. +- Schedule CRUD — implemented in Phase 3 as SvelteKit form actions on + `(protected)/(user)/schedule` (`?/setType`, `?/createBlock`, `?/updateBlock`, + `?/deleteBlock`), backed by `$lib/server/schedule.ts`. +- **Copy** — implemented in Phase 4 as the `?/copy` action plus + `copySchedule()` in `$lib/server/schedule.ts`. Copies one License Key's + Schedule to one or more other License Keys **by value, in a single database + transaction** (all targets succeed or none). The target `type` becomes the + source's; Block rows get fresh IDs; each block's `dynamicAudio` and ordered + Proclamation references are preserved. The source and every target must + belong to the caller's account; a target equal to the source, an empty + target list, or a source without a schedule are rejected. The resulting + schedule is run through the shared validator before commit. Because Block IDs + are part of the serialized payload, every target's content-derived + `version`/ETag changes, so its Kiosk refetches. --- @@ -544,6 +553,17 @@ later phase. off, the engine plays local selections but the cache keeps updating. The older `proclamation.schedule_active` flag is retained in config but is not consulted by the Phase 3 engine. +20. **Copy semantics.** Copies are **independent, by value** snapshots: no link + is kept to the source, and editing the source later does not affect targets + (and vice versa). A copy replaces any existing target Schedule in full + (type and blocks) and requires explicit confirmation in the UI when a target + already has one. No "copy to all" shortcut and no scheduled/linked copies + (per the Phase 4 brief). The copied schedule is validated before commit; a + source schedule that fails validation is rejected rather than copied. +21. **Copy and Kiosk code.** Verified against the Phase 3 Kiosk engine/fixture + tests that a copied payload (new Block IDs, same type/times/audio/refs) is + parsed and evaluated identically to any other; **no Kiosk changes were + required**. --- @@ -561,3 +581,9 @@ later phase. browser-fallback timezone note; the Kiosk adds the conditional client, atomic cache, schedule engine, and a System Settings On/Off toggle. Open items 2, 5, 7, 8, 9, 11, 15, and 16 are updated to reflect the changes. +- 2026-09-29 — Phase 4. Schedule copy is implemented as a Portal-internal + action (`?/copy`) backed by a single-transaction service that copies by + value with fresh Block IDs (so target ETags change), preserving `dynamicAudio` + and Proclamation order, scoped to the caller's account, and validated before + commit. Endpoint (e) is finalized; open items 20 and 21 record the copy + semantics and the Kiosk no-change verification.