feat(web): ChicoryTV SPA foundation (#59 stack) #120

Merged
timothy merged 37 commits from feat/59-spa-foundation into main 2026-07-04 08:11:15 +02:00
Owner

Lands the ChicoryTV SPA stack (docs/59-ui-redesign-brief + PR #112) on main, reconciled with the post-#119 backend API.

Contents

  • #59 brief + design docs, design-system/ (imported Claude Design system)
  • React SPA foundation in web/: scaffold + build integration (#78), design tokens + 3 themes (#79), component primitives (#80), typed API client + auth (#81), app shell (#82), Dashboard screen (#83)
  • Backend: enriched channel list API (#96), bulk channel operations (#98), channel on-air/now-playing state endpoint (#97, via PR #112)

Reconciliation

  • Single merge of main (6f6f37b7) into the stack; conflicts resolved as union (branch bulk/state endpoints + main's /api/guide etc.). Read-only review confirmed: no dropped work, no duplicated routes/tests.
  • v1.json + web/src/api/generated/v1.d.ts regenerated from the merged code (union of both sides' API surface).
  • Follow-up fix included: #nullable enable on #96/#98 DTOs per repo convention (removes ["null",...] unions from spec + SPA types).

Verification: dotnet build 0 errors; ErsatzTV.Tests 364 green; Core.Tests 488 green (+1 pre-existing skip); web npm run typecheck + build clean.

closes #97

🤖 Generated with Claude Code

Lands the ChicoryTV SPA stack (docs/59-ui-redesign-brief + PR #112) on main, reconciled with the post-#119 backend API. **Contents** - #59 brief + design docs, `design-system/` (imported Claude Design system) - React SPA foundation in `web/`: scaffold + build integration (#78), design tokens + 3 themes (#79), component primitives (#80), typed API client + auth (#81), app shell (#82), Dashboard screen (#83) - Backend: enriched channel list API (#96), bulk channel operations (#98), channel on-air/now-playing state endpoint (#97, via PR #112) **Reconciliation** - Single merge of main (6f6f37b7) into the stack; conflicts resolved as union (branch bulk/state endpoints + main's `/api/guide` etc.). Read-only review confirmed: no dropped work, no duplicated routes/tests. - `v1.json` + `web/src/api/generated/v1.d.ts` regenerated from the merged code (union of both sides' API surface). - Follow-up fix included: `#nullable enable` on #96/#98 DTOs per repo convention (removes `["null",...]` unions from spec + SPA types). **Verification**: `dotnet build` 0 errors; ErsatzTV.Tests 364 green; Core.Tests 488 green (+1 pre-existing skip); web `npm run typecheck` + `build` clean. closes #97 🤖 Generated with [Claude Code](https://claude.com/claude-code)
timothy added 37 commits 2026-07-04 08:03:13 +02:00
Records the locked design direction for #59: React SPA on the REST API,
API-first (design pulls the API), Komodo-slick look, ChicoryTV dark +
chicory-blue brand. Includes the 4 hero screens with real domain
vocabulary and ready-to-paste Claude Design prompts.

Refs #59

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replaces the inherited 4-editor channel-creation chain with a single
library-to-lineup builder: browse/search library with artwork, drag
shows into the lineup, friendly toggles (shuffle, always-on), one image
for logo+bug, technical settings via built-in/custom Channel Templates
with advanced override. Lists prerequisite backend gaps (composite
create endpoint, Channel Templates, library browse API, full image-type
support incl. transparent re-encode, unified logo/bug, always-on vs
on-demand) and a Claude Design prompt for the screen.

Refs #59

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Refs #59 #62

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds the Auto-tuning wizard as hero screen 5 (#69) and annotates the
Channels-list and Schedule-editor sections + prompts with the borrowed
enrichments: lineup health + resume badge (#72, #68), weighted
distribution / reroll / pad-to-boundary / seasonal (#70, #71, #77, #73).

Refs #59 #69

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
refs #78
refs #79
refs #80
refs #81
refs #82
refs #83
refs #94
refs #95
fixes #98
Living handoff document driving the sequential backend-gap work
(#97, #100-#111, merge pass, then UX screens). Each session works
one queue item and rewrites this file for the next.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
An off-air channel has no current playout item, so the handler returns
null for NowPlaying; the DTO now declares that (file-scoped #nullable —
ErsatzTV.Core has NRT disabled project-wide). Regenerated OpenAPI and
web types: nowPlaying keeps its nullable oneOf schema (matching the
schema contract test) and channelNumber loses a spurious nullable
annotation left over from an earlier draft — the field is required.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
fix(api): surface programs not filler in channel state, tighten queries
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 4m40s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 4m52s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
73455aae28
Adversarial review fixes for the #97 endpoint:

- nowPlaying now resolves like the XMLTV guide: when the wall-clock item
  is pre/mid-roll filler, surface the guide group's program item (title +
  full program bounds) instead of the filler; a guide entry with no
  program item (e.g. fallback loop) reports null. Previously an ad break
  would retitle the channel and reset progress every 30 seconds.
- covering-item lookup now runs one cheap projected query per distinct
  playout offset instead of hydrating the full metadata include tree for
  a window widened by the largest offset across ALL channels (up to 24h);
  metadata is fetched in a second query scoped to the covering guide
  groups, time-bounded to stay safe against GuideGroup recycling (mod
  10000). Regression caught while testing: covering items are keyed by
  (source channel, lookup time) so a source channel no longer inherits
  its offset mirror's lookup result.
- GetChannelStatesForApi now carries Now explicitly (controller passes
  DateTime.UtcNow), making boundary semantics testable: new tests pin
  now == Start (playing) and now == Finish (not playing), plus mid-roll,
  filler-only, and mirror-source-row cases.
- ChannelNowPlayingResponseModel title is non-nullable in the schema
  (#nullable enable) — GetDisplayTitle always returns a string.
- GetState uses the typed-return controller convention; Playouts.Mapper
  reverted to internal (handler lives in the same assembly).
- Spec doc notes the OnDemand drift caveat and filler semantics.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
# Conflicts:
#	ErsatzTV.Tests/Controllers/ChannelControllerTests.cs
#	ErsatzTV/Controllers/Api/ChannelController.cs
#	ErsatzTV/wwwroot/openapi/v1.json
fix(api): add nullable context to #96/#98 DTOs; regen OpenAPI + SPA types
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 3m57s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 4m53s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
8d89ab1624
ChannelResponseModel and BulkChannelRequests DTOs lacked file-scoped
#nullable enable, so the OpenAPI generator emitted every string
property as a ["null","string"] union (project Nullable default is
disable), polluting SPA typegen with needless `| null` unions.
ChannelStateResponseModel already complied with this convention
(precedent: PlayoutResponseModel). All properties in both files are
always populated (never null in practice), so no property needed to
become explicitly optional/nullable - just adding #nullable enable
was sufficient to clean up the generated schema.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
timothy merged commit 1b6de047a0 into main 2026-07-04 08:11:15 +02:00
Sign in to join this conversation.