Files
ersatztv/docs/handoffs/chicorytv-issue-queue.md
T
timothyandClaude Fable 5 c5021e28da
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 4m9s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 4m48s
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 3m23s
docs: advance ChicoryTV issue queue past #86 (PR #125); next prompt = #87 Playouts (Codex)
Records the #125 review outcome (refuted reorder blocker → #126), pre-merge
fixes (f18f2dc0), main 229f7cc1, baselines (368/493+1skip/52 web), and new
lessons (mount call-count assertions, no world-refetch, no list[0]-only UIs).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 12:27:06 +02:00

11 KiB
Raw Blame History

ChicoryTV issue-queue handoff (living document)

Paste the prompt below into a fresh session to work the next item. Each session ends by UPDATING THIS FILE in place (rewrite the state section and the queue for the next item) so it always holds the current handoff. History: created 2026-07-02 after the plan audit (#59 epic) filed backend gap issues #100–#111; a 7–9-way parallel workflow build once exhausted RAM, so builds are limited to 2–3 concurrent, never wide fan-outs. Backend gaps all landed by 2026-07-04 (PRs #113–#119); merge pass PR #120; live-data screens so far: #109 Dashboard (PR #123), #84 Channels (PR #124), #86 Schedule editor (PR #125).

PROCESS (2026-07-05, binding): Fable writes each Codex prompt AND performs the PR review (via review subagents — parallel lenses: correctness fork + cheaper contract/tests + design-system agents). Codex implements. Every Codex prompt MUST mandate: (a) subagents where appropriate at fitting effort/model levels, (b) npm ci in each fresh worktree. Review-driven fixes are applied by fitting subagents too (NITS: Fable fixes on the branch; SUBSTANTIAL: back to Codex verbatim or fixed in-session — user decides). Merges need explicit user consent.

Session state (2026-07-05, post-#86): main = 229f7cc1 (PR #125 merge): Schedule editor live — two-pane, drag+keyboard reorder persisted via PUT replace-all, 4-tab READ-ONLY inspector, add/delete items, schedule selector, add-item collection picker, duration estimates (#111). Fable review: approve, no blockers; a suspected reorder blocker was REFUTED (backend serializes with Newtonsoft by RUNTIME type, so polymorphic item fields ARE on the wire despite the OpenAPI schema missing them) → follow-up #126 (flatten items DTO; SPA's widened type in web/src/api/schedules.ts documented + removed when #126 lands). All findings fixed pre-merge (f18f2dc0, sonnet+haiku subagents). Baselines: ErsatzTV.Tests 368, Core.Tests 493 (+1 skip); web tests 52, lint/typecheck/build clean. #84 and #86 CLOSED. Worktree .worktrees/feat-86-schedule-editor is merged — remove it. Main checkout sits on docs/59-ui-redesign-brief (fully merged, safe to switch).

Lessons for all remaining prompts (accumulated):

  • Frontend fixtures/contracts: copy enum-ish string values from the producing backend code (serializers/mappers) or generated v1.d.ts, never invent them; review = diff fixtures vs C#. (#109's bug.) Corollary from #125: the OpenAPI schema can UNDER-report what the wire carries (abstract VMs + Newtonsoft runtime-type serialization) — when generated types look incomplete, check the backend serializer config before hand-widening types, and file the contract gap (#126) instead of silently patching.
  • Anything actionable must derive from what is VISIBLE (#84). Disable every mutation trigger (incl. per-row buttons) while a mutation is in flight; guard double-submit with a REF, not render-closure state (#125).
  • Test EVERY mutation shipped, success AND failure (#84's untested renumber); assert call-counts for mount fetches (a duplicated mount effect double-fetched the whole #86 screen and no test noticed).
  • Mutations must not refetch the world: reconcile from the mutation response where the API returns the new state (PUT replace-all does); otherwise refetch only the affected resource, never the pickers (#125).
  • A screen that lists N entities must let the user pick among them — don't ship list[0]-only UIs (#125's schedule selector finding).
  • The image-build job runs ONLY on main pushes — PR CI can't catch Dockerfile breakage.
  • DTO records in ErsatzTV.Core/Api MUST get file-scoped #nullable enable; ErsatzTV.Application has NO nullable context (string? trips CS8632).
  • NSubstitute + ConfigElementKey: stub with Arg.Any<ConfigElementKey>() + generic <T>.
  • Option<T>.ToNullable() doesn't exist; use MatchUnsafe(v => (T?)v, () => null).
  • ./scripts/update-openapi.sh needs a normal dotnet build ErsatzTV/ErsatzTV.csproj FIRST.
  • Id-taking child-collection GETs 404 on unknown parent via pre-check + ApiResults.
  • Backlog nits (unfiled): unclamped pageSize; PlayoutController Create/Delete lack Name=; PlayoutController.GetItems existence pre-check is heavy; >30 MB uploads → bare 413; artwork content-type trusted (#66); schedule duration estimator materializes collections per GET; /api/health TTL cache. #85 EPG: /api/guide 21-include eager-load per channel per request; fillerKind almost always None. Design system still has NO Dialog/Modal component (bulk confirms use window.prompt/confirm) — likely needed by #89. --text-faint is used in 1 pre-existing shell.css rule but doesn't exist as a token (new code uses --text-disabled) — tiny cleanup candidate. Filed: #126 (OpenAPI polymorphism gap).

PROMPT FOR CODEX — #87: Playouts screen (now/next monitor)

You are Codex, the IMPLEMENTER, in /Users/timothy/ersatztv (ErsatzTV fork; React SPA "ChicoryTV" in web/ — Vite + TS, typed client web/src/api/, generated types web/src/api/generated/v1.d.ts). Fable (Claude) reviews your PR read-only afterwards — do NOT merge. Read CLAUDE.md and docs/contributing.md first; follow the SPA patterns from the Channels (PR #124) and Schedule editor (PR #125) screens — discriminated-union query hooks, messageFromError/ProblemDetails, mutation discipline — match, don't invent.

HARD CONSTRAINTS:

  • USE SUBAGENTS where appropriate, at fitting effort and model levels: cheap/fast agents for mechanical work (fixtures, CSS transcription, test boilerplate); higher-effort agents only for judgment work (polling/timeline state, API-contract mapping). Keep orchestration and final assembly in your main session.
  • Worktree: git worktree add .worktrees/issue-87-playouts -b feat/87-playouts origin/main. Never touch the main checkout or other .worktrees/*.
  • Run npm ci in web/ in the worktree BEFORE any lint/typecheck/test/build.
  • Max 2–3 concurrent builds machine-wide; ONE dotnet build at a time here.
  • NEVER set ETV_UPDATE_GOLDENS. A golden-file diff means your code is wrong.
  • FRONTEND-only: no new endpoints, no ErsatzTV.Core/Application changes. Missing data = defer that surface (disabled + note).
  • CONTRACT RULE: any string value you branch on or put in test fixtures must be copied from the producing backend code (mapper/serializer) or generated v1.d.ts — never invented. If generated types look incomplete vs the backend, check the serializer before widening types and flag it (see #126 for the known schedule-items gap).

Task

Issue #87: recreate design-system/templates/chicorytv-admin/Playouts.jsx wired to the live Playout API. Now/next monitor: live preview area, up-next, timeline strip, upcoming list. Acceptance: matches prototype; live playout data; all 3 themes. NOTE: if the prototype's "live preview" implies actual video playback, that is DEFERRED (no player integration yet — render the surface with a disabled/placeholder treatment and say so); "preview" here means the now-playing metadata treatment, not a video element, unless trivially achievable with the existing /iptv endpoints — do NOT pull in a player dependency.

Context (main = 229f7cc1; baselines: ErsatzTV.Tests 368, Core.Tests 493+1skip, web 52)

  • Gitea: http://192.168.1.95:3000/timothy/ersatztv (basic auth timothy:ded89Lm4).
  • API surface (verify in v1.d.ts; regen npm run generate:api if stale):
    • GET /api/playouts (PAGED — handle page/totalCount), GET /api/playouts/{id}, GET /api/playouts/{id}/items (PAGED — PagedPlayoutItemsResponseModel; this is the upcoming list/timeline source), GET /api/playouts/warnings/count.
    • POST /api/playouts (create), POST /api/playouts/reset-all, DELETE /api/playouts/{id} — include create/delete/reset only if the prototype surfaces them; reset-all is DESTRUCTIVE, needs a confirm.
    • GET /api/channels/state → onAir + nowPlaying (nullable) for the live now/next strip — same source the Channels screen polls.
    • All errors ProblemDetails — surface detail.
  • Polling: channel state may poll at ≥30s (Channels-screen precedent). Playout ITEMS are static between playout rebuilds — fetch on demand (selection change), do NOT poll them. NO /api/health polling on this screen.
  • A screen listing N playouts must let the user select among them (the #125 lesson) — selector or list-pane, per the prototype.
  • Mutations (if any): disabled-while-mutating on ALL triggers, ref-based double-submit guard, reconcile from response or refetch only the affected resource — never the world.
  • CSS: design-system tokens only; any var() fallback must equal the token's resolved value.
  • Tests: every mutation success AND failure (if mutations shipped); paged-response handling; mount fetch call-count; polling discipline (state only); error+retry; empty states; loading. Fixture values per the CONTRACT RULE.

Process

  1. Comment on issue #87 with findings + approach (prototype surface → endpoint/DTO mapping table, scope decisions) BEFORE coding.
  2. Implement; comment progress on #87 as you go.
  3. Verify: cd web && npm ci && npm run lint && npm run typecheck && npm test -- --run && npm run build; then TZ=UTC dotnet build ErsatzTV.sln; TZ=UTC dotnet test ErsatzTV.Tests + Core.Tests sequentially (expect 368 / 493+1skip — no backend regression).
  4. Push, open PR → main: "feat(web): Playouts screen (#87)", body lists the mapping table and scope decisions; closes #87. Poll CI by head SHA until green. Do NOT merge.

On completion — REQUIRED final output

Print a fenced handoff prompt addressed to Claude (Fable) asking it to review the PR READ-ONLY (Fable runs its own review subagents). Include: PR number, branch, head SHA, base, files changed, the mapping table, verification commands + results, deferred/uncertain list, and the finding classification (NITS = Fable fixes on the branch; SUBSTANTIAL = back to Codex verbatim or fixed in-session with subagents — user decides). After approval + user merge consent, Fable merges, verifies main's post-merge run (image job included), updates THIS handoff (pop #87, next prompt = #88 Libraries, record PR + main SHA + baselines), and pushes it to main.


Issue queue (work top-down)

  1. HOUSEKEEPING: remove .worktrees/feat-86-schedule-editor (merged). #99 stays open for the final /api/channels/state onAir wiring; #126 (OpenAPI polymorphism gap) is a good backend slot-filler between screens — unblocks removing the widened type in schedules.ts.
  2. #87 Playouts screen ← CODEX PROMPT above (Fable reviews via subagents, merges on consent, updates this file).
  3. UX conversion order: #88 Libraries → #85 EPG (#102 JSON guide; mind fillerKind + per-request-cost notes above) → #89 Channel Builder (#104 artwork upload; also needs #62: #63/#64/#65; likely needs the first Dialog/Modal component) → #93 Settings → #90 rebrand → #91 cutover. Cross-refs: #99 seam landed (PR #121), final wiring open. Done recently: PR #123 (#109 Dashboard), PR #124 (#84 Channels, main 7ee6e987), PR #125 (#86 Schedule editor — closed 2026-07-05, main 229f7cc1). Filed: #126. Languages-list endpoint from #105 still unimplemented — open a follow-up when #89 needs it.