12 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), #198 OpenAPI casing (PR #201) + #193 rerun existence-check (PR #200), the first #91-gate batch #210/#211/#213-partial (PR #214), and the search/media mutation batch #208/#209 (PR #216, shared Add-to layer) are MERGED. v26.5.0 in prod; v26.6.0 tagged (awaiting Komodo pin bump in server-management).
Session state (2026-07-10, #208/#209 session): #208 + #209 CLOSED via PR #216 (merged
c63cadb8). New API surface: POST /api/playlists/{id}/items (wraps existing AddItemsToPlaylist;
handler hardened to reject system playlists — latent Blazor-path gap) and GET /api/search/all-items (wraps QuerySearchIndexAllItems; Add-All = materialize-then-add, Blazor
two-step parity — no query-based add command exists). SPA: shared Add-to layer
web/src/media/addTo/ (Collection dialog w/ inline create, Playlist dialog filtering isSystem,
Schedule dialog replicating AddProgramScheduleItem.ForMediaItem defaults —
addTo/scheduleItem.ts, SaveAsSmartCollectionDialog, AddToMenu popover) + MediaPosterCard
actions slot; wired into search (card drill-in, per-card add, Select toggle + selection bar,
Add All, Save As Smart Collection) and browse/detail (multi-select + select-all-on-page, all four
detail screens, per-show Quick/Deep scan gated Plex/Jellyfin/Emby, per-episode Media Info +
Troubleshoot, seasons browse kind). Add-to-schedule is gated to shows/seasons/artists — the
server validator (ProgramScheduleItemCommandBase.CollectionTypeMustBeValid) only accepts those
per-media-item kinds, matching Blazor's ForMediaItem call sites (found by live E2E: movie adds
422'd). Also fixed: portal font bug (nothing set a body font; portaled dialogs fell back to
Times — body rule added), a mount-debounce race (the 300ms query-debounce commit wiped
in-progress selections; lastQueryRef no-change guard in Search+Browse screens), and the
e2e-local.sh stale-asset gotcha (cp -R into an existing dir nests + serves the previous run's
assets — script now rm-first). New issues: #217 (add-items handlers validate only 4/10 kinds
— pre-existing), #218 (fresh-DB "Playouts 3" badge + "1 failing" chip). Retro/pre-review
issues filed in adversarial-reviewer: #18 (this milestone + #214), #19 (upcoming #207/#212, #202,
#91b, #197). #213 remains OPEN (reduced read-only-convenience scope).
Blazor removal (#91 phase b) remaining MUST-FIX gates, in order:
- #207 (+#212) — schedules editor full mutation depth (endpoints exist; needs languages enumeration endpoint shared with #212 channel-editor pickers) ← NEXT (prompt below)
- #202 — media-source management (write REST API + SPA screens; largest, riskiest)
- #91 phase (b) removal PR — pattern redirects + catch-all (#204 plan), blazor-final tag
(#205), auth posture note (#206), delete Pages/Shared/ViewModels/Validators + 6 packages
(MudBlazor, MediatR.Courier.DependencyInjection, Blazored.FluentValidation, BlazorSortable,
Heron.MudCalendar, Chronic.Core). Removal recon is DONE (2026-07-09 gate session): Courier
consumers are 100% Blazor (
AddCourierStartup.cs:394 dies too); the StartupMapWhen"blazor" branch (736–795) CO-HOSTS MapControllers/OpenAPI/Scalar — surgical removal only (MapBlazorHub:775, MapFallbackToPage:776, services 379–398);wwwroot/lib/*+css/site.cssare_Host-only; checkLocals/*.resxconsumers before deleting; keepExtensions/exceptNavigationManagerExtensions.cs.
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.
- blazor-route-parity.md now carries mutation-depth verdicts (2026-07-09 sweep table at the top of Section 2). A row is only trustworthy if its cluster verdict is PARITY-OK; the sweep evidence lives in the issues #207–#213. Keep the verdict table updated as gates close.
- "Screen exists" ≠ parity: the root cause of the false SPA-READY rows was same-session verification that never diffed capabilities against the Blazor page. Per-capability diff vs the Blazor page is the standard for any future parity claim.
- Several gaps are UNWIRED EXISTING endpoints (playout delete/reset, block copy, scan-show, collections add-item kinds) — check the endpoint index before building new API surface.
- 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. - Playwright-MCP E2E: never open tabs/window.open for file-download endpoints — curl them.
If browser tools stall repeatedly,
pkill -f ms-playwright-mcpand drive a fresh session. - The user's main checkout (/Users/timothy/ersatztv) stays ON main and pulled each session.
Feature work in worktrees off origin/main; copy
web/node_modulesfrom the main checkout. - Subagent connection drops = laptop sleep/transient; re-resume via SendMessage (work survives).
- Build/verify locally, then trust it; CI (VM 127) confirms (~9 min warm). Watch by commit
status:
/api/v1/repos/timothy/ersatztv/commits/{sha}/status. - 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) → 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]. - Arm a CI monitor the moment the PR opens — in the #216 session three PR runs sat red for ~an hour (a CI-only debounce-timing flake) while review/E2E ran, because CI was only checked "at the end". Also: concurrent runs can collide on the MySQL service host port 3306 ("port is already allocated") — that's infra flake, rerun; consider unpinning the host port.
- "Make X consistent with Y" review findings deserve new-code scrutiny — a #216 review fix propagated MediaBrowseScreen's mount-debounce selection-wipe race to SearchScreen instead of catching it. For any timer/effect, ask "when does this fire?" (incl. mount).
- SPA mutation affordances on media items: USE the shared
web/src/media/addTo/layer (spa-conventions §5c) — don't build screen-local pickers. Schedule adds are valid only for shows/seasons/artists (server validator). - Live E2E seeding: the local library isn't API-seedable; the #216 E2E agent generated tiny ffmpeg testsrc MKVs + inserted LibraryPath rows via SQL then scanned. Recipe not yet in docs/e2e-local.md — worth adding next time it's needed.
PROMPT — #207 + #212: schedules editor mutation depth + channel-editor gaps
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. Copy web/node_modules from the main checkout.
- PARALLELIZE BY DEFAULT: split the task into disjoint slices up front and run 3–4 implementer
agents concurrently (recon agents are free — always fan those out). 3–4 concurrent
dotnet/npm builds are fine on this Mac (M4, 10 cores, 16 GB); check
uptimebefore a big fan-out — if 1-min load > ~6, drop to 2–3. Never 5+ builds (the historic crash was an 8–9-way fan-out). NEVER set ETV_UPDATE_GOLDENS. - Never two committing agents on ONE worktree — give each parallel slice its own worktree branched off the feature branch and merge back. Sequence only where a slice genuinely depends on another's output (backend-first narrow, SPA-wide after worked well for #216).
- Merge consent in-conversation per session (prior pre-approvals do NOT carry over).
- Arm a CI monitor on the PR head sha AT PR-OPEN (commit-status endpoint), not at the end.
- 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 — close the #207 + #212 gates
Read issues #207 and #212 (bodies carry the #203-sweep evidence) and adversarial-reviewer #19
(pre-review asks). #207 is the screen the false-parity review made an example of: the SPA
schedules editor is a near read-only viewer — schedule create/edit/delete missing, every
per-item inspector control hard-coded disabled (~35 Blazor fields), add-item is defaults-only —
while the REST endpoints already exist and are unused (PUT /api/schedules/{id}/items replace,
POST .../items single-add with the FULL ScheduleItemRequest — see addTo/scheduleItem.ts for
a working payload builder). Enumerate EVERY Blazor ScheduleItemsEditor.razor field and wire it
(per-capability diff is the parity standard). #212 channel editor gaps: external logo URL, bare
create, and pickers — needs a languages enumeration endpoint shared by both editors (preferred
audio/subtitle language pickers); design that endpoint once. Record conventions in
decisions.md; full PR routine (OpenAPI regen for any new endpoint; blazor-route-parity verdict
rows for the schedules + channel-editor clusters). Comment + close #207/#212 per protocol.
On completion — REQUIRED last step
Update THIS handoff (pop the done items, promote #202 to next with a fresh prompt), commit to main, print the next prompt in a fenced code block.
Issue queue (work top-down)
- #207 (+#212) schedules editor depth + channel-editor gaps ← PROMPT above.
- #202 media-source management (write API + SPA) — largest; pre-review the API design (adversarial-reviewer #19) before building.
- #91 phase (b) Blazor removal PR (gate cleared once 1–2 close; #204/#205/#206 fold in; removal recon in session state of the 2026-07-09 entry; adversarial pass mandatory).
- #197 cold API contract+security review — HARD GATE on #58 close/go-live and MANDATORY before any remote exposure. Inputs: #215, #217, backlog nits.
- Backlog: #215 (API playout mutations skip EntityLocker gating), #217 (add-items validates
only 4/10 kinds), #218 (fresh-DB Playouts badge / "1 failing" chip), #213 remainder (logs
sort/page-size persistence, block-history page-size/gating, list filters), #99 (TS/HLS-Direct
session tracking), #66 (artwork magic-byte sniffing), CI: unpin MySQL service host port 3306
(concurrent-run collision), nits (unclamped pageSize, 30 MB bare 413, PlayoutController route
Name=/lightweight exists-check, guide 21-include eager-load + fillerKind notes — #85/#102
comments), review nits from #216 (last-used-collection memory, detailBrowseItem cast).
Cross-refs: v26.6.0 deploy = Komodo pin bump (server-management). Real-transcode E2E of the
playback screen: once on the test container (
ersatztv-testruns:latest) — local ffmpeg 8.1.2 lacks subtitles/zscale filters.