style(centralui): full-app bg-light/bg-white -> theme-aware utility sweep (M10 residual)

T34c only fixed the bounded modal-surface offenders. Bootstrap 5.3's bg-light
and bg-white are fixed light values that do NOT flip under [data-bs-theme=dark],
so every remaining use was a dark-mode contrast break. 35 swaps across 19 files:

  surface / <pre> / <code>  bg-light                -> bg-body-secondary
  panel                     bg-white                -> bg-body
  neutral badge             bg-light text-dark      -> bg-secondary-subtle text-secondary-emphasis
  muted badge / input group bg-light text-muted     -> bg-body-secondary text-body-secondary

DELIBERATELY LEFT (7 sites): the neutral member of a status-badge switch or
ternary whose siblings are all solid, non-theme-aware colours (bg-success,
bg-danger, bg-warning) — swapping only the neutral one to a subtle token breaks
the visual weight of the set, so these stay until the whole family is retoned:
  Topology.razor:513 (Current badge) and :588 (InstanceState.NotDeployed)
  InstanceConfigure.razor:1520 (same InstanceState switch)
  NotificationReport.razor StatusBadgeClass fallback
  TransportImport.razor ConflictKind badge fallback
  SecuredWrites.razor text-bg-light fallback (a text-bg-* family with no
    theme-aware member at all)
  Health.razor:377 depth ternary
Also untouched by design: SchemaBuilder.razor:88 (already bg-light-subtle,
theme-aware), bg-dark text-light console panels, and site.css / #reconnect-modal.

