docs: advance ChicoryTV queue past parity #140/#146/#147 (PRs #150/#154/#156); gaps #151-#153/#155 filed; next prompt = parity #141-#145
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 4m33s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 4m55s
Build ErsatzTV Image / Build & push image (amd64) (push) Failing after 43s

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-07 12:57:51 +02:00
co-authored by Claude Fable 5
parent d58deddc48
commit 22fa3ecb08
2 changed files with 96 additions and 81 deletions
+1 -1
View File
@@ -5,7 +5,7 @@ Custom IPTV channel server for Jellyfin. Forked from [ErsatzTV/ErsatzTV](https:/
## Architecture
- **Language**: C# / .NET 10
- **UI**: ChicoryTV React SPA (`web/`, Vite, served at `/app`) over the REST API — the default UI; root `/` and migrated legacy routes 302 there (`ErsatzTV/LegacyUiRedirects.cs`). The legacy Blazor Server UI (MudBlazor) still serves un-migrated admin screens (collections, media browse, trakt, filler, watermarks, ffmpeg profiles, blocks/decos/templates, playout editors, logs, troubleshooting; Blazor home = `/system/health`); its removal is #91 phase (b), gated on parity issues #140#147
- **UI**: ChicoryTV React SPA (`web/`, Vite, served at `/app`) over the REST API — the default UI; root `/` and migrated legacy routes 302 there (`ErsatzTV/LegacyUiRedirects.cs`). The legacy Blazor Server UI (MudBlazor) still serves un-migrated admin screens (media browse/search/trash, trakt, filler, watermarks, ffmpeg profiles, blocks/decos/templates, playout editors, logs, troubleshooting, multi/rerun collections + playlists; Blazor home = `/system/health`, reachable via the Settings → System "Classic UI" link); its removal is #91 phase (b), gated on parity issues #141#145 and API gaps #151#153/#155
- **Pattern**: CQRS via MediatR — queries/commands in `ErsatzTV.Application/`
- **Database**: EF Core (SQLite default, MySQL optional) — context in `ErsatzTV.Infrastructure/Data/TvContext.cs`
- **Media**: FFmpeg via CliWrap, SkiaSharp for logo generation
+95 -80
View File
@@ -3,50 +3,68 @@
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) and all SPA screens (#84#89, #93, #109), the rebrand (#90)
and the cutover root-flip (#91 phase a, PR #148) are MERGED. v26.5.0 tagged + DEPLOYED to prod
2026-07-07 (verified live: image :26.5.0, / 302→/app, ChicoryTV title, /api/version 26.5.0,
M3U serving). Blazor removal (#91 phase b) is blocked on parity issues #140#147.
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 issues; #140/#146/#147 closed
this pass, #141#145 remain.
**Session state (2026-07-07, post-housekeeping)**: main = 7e5a6b75 (+ this doc commit).
Housekeeping session did:
- Dep batch: PR #61 (BlazorSortable 6.0.2 + CliWrap 3.10.2 + Dapper 2.1.79, rebased) and
PR #132 (EFProfiler.Appender 6.0.6053, rebased) MERGED; #21/#48/#49 closed as superseded by
#61; #131 (Scriban 7.2.0) closed obsolete — main already has Scriban 7.2.5 (47c3c3b5).
- MCP PR #76 (#58): rebased onto main (clean, purely additive ErsatzTV.Mcp project), 12 tests
green locally, pushed (head d1c04030). Still OPEN — needs a content refresh against the full
API surface before merge, plus review.
- Filed #149: CI flake — migrations job MySQL service publishes fixed host port 3306:3306, so
two concurrent runs collide ("port is already allocated"). Fix = drop the `ports:` mapping
(job connects via `Server=mysql` service name); verify MySql drift/apply still passes.
- Worktrees: removed issue-91-cutover; .worktrees/deps was this session's scratch (remove it).
**.worktrees/parity-147 exists** (branch feat/147-classic-ui-link off 1cf4a7a9, web/ npm ci
already done) — rebase onto current main and use it for #147.
- PARITY NOT STARTED — recon done though; facts baked into the prompt below.
**Session state (2026-07-07, post parity kickoff)**: main = d58deddc. This session merged:
- #147 → PR #150 (39455a96): "Classic UI" link in Settings → System About card
(plain `<a href="/system/health">`) + "Opens in ChicoryTV" MudTooltip cue on exactly the
11 redirecting Blazor sidebar links (from LegacyUiRedirects.Map). Removal markers for #91
phase (b): grep "Classic UI" (SPA) + `ersatztv#147` (MainLayout.razor @code).
- #146 → PR #154 (1f3385a2): dedicated ChannelEditScreen at /app/edit-channel/{id}
(allowSubPaths + keyed render), hydrate-then-PUT on GET/PUT /api/channels/{id}, full
ChannelEditor.razor field parity. Builder hydration rejected (scope comment on issue).
/channels/numbers drag-reorder DEFERRED — existing Renumber toolbar accepted as parity.
- #140 → PR #156 (5995707c): CollectionsScreen (web/src/screens/) + web/src/api/collections.ts
— manual collections CRUD + UseCustomPlaybackOrder + items add(picker)/remove; smart
collections CRUD with live browse preview. getCollections/getSmartCollections moved out of
schedules.ts.
- Gap issues FILED: #151 (multi-collections API), #152 (rerun collections API), #153
(playlists API), #155 (no GET for a manual collection's items + POST /items 500s on unknown
media id). #151#153+#155 now ALSO gate full Blazor parity → #91 phase (b).
- All three PRs: adversarial fork review (SHIP) + CI green before merge; #146/#140 live-E2E'd
against a real server. Worktrees parity-147/146/140 removed. Main post-merge CI green.
**Lessons for all remaining prompts** (accumulated):
- 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, or
they report placeholders that no longer exist (this bit a recon agent on 2026-07-07).
- Gitea 1.24 has no rerun-run API; retrigger CI with an empty commit. Runs on the single
runner queue mostly sequentially; concurrent migration jobs collide on port 3306 until #149
lands.
branch. NEVER recon/edit there — point subagents at a worktree pinned to origin/main.
- Gitea 1.24 has no rerun-run API; retrigger CI with an empty commit. Concurrent migration
jobs collide on host port 3306 until #149 lands.
- GET /api/collections returns ONLY manual collections — GetAllCollectionsHandler queries the
Collection table and the mapper hardcodes CollectionType.Collection. No read-only fallback
for multi/rerun/playlist types (that's why #151#153 exist).
- GET /api/library/browse search hits are restricted to Movie/Show/Season/Artist
(MediaTypesFor) — episode/song/MV/image/other/remote-stream items are NOT enumerable via
search; matters for #141 (media browse) and any picker UI. For those four kinds, browse
`id` == `mediaItemId` (verified in GetLibraryBrowseItemsHandler).
- SPA screen pattern for sub-path routes: route entry with allowSubPaths + screen parses
window.location.pathname itself + popstate listener + `key={window.location.pathname}` on
the render branch (SettingsScreen, ChannelEditScreen precedents).
- New extracted screens go in web/src/screens/ (keep App.tsx footprint to import + one
ScreenContent branch); screen tests mock fetch by URL path (App.test.tsx pattern), API
client tests follow channelTemplates.test.ts (assert exact URLs + full toEqual bodies).
- Backend quirk: isEnabled=false && showInEpg=true 422s on PUT /api/channels/{id} (and the
handler silently stores isEnabled && showInEpg) — UI must gate the EPG switch.
- DTO records in ErsatzTV.Core/Api MUST get file-scoped `#nullable enable` (else OpenAPI emits
`["null","string"]` and SPA typegen degrades). ErsatzTV.Application has NO nullable context —
`string?` there trips CS8632.
- NSubstitute + ConfigElementKey: keys are fresh instances per access — stub with
`Arg.Any<ConfigElementKey>()` disambiguated by the generic `<T>`.
`["null","string"]`). ErsatzTV.Application has NO nullable context — `string?` trips CS8632.
- 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 child-collection GETs 404 on unknown parent via pre-check + ApiResults + an
OpenApiErrorResponseContractTests [TestCase] entry.
- Backlog nits: pageSize unclamped on paged endpoints; artwork content-type trusted (#66);
GET /api/guide runs the full 21-include eager-load per channel per request; `fillerKind`
almost always `None` in JSON guide (merged into adjacent programmes).
GET /api/guide 21-include eager-load per channel per request; `fillerKind` almost always
`None` in JSON guide; UpdateChannelHandler.cs:144151 dead null-clear (pre-existing);
ExternalLogoUrl missing from ChannelViewModel/UpdateChannelRequest (Blazor-only field);
languages-list endpoint still unimplemented (free-text language codes in SPA);
#156 review nits: Lucene name-quote lacks `\` escaping, picker silently caps at 50
(totalCount unused), delete dialog doesn't clear stale dialogError, one toMatchObject.
---
# PROMPT — Parity kickoff: #147 escape hatch → #146 channel edit → #140 collections
# PROMPT — Parity continuation: #145 logs/troubleshooting → #143 ffmpeg/filler/watermarks → #142 trakt → #141 media browse → #144 scheduling blocks
You are Fable, the ORCHESTRATOR in the main Claude Code session (Claude Code only). Fable is
EXPENSIVE: delegate (recon → Explore/haiku; mechanical → sonnet; judgment-heavy code → opus;
@@ -54,61 +72,58 @@ 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).
.worktrees/parity-147 exists (npm ci done) — rebase feat/147-classic-ui-link onto
origin/main first. `cd web && npm ci` in any NEW worktree.
`cd web && npm ci` in any NEW worktree.
- Max 23 concurrent builds; ONE dotnet build at a time. NEVER set ETV_UPDATE_GOLDENS.
- Merge consent in-conversation per PR. CI reruns = empty commit (no rerun API).
- Merge consent in-conversation per PR (user may pre-approve). CI reruns = empty commit.
- Live-E2E new screens per the #93 recipe (curl `localhost`, NOT 127.0.0.1 — host guard;
wait for "Done migrating search index").
- Adversarial review fork over each PR diff before asking merge consent; apply should-fixes.
## Recon facts (2026-07-07, verified against main)
- Routing: web/src/App.tsx (~150252). /app/collections renders PlaceholderScreen
(App.tsx ~2776). Manual pushState routing, no router lib.
- #147: web/src/screens/SettingsScreen.tsx, System pane About card (~852) is the natural home
for a "Classic UI" link to /system/health; reuse the existing `LegacyCallout` component
(ExternalLink icon + "Legacy UI" badge, used by the Media sources card ~840). Remove again
in #91 phase (b).
- #146: Channels screen edit pencil already navigates
navigateToPath(`/app/new-channel?edit=${channel.id}`) (App.tsx ~1216) but
web/src/builder/ChannelBuilder.tsx never reads query params → dead-end. Backend ready:
GET /api/channels/{id} → full ChannelViewModel; PUT /api/channels/{id} takes
UpdateChannelRequest (ChannelController.cs). Issue #146 also covers /channels/numbers bulk
renumbering — bulkRenumberChannels already exists in web/src/api/channels.ts (check what UI
exposes it). Decide: hydrate the builder for edit vs a dedicated edit view — the builder's
lineup model (from-lineup composite create) does NOT map 1:1 onto editing an existing
channel's 30+ settings; a form-style edit view calling PUT may be the honest shape.
- #140: manual + smart collections have FULL API CRUD (CollectionController incl. items
add/remove; SmartCollectionController). NO API exists for multi-collections, playlists,
rerun collections → those need backend gap issues/endpoints for full Blazor parity
(/media/multi-collections, /media/playlists, /media/rerun-collections). SPA has
getCollections()/getSmartCollections() in web/src/api/schedules.ts (no collections.ts yet).
Tests: Vitest + testing-library; App.test.tsx mocks fetch by path (mockDashboardApi).
## Known facts (2026-07-07; RECON EACH ISSUE before implementing — unlike the last pass,
## API coverage for these screens is largely UNKNOWN and gaps must be filed as issues first)
- #145 logs & troubleshooting (/system/logs, /system/troubleshooting/*): likely NO log/
troubleshooting API endpoints — check Controllers/Api. If absent, file gap issue(s) and
decide whether backend work fits in-session or gets queued.
- #143 ffmpeg profiles / filler presets / watermarks editors: read endpoints exist
(GET /api/ffmpeg/profiles(+/{id}), resolution by-name; filler/watermark LISTS exist —
DB order, client-sort). Editor CRUD (POST/PUT/DELETE) coverage unknown — recon.
- #142 trakt lists (/media/trakt/lists): API coverage unknown; Trakt auth/device flows live
in Blazor dialogs — recon both the API and how the flow maps to the SPA.
- #141 media browse/search/trash (/media/movies|tv|music|other, /search, trash): remember
the library/browse 4-kind search restriction (Lessons) — full media browse likely needs
new/wider endpoints; #155's items-GET may share design. Biggest recon surface.
- #144 scheduling blocks (blocks/decos/templates + playout detail editors): biggest build;
recon which of blocks/decos/templates/playout-detail have API CRUD.
- Suggested order: #145#143#142#141#144 (small→large; re-order after recon if
gaps invert the sizes). It is FINE for a session to output "gap issues filed + what was
buildable now" — parity needs the API gaps surfaced early anyway.
## Task (in order)
1. #147 [small, sonnet]: Classic UI link in Settings → System About card (+ optional
"opens in ChicoryTV" cue on migrated Blazor sidebar links, per the issue). Vitest
coverage. PR, CI, consent, merge.
2. #146 [opus]: channel edit path. Scope call first (builder hydration vs edit form —
recommend and confirm in an issue comment), then implement + tests + live E2E. Include or
explicitly defer /channels/numbers with an issue comment. PR, consent, merge.
3. #140 [opus, biggest]: collections screen for manual + smart collections (list/create/
edit/delete/items). File follow-up issues for multi/playlist/rerun API gaps and cover
those types read-only if feasible (they surface in GET /api/collections by CollectionType).
PR, consent, merge.
4. Adversarial review fork over each PR diff before asking merge consent.
5. Update THIS handoff: merged PRs + main SHA, pop done items, next prompt = remaining parity
(#141#145). Commit to main. Print the next prompt in a fenced block.
## Task
1. RECON [Explore agents, parallel, read-only, worktree off origin/main]: per issue above,
map Blazor pages → required operations → existing API endpoints → gaps. Product per issue:
buildable-now scope + gap list.
2. File API gap issues (pattern: #151#153/#155 — reference #91 phase b + epic #59).
3. Implement buildable-now scope per issue (sonnet for small screens, opus for big ones),
one worktree per issue, tests + live E2E, PR, fork review, CI, consent, merge — in the
suggested order. Stop when the session runs long; leave clean state.
4. Update THIS handoff: merged PRs + main SHA, pop done items, next prompt = whatever
remains (#141#145 leftovers, then #91 phase b readiness check: #151#153/#155 gate it
too now). Commit to main. Print the next prompt in a fenced block.
---
## Issue queue (work top-down)
1. #147 SPA escape-hatch link (tiny) ← PROMPT above
2. #146 channel edit + /channels/numbers (blocker for #91b)
3. #140 collections management (biggest gap; spawns multi/playlist/rerun API follow-ups)
4. #141#145 remaining parity gaps (media browse/search, trakt, filler, watermarks, ffmpeg
profiles, blocks/decos/templates, playout editors, logs, troubleshooting — see each issue)
5. #91 phase (b): remove Blazor once #140#147 close; also remove the #147 link then.
6. MCP PR #76 (#58): refresh tool catalog against the full API surface, review, merge.
7. #149 CI flake (migrations job host-port collision) — small, slot into any session.
Cross-refs: #99 (TS/HLS-Direct sessions) stays backlog. Done this pass: dep PRs #61/#132
merged; #21/#48/#49/#131 closed; prod verified on v26.5.0.
1. #145 logs & troubleshooting (likely smallest; API recon first) ← PROMPT above
2. #143 ffmpeg profiles / filler presets / watermarks editors
3. #142 trakt lists
4. #141 media browsing, search & trash (big; shares design with #155 items-GET)
5. #144 scheduling building blocks + playout detail editors (biggest)
6. API gaps gating #91b: #151 multi-collections, #152 rerun collections, #153 playlists,
#155 collection items GET (+ POST /items 500 fix) — slot into sessions as backend work.
7. #91 phase (b): remove Blazor once #141#145 + #151#153/#155 close; also remove the #147
Classic-UI link + ersatztv#147 cue then.
8. MCP PR #76 (#58): refresh tool catalog against the full API surface, review, merge.
9. #149 CI flake (migrations job host-port collision) — small, slot into any session.
Cross-refs: #99 (TS/HLS-Direct sessions) stays backlog. Done this pass: #147 (PR #150),
#146 (PR #154), #140 (PR #156); gap issues #151/#152/#153/#155 filed; prod remains v26.5.0
(next release = tag + bump Komodo pin per CLAUDE.md Deployment).