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>
149 lines
10 KiB
Markdown
149 lines
10 KiB
Markdown
# 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 (#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 2–3 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)
|
||
0. 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).
|
||
1. #84 Channels screen ← CODEX PROMPT above (review/merge/doc-update falls to the Fable
|
||
session that Codex's end-of-run handoff spawns).
|
||
2. 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.
|