ChicoryTV SPA: Guide / EPG screen #85

Closed
opened 2026-07-02 01:07:18 +02:00 by timothy · 5 comments
Owner

Recreate design-system/templates/chicorytv-admin/Epg.jsx wired 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.

Recreate `design-system/templates/chicorytv-admin/Epg.jsx` wired 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.
timothy added the frontend label 2026-07-02 01:07:18 +02:00
timothy added a new dependency 2026-07-02 01:08:41 +02:00
timothy added a new dependency 2026-07-02 01:08:42 +02:00
timothy added a new dependency 2026-07-02 19:44:15 +02:00
Author
Owner

Heads-up from #102 (JSON guide endpoint, PR incoming): (1) fillerKind on programmes will almost always be None — 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.

Heads-up from #102 (JSON guide endpoint, PR incoming): (1) `fillerKind` on programmes will almost always be `None` — 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.
Author
Owner

Approach before coding

I’m implementing this as a frontend-only Guide screen in web/, matching design-system/templates/chicorytv-admin/Epg.jsx and the existing Channels/Schedules/Playouts/Libraries patterns.

Prototype → API/client mapping:

Prototype surface Live data / SPA mapping
channel rail num, name /api/guide channels[].number, channels[].name; render with existing ChannelLogo initials precedent
programme block title/subtitle/category /api/guide programmes[].title, subTitle, category; null subtitle/category render as omitted/muted metadata
filler muted treatment programmes[].fillerKind; explicitly compare to 'None' rather than truthiness-checking
time header + block positions bounded request window, client-side Date math, clip programmes to window before computing left/width
now marker + live focal color client-side minute-ish now tick, no guide refetch; optional /api/channels/state poll at ≥30s for onAir/live channel treatment
Jump to now / prev-next navigation state changes request exactly one new bounded /api/guide?start=&end= window when needed

Scope decisions:

  • Guide request window: now - 1h through now + 12h, aligned in client code and always sent explicitly as query parameters to avoid the expensive default horizon.
  • No polling of /api/guide; interval ticks only move the marker/highlight. /api/channels/state may poll at the Channels precedent cadence if used for live focal treatment.
  • No backend changes and no new endpoints. Missing discrete filler rows remain visibly out of scope because the JSON guide currently merges filler into adjacent programmes.
  • Tests will pin: one guide fetch on mount, no guide refetch on timer ticks, one fetch per window navigation, jump-to-now behavior, now-marker math, clipping at window edges, empty channels, error+retry, and realistic fillerKind: 'None' fixtures with null subtitle/category.
## Approach before coding I’m implementing this as a frontend-only Guide screen in `web/`, matching `design-system/templates/chicorytv-admin/Epg.jsx` and the existing Channels/Schedules/Playouts/Libraries patterns. Prototype → API/client mapping: | Prototype surface | Live data / SPA mapping | |---|---| | channel rail `num`, `name` | `/api/guide` `channels[].number`, `channels[].name`; render with existing `ChannelLogo` initials precedent | | programme block title/subtitle/category | `/api/guide` `programmes[].title`, `subTitle`, `category`; null subtitle/category render as omitted/muted metadata | | filler muted treatment | `programmes[].fillerKind`; explicitly compare to `'None'` rather than truthiness-checking | | time header + block positions | bounded request window, client-side `Date` math, clip programmes to window before computing left/width | | now marker + live focal color | client-side minute-ish now tick, no guide refetch; optional `/api/channels/state` poll at ≥30s for `onAir`/live channel treatment | | Jump to now / prev-next | navigation state changes request exactly one new bounded `/api/guide?start=&end=` window when needed | Scope decisions: - Guide request window: `now - 1h` through `now + 12h`, aligned in client code and always sent explicitly as query parameters to avoid the expensive default horizon. - No polling of `/api/guide`; interval ticks only move the marker/highlight. `/api/channels/state` may poll at the Channels precedent cadence if used for live focal treatment. - No backend changes and no new endpoints. Missing discrete filler rows remain visibly out of scope because the JSON guide currently merges filler into adjacent programmes. - Tests will pin: one guide fetch on mount, no guide refetch on timer ticks, one fetch per window navigation, jump-to-now behavior, now-marker math, clipping at window edges, empty channels, error+retry, and realistic `fillerKind: 'None'` fixtures with null subtitle/category.
Author
Owner

