Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
9.7 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), #145 playback troubleshooting (PR #199), and now #198 OpenAPI casing drift (PR #201) + #193 rerun existence-check (PR #200) are MERGED. v26.5.0 in prod; v26.6.0 tagged (awaiting Komodo pin bump in server-management).
#145, #198, and #193 are CLOSED — every SPA-parity screen exists and the casing-drift bug
class is structurally closed. Blazor removal (#91 phase b) is now THE next item, 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 evening, #198+#193 session): main = 2b918371 (merge of PR #201
(#198 casing) on top of PR #200 (#193 rerun existence); post-merge main run was pending at
handoff-write time — confirm Build & test + image jobs green before relying on :latest).
Tests on main: ErsatzTV.Tests 939 (927 + 7 rerun-handler + 5 OpenAPI-contract), web
385, Core.Tests 493(+1 skip), Architecture 5. Both worktrees removed after merge.
What PR #201 (#198) added:
NewtonsoftSchemaNamingTransformer(ErsatzTV/Serialization/, registered on all 3 OpenAPI docs in Startup.cs): renames every schema property/Required key through the RUNTIMECustomContractResolver— spec mirrors runtime by construction (naming-strategy special cases AND[JsonProperty]overrides); collisions throw. Regenerated v1.json/v1.d.ts.- Real drift was only
ffmpegProfileId(CustomNamingStrategy special case) +ffmpegProfile([JsonProperty]on ChannelResponseModel — found BY the transformer; fixed a second latent live bug: channel-list FFmpeg column always "Unassigned").fFmpegPath/fFprobePath/zIndex/rFrameRateverified live as never drifted. - SPA readers/mocks fixed (ChannelEditScreen, ChannelBuilder, App.tsx); #145 escape hatch in
PlaybackTroubleshootingScreen removed. CI guard:
OpenApiSerializerContractTests(5 fully-populated DTOs through the exact runtime Newtonsoft settings vs committed v1.json). - Live E2E verified: editor shows profile 2 with 2 profiles, save keeps ffmpegProfileId=2, troubleshooting preselect, channel-list profile names. Docs: api-conventions §5a, decisions.md, spa-conventions §4.
What PR #200 (#193) added: RerunCollectionSelectionValidation — per-CollectionType,
subtype-accurate, non-tracking AnyAsync(EF.Property<int>(e,"Id")==id) existence check in
create/update Validate → 422 (deliberately not 404). First RerunCollectionHandlerTests
(7). Bonus: removed the broken Playlist option from Blazor RerunCollectionEditor (always
persisted an all-null-FK row).
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 RESOLVED — casing drift class is closed: the OpenAPI spec now mirrors the runtime
Newtonsoft serializer by construction (
NewtonsoftSchemaNamingTransformer+OpenApiSerializerContractTestsguard). Generated TS types are trustworthy for response keys again; no more escape hatches needed. The surviving lesson: spec-typed unit-test mocks can't catch runtime-shape drift — the per-PR live-E2E gate stays non-negotiable. - Keep the MAIN checkout's
web/node_modulesfresh (npm installafter pulling a PR that adds a dep) — worktrees copy it, and a stale copy broke typecheck in a #198 worktree (hls.js from #145 was missing; fixed 2026-07-09). - 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 — #91 phase (b): Blazor removal, part 1 = COLD ADVERSARIAL PLAN REVIEW
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 stays 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 — run the #91 phase-(b) hard gate, then (if cleared) start the removal
- Read issue #91 and its phase-(b) gate comment (it embeds the review brief), plus docs/blazor-route-parity.md (Section 2 = SPA-ready-but-not-redirected routes) and docs/decisions.md.
- COLD REVIEW (the gate): the review must run from the external
~/adversarial-reviewerproject with ZERO shared context — a fresh session there,/reviewwith the brief from the #91 comment. Findings come back via/dispatch. Do NOT run this review from the ersatztv session or with an ersatztv-context fork — cold means cold. If you cannot start the external session yourself, prepare the exact brief + ask the user to run it. - Resolve ALL MUST-FIX findings (file follow-up issues for SHOULDs as appropriate) before any implementation.
- If (and only if) the gate clears in-session: implement removal in a worktree — redirect
the Section-2 routes (LegacyUiRedirects.cs), delete Blazor pages + MudBlazor deps (kills
the 3 deprecated Renovate-dashboard packages incl. MediatR.Courier — but check #145's
ITroubleshootingPlaybackStatusStorenotification wiring which REPLACED the Courier dependency for the SPA path; verify nothing else still consumes Courier), update docs/blazor-route-parity.md + domain-model.md + README index in the SAME PR. Full PR routine (review fork + live-E2E + CI). Expect this to be its own session; splitting review-session / removal-session is fine and pre-approved by the queue plan.
On completion — REQUIRED last step
Update THIS handoff (pop #91 phase (b) or record partial progress, promote #197/backlog), commit to main, print the next prompt in a fenced code block.
Issue queue (work top-down)
- #91 phase (b) Blazor removal ← PROMPT above (cold review gate first).
- #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.