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.
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.
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)
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.
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
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.
**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 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.
**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 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.
**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
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.
**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 main2026-07-18 01:12:20 +02:00
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.
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
HealthCheckLinkwidened(string Link)→(string Target, HealthCheckLinkKind Kind)(ExternalDoc|AppRoute) + factories. Only the 4 link-building checks and the API mapper touched.Link.HealthCheckResponseModelevolved additively (/api/v1frozen-additive): keeps deprecatedstring? Link(still populated), addsBrief(theBriefMessagethe mapper was silently dropping) and nestedRemediation {Kind, Target}.Kindis a mapped string, not a wire enum.Mapper.GetStatusmade total —NotApplicableno longer throws (defensive; the handler still filters it).InternalsVisibleTo(ErsatzTV.Tests)added to unit-test the totality directly.media/trash→/app/trash,search?query=→/app/search); addedAppRouteremediation to actionable checks that had none (libraries / schedules / ffmpeg-profiles / settings).SPA
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)./system/healthrow fromSettingsScreen(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). OpenAPIv1.json/v1.d.ts/endpoint-index.mdregenerated.Verification
dotnet formatgate exit 0; touched.csBOM-free.GET /api/v1/healthserializesbrief+remediation {kind,target}; bothExternalDoc(FFmpeg Version) andAppRoute(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
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-detailflex wrapper became the 3rd grid child, so the pre-existingnth-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 hovertitle). 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,
linkstill 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/healthemitsbrief+remediationwith ExternalDoc & AppRoute both firing.Review-verdict: MERGEABLE @ 7e7388844a1dcd0c4fa1a196f028fb50f8b527ea
7e7388844ato0fc4b26b87Re-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 wasdocs/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:apiclean (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 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 generatedv1.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 myHealthCheckRemediationResponseModel).api-conventions.mdauto-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:apiin sync; BOM-free.Review-verdict: MERGEABLE @ 5ddae4e17e71d689bb2942fa1a79355c04221eb8
0fc4b26b87to5ddae4e17eRe-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 generatedv1.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 myHealthCheckRemediationResponseModel).api-conventions.mdauto-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:apiin sync; BOM-free.Review-verdict: MERGEABLE @ 5ddae4e17e71d689bb2942fa1a79355c04221eb8
5ddae4e17etoed6c43065fVerdict @ current head (
ed6c4306). This head is a no-op re-mint (git commit --amend, byte-identical tree to the reviewed5ddae4e1— empty diffstat) pushed only to re-trigger CI after thedecisions.md append-onlyjob 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 oned6c4306is now all green (incl. decisions.md append-only). No source changed from the reviewed diff.Review-verdict: MERGEABLE @
ed6c43065f