15 KiB
ChicoryTV issue-queue handoff (living document)
Paste the prompt below into a fresh session to work the next issue. Each session ends by UPDATING THIS FILE in place (rewrite the "Issue #N state" section and the queue for the next issue) 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. #97 finished 2026-07-02 (PR #112, stacked on docs/59-ui-redesign-brief; issue stays open until the stack reaches main). #105 finished 2026-07-02 (PR #113 merged, closed). #108 finished 2026-07-03 (PR #114 merged to main, issue closed; CI green incl. post-merge).
NEW 2026-07-03: the queue is now worked by TWO parallel sessions — a Claude session (prompt A
below) on the playout read API, and a Codex session (prompt B below) on media sources + scan
status. Disjoint files except ErsatzTV/wwwroot/openapi/v1.json, which is regen-authoritative:
whichever PR merges second must rebase onto the new main and re-run ./scripts/update-openapi.sh.
Merge the two PRs sequentially, never simultaneously.
PROMPT A (Claude session) — Finish issues #100+#101+#107+#110 — playout read API (review WIP → implement → verify → review → PR)
You are the ORCHESTRATOR in the main conversation (Fable). Fable is EXPENSIVE — reserve it for orchestration decisions and the adversarial review; DELEGATE the bulk work to cheaper models:
- Recon/reads (issue bodies, WIP diffs, Blazor precedents): Explore agent or model: haiku.
- Implementation (handlers, DTOs, controllers, tests): Agent with model: sonnet (opus only if sonnet's attempt fails review twice). Give it exact file paths, conventions, and the diff.
- Mechanical chores (regen scripts, commit wording, API curls): inline is fine, but don't spawn Fable subagents for them.
- Adversarial review (final review step): the ONE place to use a fable subagent (read-only, high effort). HARD CONSTRAINTS:
- Max 2–3 concurrent build/test-executing agents or commands MACHINE-WIDE — and a parallel Codex session may be running builds for prompt B, so keep YOUR builds to one at a time. Never a wide build fan-out (a 7–9-way parallel dotnet build previously crashed the machine).
- Do NOT use the Workflow tool. Sequential Agent calls or inline work only.
- This session works ONLY this branch. A Codex session may be working feat/103-media-sources-api in parallel — do not touch that branch, its worktree, or MediaSources/Libraries controller files.
- End by updating this handoff file (see "On completion").
- The merge of your own green PR may be blocked by the permission classifier — if so, ask the user for a one-word "merge" consent instead of retrying.
Project context (read CLAUDE.md first; docs/contributing.md + docs/rest-api.md before code)
- Repo: /Users/timothy/ersatztv — ErsatzTV fork (.NET 10, CQRS/MediatR, LanguageExt, EF Core), being rebuilt as React SPA "ChicoryTV" (web/) over its REST API. Gitea: http://192.168.1.95:3000/timothy/ersatztv (API auth: basic timothy:ded89Lm4).
- Tests: NUnit + Shouldly + NSubstitute in ErsatzTV.Tests (NOT xUnit). Error contract: ProblemDetails; 404 title "Resource not found", 422 "Validation failed".
- OpenAPI regen: ./scripts/update-openapi.sh (rewrites ErsatzTV/wwwroot/openapi/v1.json). This branch is based on origin/main which has NO web/ dir — SKIP web type-regen steps.
- BRANCH TOPOLOGY: origin/main has the REST API foundation + #105 picker lists + #108 health. docs/59-ui-redesign-brief carries the SPA foundation + #96/#98; PR #112 (#97) targets it — all unmerged to main. feat/playouts-read-api is based on the PRE-#113 origin/main: REBASE onto origin/main first — v1.json WILL conflict with the #113/#114 regens; resolve by taking either side and re-running ./scripts/update-openapi.sh at the end (regen is authoritative).
- Patterns proven in #97/#105/#108: parameterless 200-only GETs use typed returns
(Task<List>/Task) with
[HttpGet("/api/...", Name = "...")],[Tags],[EndpointSummary],[EndpointGroupName("general")](precedent: FillerPresetController, FFmpegProfileController, HealthController); id-taking GETs that can 404 use the ApiResults/ProblemDetails path and NEED an OpenApiErrorResponseContractTests entry (it is an explicit [TestCase] allowlist); DTO records live in ErsatzTV.Core/Api/... (project has Nullable=disable — add file-scoped#nullable enablewhen a property is genuinely nullable; precedent: ErsatzTV.Core/Api/Health/HealthCheckResponseModel.cs); time-dependent handlers carry Now on the request record; handler tests use the ErsatzTV.Tests/Support/InMemoryTvContext harness; controller tests assert route template + Name + mediator pass-through (see ErsatzTV.Tests/Controllers/FillerPresetControllerTests.cs, HealthControllerTests.cs). Branch protection: PR must pass "Build & test (.NET)" to merge. - LanguageExt trap found in the #108 WIP — check for it here too:
Option<T>.MatchTHROWSResultIsNullExceptionif a branch returns null; useMatchUnsafewhen a null result is intended. The salvaged WIP drafts share authorship — audit for this pattern.
Issue state (UNTRUSTED WIP — review before trusting anything)
- ONE branch feat/playouts-read-api covers FOUR issues: #100, #101, #107, #110 (playout read API; unblocks #87 Playouts screen). Read all four issue bodies + comments via the Gitea API.
- WIP commit
49a6d4ee"wip: partial implementation salvaged from interrupted workflow run" — never built, never tested. ~271 insertions across 10 files incl. NEW+MODIFIED files: ErsatzTV/Controllers/Api/PlayoutController.cs and ChannelController.cs are MODIFIED (existing endpoints — don't break them), ErsatzTV.Core/Api/Playouts/PlayoutResponseModel.cs modified (8 deletions somewhere in the diff), plus PlayoutControllerTests additions. - The #108 session pattern that worked: recon brief (haiku) → sonnet implement+test → inline regen → fable adversarial review → fix minors → PR. Reuse it. In #108 the WIP's structure was sound but contained one real runtime bug and missing endpoint attributes — expect similar.
- Work in a worktree: git worktree add .worktrees/feat-playouts feat/playouts-read-api. NOTE: .worktrees/issue-97-channel-state-api still exists for open PR #112 — leave it alone. A Codex worktree for feat/103-media-sources-api may also exist — leave it alone.
Plan
- RECON [Explore agent or haiku]: fetch all 4 issue bodies; diff origin/main...origin/feat/playouts-read-api; read the existing playout query handlers in ErsatzTV.Application/Playouts and how Blazor pages (Playouts.razor etc.) load playout/detail data; structured brief with per-file WIP verdicts + precedent refs.
- IMPLEMENT/FIX [Agent, model: sonnet]: rebase onto origin/main (v1.json conflict — any resolution, regen wins later), then fix/complete per recon. Handler tests (InMemoryTvContext where DB-backed) + controller tests; OpenApiErrorResponseContractTests entries for any endpoint that can 404/422. One build slot.
- VERIFY [inline]: dotnet build ErsatzTV.sln; TZ=UTC dotnet test ErsatzTV.Tests/ErsatzTV.Tests.csproj; ./scripts/update-openapi.sh; commit regen.
- FINAL REVIEW [fable subagent, high effort, read-only]: diff origin/main...HEAD. Focus: correctness vs Blazor's own playout queries, N+1s (playout items can be large — check paging/projection), error contract, OpenAPI consistency, test meaningfulness. Fix blockers/majors (+ quick minors), re-verify, push (force-push fine post-rebase).
- PR against main [inline]: title "feat(api): playout read endpoints (#100 #101 #107 #110)",
body listing endpoints + review verdict, "fixes #100, fixes #101, fixes #107, fixes #110",
footer "🤖 Generated with Claude Code". Wait for CI green —
poll .../actions/runs?limit=10 by head_sha (zsh: don't name a variable
status). Merge when green (ask for one-word "merge" consent if the classifier blocks); verify main's post-merge run also green. COORDINATION: if the Codex PR (prompt B) merged first, rebase + regen before merging. - Comment on all 4 issues as you work + closing comments per CLAUDE.md Task Completion Protocol.
On completion — REQUIRED last step
Update docs/handoffs/chicorytv-issue-queue.md in place: pop the finished issues from the queue, rewrite the prompt section(s) for the next issue(s) (same structure), and record anything the next session must know (PR numbers, branch state, whether the Codex session finished). Print the new prompt(s) in fenced code blocks.
PROMPT B (Codex session, parallel) — Finish issues #103+#106 — media sources + scan status API
Self-contained task for Codex (single strong agent, no sub-delegation). A Claude session may be working feat/playouts-read-api in parallel on this machine — do NOT touch that branch/worktree or Playout/Channel controller files, and run only ONE dotnet build/test at a time yourself (machine-wide limit is 2–3 concurrent builds; a 7–9-way fan-out once crashed this machine).
Context
- Repo /Users/timothy/ersatztv — ErsatzTV fork (.NET 10, CQRS/MediatR, LanguageExt, EF Core), gaining a REST API for the ChicoryTV React SPA. Gitea: http://192.168.1.95:3000/timothy/ersatztv (basic auth timothy:ded89Lm4). Read CLAUDE.md, docs/contributing.md, docs/rest-api.md first.
- Task: ONE branch feat/103-media-sources-api covers TWO issues — #103 (media sources API) and #106 (library scan status) — unblocking #88 (Libraries screen). Read both issue bodies + comments via the Gitea API (GET /api/v1/repos/timothy/ersatztv/issues/{103,106} and .../comments).
- The branch has ONE untrusted WIP commit
46320a17(~219 insertions, 11 files: new MediaSourcesController + LibrariesController additions, IScannerProxyService/ScannerProxyService touches, Application handlers, tests) — never built or tested. Review it against the issues + conventions; keep what's good, rewrite what isn't. - Work in a worktree: git worktree add .worktrees/feat-103 feat/103-media-sources-api. Leave .worktrees/issue-97-channel-state-api and any feat-playouts worktree alone.
Conventions (established by #97/#105/#108 — copy precedents, don't invent)
- Tests: NUnit + Shouldly + NSubstitute in ErsatzTV.Tests (NOT xUnit). Error contract: ProblemDetails; 404 title "Resource not found", 422 "Validation failed".
- Parameterless 200-only GETs: typed return Task<List>,
[HttpGet("/api/...", Name = "...")],[Tags],[EndpointSummary],[EndpointGroupName("general")]— precedent ErsatzTV/Controllers/Api/FillerPresetController.cs, HealthController.cs. Id-taking GETs that can 404 use the ApiResults/ProblemDetails helpers AND need an entry in the explicit [TestCase] allowlist in ErsatzTV.Tests OpenApiErrorResponseContractTests. - DTO records in ErsatzTV.Core/Api/... — the project has Nullable=disable; add file-scoped
#nullable enablewhen a property is genuinely nullable (precedent: ErsatzTV.Core/Api/Health/HealthCheckResponseModel.cs). - Handler tests use ErsatzTV.Tests/Support/InMemoryTvContext for DB-backed queries; controller tests assert route template + Name + mediator pass-through (see ErsatzTV.Tests/Controllers/HealthControllerTests.cs).
- LanguageExt trap (bit the #108 WIP):
Option<T>.Matchthrows ResultIsNullException if a branch returns null — useMatchUnsafefor intentionally-nullable results. Audit the WIP.
Steps
- git fetch origin; create the worktree;
git rebase origin/main(v1.json WILL conflict with the #113/#114 regens on main — any resolution is fine, the file is regenerated in step 4). - Review the WIP diff vs origin/main against the two issue bodies and the conventions above; fix/complete handlers, DTOs, thin controllers, tests. Match how the Blazor UI loads media sources/libraries/scan progress (ErsatzTV/Pages MediaSources*/Libraries* pages and their MediatR queries) — the API must surface the same data the same way.
- dotnet build ErsatzTV.sln && TZ=UTC dotnet test ErsatzTV.Tests/ErsatzTV.Tests.csproj — all green (build may take a few minutes; ~278 tests pre-existing).
- ./scripts/update-openapi.sh; commit the v1.json regen separately.
- Commit(s) with conventional messages referencing the issues; push the branch; open a PR against main titled "feat(api): media sources + scan status endpoints (#103 #106)" with body summary + "fixes #103, fixes #106". Do NOT merge — wait for "Build & test (.NET)" green and report back; a human (or the Claude session) merges. If the Claude playouts PR merged first, rebase onto new main + re-run the regen before reporting done.
- Comment on #103 and #106 with what was found/changed (Gitea API), per the repo's Task Completion Protocol in CLAUDE.md.
Issue queue (work top-down)
All feat/* branches below already exist on origin with a "wip: partial implementation salvaged from interrupted workflow run" commit — UNTRUSTED drafts (never built/tested): review the WIP, keep what's good, rewrite what isn't. Branches were based on the PRE-#113 origin/main. Since #113 and #114 changed wwwroot/openapi/v1.json on main, EVERY remaining feat/* branch will conflict on v1.json — rebase onto origin/main first and let ./scripts/update-openapi.sh regen win.
- #100+#101+#107+#110 playout read API — feat/playouts-read-api ← PROMPT A (Claude; unblocks #87)
- #103+#106 media sources + scan status — feat/103-media-sources-api ← PROMPT B (Codex, parallel; unblocks #88)
- #104 artwork upload — branch feat/104-artwork-upload (unblocks #89)
- #102 JSON guide endpoint — branch feat/102-json-guide (hardest: extract shared projection from RefreshChannelDataHandler; XMLTV golden tests = regression net, do NOT regen goldens; unblocks #85)
- #111 schedule duration estimates — branch feat/111-schedule-durations (unblocks #86 polish)
- MERGE PASS: land the remaining backend PRs into main sequentially (each: rebase, re-run update-openapi.sh, wait for CI green, merge; they all conflict on v1.json). Then rebase the docs/59-ui-redesign-brief stack (merge PR #112 = #97 into it first) onto the new main once, and PR the stack to main (auto-closes #97 and validates closed SPA issues #78–#83/#96/#98 on main).
- #109 Dashboard follow-up (frontend; needs merge pass done). NOTE from #108: GET /api/health re-runs all ~14 checks per request (only the warn/error summary is cached) — the SPA footer/Dashboard must load on demand or poll gently; add a TTL cache first if it needs to poll.
- Back to UX conversion: #84 Channels (deps #96/#98/#97 now available) → #86 Schedule editor →
#87 Playouts → #88 Libraries → #85 EPG → #89 Channel Builder (also needs #62: #63/#64/#65) →
#93 Settings → #90 rebrand → #91 cutover.
Cross-refs: #99 (TS/HLS-Direct sessions) stays backlog; "Definition of Ready" for screen issues
lives in the #59 epic body. Done: #105 (PR #113), #108 (PR #114, merged 2026-07-03 — first
#nullable enableDTO precedent in Core/Api; languages-list endpoint from #105 still unimplemented — open a follow-up when #86/#89 need it; filler/watermark lists return DB order — SPA should client-sort).