docs: advance ChicoryTV queue past parity #140/#146/#147 (PRs #150/#154/#156); gaps #151-#153/#155 filed; next prompt = parity #141-#145
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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:144–151 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 2–3 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 (~150–252). /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).
|
||||
|
||||
Reference in New Issue
Block a user