Files
ersatztv/docs/handoffs/chicorytv-issue-queue.md
T
timothyandClaude Fable 5 39d76080de
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 4m15s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 4m56s
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 3m21s
docs: advance ChicoryTV issue queue past #109 (PR #123); next prompt = #84 Channels screen (Codex)
Records: main=4ffd777b, review-fix cycle lesson (fixture values must come from
backend serializers), health no-poll policy, baselines 368/493 + 31 web tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 21:23:24 +02:00

10 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 79-way parallel workflow build once exhausted RAM, so builds are limited to 23 concurrent, never wide fan-outs. Backend gaps all landed by 2026-07-04 (#105/#108/#100+#101+#107+#110/#103+#106/#104/#111/#102 → PRs #113#119). The MERGE PASS (2026-07-04) then landed the whole SPA stack on main via PR #120.

Session state (2026-07-04, post-#109): main = 4ffd777b (PR #123 "Dashboard real data sources"): the Dashboard is the first SPA screen running fully on live API data. On main: design-system/ + web/ foundation (#78#83), backend #96/#98/#97, Dockerfile hotfix #122, direct-stream session tracker (#99 seam, PR #121), Dashboard data (#109, PR #123). #109 closed. #99 remains open ONLY for the final /api/channels/state wiring (combine IFFmpegSegmenterService.IsActive with IDirectStreamSessionTracker). Baselines: ErsatzTV.Tests 368, Core.Tests 493 (+1 skip); web tests 31 (App.test.tsx 20); web lint/typecheck/build clean (build output ErsatzTV/wwwroot/app/, gitignored; SPA types web/src/api/generated/v1.d.ts, regen npm run generate:api).

  • #109 review cycle (pattern that worked): Codex implemented → Fable review caught 1 substantial bug (health widget matched status strings the API never emits — error/failed/unhealthy+warning/degraded vs the REAL pass|fail|warn|info from Health/Mapper.cs; fictional test fixtures masked it) + 4 nits → all fixed in-session by a sonnet subagent (e6197d63) → CI green → merged. LESSON: frontend tests must use fixture values copied from the backend serializer, not invented ones; reviewers should diff fixtures against the producing C# code.
  • Dashboard health policy (binding for future screens): /api/health is fetched on mount + guarded manual refresh ONLY (no intervals) — it re-runs ~14 checks per request. A backend TTL cache is the unfiled prerequisite for any live health display.
  • Worktrees: remove .worktrees/issue-99-session-tracking and .worktrees/issue-109-dashboard (both merged). The main checkout still sits on docs/59-ui-redesign-brief — fully merged, safe to switch to main.

Lessons for all remaining prompts (accumulated):

  • Frontend fixtures/contracts: copy enum-ish string values from the producing backend code (serializers/mappers), never invent them; review = diff fixtures vs C#. (#109's bug.)
  • The image-build job runs ONLY on main pushes — PR CI cannot catch Dockerfile breakage. Any Dockerfile change: verify base tags exist (hub.docker.com API) and, where docker is available, docker build --target <stage> locally before merging. (node:22-noble cost a red main run; node official images have no -noble variant.)
  • DTO records in ErsatzTV.Core/Api MUST get file-scoped #nullable enable — else the spec emits ["null","string"] unions and SPA types get needless | null. NOTE: ErsatzTV.Application has NO nullable context — string? there trips CS8632.
  • NSubstitute + ConfigElementKey: ConfigElementKey.X is a fresh instance per access — stub with Arg.Any<ConfigElementKey>() disambiguated by the generic <T>.
  • Option<T>.ToNullable() doesn't exist here; use MatchUnsafe(v => (T?)v, () => null).
  • ./scripts/update-openapi.sh only runs -t:GenerateOpenApiDocuments — do a normal dotnet build ErsatzTV/ErsatzTV.csproj FIRST or it fails with MSB3680.
  • Id-taking child-collection GETs 404 on unknown parent via pre-check + ApiResults (precedent: ScheduleController.GetItems) + an OpenApiErrorResponseContractTests entry.
  • Backlog nits (unfiled): unclamped pageSize on paged endpoints; PlayoutController Create/Delete lack route Name=; PlayoutController.GetItems existence pre-check is heavy;

    30 MB uploads return bare Kestrel 413; artwork content-type trusted from client (#66); schedule duration estimator: expression/zero Count → null estimate, materializes each referenced collection per GET; /api/health TTL cache (prereq for live health UI). From #102 (also on #85): GET /api/guide runs the full 21-include eager-load per channel per request — trim projection or add caching before the EPG grid polls it; fillerKind is almost always None (projector merges filler into adjacent programmes) — discrete filler entries need a JSON-only projection mode.


PROMPT FOR CODEX — #84: Channels screen (management table + live treatment)

You are Codex working solo in /Users/timothy/ersatztv (ErsatzTV fork; the React SPA "ChicoryTV" lives in web/ — Vite + TS, typed client in web/src/api/, generated types web/src/api/generated/v1.d.ts). Read CLAUDE.md and docs/contributing.md first; follow the SPA foundation's existing patterns (#78#83) and the Dashboard's data-layer idioms from PR #123 (web/src/api/dashboard.ts hooks with the active unmount guard) — match, don't invent.

HARD CONSTRAINTS:

  • Work in a NEW git worktree: git worktree add .worktrees/issue-84-channels -b feat/84-channels-screen origin/main (branch from origin/main; never touch the main checkout or other .worktrees/*).
  • Max 23 concurrent builds machine-wide; keep to ONE dotnet build at a time here.
  • NEVER set ETV_UPDATE_GOLDENS. A golden-file diff means your code is wrong.
  • Do NOT merge anything. Open the PR, get CI green, and stop.
  • Backend scope guard: FRONTEND issue. All needed endpoints exist (#96/#98/#97 below). If something is missing, note it in the PR and stub cleanly — do not add endpoints.
  • CONTRACT RULE (#109 lesson): any string value you branch on or use in test fixtures must be copied from the producing backend code (mapper/serializer) or the generated v1.d.ts — never invented.

Context

  • main = 4ffd777b (post-#123). Baselines: ErsatzTV.Tests 368, Core.Tests 493(+1 skip); web tests 31, lint/typecheck/build clean. Gitea: http://192.168.1.95:3000/timothy/ersatztv (basic auth timothy:ded89Lm4).
  • Issue #84: the Channels screen — read its body AND the #59 epic's "Definition of Ready" for screen issues. Visual reference: design-system/templates/chicorytv-admin/Channels.jsx (static prototype) + the design brief; reuse web/src/components/* primitives.
  • Available data (all on main):
    • GET /api/channels — enriched list (#96): group, enabled, EPG visibility, sort number…
    • Bulk ops (#98): POST /api/channels/bulk/renumber | /bulk/group | /bulk/delete (204 on success, 404/422 with ProblemDetails — see OpenApiErrorResponseContractTests).
    • GET /api/channels/state (#97): onAir + nowPlaying (nullable!) for live badges/rows. onAir is still segmenter-session-based until the final #99 wiring — render as-is.
    • Channel CRUD: POST/PUT/DELETE /api/channels{,/id} for create/edit/delete flows if the issue's scope includes them (check the issue body; the full Channel Builder is #89 — do NOT build it here).
  • Polling: channel state may poll gently (≥30s) if the design calls for live updates; /api/health is NOT this screen's concern.

Process

  1. Comment on issue #84 with findings + approach (table/widget → endpoint mapping, which CRUD flows are in/out of scope) before coding.
  2. Implement with loading/error/empty states per design-system; bulk selection UX per the prototype; prefer refetch-after-mutate over optimistic updates unless the foundation already has an optimistic idiom (it does not, as of #123).
  3. Verify: cd web && npm ci && npm run lint && npm run typecheck && npm test -- --run && npm run build; plus 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): Channels screen (#84), body lists the mapping and scope decisions; closes #84. Poll CI by head SHA until green. Do not merge.
  5. Comment progress on #84 as you go.

On completion — REQUIRED final output

Print a fenced handoff prompt addressed to Claude (Fable) asking it to:

  • Review the PR diff READ-ONLY in one Fable pass (endpoint/contract correctness incl. fixture values vs backend serializers, bulk-op error handling, polling discipline, type safety, design-system adherence, scope).
  • Classify findings: NITS Fable may fix directly on the branch; SUBSTANTIAL issues either go back to Codex as a verbatim prompt or get fixed in-session by subagents — the user decides which at review time.
  • After review: comment the verdict on the PR and #84; on approval + user merge consent, merge, verify main's post-merge run (image job included), then update THIS handoff file (pop #84, next prompt = #86 Schedule editor for Codex, record PR number + main SHA + baselines) and push it to main. Include: PR number, branch, head SHA, files changed, mapping table, test/web results, anything deferred or uncertain.

Issue queue (work top-down)

  1. HOUSEKEEPING (carry into next session): remove .worktrees/issue-99-session-tracking and .worktrees/issue-109-dashboard if still present; #99 stays open for the final /api/channels/state onAir wiring (good small backend slot-filler between screens).
  2. #84 Channels screen ← CODEX PROMPT above (review/merge/doc-update falls to the Fable session that Codex's end-of-run handoff spawns).
  3. UX conversion order: #86 Schedule editor (#111 durations) → #87 Playouts → #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) → #93 Settings → #90 rebrand → #91 cutover. Cross-refs: #99 seam landed (PR #121), final wiring open. Done this pass: MERGE PASS → PR #120 (closed #97; validated #78#83/#96/#98), hotfix PR #122, PR #121 (#99 seam), PR #123 (#109 Dashboard — first live-data screen). 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.