Build ErsatzTV Image / Build & test (.NET) (push) Successful in 7m49s
Build ErsatzTV Image / Docs update reminder (push) Has been skipped
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 9m46s
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 11m19s
160 lines
12 KiB
Markdown
160 lines
12 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), #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:**
|
||
1. **#207 (+#212)** — schedules editor full mutation depth (endpoints exist; needs languages
|
||
enumeration endpoint shared with #212 channel-editor pickers) ← NEXT (prompt below)
|
||
2. **#202** — media-source management (write REST API + SPA screens; largest, riskiest)
|
||
3. **#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 (`AddCourier` Startup.cs:394 dies too); the Startup `MapWhen`
|
||
"blazor" branch (736–795) CO-HOSTS MapControllers/OpenAPI/Scalar — surgical removal only
|
||
(MapBlazorHub:775, MapFallbackToPage:776, services 379–398); `wwwroot/lib/*`+`css/site.css`
|
||
are `_Host`-only; check `Locals/*.resx` consumers before deleting; keep `Extensions/`
|
||
except `NavigationManagerExtensions.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_modules` fresh (`npm install` after 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-mcp` and 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_modules` from 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.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) → 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].
|
||
- **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 `uptime` before 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)
|
||
1. **#207 (+#212)** schedules editor depth + channel-editor gaps ← PROMPT above.
|
||
2. **#202** media-source management (write API + SPA) — largest; pre-review the API design
|
||
(adversarial-reviewer #19) before building.
|
||
3. **#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).
|
||
4. **#197** cold API contract+security review — HARD GATE on #58 close/go-live and MANDATORY
|
||
before any remote exposure. Inputs: #215, #217, backlog nits.
|
||
5. 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-test` runs `:latest`) — local ffmpeg
|
||
8.1.2 lacks subtitles/zscale filters.
|