Build ErsatzTV Image / Build & test (.NET) (push) Successful in 7m36s
Build ErsatzTV Image / Docs update reminder (push) Has been skipped
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 10m7s
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 11m28s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
141 lines
9.8 KiB
Markdown
141 lines
9.8 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);
|
||
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, `TroubleshootingNotifier` precedent)
|
||
filled by a MediatR `INotificationHandler<PlaybackTroubleshootingCompletedNotification>`,
|
||
reset at session lock in `PrepareTroubleshootingPlaybackHandler` — the pollable replacement
|
||
for Blazor's in-process Courier completion event. `GET /api/graphics-elements?refresh=true`.
|
||
- SPA: `/app/troubleshooting/playback` (`?channel=`/`?mediaItem=` modes) mirroring
|
||
`PlaybackTroubleshooting.razor`; reusable `web/src/media/HlsPlayer.tsx` (hls.js; `playToken`
|
||
prop 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:
|
||
`PlaybackTroubleshootingScreen` default-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-mcp`
|
||
and 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_modules` from the main
|
||
checkout into new web worktrees instead of `npm 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.sh` then `npm 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.Apply` collapses 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).**
|
||
1. 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).
|
||
2. 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.
|
||
3. 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.
|
||
4. Live-E2E the channel editor with 2 profiles (the repro case) + playback screen preselect.
|
||
5. 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)
|
||
1. **#198** casing drift + **#193** rerun existence-check ← PROMPT above.
|
||
2. **#91 phase (b)** Blazor removal — HARD GATE: cold adversarial plan review first (brief in
|
||
the #91 gate comment; run `/review` from `~/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).
|
||
3. **#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.
|
||
4. Backlog: #99 (TS/HLS-Direct session tracking — makes channel-state `onAir` universal),
|
||
#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-test` runs `:latest`) — local ffmpeg
|
||
8.1.2 lacks subtitles/zscale filters.
|