ChicoryTV SPA: Guide / EPG screen #85
Closed
opened 2026-07-02 01:07:18 +02:00 by timothy
·
5 comments
No Branch/Tag Specified
main
renovate/lucene.net
renovate/dotnet-monorepo
renovate/sqlitepclraw.bundle_e_sqlite3-3.x
renovate/meziantou.analyzer-3.x
renovate/cliwrap-3.x
913-scripted-playout-401
release/v26.15.0-notes
issue-806-guard-populations
scratch/767b-poisoned
scratch/767b-control
release/v26.14.0-notes
release/v26.14.0
docs/510-skill-logo-bug-policy
fix/510-watermark-resolution-policy
fix/629-verdict-classifier-falseopens
fix/609-decisions-edit-token-scope
issue-135-clear-to-none
release/v26.12.0-notes
fix/409b-lastscan-api-parity
fix/401-updatechannel-mirror-422
fix/327-playlist-rename-validation
fix/410-scancancel-log-level
fix/409-447-librariesscreen-neverscanned
fix/338-zap-exit-code
fix/367-plex-budget-message
fix/310-debom-legacy-cs
ci/604-lane-rebalance
feat/388-design-mirror
feat/247-test-ownership
feat/247-primary-action
feat/357-player-owned-playback
feat/357-jellyfin-plugin-poc
fix/289-mcp-hardening
issue58-mcp
feat/244-channels-extract
ci/auto-bump-prod-compose
feat/multi-rerun-collections-api
feat/collections-api
feat/quick-wins
feat/185-docs-part2
feat/140-collections-screen
feat/146-channel-edit
feat/147-classic-ui-link
issue22-renovate-dashboard
feat/91-cutover
feat/63-composite-create
feat/65-library-browse
feat/85-epg
feat/86-schedule-editor
feat/109-dashboard-data
feat/99-session-tracking
fix/dockerfile-node-tag
feat/59-spa-foundation
docs/59-ui-redesign-brief
feat/102-json-guide
feat/111-schedule-durations
feat/104-artwork-upload
feat/103-media-sources-api
feat/playouts-read-api
feat/108-health-api
feat/105-picker-list-endpoints
issue-97-channel-state-api
issue42-jellyfin-musicvideos
issue46-rest-api-error-contract
dependabot/nuget/ErsatzTV.FFmpeg.Tests/multi-d307a2e06f
qsv-improvements
hdr-vulkan-cuda-test
v26.16.0
v26.15.0
v26.14.0
v26.13.0
v26.12.0
v26.11.0
v26.10.0
v26.9.0
v26.8.0
v26.7.0
blazor-final
v26.6.0
v26.5.0
v26.4.0
v26.3.1
v26.3.0
v26.2.0
v26.1.1
v26.1.0
v25.9.0
v25.8.0
v25.7.1
v25.7.0
v25.6.0
v25.5.0
v25.4.0
v25.3.1
v25.3.0
v25.2.0
v25.1.0
v0.8.8-beta
v0.8.7-beta
v0.8.6-beta
v0.8.5-beta
v0.8.4-beta
v0.8.3-beta
v0.8.2-beta
v0.8.1-beta
v0.8.0-beta
v0.7.9-beta
v0.7.8-beta
v0.7.7-beta
v0.7.6-beta
v0.7.5-beta
v0.7.4-beta
v0.7.3-beta
v0.7.2-beta
v0.7.1-beta
v0.7.0-beta
v0.6.9-beta
v0.6.8-beta
v0.6.7-beta
v0.6.6-beta
v0.6.5-beta
v0.6.4-beta
v0.6.3-beta
v0.6.2-beta
v0.6.1-beta
v0.6.0-beta
v0.5.8-beta
v0.5.7-beta
v0.5.6-beta
v0.5.5-beta
v0.5.4-beta
v0.5.3-beta
v0.5.2-beta
v0.5.1-beta
v0.5.0-beta
v0.4.5-alpha
v0.4.4-alpha
v0.4.3-alpha
v0.4.2-alpha
v0.4.1-alpha
v0.4.0-alpha
v0.3.8-alpha
v0.3.7-alpha
develop
v0.3.6-alpha
v0.3.5-alpha
v0.3.4-alpha
v0.3.3-alpha
v0.3.2-alpha
v0.3.1-alpha
v0.3.0-alpha
v0.2.5-alpha
v0.2.4-alpha
v0.2.3-alpha
v0.2.2-alpha
v0.2.1-alpha
v0.2.0-alpha
v0.1.5-alpha
v0.1.4-alpha
v0.1.3-alpha
v0.1.2-alpha
v0.1.1-alpha
v0.1.0-alpha
v0.0.62-alpha
v0.0.61-alpha
v0.0.60-alpha
v0.0.59-alpha
v0.0.58-alpha
v0.0.57-alpha
v0.0.56-alpha
v0.0.55-alpha
v0.0.54-alpha
v0.0.53-alpha
v0.0.52-alpha
v0.0.51-alpha
v0.0.50-alpha
v0.0.49-prealpha
v0.0.48-prealpha
v0.0.47-prealpha
v0.0.46-prealpha
v0.0.45-prealpha
v0.0.44-prealpha
v0.0.43-prealpha
v0.0.42-prealpha
v0.0.41-prealpha
v0.0.40-prealpha
v0.0.39-prealpha
v0.0.38-prealpha
v0.0.37-prealpha
v0.0.36-prealpha
v0.0.35-prealpha
v0.0.34-prealpha
v0.0.33-prealpha
v0.0.32-prealpha
v0.0.31-prealpha
v0.0.30-prealpha
v0.0.29-prealpha
v0.0.28-prealpha
v0.0.27-prealpha
v0.0.26-prealpha
v0.0.25-prealpha
v0.0.24-prealpha
v0.0.23-prealpha
v0.0.22-prealpha
v0.0.21-prealpha
v0.0.20-prealpha
v0.0.19-prealpha
v0.0.18-prealpha
v0.0.17-prealpha
v0.0.16-prealpha
v0.0.15-prealpha
v0.0.14-prealpha
v0.0.13-prealpha
v0.0.12-prealpha
v0.0.11-prealpha
v0.0.10-prealpha
v0.0.9-prealpha
v0.0.8-prealpha
v0.0.7-prealpha
v0.0.6-prealpha
v0.0.5-prealpha
v0.0.4-prealpha
v0.0.3-prealpha
v0.0.2-prealpha
v0.0.1-prealpha
Labels
Clear labels
ad-hoc
api
bug
ci-cd
content
dependencies
enhancement
frontend
in-progress
jellyfin
parked
priority: high
priority: low
priority: medium
review
security
One-off / ad-hoc work not tracked by a dedicated issue
REST API / HTTP endpoints
Something isn't working
Build, test, deploy pipeline
Channel content / schedules / playlists
Dependency updates (Renovate)
New feature or improvement
ChicoryTV React SPA frontend
Claimed by an active session — do not pick up
Jellyfin tuner / IPTV integration
Excluded from automatic queue pickup; work only when explicitly selected
Adversarial review finding
Security / vulnerability fix
No labels
frontend
Milestone
No items
No Milestone
Projects
Clear projects
No projects
No Assignees
Notifications
Due Date
No due date set.
Blocks
Depends on
#91 ChicoryTV SPA: Blazor → SPA cutover (retire old UI)
timothy/ersatztv
#82 ChicoryTV SPA: app shell (sidebar + top bar + routing)
timothy/ersatztv
#102 Backend: JSON channel-guide endpoint for the EPG grid
timothy/ersatztv
Reference: timothy/ersatztv#85
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Recreate
design-system/templates/chicorytv-admin/Epg.jsxwired to the channel guide data (XMLTV/guide handler).Dark channels×time grid, "now" marker, live focal color, jump-to-now. Acceptance: matches prototype; live guide data; all 3 themes.
Heads-up from #102 (JSON guide endpoint, PR incoming): (1)
fillerKindon programmes will almost always beNone— the shared XMLTV/JSON projector merges filler into adjacent programmes; discrete filler entries would need a JSON-only projection mode (open a follow-up if the grid needs it). (2) The endpoint eager-loads the full metadata graph per channel per request — do not poll it aggressively; a trimmed projection/caching follow-up is advised before this grid ships.Approach before coding
I’m implementing this as a frontend-only Guide screen in
web/, matchingdesign-system/templates/chicorytv-admin/Epg.jsxand the existing Channels/Schedules/Playouts/Libraries patterns.Prototype → API/client mapping:
num,name/api/guidechannels[].number,channels[].name; render with existingChannelLogoinitials precedent/api/guideprogrammes[].title,subTitle,category; null subtitle/category render as omitted/muted metadataprogrammes[].fillerKind; explicitly compare to'None'rather than truthiness-checkingDatemath, clip programmes to window before computing left/width/api/channels/statepoll at ≥30s foronAir/live channel treatment/api/guide?start=&end=window when neededScope decisions:
now - 1hthroughnow + 12h, aligned in client code and always sent explicitly as query parameters to avoid the expensive default horizon./api/guide; interval ticks only move the marker/highlight./api/channels/statemay poll at the Channels precedent cadence if used for live focal treatment.fillerKind: 'None'fixtures with null subtitle/category.Progress
Implemented the Guide/EPG screen in the isolated
feat/85-epgworktree:web/src/api/guide.tsfor bounded/api/guide?start=&end=loading plus optional/api/channels/statelive-state refresh./app/guideplaceholder with a channels × time grid, sticky channel rail/time header, programme clipping, empty-channel rows, now marker, previous/next window controls, and jump-to-now./api/guidefetches explicit and navigation-driven only; the now marker moves on a client-side timer without refetching guide data.fillerKind: 'None', and error+retry.Verification so far:
npm cipassednpm run typecheckpassednpm run lintpassednpm test -- --runpassed (79 tests)npm run buildpassedTZ=UTC dotnet build ErsatzTV.slnpassed with existing NuGet audit warnings and existing nullable warnings inArtworkUploadControllerTestsTZ=UTC dotnet test ErsatzTV.Tests/ErsatzTV.Tests.csprojpassed (368)TZ=UTC dotnet test ErsatzTV.Core.Tests/ErsatzTV.Core.Tests.csprojpassed (493 passed, 1 skipped)A read-only code review agent is running now before I push/open the PR.
Done
What was done: Implemented the Guide / EPG screen in PR #129 (
feat/85-epg, head7d93688841aa0115c2d603cf274153643f0f768e). The screen uses a bounded/api/guide?start=&end=window, renders the channels × time grid with clipped programme blocks, moves the now marker client-side without guide polling, uses/api/channels/statefor live styling, supports previous/next window and jump-to-now, and includes error/retry and empty-row states.Root cause: n/a — frontend feature implementation.
Files changed:
web/src/api/guide.ts,web/src/api/index.ts,web/src/App.tsx,web/src/App.test.tsx,web/src/shell.css.Verification:
npm ci;npm run typecheck;npm run lint;npm test -- --run(79 passed);npm run build;TZ=UTC dotnet build ErsatzTV.sln(passed with existing NuGet audit warnings);TZ=UTC dotnet test ErsatzTV.Tests/ErsatzTV.Tests.csproj(368 passed);TZ=UTC dotnet test ErsatzTV.Core.Tests/ErsatzTV.Core.Tests.csproj(493 passed, 1 skipped). A read-only review found three risks; all were addressed before push: same-window request sequencing, live matching by title+timestamps, and toolbar wrapping.Deferred: Discrete filler-entry display remains deferred until the backend exposes a JSON-only projection mode. Channel group filtering is visibly disabled until guide rows expose group data.
/api/guideremains expensive, so the screen intentionally avoids polling it.Follow-up issues: none opened in this session.
Docs updated: PR body documents the endpoint mapping, scope decisions, review fixes, and deferred items.
Status: PR #129 is open and pushed. Issue #85 should remain open until Fable review, CI, user merge consent, and merge. CI is currently pending/queued on Gitea run 247.
Done
What was done: Addressed Fable review comment 6382 on PR #129 and pushed commit
a8d4979b0ceeb5b236860479d22821831fe77d45. The omitted-nowPlayingcrash is fixed with a truthiness guard and a fixture that omits the key; live highlighting now matches by exact start/finish timestamps only; optional channel-state failure no longer takes down initial guide loading; shared guide window constants/helpers are reused; the jump-to-now duplicate lookback is removed; and the crosshair icon now uses lucide.Root cause: The frontend assumed
nowPlayingwould deserialize asnull, but the API omits null fields. The live-match logic also relied on title equality across two endpoints with different title contracts: guide programme titles are show-level, while now-playing titles are display titles.Files changed:
web/src/api/guide.ts,web/src/App.tsx,web/src/App.test.tsx,web/src/shell.css.Verification:
npm run typecheck;npm run lint;npm test -- --run(80 passed);npm run build. Earlier full PR verification also covered .NET build/tests. Gitea Actions run 512 is currently in progress for the new head SHA.Deferred: Shared poll-loop extraction and full-grid memoization remain non-blocking cleanups. Existing deferred scope remains: discrete filler rows need backend projection support; channel group filtering awaits group data in guide rows;
/api/guideis intentionally not polled.Follow-up issues: none opened; timestamp-only matching resolved the review's #2 without needing a backend ID follow-up for this PR.
Docs updated: PR #129 body updated with the corrected review-fix summary and current verification.
Status: PR #129 remains open. Issue #85 remains open until the new CI run is green, Fable/user consent is given, and the PR is merged.