Progress

Implemented the Guide/EPG screen in the isolated feat/85-epg worktree:

  • Added web/src/api/guide.ts for bounded /api/guide?start=&end= loading plus optional /api/channels/state live-state refresh.
  • Replaced the /app/guide placeholder 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.
  • Kept /api/guide fetches explicit and navigation-driven only; the now marker moves on a client-side timer without refetching guide data.
  • Tests cover bounded query params, no guide polling on timer tick, one guide fetch per window navigation, jump-to-now, clipping fixture data, empty channels, fillerKind: 'None', and error+retry.

Verification so far:

  • npm ci passed
  • npm run typecheck passed
  • npm run lint passed
  • npm test -- --run passed (79 tests)
  • npm run build passed
  • TZ=UTC dotnet build ErsatzTV.sln passed with existing NuGet audit warnings and existing nullable warnings in ArtworkUploadControllerTests
  • TZ=UTC dotnet test ErsatzTV.Tests/ErsatzTV.Tests.csproj passed (368)
  • TZ=UTC dotnet test ErsatzTV.Core.Tests/ErsatzTV.Core.Tests.csproj passed (493 passed, 1 skipped)

A read-only code review agent is running now before I push/open the PR.

## Progress Implemented the Guide/EPG screen in the isolated `feat/85-epg` worktree: - Added `web/src/api/guide.ts` for bounded `/api/guide?start=&end=` loading plus optional `/api/channels/state` live-state refresh. - Replaced the `/app/guide` placeholder 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. - Kept `/api/guide` fetches explicit and navigation-driven only; the now marker moves on a client-side timer without refetching guide data. - Tests cover bounded query params, no guide polling on timer tick, one guide fetch per window navigation, jump-to-now, clipping fixture data, empty channels, `fillerKind: 'None'`, and error+retry. Verification so far: - `npm ci` passed - `npm run typecheck` passed - `npm run lint` passed - `npm test -- --run` passed (79 tests) - `npm run build` passed - `TZ=UTC dotnet build ErsatzTV.sln` passed with existing NuGet audit warnings and existing nullable warnings in `ArtworkUploadControllerTests` - `TZ=UTC dotnet test ErsatzTV.Tests/ErsatzTV.Tests.csproj` passed (368) - `TZ=UTC dotnet test ErsatzTV.Core.Tests/ErsatzTV.Core.Tests.csproj` passed (493 passed, 1 skipped) A read-only code review agent is running now before I push/open the PR.
Author
Owner

Done

What was done: Implemented the Guide / EPG screen in PR #129 (feat/85-epg, head 7d93688841aa0115c2d603cf274153643f0f768e). 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/state for 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/guide remains 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**: Implemented the Guide / EPG screen in PR #129 (`feat/85-epg`, head `7d93688841aa0115c2d603cf274153643f0f768e`). 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/state` for 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/guide` remains 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.
Author
Owner

Done

What was done: Addressed Fable review comment 6382 on PR #129 and pushed commit a8d4979b0ceeb5b236860479d22821831fe77d45. The omitted-nowPlaying crash 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 nowPlaying would deserialize as null, 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/guide is 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.

## Done **What was done**: Addressed Fable review comment 6382 on PR #129 and pushed commit `a8d4979b0ceeb5b236860479d22821831fe77d45`. The omitted-`nowPlaying` crash 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 `nowPlaying` would deserialize as `null`, 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/guide` is 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.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Reference: timothy/ersatztv#85