InstanceConfigure.razor is edited concurrently elsewhere; its change here is 5
pure class-string swaps on existing lines, no reflow. No test asserted any of
these classes. Also marks all four M10 residuals done in the plan doc.
This commit is contained in:
Joseph Doherty
2026-08-01 11:27:13 -04:00
parent 316153dc98
commit 410349767d
18 changed files with 39 additions and 39 deletions
+6 -6
View File
@@ -459,15 +459,15 @@ Markup: two `<input type="datetime-local">` with `id="@(IdPrefix)-from"`/`-to`,
## Deferred / out of scope (log as FOLLOWUPs at INT)
- Unified offset+keyset pagination framework (blocked by total-count mismatch).
- `Deployments.razor` PagerWindow → OffsetPager (different windowed UX; intentionally left).
- Complex `TemplateEdit` page-embedded modals → host migration.
- TreeView arrow-key navigation (R7).
- ~~Complex `TemplateEdit` page-embedded modals → host migration.~~ **DONE 2026-08-01.**
- ~~TreeView arrow-key navigation (R7).~~ **DONE 2026-08-01.**
- Theme-package side-rail dark theming, IF the spike verdict is "rail stays light" (coordination follow-up).
## Follow-ups logged at delivery (INT findings)
- **#207 — FIXED (2026-08-01 bookkeeping sync).** ~~(pre-existing, open since M6/K14):~~ `QueryStringDrillInTests` now registers the `IKpiHistoryQueryService` substitute in its fixture (lines ~165-167), so the 3 `SiteCallsReport` drill-in tests are green.
- **#163 (pre-existing):** `InstanceConfigureListOverrideTests` codec roundtrip red; pre-dates M10.
- **NotificationReport `OffsetPager` always-visible:** pager is now always visible when results exist (previously hidden on sub-page-size sets); buttons are correctly disabled on a single page — product decision whether to re-add an `@if (_totalCount > _pageSize)` guard.
- **NotificationReport `OffsetPager` always-visible — DECIDED + DONE (2026-08-01).** A pager whose only controls are permanently disabled is noise, so the conventional behaviour ships: the bar is hidden on a provably single page. Implemented **reusably but opt-in** — `OffsetPager.HideWhenSinglePage` (default `false`, so no consumer changes behaviour implicitly), set `true` at the `NotificationReport` call site. It is NOT defaulted on because the pager's summary span doubles as the "N total" result-count readout on `ConfigurationAuditLog`, which has no other place to show it (and whose Playwright fixture asserts `3 total` on a 3-row single page). The guard only fires when single-page-ness is *positively* established (`PageCount <= 1` **and** `Page <= 1` **and** `!HasNextPage`), so a null `TotalCount` still renders the bar.
- **`Deployments.razor` PagerWindow intentionally kept:** windowed numbered-button UX is deliberate; NOT migrated to `OffsetPager`.
- **`TemplateEdit` inline modals NOT migrated:** the page-embedded modals (wave-3 T34c already tokenized their backdrops); full migration to the host is deferred.
- **TreeView full arrow-key navigation (R7) still deferred.**
- **Full-app `bg-light`/`bg-white` → theme-aware utility sweep deferred:** only the bounded modal-surface offenders were addressed in T34c; INT dark-mode smoke may surface additional instances.
- **`TemplateEdit` inline modals — MIGRATED (2026-08-01).** All four page-embedded modals (Attribute, Alarm, Native Alarm Source, Script) now open through `IDialogService.ShowAsync`, so the host owns the backdrop, focus trap, Escape and focus restoration. Each body was extracted to its own component next to the page — `TemplateAttributeDialog`, `TemplateAlarmDialog`, `TemplateNativeAlarmSourceDialog`, `TemplateScriptDialog` — following the `MoveDataConnectionDialog` pattern: the body owns the form state and renders validation/server errors INLINE while staying open, closing with `Close(true)` only on a successful save. Persistence stayed on the page (it owns `TemplateService` and the inherited-member rules) and is reached through an `OnSaveAsync` delegate returning `null` on success or the message to display. The script dialog keeps all four tab panels mounted (Monaco/JSONJoy must not tear down on tab switch) and hosts the Test Run panel, which needs the live unsaved buffer. Extraction moved markup that three structural source-scanning tests pinned (`TemplateNativeAlarmSourceEditorTests`, `AttributeListEditorTests`, `TestRunWarningTests`) — all three were repointed at the new files.
- **~~TreeView full arrow-key navigation (R7) still deferred.~~ DONE 2026-08-01** — the WAI-ARIA tree pattern (roving tabindex, Arrow/Home/End movement, Enter/Space activation, `aria-level`) now ships on `TreeView<TItem>`; see `docs/components/TreeView.md`.
- **Full-app `bg-light`/`bg-white` → theme-aware utility sweep — DONE 2026-08-01.** 35 class swaps across 19 Central UI files: surfaces/`<pre>`/`<code>` `bg-light` → `bg-body-secondary`, panel `bg-white` → `bg-body`, neutral `badge bg-light text-dark` → `badge bg-secondary-subtle text-secondary-emphasis`, `bg-light text-muted border` → `bg-body-secondary text-body-secondary border`. **Deliberately left** (7 sites): the neutral member of a *status-badge* switch/ternary whose siblings are all solid non-theme-aware colours (`bg-success`/`bg-danger`/`bg-warning`), where swapping only the neutral one breaks the set's visual weight — `Topology.razor:513,588`, `InstanceConfigure.razor:1520`, `NotificationReport.razor` `StatusBadgeClass` fallback, `TransportImport.razor` ConflictKind fallback, `SecuredWrites.razor` `text-bg-light` fallback, `Health.razor:377`. `SchemaBuilder.razor:88` was already `bg-light-subtle` (theme-aware). `bg-dark text-light` console panels and `site.css`/`#reconnect-modal` are intentional fixed colours.
@@ -67,7 +67,7 @@
}
@if (!string.IsNullOrEmpty(Event.ErrorDetail))
{
<pre class="bg-light border rounded p-2 mb-0 drawer-pre">@Event.ErrorDetail</pre>
<pre class="bg-body-secondary border rounded p-2 mb-0 drawer-pre">@Event.ErrorDetail</pre>
}
</section>
}
@@ -119,7 +119,7 @@
{
<section class="mb-3" data-test="section-extra">
<h6 class="text-uppercase text-muted small fw-semibold mb-1">Extra</h6>
<pre class="bg-light border rounded p-2 mb-0 drawer-pre json">@PrettyPrintJson(Event.Extra!)</pre>
<pre class="bg-body-secondary border rounded p-2 mb-0 drawer-pre json">@PrettyPrintJson(Event.Extra!)</pre>
</section>
}
@@ -56,7 +56,7 @@
<small class="text-muted ms-1">@match.Path</small>
@if (!string.IsNullOrEmpty(node.DataType))
{
<span class="badge bg-light text-muted border ms-1">@node.DataType</span>
<span class="badge bg-body-secondary text-body-secondary border ms-1">@node.DataType</span>
}
</li>
}
@@ -21,7 +21,7 @@
</a>
@if (!string.IsNullOrEmpty(Node.DataType))
{
<span class="badge bg-light text-muted border ms-1" data-test="node-type">@Node.DataType</span>
<span class="badge bg-body-secondary text-body-secondary border ms-1" data-test="node-type">@Node.DataType</span>
}
}
else
@@ -79,7 +79,7 @@
@if (result.FailureKind == VerifyFailureKind.UntrustedCertificate
&& result.Cert is { } cert)
{
<div class="border rounded bg-light p-2 mt-2 small" data-test="verify-cert-panel">
<div class="border rounded bg-body-secondary p-2 mt-2 small" data-test="verify-cert-panel">
<div class="text-muted mb-1">Untrusted server certificate</div>
<dl class="row mb-1 small">
<dt class="col-sm-3">Subject</dt>
@@ -155,7 +155,7 @@
{
<tr>
<td colspan="7">
<pre class="bg-light p-2 rounded small mb-0" style="max-height: 200px; overflow: auto;">@FormatJson(entry.AfterStateJson!)</pre>
<pre class="bg-body-secondary p-2 rounded small mb-0" style="max-height: 200px; overflow: auto;">@FormatJson(entry.AfterStateJson!)</pre>
</td>
</tr>
}
@@ -179,7 +179,7 @@
<button type="button" class="btn-close" @onclick="CloseStateModal" aria-label="Close"></button>
</div>
<div class="modal-body">
<pre class="bg-light p-2 rounded small mb-0">@FormatJson(_modalEntry.AfterStateJson!)</pre>
<pre class="bg-body-secondary p-2 rounded small mb-0">@FormatJson(_modalEntry.AfterStateJson!)</pre>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary btn-sm" @onclick="CloseStateModal">Close</button>
@@ -240,13 +240,13 @@
<tr data-test="override-row-@attr.Name">
<td class="small">@attr.Name</td>
<td>
<span class="badge bg-light text-dark">@attr.DataType</span>
<span class="badge bg-secondary-subtle text-secondary-emphasis">@attr.DataType</span>
@if (attr.DataType == DataType.List)
{
@* Element type is fixed by the base attribute — shown
read-only here (the List editor renders it hidden via
ShowElementType="false"). *@
<span class="badge bg-light text-dark border ms-1"
<span class="badge bg-secondary-subtle text-secondary-emphasis border ms-1"
data-test="override-element-type">
of @(attr.ElementDataType ?? DataType.String)
</span>
@@ -326,7 +326,7 @@
<tr data-test="alarm-override-row-@alarm.Name">
<td class="small">@alarm.Name</td>
<td>
<span class="badge bg-light text-dark border">@alarm.TriggerType</span>
<span class="badge bg-secondary-subtle text-secondary-emphasis border">@alarm.TriggerType</span>
</td>
<td class="small text-muted text-truncate font-monospace" style="max-width: 280px;"
title="@alarm.TriggerConfiguration">
@@ -373,14 +373,14 @@
<div class="modal-header">
<h6 class="modal-title">
Edit override: @_editingAlarm.Name
<span class="badge bg-light text-dark border ms-1">@_editingAlarm.TriggerType</span>
<span class="badge bg-secondary-subtle text-secondary-emphasis border ms-1">@_editingAlarm.TriggerType</span>
</h6>
<button type="button" class="btn-close" aria-label="Close" @onclick="CancelEditOverride"></button>
</div>
<div class="modal-body">
<div class="mb-3 small">
<div class="text-muted text-uppercase fw-semibold mb-1">Inherited from template</div>
<code class="d-block bg-light p-2 rounded text-break">@(_editingAlarm.TriggerConfiguration ?? "(none)")</code>
<code class="d-block bg-body-secondary p-2 rounded text-break">@(_editingAlarm.TriggerConfiguration ?? "(none)")</code>
</div>
<div class="mb-3">
@@ -148,9 +148,9 @@
{
<div class="mb-3">
<label class="form-label small text-success mb-1">
Return value <span class="badge bg-light text-dark ms-1">@_runResult.ReturnTypeName</span>
Return value <span class="badge bg-secondary-subtle text-secondary-emphasis ms-1">@_runResult.ReturnTypeName</span>
</label>
<pre class="bg-light border rounded p-2 small mb-0 font-monospace" style="white-space: pre-wrap;">@_runResult.ReturnValueJson</pre>
<pre class="bg-body-secondary border rounded p-2 small mb-0 font-monospace" style="white-space: pre-wrap;">@_runResult.ReturnValueJson</pre>
</div>
}
else
@@ -111,7 +111,7 @@
Backup endpoint
@if (!_showBackup)
{
<span class="badge bg-light text-muted border ms-2">Optional</span>
<span class="badge bg-body-secondary text-body-secondary border ms-2">Optional</span>
}
</h6>
@if (!_showBackup)
@@ -175,8 +175,8 @@
<p class="small text-muted text-truncate mb-1" title="@es.EndpointUrl">@es.EndpointUrl</p>
<div>
<span class="badge bg-secondary me-1">@es.AuthType</span>
<span class="badge bg-light text-dark me-1">Max @es.MaxRetries retries</span>
<span class="badge bg-light text-dark">Delay @es.RetryDelay.TotalSeconds s</span>
<span class="badge bg-secondary-subtle text-secondary-emphasis me-1">Max @es.MaxRetries retries</span>
<span class="badge bg-secondary-subtle text-secondary-emphasis">Delay @es.RetryDelay.TotalSeconds s</span>
</div>
</div>
</div>
@@ -248,8 +248,8 @@
@* Connection strings carry credentials — never rendered here (not even for admins). Shown only on the create/edit form. *@
<p class="small text-muted fst-italic mb-1">Connection string hidden — edit to view</p>
<div>
<span class="badge bg-light text-dark me-1">Max @dc.MaxRetries retries</span>
<span class="badge bg-light text-dark">Delay @dc.RetryDelay.TotalSeconds s</span>
<span class="badge bg-secondary-subtle text-secondary-emphasis me-1">Max @dc.MaxRetries retries</span>
<span class="badge bg-secondary-subtle text-secondary-emphasis">Delay @dc.RetryDelay.TotalSeconds s</span>
</div>
</div>
</div>
@@ -325,7 +325,7 @@
<pre class="small text-muted font-monospace mb-2"
style="white-space: pre-wrap; word-break: break-all;"
title="@m.Script">@preview</pre>
<span class="badge bg-light text-dark">Timeout @m.TimeoutSeconds s</span>
<span class="badge bg-secondary-subtle text-secondary-emphasis">Timeout @m.TimeoutSeconds s</span>
</div>
</div>
</div>
@@ -141,9 +141,9 @@
{
<div class="mb-3">
<label class="form-label small text-success mb-1">
Return value <span class="badge bg-light text-dark ms-1">@_runResult.ReturnTypeName</span>
Return value <span class="badge bg-secondary-subtle text-secondary-emphasis ms-1">@_runResult.ReturnTypeName</span>
</label>
<pre class="bg-light border rounded p-2 small mb-0 font-monospace" style="white-space: pre-wrap;">@_runResult.ReturnValueJson</pre>
<pre class="bg-body-secondary border rounded p-2 small mb-0 font-monospace" style="white-space: pre-wrap;">@_runResult.ReturnValueJson</pre>
</div>
}
else
@@ -87,8 +87,8 @@
title="@s.Code">@preview</pre>
<div>
<span class="badge bg-light text-dark me-1">@paramCount params</span>
<span class="badge bg-light text-dark">
<span class="badge bg-secondary-subtle text-secondary-emphasis me-1">@paramCount params</span>
<span class="badge bg-secondary-subtle text-secondary-emphasis">
@(returnLabel == "void" ? "void" : $"returns {returnLabel}")
</span>
</div>
@@ -295,7 +295,7 @@
</dl>
<div class="text-muted text-uppercase small fw-semibold mb-1">Error</div>
<pre class="bg-light border rounded p-2 small mb-0 parked-error-pre">@_drawerMessage.ErrorMessage</pre>
<pre class="bg-body-secondary border rounded p-2 small mb-0 parked-error-pre">@_drawerMessage.ErrorMessage</pre>
</div>
<div class="border-top p-3 d-flex gap-2">
<button class="btn btn-outline-success btn-sm flex-grow-1"
@@ -74,7 +74,7 @@
var contact = list.Type == NotificationType.Sms
? r.PhoneNumber
: r.EmailAddress;
<span class="badge bg-light text-dark me-1 mb-1">@r.Name &lt;@contact&gt;</span>
<span class="badge bg-secondary-subtle text-secondary-emphasis me-1 mb-1">@r.Name &lt;@contact&gt;</span>
}
}
</td>
@@ -9,7 +9,7 @@
RangeViolation { attributeName, min, max }
RateOfChange { attributeName, thresholdPerSecond, windowSeconds, direction } *@
<div class="border rounded bg-white p-3">
<div class="border rounded bg-body p-3">
@* ── Monitored attribute ───────────────────────────────────────────── *@
@* Expression triggers reference attributes inside the C# expression itself,
@@ -54,7 +54,7 @@
</select>
@if (_selectedDataType is { } dt)
{
<span class="input-group-text bg-light text-muted small">@dt</span>
<span class="input-group-text bg-body-secondary text-body-secondary small">@dt</span>
}
</div>
@@ -30,11 +30,11 @@
<div class="row g-3">
<div class="col-md-6">
<div class="small text-muted mb-1">Before</div>
<pre class="border rounded p-2 small bg-light mb-0" style="max-height: 50vh; overflow: auto; white-space: pre-wrap;">@Before</pre>
<pre class="border rounded p-2 small bg-body-secondary mb-0" style="max-height: 50vh; overflow: auto; white-space: pre-wrap;">@Before</pre>
</div>
<div class="col-md-6">
<div class="small text-muted mb-1">After</div>
<pre class="border rounded p-2 small bg-light mb-0" style="max-height: 50vh; overflow: auto; white-space: pre-wrap;">@After</pre>
<pre class="border rounded p-2 small bg-body-secondary mb-0" style="max-height: 50vh; overflow: auto; white-space: pre-wrap;">@After</pre>
</div>
</div>
}
@@ -76,7 +76,7 @@ else
/// </summary>
private RenderFragment PropertyList(SchemaNode node, bool isRoot = false) => __builder =>
{
<div class="border rounded bg-white p-2">
<div class="border rounded bg-body p-2">
@if (node.Properties.Count == 0)
{
<div class="text-muted small fst-italic px-1 py-2">
@@ -11,7 +11,7 @@
Expression { expression, mode }
Call { } *@
<div class="border rounded bg-white p-3">
<div class="border rounded bg-body p-3">
@* ── Trigger type ──────────────────────────────────────────────────── *@
<div class="mb-3">
@@ -61,7 +61,7 @@
Unrecognized trigger type <code>@_rawType</code>. Its stored
configuration is shown below and left untouched — pick a known
trigger type above to reconfigure it.
<pre class="bg-light border rounded p-2 mt-2 mb-0">@(string.IsNullOrWhiteSpace(TriggerConfig) ? "(empty)" : TriggerConfig)</pre>
<pre class="bg-body-secondary border rounded p-2 mt-2 mb-0">@(string.IsNullOrWhiteSpace(TriggerConfig) ? "(empty)" : TriggerConfig)</pre>
</div>
break;
}