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.
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.
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).
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)
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>
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.
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 inErsatzTV.Core/Api/Settings(#nullable enable), string-enum schemas extended toOutputFormatKind+LogEventLevel.GET/POST /api/settings/resolutions,DELETE /api/settings/resolutions/{id}— custom resolution management (404 unknown / 422 non-custom).GET /api/media-sources500'd on every fresh database (Dapper builds a(long, byte[])deserializer because SQLite reportsCOUNT(*)as BLOB with zero rows) — replaced with EF LINQ + regression test.Frontend
SettingsScreenwith 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).allSettledwith per-resource failure notes; partial saves merge succeeded groups.web/src/api/settings.ts+ generated types;mockDashboardApiextended.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
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>