Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
9.8 KiB
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); all backend gap issues (#100–#111), all SPA screens (#84–#89, #93, #109), the rebrand (#90), the cutover root-flip (#91 phase a), full scheduling parity (#144/#162), media/troubleshooting parity (#141/#161/#158/#180), onboarding docs (#185), the collections/API-gap batch (#155/#151/#152-backend/#184), the multi/rerun/playlist SPA editors (#151/#152 PR #194, #153 PR #195), CI speedups (#190 PR #192), and now #145 playback troubleshooting (PR #199) are MERGED. v26.5.0 in prod; v26.6.0 tagged (awaiting Komodo pin bump in server-management).
#145 is CLOSED — every SPA-parity screen exists. Blazor removal (#91 phase b) is now gated
ONLY on the cold adversarial review (hard-gate comment on #91; runs from the external
~/adversarial-reviewer project, zero shared context, brief embedded in the #91 comment).
Session state (2026-07-09, #145 playback troubleshooting session): main = af17dafc
(post PR #199, merge pre-approved by user after CI green; post-merge main run was being
verified at handoff-write time — confirm Build & test + image jobs green before relying on
:latest). Tests on main: ErsatzTV.Tests 927, web 385, Core.Tests 493(+1 skip),
Architecture 5 — all green. Worktree .worktrees/feat-145 removed after merge.
What PR #199 added:
- Backend:
GET /api/troubleshoot/playback/{stream-selectors,subtitles/{id},status};ITroubleshootingPlaybackStatusStore(Core singleton,TroubleshootingNotifierprecedent) filled by a MediatRINotificationHandler<PlaybackTroubleshootingCompletedNotification>, reset at session lock inPrepareTroubleshootingPlaybackHandler— the pollable replacement for Blazor's in-process Courier completion event.GET /api/graphics-elements?refresh=true. - SPA:
/app/troubleshooting/playback(?channel=/?mediaItem=modes) mirroringPlaybackTroubleshooting.razor; reusableweb/src/media/HlsPlayer.tsx(hls.js;playTokenprop forces re-attach — REQUIRED because the m3u8 GET is what starts a session and identical URLs are React no-ops); 2s status polling with a starting→running→settled phase machine (30s start timeout); entry points: channels-table Troubleshoot action + movie detail page. - New issues filed: #198 (OpenAPI spec/runtime property-casing drift, see lessons — M, includes a LIVE ChannelEditScreen bug on main), #197 (cold API contract+security review, runs when MCP PR #76 stabilizes; HARD GATE on #58 closing/go-live). #91 got the phase-(b) review-gate comment.
Lessons for all remaining prompts (conventions live in docs/; this is workflow lore):
- READ docs/README.md → the convention docs FIRST; point recon/implementer agents at specific doc sections. Only recon the task-specific delta.
- #198 / spec-typed mocks blind spot: runtime JSON is Newtonsoft-cased
(
FFmpegProfileId→ffmpegProfileId) but the OpenAPI generator uses STJ naming (fFmpegProfileId) — generated TS types are WRONG for leading-acronym response fields, and unit-test mocks typed from them stay green while the real screen breaks. Until #198 lands: read such fields via a typed escape hatch pinned to the runtime key (precedent:PlaybackTroubleshootingScreendefault-profile derivation), and make test mocks use the runtime casing. Only live E2E catches this class — one more reason the per-PR live-E2E gate is non-negotiable. - Playwright-MCP E2E: never open tabs/window.open for file-download endpoints — a download
never renders a page and wedges the browser session hard enough to kill the agent (stalled a
sonnet E2E agent 3× this session; had to pkill the mcp-chrome profile). Verify downloads with
plain HTTP (curl) instead. If browser tools stall repeatedly,
pkill -f ms-playwright-mcpand drive a fresh session. - The user's main checkout (/Users/timothy/ersatztv) is now ON main and pulled each session —
keep it that way (stale-checkout confusion resolved 2026-07-09; stale branches pruned).
Still do feature work in worktrees off origin/main; copy
web/node_modulesfrom the main checkout into new web worktrees instead ofnpm ci. - Subagent connection drops = laptop sleep; re-resume via SendMessage (work survives).
- Build/verify locally, then trust it; CI (VM 127) is the slow confirming step (~9 min warm).
Watch by commit status:
/api/v1/repos/timothy/ersatztv/commits/{sha}/status. A red MAIN push is most likely a CANCELLED job from VM churn — check conclusion before assuming regression. - PR routine (works): worktree off origin/main → implement (opus judgment-heavy / sonnet
mechanical; doc pointers + exact facts; csharp-lsp for C#) → API changes: build app project
FIRST then
./scripts/update-openapi.shthennpm run generate:api→ full local test pass → push, PR → fork adversarial review (SCOPED "review only") + live-E2E for UI changes (scripts/e2e-local.sh) → review fixes as follow-up COMMITS (never amend/force-push) → CI green → merge (consent per session; 2026-07-09 session had pre-approval for #199 only) → structured close comments per CLAUDE.md protocol. - Never run TWO committing agents on one worktree concurrently. Read-only review forks are fine.
- Reusable API facts: DTO records in ErsatzTV.Core/Api need file-scoped
#nullable enable; ErsatzTV.Application has NO nullable context (CS8632);Option<T>→nullable =MatchUnsafe(v => (T?)v, () => null);LanguageExtensions.Applycollapses NotFoundError→422, so real 404s need a controller pre-check; id-taking child GETs get an OpenApiErrorResponseContractTests [TestCase].
PROMPT — #198 OpenAPI casing drift (live ChannelEditScreen bug) + #193 warm-up
You are Fable, the ORCHESTRATOR in the main Claude Code session. Fable is EXPENSIVE: delegate (recon → Explore/haiku; mechanical → sonnet; judgment-heavy → opus; fable forks for review). FIRST read CLAUDE.md, docs/README.md + the convention docs it indexes, and the Lessons above.
HARD CONSTRAINTS:
- Work in worktrees off origin/main. The main checkout should stay on main/pulled — safe for reading, but do feature work in a worktree. Copy web/node_modules from the main checkout.
- Up to 3 concurrent builds; never 5+. NEVER set ETV_UPDATE_GOLDENS.
- Merge consent in-conversation per session (prior pre-approvals do NOT carry over).
- Live-E2E via scripts/e2e-local.sh; NEVER exercise download endpoints via browser tabs (curl them). Adversarial review fork per PR diff, SCOPED "review only".
Task 1 — #198 OpenAPI spec/runtime casing drift [M]
Read issue #198 first — it has the full analysis. Summary: MVC responses serialize via
Newtonsoft (leading acronyms fully lowercased: ffmpegProfileId) but the OpenAPI generator
uses STJ naming (fFmpegProfileId), so generated TS types lie about response keys.
ChannelEditScreen.tsx:122 reads the phantom key → with >1 ffmpeg profile, editing a channel
shows/saves the WRONG profile (repro'd live 2026-07-09).
- Root fix: make spec generation match runtime naming (schema transformer or serializer alignment — investigate what ErsatzTV/Startup.cs + the OpenAPI pipeline allow). Regen v1.json/endpoint-index/web types (expect broad-but-mechanical v1.json diff).
- Fix all SPA readers of drifted fields (grep web/src for
fFmpeg/fFprobe/rFrameRate/zIndex; at minimum ChannelEditScreen + remove the #145 escape hatch in PlaybackTroubleshootingScreen) and their test mocks; verify each candidate field against a live scratch instance (curl) — the single-leading-cap ones (zIndex,rFrameRate) may not actually drift. - Add the CI guard from the issue: a test that round-trips a known DTO through the real MVC serializer and asserts the OpenAPI schema uses those exact keys.
- Live-E2E the channel editor with 2 profiles (the repro case) + playback screen preselect.
- Full PR routine; close #198.
Task 2 (warm-up/interleave) — #193 rerun-collection existence check [S, backend]
Per issue: existence pre-check for selectedId per selection type in RerunCollection
create/update → 404/422 ProblemDetails instead of dangling FK/500. api-conventions §3
precedent. Can ride as its own small PR.
On completion — REQUIRED last step
Update THIS handoff (pop #198/#193, promote the #91 phase-(b) queue item), commit to main, print the next prompt in a fenced code block.
Issue queue (work top-down)
- #198 casing drift + #193 rerun existence-check ← PROMPT above.
- #91 phase (b) Blazor removal — HARD GATE: cold adversarial plan review first (brief in
the #91 gate comment; run
/reviewfrom~/adversarial-reviewer, zero shared context, findings via/dispatch, MUST-FIXes resolved before implementation). Then: redirect the Section-2 routes in docs/blazor-route-parity.md, delete Blazor pages/MudBlazor deps (also kills the 3 deprecated Renovate-dashboard packages incl. MediatR.Courier), update docs. Likely its own session (or two: review, then removal). - #197 cold API contract+security review — runs when MCP PR #76 stabilizes; HARD GATE on #58 close/go-live and MANDATORY before any remote exposure.
- Backlog: #99 (TS/HLS-Direct session tracking — makes channel-state
onAiruniversal), #66 (artwork magic-byte sniffing), backlog nits (unclamped pageSize, 30 MB bare 413, PlayoutController route Name=/lightweight exists-check, guide 21-include eager-load + fillerKind notes — see #85/#102 issue comments). Cross-refs: v26.6.0 deploy = Komodo pin bump (server-management). Real-transcode E2E of the playback screen: do once on the test container (ersatztv-testruns:latest) — local ffmpeg 8.1.2 lacks subtitles/zscale filters.