docs: advance ChicoryTV handoff past the multi/rerun/playlist SPA editors + CI/runner session
#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:
@@ -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 ~4–5 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 2–3 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.
|
||||
|
||||
Reference in New Issue
Block a user