# Admin-UI cleanup playbook (`ZB.MOM.WW.Theme` + Bootstrap Blazor apps) A reusable sweep recipe for the family's Blazor Server admin UIs, distilled from five completed rounds: - **ignitionoee router** — `../ignitionoee/docs/plans/2026-08-05-router-ui-sweeps.md` (+ its render-validation companion plan) - **OtOpcUa AdminUI** — `../OtOpcUa/docs/plans/2026-08-10-adminui-ui-sweeps.md` - **MxAccessGateway dashboard** — `../MxAccessGateway/docs/plans/2026-08-11-dashboard-ui-sweeps.md` - **HistorianGateway dashboard** — `../HistorianGateway/docs/plans/2026-08-11-dashboard-ui-sweep.md` - **ScadaBridge CentralUI** — `../ScadaBridge/docs/plans/2026-08-11-central-ui-cleanup-sweep.md` All five sit on **ZB.MOM.WW.Theme 0.3.1 + self-hosted Bootstrap 5.3** (mxgw included — an earlier claim that it was Bootstrap-free was false; see the exemption rule below), and all five found the same *classes* of defect with different instances. **Run the discovery phase before assuming which instances your app has.** The spread so far: the router had ghost layout classes and OtOpcUa had none; OtOpcUa had a dead scoped-CSS bundle and mxgw/HistorianGateway have no `*.razor.css` at all while ScadaBridge's bundle was already correctly wired; HistorianGateway's phantom tokens were all fallback-masked and ScadaBridge had zero; ScadaBridge's *pages* were right and its *docs* were wrong, inverting §1c. **Determine the test harness per app — do not assume.** The router and OtOpcUa have no bUnit, so a live browser pass is their only gate. HistorianGateway (`bunit 1.40.0`, ten Dashboard test files) and ScadaBridge (`CentralUI.Tests`, 990 tests) both have one, which changes discovery materially (see §1a-1). **A live pass is still mandatory everywhere**, because computed styles and cascade order are exactly what bUnit cannot see. ### Exemptions need their premise re-derived If a family-wide sweep or gate excuses one app on a property claim about that app — "it's Bootstrap-free", "it has no dark mode", "it has no scoped CSS" — **re-derive that property from that repo at sweep time.** It happened once already: the 2026-07-19 `/admin/secrets` modal gate skipped mxgw as "Bootstrap-free so never affected", which was false; mxgw self-hosts Bootstrap 5.3.3 and *was* affected, and only escaped because it had taken the fix independently. The exemption reached the right conclusion on false reasoning and nobody re-checked for three weeks. This is the same discipline §1c applies to cited config keys, aimed at scope decisions instead of prose. --- ## 1. Discovery — five read-only scans (1a–1d parallelize per file; 1d also needs a live walk) Run these as parallel read-only agents (or by hand) and write the inventories down with `file:line` before changing anything. The inventories become the per-batch fix briefs. ### 1a. Foundation / CSS audit The theme ships less than pages assume. Check, in order: 1. **Shipped-vs-used class matrix.** Extract every CSS class used in `*.razor` markup and check each against (a) the theme package's `staticwebassets/css/*.css` (`~/.nuget/packages/zb.mom.ww.theme//`), (b) Bootstrap, (c) the app's own stylesheet, (d) page `