From dc53f04b81aa59ccc6835f4b238515a5c3d9f385 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Tue, 11 Aug 2026 05:47:52 -0400 Subject: [PATCH 1/2] build(theme): adopt ZB.MOM.WW.Theme 0.4.1 button sizing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit site.css declared `font-size: 0.82rem` literally on `.btn`. Bootstrap sizes buttons through `font-size: var(--bs-btn-font-size)`, and `.btn-sm` / `.btn-group-sm > .btn` do nothing but redefine that variable — so a literal at equal specificity, loaded after Bootstrap, silently flattened every small button in the dashboard into a padding-only difference. `btn-sm` was inert app-wide. The kit now owns the sizing: 0.4.0 shipped the four `--bs-btn-*` overrides in layout.css, which ThemeHead emits ahead of site.css, so removing the local literal restores `.btn-sm` without a local copy. 0.4.1 is a pin-only follow-up (it fixes `.rail-btn-block`, which this app does not use; `theme.css` is byte-identical and the `.btn` rule is unchanged between the two). The rest of the local rule is kept deliberately. 0.4.0 upstreamed sizing only, not `border-radius` / `font-weight` / `white-space`, so deleting the block wholesale would have dropped three app-specific declarations. `border-radius` also stays a literal rather than `--bs-btn-border-radius`, because `.btn-sm` redefines that variable and small buttons would shrink to Bootstrap's radius. Verified in a browser against the running dashboard: `.btn` 13.6px and `.btn-sm` 12.48px, btn-group seams intact, and `--bs-btn-font-size` now declared in exactly two sheets (bootstrap.min.css, layout.css) instead of three. --- .../ZB.MOM.WW.MxGateway.Server.csproj | 2 +- .../wwwroot/css/site.css | 15 +++++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/ZB.MOM.WW.MxGateway.Server/ZB.MOM.WW.MxGateway.Server.csproj b/src/ZB.MOM.WW.MxGateway.Server/ZB.MOM.WW.MxGateway.Server.csproj index a402153..8d299d8 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/ZB.MOM.WW.MxGateway.Server.csproj +++ b/src/ZB.MOM.WW.MxGateway.Server/ZB.MOM.WW.MxGateway.Server.csproj @@ -15,7 +15,7 @@ - + diff --git a/src/ZB.MOM.WW.MxGateway.Server/wwwroot/css/site.css b/src/ZB.MOM.WW.MxGateway.Server/wwwroot/css/site.css index 40542db..fa82bbd 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/wwwroot/css/site.css +++ b/src/ZB.MOM.WW.MxGateway.Server/wwwroot/css/site.css @@ -183,8 +183,19 @@ code { } /* ── Buttons ───────────────────────────────────────────────────────────────── - Flatten Bootstrap buttons onto the single accent + hairline palette. */ -.btn { border-radius: 5px; font-size: 0.82rem; font-weight: 500; white-space: nowrap; } + Flatten Bootstrap buttons onto the single accent + hairline palette. + + Button *sizing* is not here: ZB.MOM.WW.Theme 0.4.0 upstreamed it into + layout.css as --bs-btn-* variable overrides, and ThemeHead emits that sheet + ahead of this one, so it applies without a local copy. Do not reintroduce a + local font-size on .btn — a literal at equal specificity beats Bootstrap's + `font-size: var(--bs-btn-font-size)` and flattens .btn-sm into a + padding-only difference across the whole app. + + What remains is shape, not size, and is app-specific. border-radius stays a + literal rather than --bs-btn-border-radius so .btn-sm cannot shrink it; the + .btn-group seam rules outrank .btn, so groups still render seamed. */ +.btn { border-radius: 5px; font-weight: 500; white-space: nowrap; } .btn-primary { background: var(--accent); border-color: var(--accent); From 01033d7aafce5a82d6f359ae583cfc71c2112048 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Tue, 11 Aug 2026 05:48:22 -0400 Subject: [PATCH 2/2] fix(dashboard): admin-UI cleanup sweep MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Family-wide admin-UI cleanup pass (scadaproj admin_ui_cleanup.md) applied to the Blazor dashboard. Behaviour is unchanged throughout — no @onclick, disabled, binding, auth gate, or arm->confirm flow was touched. Uncontrolled error text is now truncated at the render site. Fault messages, Galaxy load errors, and browse-tree load failures were rendered in full into fixed-width table cells, where a long exception string blows out the column. Each site gets DashboardDisplay.Abbreviate plus a title attribute carrying the untruncated text, so nothing becomes unreachable. Abbreviate is length-checked rather than a bare range slice: `value[..n]` on a shorter string throws and takes the whole page render down with it. The two detail views whose entire purpose is to show one fault in full — SessionDetailsPage and GalaxyPage's Last Error — are deliberately left untruncated. Two classes referenced from markup had no definition anywhere in the sheet. .browse-stale-banner was inert; .tree-load-status was a real visual defect — loading and failed-to-load rows sit among .tree-row siblings and carry the same leading .tree-toggle-empty spacer, but that spacer only takes its width as a flex item, so without a flex container those rows lost their indent. Confirm/cancel pairs in ConfirmDialog and the API-key create form are now btn-groups with role="group" and an aria-label, replacing margin-spaced loose buttons. Removes a paragraph on GalaxyPage naming internal RPCs (DiscoverHierarchy, GetLastDeployTime) — implementation detail with no meaning to a dashboard operator. Verified in a real browser, not bUnit: full build clean, 879/879 tests, and a live gate against a running dashboard with a genuine ~250-char SqlClient exception as the erroring row. Results per check, including the checks that could NOT be exercised without an x86 worker, are recorded in docs/plans/2026-08-11-dashboard-ui-sweeps.md. That plan doc also records a correction: this app is NOT Bootstrap-free. The sweep brief said it was, citing the scadaproj index; libman.json pins bootstrap 5.3.3 and App.razor:7 links it ahead of the theme. The stale claim had already cost this app one skipped family sweep (scadaproj#2, the /admin/secrets modal), so that modal was live-gated here too and passes. --- docs/plans/2026-08-11-dashboard-ui-sweeps.md | 352 ++++++++++++++++++ .../Dashboard/Components/DashboardDisplay.cs | 26 ++ .../Components/Pages/ApiKeysPage.razor | 6 +- .../Components/Pages/DashboardHome.razor | 3 +- .../Components/Pages/GalaxyPage.razor | 5 - .../Components/Pages/SessionsPage.razor | 3 +- .../Components/Pages/WorkersPage.razor | 3 +- .../Shared/BrowseTreeNodeView.razor | 6 +- .../Components/Shared/ConfirmDialog.razor | 22 +- .../Components/Shared/FaultList.razor | 2 +- .../wwwroot/css/site.css | 24 ++ 11 files changed, 429 insertions(+), 23 deletions(-) create mode 100644 docs/plans/2026-08-11-dashboard-ui-sweeps.md diff --git a/docs/plans/2026-08-11-dashboard-ui-sweeps.md b/docs/plans/2026-08-11-dashboard-ui-sweeps.md new file mode 100644 index 0000000..f8bbcd3 --- /dev/null +++ b/docs/plans/2026-08-11-dashboard-ui-sweeps.md @@ -0,0 +1,352 @@ +# Dashboard UI cleanup sweep (2026-08-11) + +Runs the family admin-UI cleanup playbook (`../scadaproj/admin_ui_cleanup.md`) against the +MXAccess Gateway Blazor dashboard. Third app in the family after the ignitionoee router and the +OtOpcUa AdminUI. + +## 0. Platform correction — this app is *not* Bootstrap-free + +The umbrella index describes mxaccessgw as the family's Bootstrap-free app. That is wrong, and +every Bootstrap-dependent recipe in the playbook applies here unchanged. What actually ships: + +| Layer | Evidence | +|---|---| +| Bootstrap 5.3.3, self-hosted | `libman.json:5`; `wwwroot/lib/bootstrap/css/bootstrap.min.css` | +| Linked first in the head | `Dashboard/Components/App.razor:7` | +| `ZB.MOM.WW.Theme` 0.3.1 at discovery, **0.4.0 after §6** | `ZB.MOM.WW.MxGateway.Server.csproj` ``; `` at `App.razor:8` | +| App stylesheet, loaded last | `App.razor:9` → `wwwroot/css/site.css` | +| Bootstrap JS bundle | `App.razor:14` | + +What CLAUDE.md actually forbids is Blazor **component libraries** (MudBlazor, Radzen, FluentUI) — +not Bootstrap's CSS/JS. No Bootstrap was introduced by this sweep. + +Playbook §2 foundation item 1 (*app stylesheet after `` so it wins the cascade*) was +therefore **already satisfied** before the sweep. + +## 1. Discovery + +### 1a. Foundation / CSS audit + +**Shipped-vs-used class matrix.** Every class in `Dashboard/Components/**/*.razor` checked against +theme 0.3.1 `staticwebassets/css/{theme,layout}.css`, `bootstrap.min.css`, and `site.css`. Three +classes are defined nowhere: + +| Ghost class | Site | Consequence | Verdict | +|---|---|---|---| +| `tree-load-status` | `Shared/BrowseTreeNodeView.razor:42`, `:49` | **Real visual defect.** The row's indent spacer is ``; `.tree-toggle` sets `flex:none;width:1.1rem`, which only takes effect on a flex item. `.tree-row`/`.tree-attr` are flex; this container is an undefined block, so the span stays inline and `width` is ignored — "⌛ Loading…" and "Failed to load: …" render flush left, out of alignment with every sibling row. | **Define it** | +| `browse-stale-banner` | `Pages/BrowsePage.razor:78` | The banner carries `@onclick="ClearStaleBanner"` with no pointer affordance and no styling of its own — a click-to-dismiss control that does not look clickable. | **Define it** | +| `tree-node` | `Shared/BrowseTreeNodeView.razor:15` | Structural wrapper only; nothing needs to style it. A no-op, but a deliberate one. | **Leave** | + +(Reported as ghosts by the raw extractor but false positives: `h-100` at `Shared/MetricCard.razor:1` +— Bootstrap-defined, mangled by the extractor's handling of the inline `@(...)` class expression.) + +**Scoped-CSS bundle.** N/A — the project contains **zero** `*.razor.css` files, so no +`*.bundle.scp.css` is emitted and nothing is missing from the head. (This was OtOpcUa's finding; it +does not exist here.) + +**Phantom CSS custom properties.** Zero. `Dashboard/Components/` contains no `var(--…)` at all — +tokens are used only from `site.css`, and every one of them (`--ink`, `--ink-soft`, `--ink-faint`, +`--card`, `--rule`, `--rule-strong`, `--mono`, `--accent`, `--accent-deep`, `--ok`, `--ok-bg`, +`--bad`, `--bad-bg`, `--warn`, `--warn-bg`, `--idle`, `--idle-bg`) resolves against theme 0.3.1. +This app has the OtOpcUa-clean result, not the router's. + +**Button sizing — the one real foundation defect.** Theme 0.3.1 ships no `.btn` rule (confirmed: +`.btn` appears in `layout.css` only inside a comment). But `site.css:187` does, and it sets +`font-size` **directly** rather than through Bootstrap's variable: + +```css +.btn { border-radius: 5px; font-size: 0.82rem; font-weight: 500; white-space: nowrap; } +``` + +Bootstrap renders `.btn { font-size: var(--bs-btn-font-size) }`, and `.btn-sm` / +`.btn-group-sm > .btn` size themselves purely by *redefining that variable* +(`.btn-sm{--bs-btn-font-size:0.875rem}`). A literal `font-size` on `.btn` at equal specificity, +loaded later, wins over the variable-driven declaration for **every** button — so `btn-sm` and +`btn-group-sm` are font-size no-ops app-wide and small buttons differ from full-size ones by +padding alone. This is the same class of defect the other two apps hit from the opposite +direction (no `.btn` rule at all), and it takes the same fix. + +**Dark scheme.** Theme 0.3.1 is light-only; `site.css` makes no `prefers-color-scheme` / +`data-bs-theme` claim, and its header comment ("Layers over theme.css … every colour resolves to a +theme.css token") is accurate. Nothing to correct. + +### 1b. Button inventory + +`grep -rn "btn-group"` returns **three** — this app already uses the convention where it matters: + +| Site | Members | State | +|---|---|---| +| `Pages/ApiKeysPage.razor:189` | Rotate / Revoke, or Delete | Correct — `btn-group btn-group-sm`, no per-button `btn-sm`, `@if` inside the group | +| `Pages/SessionsPage.razor:90` | Close / Kill | Correct | +| `Pages/SessionDetailsPage.razor:34` | Close session / Kill worker | Correct | + +Adjacent related buttons **not** yet grouped (both are feet, the playbook's named case): + +| Site | Members | Fix | +|---|---|---| +| `Shared/ConfirmDialog.razor:17,22` | Cancel + `@ConfirmButtonClass` confirm, in a `modal-footer` | Wrap in `btn-group` | +| `Pages/ApiKeysPage.razor:136,137` | Save (`type="submit"`) + Cancel, in the create-key card body | Wrap in `btn-group btn-group-sm`; fold the two `btn-sm` and drop the `me-1` spacer | + +Refused / not candidates: + +- `Pages/WorkersPage.razor:74` — a lone Kill button. Nothing to group. +- `Pages/ApiKeysPage.razor:22` — lone page-head "Create API Key". +- `Pages/BrowsePage.razor:109` (`Clear all`) and `:167` (`Remove`) — lone buttons. +- `Shared/BrowseTreeNodeView.razor:19` `.tree-toggle` — a bare expander, deliberately unstyled as a + button; `MainLayout.razor:32` Sign Out / `:36` Sign In are the theme's `rail-btn`, one per + auth branch and mutually exclusive. + +**Row actions styled as links**: none. Every action in the app is already a real `