docs: advance ChicoryTV handoff past the multi/rerun/playlist SPA editors + CI/runner session
Build ErsatzTV Image / Build & test (.NET) (push) Waiting to run
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Waiting to run
Build ErsatzTV Image / Build & push image (amd64) (push) Has been cancelled

#151/#152 (PR #194) + #153 (PR #195) + #190 (PR #192) merged & closed; next = #145
playback troubleshooting (last gate for #91 phase b). Records the VM-127 runner caveat.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-08 22:12:31 +02:00
co-authored by Claude Opus 4.8
parent 81bcfec8ad
commit 3531211984
+110 -132
View File
@@ -4,150 +4,128 @@ Paste the prompt below into a fresh session to work the next item. Each session
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), parity pass 2 (#140/#142/#143/#146/#147), the FULL
scheduling parity #144/#162 (PRs #170#175/#179), the media/troubleshooting parity
#141/#161 + #158 + #180 (PRs #181/#182/#183), onboarding docs (part 1 PR #~183-era, part 2
#185 PR #186), and the collections/API-gap batch #155/#151/#152-backend + #184 quick wins
(PRs #187/#189/#188) are MERGED. **v26.5.0 in prod; v26.6.0 TAGGED 2026-07-08** (first
release carrying the full SPA-parity feature set — awaiting the Komodo pin bump 26.5.0→26.6.0
in server-management). Blazor removal (#91 phase b) is now gated ONLY on: #151/#152 SPA
**editors** (backend APIs done), #153 (playlists CRUD+editor), and #145 (playback
troubleshooting).
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), and now the **multi/rerun/playlist SPA editors** (#151/#152 PR
#194, #153 PR #195) + **CI speedups** (#190 PR #192) are MERGED. **v26.5.0 in prod; v26.6.0
tagged** (awaiting a Komodo pin bump 26.5.0→26.6.0 in server-management to deploy).
**Session state (2026-07-08, collections/API-gap + release session)**: main = 1c035cc9 +
this docs commit. Merged FOUR PRs (all fork-reviewed + CI-green before merge):
- PR #186**#185 CLOSED**: onboarding docs part 2 — `docs/testing.md` (authoritative
testing map; contributing.md §8 shrunk to a pointer) + `scripts/generate-endpoint-index.py`
→ generated `docs/endpoint-index.md`, hooked into `scripts/update-openapi.sh` (guarded on a
successful spec build).
- PR #187**#184 CLOSED**: `ApiControllerSecurityTests` now ASSEMBLY-SCANS the
`ErsatzTV.Controllers.Api` namespace (no hand-maintained registry; `>=20` min-count guard;
intentionally NOT filtered on `ControllerBase` so non-`ControllerBase` controllers like the
exempt `ScannerController` stay in scope). Quick wins in the same PR: Trakt matched-items
link → SPA `/app/search`; `GET /api/search` parallelized (`Task.WhenAll`, each `ForKind`
gets its own `TvContext` via `IDbContextFactory`); dead "New Group" header buttons removed.
- PR #189**#155 CLOSED**: `GET /api/collections/{id}/items` (paged) returns a manual
collection's FULL contents across all kinds — reuses `PagedLibraryBrowseItemsResponseModel`;
the per-kind projection + rooted-artwork logic was extracted into a shared
`ErsatzTV.Application/LibraryBrowse/LibraryBrowseItemMapper.cs` (browse handler unchanged).
SPA `CollectionsScreen` now lists real members (Load-more pager); honest-banner +
`getCollectionItemsPreview` removed. POST-items 500 sub-item confirmed ALREADY fixed
(fb3f2856 `ValidateMediaItems` → 422); not re-added.
- PR #188**#151/#152 STILL OPEN** (backend only): `MultiCollectionController` +
`RerunCollectionController` CRUD over existing handlers + thin `web/src/api/{multiCollections,
rerunCollections}.ts`. Rerun request DTO takes `CollectionType`+`SelectedId` and builds a
minimal id-only VM (handlers read only `.Id`/`.MediaItemId`); a guard rejects unsupported
`CollectionType` (Playlist/RerunFirstRun/RerunRerun/SearchQuery/Fake*) with 422
(`IsSupportedSelectionType`). **SPA editor screens are the remaining work → keep #151/#152
open.**
- New issues: **#190** (CI speed — cache NuGet packages + Docker/checkout wins, S). Deferred
review nit (NOT yet filed): rerun create/update doesn't existence-check the selected id (a
bad `SelectedId` can dangling-FK-row or 500 on SaveChanges) — file a small hardening issue
when touching #151/#152 SPA work; consistent with api-conventions §3 deep-FK precedent.
- Tests on main: ErsatzTV.Tests ~870, web 330 (40 files), Core.Tests 493(+1 skip),
Architecture 5 — all green. main CI (test+migrations+image) green at 1c035cc9.
- Worktrees to remove (all merged): `.worktrees/feat-185`, `feat-quickwins`,
`feat-collections`, `feat-multirerun`, and `main-handoff` (after this doc commit).
**Blazor removal (#91 phase b) is now gated ONLY on #145 (playback troubleshooting)** — every
API gap (#151/#152/#153/#155) is closed.
**Session state (2026-07-08, multi/rerun/playlist editors + CI/runner session)**: main =
81bcfec8. Merged THREE PRs (all fork-reviewed + live-E2E'd + CI-green before merge):
- PR #192**#190 CLOSED**: CI speedups — NuGet package cache (`actions/cache@v4` on
`~/.nuget/packages`) in the `test`+`migrations` jobs, shallow checkout for `test`
(`fetch-depth 1`), and a Dockerfile openapi-generator jar-layer reorder. Validated on main's
image build.
- PR #194**#151/#152 CLOSED**: SPA multi-collection editor (`/app/multi-collections`) +
rerun-collection editor (`/app/rerun-collections`), on the PR-#188 REST APIs. Mirror
`CollectionsScreen` dialogs + a type→picker→playback-order machinery. Follow-up commit unions
an out-of-set stored playback order into the rerun edit-load Select.
- PR #195**#153 CLOSED**: playlist CRUD REST API (9 endpoints + `RenamePlaylistGroup`
command + DTOs) **and** SPA editor (`/app/playlists`: group tree + item editor with
reorder/copy + playout preview). Review hardening: HIGH — `PUT /api/playlists/{id}` now guards
`IsSystem`→422 (a system playlist could be renamed/wiped via API); MEDIUMs — system-group
rename guard + preview draft validation→422; SPA — `Promise.allSettled` on the tree load.
- New issues filed: **#193** (rerun create/update should existence-check `selectedId` — a bad id
can dangling-FK/500; S, backend-only, still OPEN) and **server-management#570** (relocate the
Gitea act-runner off bumblebee — DONE by user, runner now on **VM 127** on pve4).
- Tests on main: `ErsatzTV.Tests` **913**, web **369** (45 files), `Core.Tests` 493(+1 skip),
Architecture 5 — all green (verified via the PR runs).
**⚠️ CI RUNNER CAVEAT (server-management#570, live)**: the act-runner moved off bumblebee to
**VM 127** — this fixed the prod-contention that was starving Kodi/Jellyfin playback, BUT VM 127
is currently **~4× slower** (test job ~19 min cold vs ~45 min on bumblebee) and
**intermittently fails in a cache-save post-step** (the `actions/cache` tar hangs ~15 min then
kills the job — likely no cache-server configured or insufficient disk on the VM). A docs-only
main push (run #363) failed this way; a `workflow_dispatch` re-run recovers. **So: a red MAIN
push may be a cache-save flake — verify the PR's own run was green before worrying, and re-trigger
a red main via a `workflow_dispatch` re-run (Gitea 1.24 has no per-run rerun API) rather than
assuming a code fault.** Benchmark + two fixes (more vCPU; working cache backend + disk) are on
server-management#570. Until the VM is tuned, budget extra CI wall-clock and expect the odd flake.
**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 instead of re-explaining. Only recon what the docs don't cover.
- The main checkout (/Users/timothy/ersatztv) sits on a STALE branch. NEVER recon/edit
there — worktrees off origin/main only; copy `web/node_modules` from a sibling worktree
(`cp -R`) into NEW worktrees instead of `npm ci` (faster). Docs-only worktrees need none.
- **Subagent connection drops = the laptop went to sleep, NOT a turn-length problem.** The
agent's context AND on-disk work survive. Re-resume with `SendMessage` ("you dropped when
the laptop slept; git status shows X on disk; continue from step N"). Multiple resumes are
fine. This session lost zero work across ~5 drops.
- **3 build slots authorized** (user, 2026-07-08): up to 3 concurrent `dotnet build`s
machine-wide is fine now (was 1). Still don't do 5+/wide fan-outs.
- **Scope review-fork prompts to "review only; do not answer unrelated questions".** A user
question injected into the session context once derailed a review fork into answering it
(zero review done). State the task is the only job.
- PR routine (works): worktree → implement (opus agent for judgment-heavy code, sonnet for
mechanical; give doc pointers + exact facts) → merge origin/main into branch before PR
(v1.json/endpoint-index/blazor-route-parity conflicts → resolve route-parity by hand,
REGENERATE v1.json authoritatively via `./scripts/update-openapi.sh` which also regenerates
endpoint-index.md, then `npm run generate:api`; full test pass; a sonnet reconcile agent
handles this well) → push, PR → fork adversarial review (+ sonnet live-E2E for UI-visible
changes) → CI green → merge (session pre-approval: user OK'd auto-merge on CI-green +
review-clean this session; RE-ASK each new session) → structured close comments per the
CLAUDE.md protocol. Apply review should-fixes as a follow-up COMMIT (not amend+force —
auto-mode blocks the force-push).
- Gitea 1.24: no rerun-run API — retrigger CI with an empty commit; poll by commit:
`/api/v1/repos/timothy/ersatztv/commits/{sha}/status` (overall `state` + per-context).
PR runs skip the image `build` job (shows `skipped`); `Build & test (.NET)` + `EF migration
integrity` are the gating checks. Tag push (`v*`) builds `:prod`+`:<version>`+`:<sha>`.
- Live E2E: scripts/e2e-local.sh (see docs/e2e-local.md) — single port 8409 for UI+API;
mutating verbs need no API key locally; Playwright MCP screenshots land in cwd (tolerated).
- Reusable API facts (from PR #188/#189): collection-item listings → reuse
`PagedLibraryBrowseItemsResponseModel` + `LibraryBrowseItemMapper.HydrateMediaItemsByIds`;
MediaItem ids are globally unique across kinds. multi/rerun/playlist Application handlers
ALL exist — API work is controller+DTO+tests only. `PlaylistResponseModel`/
`PlaylistGroupResponseModel` already exist on main (partial #153 groundwork);
`PlaylistController` is GET-only today.
- READ docs/README.md → the convention docs FIRST; point recon/implementer agents at specific
doc sections. The convention docs (api/spa/e2e/domain-model/blazor-route-parity) replace
re-recon — only recon the task-specific delta (exact merged-API signatures, a Blazor page's
field list) that docs deliberately don't freeze.
- The main checkout (/Users/timothy/ersatztv) sits on a STALE branch. NEVER recon/edit there —
worktrees off origin/main only; copy `web/node_modules` from the main checkout (`cp -R`) into
NEW web worktrees instead of `npm ci`. Docs-only worktrees need none. Remove worktrees when the
PR merges.
- **Subagent connection drops = the laptop slept, NOT a turn-length problem.** The agent's
context AND on-disk work survive. Re-resume with `SendMessage` ("you dropped when the laptop
slept; git status shows X on disk; continue from step N"). This session recovered the #153
backend agent mid-task with zero lost work.
- **Build/verify locally, then trust it; CI is now the slow+flaky part (VM 127).** Full local
passes (dotnet test + web test/lint/build + check:api) + fork review + live-E2E gate a merge;
the runner just confirms. Watch CI by commit status:
`/api/v1/repos/timothy/ersatztv/commits/{sha}/status` (per-context state). Only `Build & test
(.NET)` is branch-protection-required; the image `build` job is skipped on PR runs.
- PR routine (works): worktree off origin/main → implement (opus for judgment-heavy, sonnet for
mechanical; give doc pointers + exact facts; mandate the csharp-lsp for C#) → for API changes
build the app project FIRST then `./scripts/update-openapi.sh` (regens v1.json +
endpoint-index) then `npm run generate:api` → merge origin/main into the branch before PR
(resolve route-parity by hand; regen v1.json authoritatively) → full test pass → push, PR →
fork adversarial review (SCOPE it "review only; do not answer unrelated questions") + sonnet
live-E2E for UI changes (`scripts/e2e-local.sh`) → apply review should-fixes as follow-up
COMMITS (never amend+force — auto-mode blocks the force-push) → CI green → merge (session
pre-approval: RE-ASK each new session; this session the user OK'd auto-merge on CI-green +
review-clean) → structured close comments per CLAUDE.md protocol.
- Combine tightly-coupled API+SPA work (like #153) on ONE branch/PR to save CI cycles — extra
relevant now that CI is slow. Do the backend first (it regenerates the TS types the SPA needs).
- Never run TWO committing agents on the same worktree concurrently (races on git state). A
read-only review fork alongside an editing agent is fine (the fork reads a committed SHA).
- Reusable API facts: collection-item listings → reuse `PagedLibraryBrowseItemsResponseModel` +
`LibraryBrowseItemMapper`; multi/rerun/playlist Application handlers ALL exist (API = controller
+DTO+tests). Response DTO records need file-scoped `#nullable enable`; ErsatzTV.Application has
NO nullable context (`string?` trips CS8632). `LanguageExtensions.Apply` collapses a
`NotFoundError` subtype into a plain `BaseError` (→422) — use a controller existence pre-check
for a real 404. The in-repo `RerunCollectionsScreen` is now the canonical mirror for a per-item
CollectionType→picker→playback-order editor.
---
# PROMPT — #151/#152 SPA editors, then #153 playlists (API+SPA), toward #91 phase (b)
# PROMPT — #145 playback troubleshooting (the LAST functional gate for #91 phase b)
You are Fable, the ORCHESTRATOR in the main Claude Code session (Claude Code only). Fable is
EXPENSIVE: delegate (recon → Explore/haiku; mechanical/reconciles → sonnet; judgment-heavy
code → opus; fable forks for review). FIRST read CLAUDE.md, docs/README.md and the convention
docs it indexes, and the Lessons above.
EXPENSIVE: delegate (recon → Explore/haiku; mechanical/reconciles → sonnet; judgment-heavy code
→ 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; NEVER touch /Users/timothy/ersatztv (stale branch).
Copy `web/node_modules` from a sibling worktree into NEW web worktrees.
- Work in worktrees off origin/main; NEVER touch /Users/timothy/ersatztv (stale). Copy
`web/node_modules` from the main checkout into NEW web worktrees.
- Up to 3 concurrent builds OK; never 5+/wide fan-outs. NEVER set ETV_UPDATE_GOLDENS.
- Subagent drops = laptop sleep; re-resume via SendMessage (work survives).
- Merge consent in-conversation per session (last session's pre-approval does NOT carry over).
CI reruns = empty commit. Apply review should-fixes as a follow-up commit (no force-push).
- Live-E2E new screens via scripts/e2e-local.sh (docs/e2e-local.md).
- Adversarial review fork over each PR diff before merge; SCOPE it to "review only".
- Any PR that changes a convention/route/decision updates the relevant doc in the SAME PR
(esp. docs/blazor-route-parity.md rows).
- **CI is on VM 127 now — slow + occasionally flaky (server-management#570). Verify the PR's own
run was green; a red MAIN push may be a cache-save flake — re-trigger via a `workflow_dispatch`
re-run, don't assume a code fault.** Apply review should-fixes as follow-up commits (no force-push).
- Live-E2E new screens via scripts/e2e-local.sh (docs/e2e-local.md). Adversarial review fork per
PR diff, SCOPED "review only". Update docs/blazor-route-parity.md rows in the same PR.
## Task — #145 playback troubleshooting [L, OWN SESSION]
This is the last gate before #91 phase (b) (Blazor removal). It is LARGE and was deliberately
deferred to its own session. The #91 issue body has the phase-(b) checklist.
1. RECON [Explore, thorough]: map the legacy Blazor playback-troubleshooting UI (`ErsatzTV/Pages/`
— the FFmpeg/playback troubleshooting page(s), ffmpeg-troubleshooting report/download,
session/HLS diagnostics) and what REST API exists vs is missing for it. Playback/session
endpoints may be partial (see #99 TS/HLS-Direct sessions, still backlog). Product a mirror
map: Blazor fields/actions → needed API → SPA screen.
2. Decide the API gap: what troubleshooting/diagnostics endpoints must be added (mirror the
controller+DTO+tests pattern; handlers may already exist) before the SPA screen.
3. Implement backend (if needed) → SPA screen → live-E2E → full PR routine → close #145.
4. Then #91 phase (b): with #145 done, plan/execute the Blazor removal (redirect remaining routes,
delete legacy pages, update blazor-route-parity.md). This may itself be a separate session —
scope it when #145 lands.
5. Consider clearing #193 (rerun existence-check hardening, S backend) as a warm-up/interleave.
6. Update THIS handoff, commit to main, print the next prompt in a fenced block.
## Known facts (2026-07-08 — re-verify cheaply)
- main = 1c035cc9 + the handoff doc commit; all green. v26.6.0 tagged (deploy = user bumps
Komodo pin in server-management).
- #151/#152 backend APIs are LIVE (`/api/multi-collections`, `/api/rerun-collections`, thin
api-clients in web/src/api/). The remaining work is the SPA EDITOR SCREENS. Blazor refs:
`ErsatzTV/Pages/MultiCollectionEditor.razor` (Name + dual add-pickers over collections /
smart-collections + per-item ScheduleAsGroup/PlaybackOrder) and
`ErsatzTV/Pages/RerunCollectionEditor.razor` (CollectionType select → conditional single
autocomplete over collections/multi/smart/shows/seasons/artists + first-run/rerun
PlaybackOrders; note the API rejects Playlist/internal types). Mirror the existing SPA
collections editors (web/src/screens/CollectionsScreen.tsx + smart-collection editor) for
the three-touch-point pattern (ScreenId union / ScreenRoute / mediaNavIds).
- #153 playlists: `PlaylistController` is GET-only; response DTOs (`PlaylistResponseModel`,
`PlaylistGroupResponseModel`) already exist on main. Needs playlist-group + playlist CRUD +
item management (+ probably a preview endpoint via `PreviewPlayout`), then a SPA editor
(Blazor refs `Playlists.razor` group tree + `PlaylistEditor.razor`). [M — API+SPA]
- #145 playback troubleshooting = the last functional gate. L, OWN SESSION, do NOT start late.
- #190 CI NuGet cache = independent S quick win (changes CI itself → its own PR, watch it land).
## Task
1. Verify main green (post-#188) + confirm v26.6.0 image built (tags list / registry).
2. **#151/#152 SPA editors** [M, mirror pair] — build the multi/rerun collection editor
screens on the merged APIs; live-E2E via e2e-local.sh; full PR routine; update
docs/blazor-route-parity.md rows to "SPA DONE"; CLOSE #151/#152 per protocol.
3. **#153 playlists** [M, API+SPA] — CRUD backend (copy CollectionController pattern; DTOs
partly exist) + SPA editor; full routine; close #153.
4. Optionally interleave **#190** (CI NuGet cache) as a standalone S PR early.
5. Do NOT start #145 late — own session.
6. Consider filing the deferred rerun existence-check hardening issue while in #151/#152.
7. Update THIS handoff, commit to main, print the next prompt in a fenced block.
---
## Issue queue (work top-down)
1. #151 + #152 SPA editors — multi/rerun collection editor screens (backend merged) [M, pair]
2. #153 playlists/playlist-groups CRUD (API GET-only today) + SPA editor [M]
3. #190 CI NuGet cache (+ Docker openapi-jar, shallow checkout) [S, standalone]
4. #145 playback troubleshooting screen [L, OWN SESSION — the last phase-(b) functional gate]
5. #91 phase (b): Step 1 redirect sweep [S, independent]; Steps 23 Blazor deletion +
verification [M-L, own session, after #151/#152/#153/#145 close]
Backlog (non-blocking): rerun-create existence-check hardening (file it), #172 API hardening,
#163 playout goldens, #164 health UX, #176 PseudoTV-style creation (post-phase-b), #177/#178
Jellyfin, #66 artwork sniffing.
- main = 81bcfec8; ErsatzTV.Tests 913 / web 369 / Core.Tests 493(+1) / Arch 5 all green.
- v26.6.0 tagged (deploy = user bumps Komodo pin). CI runner = VM 127 (slow/flaky, #570).
- Open follow-ups: #193 (rerun existence-check, S). #99 (TS/HLS-Direct sessions) still backlog —
likely relevant to #145's diagnostics.
- #91 phase (b) gated ONLY on #145 now.