feat(164): guided remediation for health checks #430

Merged
timothy merged 1 commits from feat/164-health-checks-ux into main 2026-07-18 01:12:20 +02:00
Owner

Makes the ~14 backend health checks actionable (issue #164): each check that has a fix now declares where to go via server metadata, and the SPA renders it as a one-click remediation.

What shipped

Backend

  • Domain HealthCheckLink widened (string Link) → (string Target, HealthCheckLinkKind Kind) (ExternalDoc | AppRoute) + factories. Only the 4 link-building checks and the API mapper touched .Link.
  • HealthCheckResponseModel evolved additively (/api/v1 frozen-additive): keeps deprecated string? Link (still populated), adds Brief (the BriefMessage the mapper was silently dropping) and nested Remediation {Kind, Target}. Kind is a mapped string, not a wire enum.
  • Mapper.GetStatus made total — NotApplicable no longer throws (defensive; the handler still filters it). InternalsVisibleTo(ErsatzTV.Tests) added to unit-test the totality directly.
  • Fixed 2 stale Blazor route links (media/trash → /app/trash, search?query= → /app/search); added AppRoute remediation to actionable checks that had none (libraries / schedules / ffmpeg-profiles / settings).

SPA

  • Dashboard health panel renders remediation: AppRoute → client-side nav button, ExternalDoc → new-tab anchor; long detail text truncates with a hover title (restored the ellipsis affordance the flex wrapper broke — from review).
  • Removed the dead "Open Classic UI" → /system/health row from SettingsScreen (a #91b leftover that just 302'd to /app); regression test updated.

Docs: decisions.md (#164), api-conventions.md (deprecate-in-place DTO evolution), blazor-route-parity.md (Section 4 correction). OpenAPI v1.json / v1.d.ts / endpoint-index.md regenerated.

Verification

  • Full solution build clean; entire C# test suite passes; SPA vitest 796 pass; typecheck + lint clean; dotnet format gate exit 0; touched .cs BOM-free.
  • Independent adversarial review: MERGEABLE — its one finding (CSS ellipsis regression) fixed in this PR.
  • Live-verified on a booted instance: GET /api/v1/health serializes brief + remediation {kind,target}; both ExternalDoc (FFmpeg Version) and AppRoute (Hardware Acceleration → /app/ffmpeg-profiles) observed firing.

Deferred (own issue)

A TTL cache for PerformHealthChecks (#108 — every request re-runs all 14 checks, 4 shelling out to ffmpeg; existing summary cache is write-only dead code). Orthogonal to the UX; will be filed separately.

fixes #164

🤖 Generated with Claude Code

Makes the ~14 backend health checks **actionable** (issue #164): each check that has a fix now declares *where to go* via server metadata, and the SPA renders it as a one-click remediation. ## What shipped **Backend** - Domain `HealthCheckLink` widened `(string Link)` → `(string Target, HealthCheckLinkKind Kind)` (`ExternalDoc` | `AppRoute`) + factories. Only the 4 link-building checks and the API mapper touched `.Link`. - `HealthCheckResponseModel` evolved **additively** (`/api/v1` frozen-additive): keeps deprecated `string? Link` (still populated), adds `Brief` (the `BriefMessage` the mapper was silently dropping) and nested `Remediation {Kind, Target}`. `Kind` is a mapped string, not a wire enum. - `Mapper.GetStatus` made **total** — `NotApplicable` no longer throws (defensive; the handler still filters it). `InternalsVisibleTo(ErsatzTV.Tests)` added to unit-test the totality directly. - Fixed 2 **stale Blazor route links** (`media/trash` → `/app/trash`, `search?query=` → `/app/search`); added `AppRoute` remediation to actionable checks that had none (libraries / schedules / ffmpeg-profiles / settings). **SPA** - Dashboard health panel renders remediation: `AppRoute` → client-side nav button, `ExternalDoc` → new-tab anchor; long detail text truncates with a hover title (restored the ellipsis affordance the flex wrapper broke — from review). - Removed the dead **"Open Classic UI" → `/system/health`** row from `SettingsScreen` (a #91b leftover that just 302'd to `/app`); regression test updated. **Docs**: `decisions.md` (#164), `api-conventions.md` (deprecate-in-place DTO evolution), `blazor-route-parity.md` (Section 4 correction). OpenAPI `v1.json` / `v1.d.ts` / `endpoint-index.md` regenerated. ## Verification - Full solution build clean; **entire C# test suite passes**; SPA vitest **796 pass**; typecheck + lint clean; `dotnet format` gate exit 0; touched `.cs` BOM-free. - **Independent adversarial review: MERGEABLE** — its one finding (CSS ellipsis regression) fixed in this PR. - **Live-verified** on a booted instance: `GET /api/v1/health` serializes `brief` + `remediation {kind,target}`; both `ExternalDoc` (FFmpeg Version) and `AppRoute` (Hardware Acceleration → `/app/ffmpeg-profiles`) observed firing. ## Deferred (own issue) A TTL cache for `PerformHealthChecks` (#108 — every request re-runs all 14 checks, 4 shelling out to ffmpeg; existing summary cache is write-only dead code). Orthogonal to the UX; will be filed separately. fixes #164 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Author
Owner

Independent cold-context adversarial review complete (read the full diff: domain/DTO/mapper, all 10 edited checks, SPA render + dead-link removal, tests, docs).

Verdict: MERGEABLE. One Medium finding — the .ctv-health-detail flex wrapper became the 3rd grid child, so the pre-existing nth-child(3) ellipsis rule no longer truncated long detail text — was fixed in-PR exactly as prescribed (moved the ellipsis triad onto the inner text span .ctv-health-detail > span:first-child, plus a hover title). That fix is included in the head commit below.

Verified correct by the reviewer: mapper totality (NotApplicable no longer throws) + its test, additive DTO back-compat (positional order consistent, link still populated), per-check link placement (link only on Fail/Warning branches, never Ok/Info/NotApplicable — enumerated all 10), AppRoute targets are real SPA routes, dead-link removal has no dangling imports, and all new tests are non-vacuous. Live-verified: GET /api/v1/health emits brief+remediation with ExternalDoc & AppRoute both firing.

Review-verdict: MERGEABLE @ 7e7388844a1dcd0c4fa1a196f028fb50f8b527ea

Independent cold-context adversarial review complete (read the full diff: domain/DTO/mapper, all 10 edited checks, SPA render + dead-link removal, tests, docs). **Verdict: MERGEABLE.** One Medium finding — the `.ctv-health-detail` flex wrapper became the 3rd grid child, so the pre-existing `nth-child(3)` ellipsis rule no longer truncated long detail text — was fixed in-PR exactly as prescribed (moved the ellipsis triad onto the inner text span `.ctv-health-detail > span:first-child`, plus a hover `title`). That fix is included in the head commit below. Verified correct by the reviewer: mapper totality (NotApplicable no longer throws) + its test, additive DTO back-compat (positional order consistent, `link` still populated), per-check link placement (link only on Fail/Warning branches, never Ok/Info/NotApplicable — enumerated all 10), AppRoute targets are real SPA routes, dead-link removal has no dangling imports, and all new tests are non-vacuous. Live-verified: `GET /api/v1/health` emits `brief`+`remediation` with ExternalDoc & AppRoute both firing. Review-verdict: MERGEABLE @ 7e7388844a1dcd0c4fa1a196f028fb50f8b527ea
timothy force-pushed feat/164-health-checks-ux from 7e7388844a to 0fc4b26b87 2026-07-17 23:29:04 +02:00 Compare
Author
Owner

Re-verdict after rebase onto origin/main (ba707f2e). origin/main advanced 7 commits (all CI/docs: H13 pre-push guard #424, review-before-CI lore #426, #416 shallow-detect fix) during the Gitea maintenance window. Rebased; the only conflict was docs/decisions.md (append-only — resolved by keeping both the landed #416/H13 entries and my #164 entry in chronological order). The code diff is byte-identical to the previously-reviewed head 7e738884 (30 files, +401/-44 unchanged); no source changed.

Post-rebase re-verification on the new base: build 0 errors, health tests 16/16, npm run check:api clean (generated artifacts in sync), touched .cs BOM-free.

The prior MERGEABLE adversarial review (with its one CSS finding already fixed) therefore stands for this head.

Review-verdict: MERGEABLE @ 0fc4b26b8704b67d0dc30e2ac51fb70c204fba28

**Re-verdict after rebase onto origin/main (`ba707f2e`).** origin/main advanced 7 commits (all CI/docs: H13 pre-push guard #424, review-before-CI lore #426, #416 shallow-detect fix) during the Gitea maintenance window. Rebased; the **only** conflict was `docs/decisions.md` (append-only — resolved by keeping both the landed #416/H13 entries and my #164 entry in chronological order). The **code diff is byte-identical** to the previously-reviewed head 7e738884 (30 files, +401/-44 unchanged); no source changed. Post-rebase re-verification on the new base: build 0 errors, health tests 16/16, `npm run check:api` clean (generated artifacts in sync), touched .cs BOM-free. The prior MERGEABLE adversarial review (with its one CSS finding already fixed) therefore stands for this head. Review-verdict: MERGEABLE @ 0fc4b26b8704b67d0dc30e2ac51fb70c204fba28
Author
Owner

Re-verdict after 2nd rebase — now onto 1abc5df1 (includes #385 auto-tune per-channel overrides, merged ~30 min ago). origin/main moved again during the previous CI run. Conflicts this round: decisions.md (append — kept #385 + #164 entries), and the generated v1.json/v1.d.ts/endpoint-index.md (auto-merged by git, then regenerated from the merged source per the generated-artifact rule — the spec now carries both #385's DTOs and my HealthCheckRemediationResponseModel). api-conventions.md auto-merged (both #385's and my additions present). My health source is unchanged from the reviewed head.

Full local gate re-run on the rebased tree: solution build 0 errors; entire C# suite green (1691 pass, incl. #385's tests); SPA typecheck clean; affected screen tests 30/30; check:api in sync; BOM-free.

Review-verdict: MERGEABLE @ 5ddae4e17e71d689bb2942fa1a79355c04221eb8

**Re-verdict after 2nd rebase — now onto `1abc5df1` (includes #385 auto-tune per-channel overrides, merged ~30 min ago).** origin/main moved again during the previous CI run. Conflicts this round: `decisions.md` (append — kept #385 + #164 entries), and the generated `v1.json`/`v1.d.ts`/`endpoint-index.md` (auto-merged by git, then **regenerated from the merged source** per the generated-artifact rule — the spec now carries both #385's DTOs and my `HealthCheckRemediationResponseModel`). `api-conventions.md` auto-merged (both #385's and my additions present). My health source is unchanged from the reviewed head. Full local gate re-run on the rebased tree: solution build 0 errors; **entire C# suite green (1691 pass, incl. #385's tests)**; SPA typecheck clean; affected screen tests 30/30; `check:api` in sync; BOM-free. Review-verdict: MERGEABLE @ 5ddae4e17e71d689bb2942fa1a79355c04221eb8
timothy force-pushed feat/164-health-checks-ux from 0fc4b26b87 to 5ddae4e17e 2026-07-18 00:30:19 +02:00 Compare
Author
Owner

Re-verdict after 2nd rebase — now onto 1abc5df1 (includes #385 auto-tune per-channel overrides, merged ~30 min ago). origin/main moved again during the previous CI run. Conflicts this round: decisions.md (append — kept #385 + #164 entries) and the generated v1.json/v1.d.ts/endpoint-index.md (auto-merged by git, then regenerated from the merged source per the generated-artifact rule — the spec now carries both #385's DTOs and my HealthCheckRemediationResponseModel). api-conventions.md auto-merged (both additions present). My health source is unchanged from the reviewed head.

Full local gate on the rebased tree: solution build 0 errors; entire C# suite green (1691 pass, incl. #385's tests); SPA typecheck clean; affected screen tests 30/30; check:api in sync; BOM-free.

Review-verdict: MERGEABLE @ 5ddae4e17e71d689bb2942fa1a79355c04221eb8

**Re-verdict after 2nd rebase — now onto `1abc5df1` (includes #385 auto-tune per-channel overrides, merged ~30 min ago).** origin/main moved again during the previous CI run. Conflicts this round: `decisions.md` (append — kept #385 + #164 entries) and the generated `v1.json`/`v1.d.ts`/`endpoint-index.md` (auto-merged by git, then **regenerated from the merged source** per the generated-artifact rule — the spec now carries both #385's DTOs and my `HealthCheckRemediationResponseModel`). `api-conventions.md` auto-merged (both additions present). My health source is unchanged from the reviewed head. Full local gate on the rebased tree: solution build 0 errors; **entire C# suite green (1691 pass, incl. #385's tests)**; SPA typecheck clean; affected screen tests 30/30; `check:api` in sync; BOM-free. Review-verdict: MERGEABLE @ 5ddae4e17e71d689bb2942fa1a79355c04221eb8
timothy force-pushed feat/164-health-checks-ux from 5ddae4e17e to ed6c43065f 2026-07-18 00:51:43 +02:00 Compare
Author
Owner

Verdict @ current head (ed6c4306). This head is a no-op re-mint (git commit --amend, byte-identical tree to the reviewed 5ddae4e1 — empty diffstat) pushed only to re-trigger CI after the decisions.md append-only job was killed by a runner restart during the maintenance/churn on the prior head (its log file was missing on the server and it "ran" 10 min for a seconds-long git-diff — a killed job, not a real violation; the guard passes locally against full history, 0 deletions). CI on ed6c4306 is now all green (incl. decisions.md append-only). No source changed from the reviewed diff.

Review-verdict: MERGEABLE @ ed6c43065f

**Verdict @ current head (`ed6c4306`).** This head is a no-op re-mint (`git commit --amend`, **byte-identical tree** to the reviewed `5ddae4e1` — empty diffstat) pushed only to re-trigger CI after the `decisions.md append-only` job was **killed by a runner restart** during the maintenance/churn on the prior head (its log file was missing on the server and it "ran" 10 min for a seconds-long git-diff — a killed job, not a real violation; the guard passes locally against full history, 0 deletions). CI on `ed6c4306` is now **all green** (incl. decisions.md append-only). No source changed from the reviewed diff. Review-verdict: MERGEABLE @ ed6c43065fa73a8b077ac1de601d5b377481e28f
timothy merged commit 0a34d1a585 into main 2026-07-18 01:12:20 +02:00
timothy deleted branch feat/164-health-checks-ux 2026-07-18 01:12:21 +02:00
Sign in to join this conversation.