Files
ersatztv/docs/handoffs/chicorytv-issue-queue.md
T
timothyandClaude Fable 5 e00b7e4d11
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 4m38s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 4m49s
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 4m49s
docs: advance ChicoryTV queue past parity pass 2 (#142/#143 closed, #141/#145 partial, #157 fixed; PRs #165-#169); next = #144 scheduling slices
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 16:24:18 +02:00

12 KiB
Raw Blame History

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) and the cutover root-flip (#91 phase a) are MERGED. v26.5.0 tagged + DEPLOYED to prod 2026-07-07. Blazor removal (#91 phase b) is blocked on parity; #140/#142/#143/#146/#147 CLOSED. Remaining parity: #144 (all of it), #145 + #141 leftovers. Gap issues #151–#153/#155 (collections/playlists APIs) + #158 items 4–5 + #161 items 3+5 + #162 also gate phase (b).

Session state (2026-07-07, parity pass 2): main = 3c37ec5f, post-merge CI green through PR #169 (#168's post-merge run pending at handoff time — verify). This session merged FIVE PRs:

  • #145 partial → PR #165: SPA Logs (/app/logs, paged+filter over new GET /api/logs) + Troubleshooting (/app/troubleshooting, GET /api/troubleshoot/info); playback endpoints now in the OpenAPI spec; new "System" nav group. #145 stays OPEN (leftovers below).
  • #143 + #159 CLOSED → PR #166: FFmpeg Profiles / Filler Presets / Watermarks editor screens; filler+watermark full CRUD, FFmpeg response DTO round-trip complete, hwaccel-kinds lookup; 404-vs-422 fix in Update/Delete handlers (NotFoundError lost through Validation aggregation — use Option.Match precedent); WatermarkLocation/Size string-enum spec fix.
  • #142 + #160 CLOSED → PR #167: Trakt Lists screen + TraktController (202 + background worker channel + IEntityLocker, GET /api/trakt/status for polling; lock released on failed enqueue — review find). No OAuth: public lists via TRAKT__CLIENTID.
  • #141 core → PR #168: /app/media (browse ALL 10 media kinds — LibraryBrowseMediaType extended, hydrators + Subtitle field), /app/search (new grouped GET /api/search, TopBar wired), /app/trash (new DELETE /api/media-items + existing empty_trash). Review fixes: ChannelBuilder library picker + Collections add-items dialog fan out per-kind (unscoped browse now mixes all kinds). #141 + #161 stay OPEN (leftovers below).
  • #157 CLOSED → PR #169: Libraries scan-polling tests made deterministic (root cause: captured-interval array empty when ticked — passive-effect registration raced findBy*; fix: runPollTick helper — act-flush, assert handlers captured, tick inside act).
  • Gap issues FILED from recon: #158 (logs/troubleshoot API — items 1–3 DONE in #165), #159/#160 (CLOSED with #166/#167), #161 (media API — items 1/2/4 DONE in #168), #162 (scheduling REST surface, 6 slices S1–S6 — untouched). Backlog (non-blocking): #163 (playout-build golden tests), #164 (health checks UX audit + guided remediation).
  • All PRs: adversarial fork review + should-fixes applied + CI green before merge; all screens live-E2E'd. Main flaked twice on #157 (empty-commit retriggers) before PR #169 fixed it. Worktrees feat-145/143/142/141 + fix-157 removed; parity-recon may remain.

Lessons for all remaining prompts (accumulated; pruned of fixed items):

  • The main checkout (/Users/timothy/ersatztv) sits on the STALE docs/59-ui-redesign-brief branch. NEVER recon/edit there — point subagents at a worktree pinned to origin/main.
  • PARALLEL PARITY BRANCHES ALL CONFLICT on web/src/App.tsx (ScreenId/routes/nav), SettingsScreen Classic-UI help text, v1.json/v1.d.ts, and the security/contract test lists. Routine: implement off a main snapshot → merge origin/main into the branch before PR → resolve as UNION (one System nav group; help text lists only still-Blazor screens) → v1.json/v1.d.ts: take either side then REGENERATE (authoritative) → full test pass.
  • Gitea 1.24 has no rerun-run API; retrigger CI with an empty commit.
  • Live E2E via dotnet run: content root resolves to bin/Debug/net10.0 — copy wwwroot in (and clean up) if the SPA 404s. Scratch ETV_CONFIG_FOLDER; curl localhost NOT 127.0.0.1; wait for "Done migrating search index". Without an API key configured, mutating /api verbs are open (ApiKeyAuthorizationFilter) — E2E can POST/PUT/DELETE.
  • GET /api/library/browse now serves ALL 10 media kinds; mediaType-less calls mix kinds — pickers must pass mediaType or fan out per-kind (ChannelBuilder loadLibraryItems / CollectionsScreen runSearch are the precedents). Browse id == mediaItemId for media kinds. GET /api/search returns a stable 10-group shape; state:FileNotFound = trash.
  • Background-work API pattern (Trakt precedent, reuse for scans etc.): validate sync (422) → IEntityLocker lock (409 if held) → enqueue to ChannelWriter inside try/catch that UNLOCKS on failure → 202; plus a GET status endpoint wrapping the lock for SPA polling (poll only while busy, refresh on busy→idle).
  • Update/Delete handlers that feed NotFoundError through LanguageExt Validation lose the subtype (422 instead of 404) — use the Option.Match precedent (NotFoundError as direct Either Left). Watch for enums living in ErsatzTV.FFmpeg documented as ints — register as OpenAPI string enums in Startup.cs (WatermarkLocation/Size precedent).
  • Request DTOs (Controllers/Api/Requests) historically lack #nullable enable → their strings typegen as string | null; SPA handles with ?? ''. New RESPONSE DTOs in ErsatzTV.Core/Api MUST get file-scoped #nullable enable. ErsatzTV.Application has NO nullable context (CS8632).
  • SPA polling tests: capture-interval mocks MUST act-flush effects then assert handlers were captured before ticking (runPollTick in App.test.tsx) — silent no-op ticks were the #157 flake. Fetch races: seqRef per-request counter (LogsScreen precedent).
  • NSubstitute + ConfigElementKey: stub with Arg.Any<ConfigElementKey>() + generic <T>.
  • Option<T>.ToNullable() doesn't exist; use MatchUnsafe(v => (T?)v, () => null).
  • ./scripts/update-openapi.sh needs a prior normal dotnet build ErsatzTV/ErsatzTV.csproj.
  • Id-taking GETs 404 on unknown id via pre-check + ApiResults + an OpenApiErrorResponseContractTests [TestCase] entry. New controllers with mutating verbs go into ApiControllerSecurityTests' array.
  • SPA screen pattern for sub-path routes: allowSubPaths + screen parses pathname + popstate
    • keyed render branch. New screens in web/src/screens/; API clients in web/src/api/ with URL-assert tests.
  • Backlog nits: GET /api/search dispatches its 10 per-kind queries SERIALLY (Task.WhenAll safe — per-dispatch DbContext); ADDABLE_TYPES could grow beyond 4 kinds now browse serves them (Blazor allowed adding seasons/episodes to collections); Trakt matched-items link still points at classic /search — swap to /app/search now it exists; MediaBrowse/Search key={location.search} remount staleness (replaceState, no popstate) is fragile-but-OK; /api/logs + /api/troubleshoot/info unauthenticated GETs — list for API auth hardening; artwork content-type trusted (#66); GET /api/guide 21-include eager-load per request; languages-list endpoint still unimplemented; VAAPI display/device lists have no lookup endpoint (free-text in SPA); FFmpeg profile "Copy" is a client-side clone.

PROMPT — Parity finale: #144 scheduling editors (S1–S6), then #145/#141 leftovers → #91 phase (b) readiness

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). Read CLAUDE.md + the Lessons above first.

HARD CONSTRAINTS:

  • Work in worktrees off origin/main; NEVER touch /Users/timothy/ersatztv (stale branch). cd web && npm ci in any NEW worktree.
  • Max 2–3 concurrent builds; ONE dotnet build at a time. NEVER set ETV_UPDATE_GOLDENS.
  • Merge consent in-conversation per PR (user may pre-approve). CI reruns = empty commit.
  • Live-E2E new screens (Lessons: localhost, scratch config, wwwroot→bin copy).
  • Adversarial review fork over each PR diff before merge; apply should-fixes.
  • Merge origin/main into every branch before its PR (Lessons: the App.tsx/v1.json union routine) — sessions now run stacked parallel branches and they ALL conflict there.

Known facts (2026-07-07, from #162 recon — re-verify cheaply, don't re-recon)

  • #144 = the LAST whole parity issue. Zero REST endpoints exist for scheduling editors, but the full CQRS layer exists (ErsatzTV.Application/Scheduling/ + Playouts/Commands/) — API work is thin wrappers mirroring ScheduleController + Requests/. Ordering = item Index in bulk Replace*Items (no move/reorder commands — parity-faithful). Slices from #162: S1 blocks CRUD+items+preview [L] → S2 templates [M, needs S1] → S3 decos [XL form, ~28 fields] → S4 deco-templates [M, needs S3] → S5 playout create ALL kinds + detail update endpoints (fixes the disabled Add Playout button; UpdateDefaultDeco needs S3 for its picker) [M] → S6 alternate-schedules + playout-templates editors [L, needs S2+S4]. RECOMMENDED START: S5 first (small, self-contained, user-visible Add-Playout fix; classic kind needs only existing schedules API; block/sequential/scripted/externaljson create = channel + ScheduleFile path), then S1→S2, S3→S4, S6. One PR per slice or per pair — do NOT attempt all six in one PR.
  • #145 leftovers (issue open): block-playout troubleshooting + history screens (#158 item 4 endpoints: GetAllBlockPlayouts/GetAllBlocksForPlayout/GetBlockPlayoutHistory/ DecodePlayoutHistory) [M]; sequential-schedule YAML validator (#158 item 5: POST body, not server path) [S-M]; playback-troubleshooting screen (endpoints live + in spec; needs HLS player + a session-log retrieval design) [L — consider its own session].
  • #141 leftovers (issue open): detail endpoints+pages for movie/show/season/artist + GetMediaItemInfo (#161 item 3) [M-L]; image folder browser (#161 item 5) [S-M].
  • Quick wins interleavable anytime: parallelize GET /api/search (Task.WhenAll); swap Trakt matched-items link to /app/search.

Task

  1. Verify main green (post-#168 run if still pending at session start).
  2. #144 slice-by-slice per the recommended order: worktree per slice/pair, opus for S1/S3/S6
    • sonnet for S2/S4/S5 screens, tests + live E2E, merge main in, PR, fork review, CI, consent, merge. Comment progress on #144/#162 per slice.
  3. If session capacity remains: #145 leftovers (block-playout history first), then #141 detail pages.
  4. When #144+#145+#141 fully close: run the #91 phase (b) READINESS CHECK — enumerate remaining Blazor-only routes (grep @page in ErsatzTV/Pages minus migrated), reconcile against open gaps (#151–#153/#155, #158, #161), and either declare ready (file the removal plan on #91) or file what's missing.
  5. Update THIS handoff (state, lessons, next prompt), commit to main, print next prompt in a fenced block.

Issue queue (work top-down)

  1. #144 S5 playout create/detail-update (Add-Playout fix) ← PROMPT above
  2. #144 S1 blocks → S2 templates
  3. #144 S3 decos → S4 deco-templates
  4. #144 S6 alternate-schedules + playout-templates
  5. #145 leftovers: block-playout history [M] → YAML validator [S-M] → playback troubleshooting [L, own session]
  6. #141 leftovers: detail endpoints+pages [M-L] → image folder browser [S-M]
  7. #91 phase (b) readiness check (also gated on #151–#153/#155) Backlog (non-blocking): #163 playout goldens, #164 health UX, #66 artwork sniffing, search parallelization, Trakt link swap, API auth hardening list.