feat(web): Settings screen (#93) #138

Merged
timothy merged 6 commits from feat/93-settings into main 2026-07-07 09:34:47 +02:00
Owner

closes #93

First screen through the full design-first workflow: prototyped in Claude Design (design_handoff_settings/ bundle synced both ways via DesignSync), then implemented in the SPA.

Backend

  • GET/PUT /api/settings/{ffmpeg,playout,xmltv,scanner,logging,ui,hdhr} — thin wrappers over the existing MediatR settings handlers; response DTOs in ErsatzTV.Core/Api/Settings (#nullable enable), string-enum schemas extended to OutputFormatKind + LogEventLevel.
  • GET/POST /api/settings/resolutions, DELETE /api/settings/resolutions/{id} — custom resolution management (404 unknown / 422 non-custom).
  • Drive-by fix: GET /api/media-sources 500'd on every fresh database (Dapper builds a (long, byte[]) deserializer because SQLite reports COUNT(*) as BLOB with zero rows) — replaced with EF LINQ + regression test.

Frontend

  • SettingsScreen with linkable sub-sections (/app/settings/<section>): General / Streaming / Playout / Guide (XMLTV) / Scanner / Logging / System, per the design handoff (row primitive, floating save bar that PUTs only changed groups, legacy-UI callouts for read-only resources).
  • Degrades gracefully: reference data (profiles, watermarks, sources, health…) loads via allSettled with per-resource failure notes; partial saves merge succeeded groups.
  • web/src/api/settings.ts + generated types; mockDashboardApi extended.

Review

Three-lens adversarial review (correctness fork + contract/tests + design fidelity) → fix pass → independent fork verification: SHIP. Verified live end-to-end against a fresh local server (edit → save → PUT → persisted).

Tests

ErsatzTV.Tests 495/495 · ErsatzTV.Core.Tests 493/493 (+1 skipped, goldens untouched) · web 145/145 · typecheck/lint/build/check:api clean.

🤖 Generated with Claude Code

closes #93 First screen through the full design-first workflow: prototyped in Claude Design (`design_handoff_settings/` bundle synced both ways via DesignSync), then implemented in the SPA. ## Backend - `GET/PUT /api/settings/{ffmpeg,playout,xmltv,scanner,logging,ui,hdhr}` — thin wrappers over the existing MediatR settings handlers; response DTOs in `ErsatzTV.Core/Api/Settings` (`#nullable enable`), string-enum schemas extended to `OutputFormatKind` + `LogEventLevel`. - `GET/POST /api/settings/resolutions`, `DELETE /api/settings/resolutions/{id}` — custom resolution management (404 unknown / 422 non-custom). - Drive-by fix: `GET /api/media-sources` 500'd on every fresh database (Dapper builds a `(long, byte[])` deserializer because SQLite reports `COUNT(*)` as BLOB with zero rows) — replaced with EF LINQ + regression test. ## Frontend - `SettingsScreen` with linkable sub-sections (`/app/settings/<section>`): General / Streaming / Playout / Guide (XMLTV) / Scanner / Logging / System, per the design handoff (row primitive, floating save bar that PUTs only changed groups, legacy-UI callouts for read-only resources). - Degrades gracefully: reference data (profiles, watermarks, sources, health…) loads via `allSettled` with per-resource failure notes; partial saves merge succeeded groups. - `web/src/api/settings.ts` + generated types; `mockDashboardApi` extended. ## Review Three-lens adversarial review (correctness fork + contract/tests + design fidelity) → fix pass → independent fork verification: **SHIP**. Verified live end-to-end against a fresh local server (edit → save → PUT → persisted). ## Tests ErsatzTV.Tests 495/495 · ErsatzTV.Core.Tests 493/493 (+1 skipped, goldens untouched) · web 145/145 · typecheck/lint/build/check:api clean. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
timothy added 6 commits 2026-07-07 09:27:30 +02:00
GET/PUT /api/settings/{ffmpeg,playout,xmltv,scanner,logging,ui,hdhr}
wrapping the existing MediatR settings handlers, plus custom resolution
list/create/delete under /api/settings/resolutions. String-enum OpenAPI
schemas extended to OutputFormatKind + LogEventLevel.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
templates/chicorytv-admin/Settings.jsx wired into app.html;
design_handoff_settings/ carries the implementation spec + API mapping.
Synced to the Claude Design project via DesignSync.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
SettingsScreen with linkable sub-sections (/app/settings/<section>),
settings API module + useSettingsScreenQuery, floating save bar that
PUTs only changed groups, custom resolution add/delete, legacy-UI
callouts for read-only resources. mockDashboardApi extended; 23 new
tests (suite 112 -> 134).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
GET /api/media-sources threw on any empty database: with zero result
rows, Microsoft.Data.Sqlite reports the COUNT(*) column as BLOB, so
Dapper generated a (long, byte[]) deserializer that cannot match
LibraryItemCount(long, long). Replaced the Dapper query with EF LINQ
GroupBy (provider-agnostic, no schema inference) and added an
empty-database regression test.

Also carries the Scriban.Signed 6.5.2 -> 7.2.5 bump (GHSA-5wr9-m6jw-xx44,
PR #137) so this branch builds; redundant once #137 lands on main.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reference data (profiles, watermarks, fillers, media sources, version,
health) now loads via allSettled with per-resource failure notes; only
the settings groups themselves gate the screen. Error state renders
before the loading guard (was unreachable). +2 tests (136 total).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
fix: address #93 adversarial review findings
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 4m51s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 5m21s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
540def7f17
Backend: 401 documented on all mutating settings/resolution routes;
resolution delete distinguishes 404 (unknown) from 422 (not custom);
XMLTV enum bridging via exhaustive switch instead of int casts;
field-level Arg.Is assertions incl. non-null watermark/filler flow.

Frontend: resolution add/delete failures surfaced inline (were
silent); partial saves merge succeeded groups via allSettled; empty
numeric fields invalid + tunerCount min 1; HLS Direct select shows
out-of-list wire values; media-source rows show derived last-scan.

ErsatzTV.Tests 495; web suite 145.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
timothy merged commit 17f49304e5 into main 2026-07-07 09:34:47 +02:00
timothy deleted branch feat/93-settings 2026-07-07 09:34:47 +02:00
Sign in to join this conversation.