Publishes the three ZB.MOM.WW.LocalDb packages to the Gitea feed (restore-verified from a scratch consumer) and adds the build/push.sh the other shared libs already have. The status prose across CLAUDE.md, README.md and components/*/GAPS.md had drifted from reality, so it was re-derived from the feed listing and the actual PackageReferences + registration calls on each consumer's default branch rather than from prior claims. Five claims were false: Health "not yet adopted" (all four apps wire MapZbHealth), GalaxyRepository's mxaccessgw adoption "a follow-on" (its Server wires AddZbGalaxyRepository), Configuration "not yet pushed", Secrets G-8 "not yet committed", and Theme pinned at 0.2.0 (all four are on 0.3.1). Every doc also said "three apps" while HistorianGateway is a fourth consumer of seven libs, and all eight libraries' test counts were stale (re-ran each suite; all green). Surfaces one previously unrecorded gap: Secrets source is at 0.1.3 with KEK rotation committed, but the feed tops out at 0.1.2, so no app can consume rotation until it is published. Health and observability divergence tables are labelled historical, not re-verified — the libraries are proven wired, but per-app probe coverage vs spec was not re-walked. Claude-Session: https://claude.ai/code/session_01BL2Vu1ESDQ9SCN4gVKkdts
8.5 KiB
UI Theme — gaps & adoption backlog
Divergence of each project from spec/SPEC.md, and the ordered backlog to
reach adoption of the ZB.MOM.WW.Theme shared RCL. Status legend: ⛔ gap · 🟡 partial · ✅ matches.
Current state (verified 2026-07-18): the kit has moved on from the
0.2.0this banner describes — the Gitea feed carries0.2.0,0.2.1,0.3.0,0.3.1, and all four apps are pinned to0.3.1(HistorianGateway is a fourth consumer this file predates). The0.3.0/0.3.1nav/aria/login fixes are recorded inthemeissues.md, not here. Library suite: 48 bUnit tests, green 2026-07-18. History below is accurate for the original cutover.✅ ADOPTED 2026-06-03 (local-only). Backlog #2–#4 implemented across all three apps on each repo's
feat/adopt-zb-themebranch — full canonical cutover (SPEC §7):<ThemeHead/>/<ThemeScripts/>, thinMainLayout→<ThemeShell>+NavRailItem/NavRailSection, per-apptheme.css/IBM-Plex fonts/nav-state.jsdeleted,<LoginCard>sign-in, andStatusPill(OtOpcUa's deadStatusBadgedeleted; MxGateway'sStatusBadgeredirected to a thinStatusPilladapter; inline domain.chip-*kept as page content per §6). Library first enhanced to0.2.0— nav-expand persistence promoted INTO the kit (NavRailSection.Key→data-nav-key+ a localStoragenav-state.jsenhancer emitted by a new<ThemeScripts/>), so all three apps get uniform persistence from one source (OtOpcUa's bespoke cookie/JS-interop nav island retired). 0.2.0 published to the Gitea feed; 44 bUnit tests. MxGateway additionally gained a net-new Blazor<LoginCard>/loginpage reusing its existing hardenedPOST /loginendpoint (antiforgery +SanitizeReturnUrl+SignInAsyncpreserved). Every task spec+code reviewed (high-risk via serial spec→code; the MxGateway login via an Opus security review), then fast-forward-merged into each repo's local default and PUSHED to origin (gitea) 2026-06-03 (in sync;feat/*kept locally): OtOpcUamaster@11de14d, ScadaBridgemain@58352a6, MxGatewaymain@73e54e2. Plan:docs/plans/2026-06-03-ui-theme-adoption*.md. The ⛔/🟡 cells below describe the PRE-adoption divergence (kept for history).Post-adoption CSS prune (2026-06-03, branch
chore/theme-css-pruneper app). An audit found each app's keptsite.cssstill carried the old shell CSS the kit now owns — broader than first logged. Pruned: OtOpcUa shed a near-verbatim copy of the kit'slayout.css(.app-shell/.side-rail/.rail-link/.rail-foot/.login-*) plus dead#sidebar-collapse(kit emits#theme-rail) and.rail-eyebrow-chevron(−167 lines), keeping only app-only.rail-eyebrow+.chip-alert/.chip-caution; ScadaBridge shed the dead.sidebar/.nav-link/.nav-section-toggleblock (−95), keeping#reconnect-modal/.script-editor-modal; MxGateway shed the dead.sidebarblock + orphaned.dashboard-login/.login-card(−106), keeping.app-bar(still used by/denied) + the.chipoverride. Each verified unreferenced before removal; all three build clean (0 warn/0 err). OtOpcUa's copy was the notable one — it overrode the kit, not just dead code. Still deferred: a kit-sidelayout.csscalc(100vh - 3.3rem)review; and ScadaBridge'sHostconsumes the kit only transitively viaCentralUI(no directPackageReference) — builds green, but an implicit dependency.Feed note: the same audit re-confirmed
ZB.MOM.WW.Theme 0.2.0is genuinely on the Gitea feed (registrationcount:1, package baseversions:["0.2.0"], searchtotalHits:1) — the publish was real, not optimism.
Divergence vs spec
§1 Design tokens — theme.css
| Item | OtOpcUa | MxAccessGateway | ScadaBridge |
|---|---|---|---|
| Tokens identical to canonical | ✅ identical | ✅ identical | ✅ identical |
| File maintained in one place (RCL) | ⛔ own copy | ⛔ own copy | ⛔ own copy |
Font path url('../fonts/…') |
⛔ url('fonts/…') — latent 404 |
🟡 url('/fonts/…') — absolute, not portable |
✅ url('../fonts/…') — correct |
| IBM Plex fonts in one place | ⛔ own wwwroot/fonts/ |
⛔ own wwwroot/fonts/ |
⛔ own wwwroot/fonts/ |
→ Gap T1: All three apps maintain a copy of theme.css — the single-source guarantee
is broken today. Any token change must be applied in four places (three apps + the RCL)
once the RCL exists.
→ Gap T2: OtOpcUa url('fonts/…') is a latent 404 masked by system-font fallback.
Adoption fixes it automatically.
→ Gap T3: Each app vendors fonts — 3× duplication. The RCL eliminates it.
§2 Typography
All three apps reference IBM Plex via the token stacks. No typography divergence — the token values are identical. Gap is delivery (T3 above).
§3 Canonical side-rail layout
| Item | OtOpcUa | MxAccessGateway | ScadaBridge |
|---|---|---|---|
.app-shell root element |
✅ div.app-shell |
⛔ div.d-flex … (no .app-shell) |
⛔ div.d-flex … (no .app-shell) |
| Rail CSS class | ✅ .side-rail |
⛔ .sidebar |
⛔ .sidebar |
| Nav item CSS class | ✅ .rail-link |
⛔ .nav-link |
⛔ .nav-link |
| Nav item element | ✅ <a> (NavLink) |
⛔ <li><NavLink> inside <ul> |
⛔ <li><NavLink> inside <ul> |
| Shell component | ⛔ bespoke MainLayout + NavSidebar |
⛔ combined MainLayout (210 lines) |
⛔ MainLayout + NavMenu |
| Thin-MainLayout pattern | ⛔ not yet | ⛔ not yet | ⛔ not yet |
→ Gap L1: OtOpcUa already uses the right CSS classes but the component structure
doesn't use ThemeShell. Low-risk migration.
→ Gap L2: MxAccessGateway and ScadaBridge use .sidebar / .nav-link / <ul><li>.
Migration requires class name changes throughout their nav markup and site.css sidebar
blocks. Medium (ScadaBridge) to high (MxGateway combined layout) risk.
§4 Component contract
| Component | OtOpcUa | MxAccessGateway | ScadaBridge |
|---|---|---|---|
StatusPill (vs bespoke StatusBadge) |
⛔ StatusBadge (string CSS class) |
⛔ StatusBadge (string text → class) |
⛔ raw .chip-* classes inline |
LoginCard |
⛔ inline markup in Login.razor |
⛔ no Blazor login page | ⛔ Bootstrap .card markup in Login.razor |
NavRailItem / NavRailSection |
⛔ NavLink + NavSection (interactive) |
⛔ NavLink+<li> + NavSection |
⛔ NavLink+<li> + NavSection |
ThemeShell / thin MainLayout |
⛔ not yet | ⛔ not yet | ⛔ not yet |
ThemeHead |
⛔ manual <link> tags |
⛔ manual <link> tags |
⛔ manual <link> tags |
§5 Delivery
| Item | OtOpcUa | MxAccessGateway | ScadaBridge |
|---|---|---|---|
Asset via _content/ZB.MOM.WW.Theme/… |
⛔ _content/…AdminUI/css/… |
⛔ root-relative /css/… |
⛔ _content/…CentralUI/css/… |
<ThemeHead /> in <head> |
⛔ manual <link> tags |
⛔ manual <link> tags |
⛔ manual <link> tags |
Adoption backlog (ordered)
| # | Item | Projects | Priority | Effort | Risk | Notes |
|---|---|---|---|---|---|---|
| 1 | Build ZB.MOM.WW.Theme RCL |
scadaproj | High | M | Low | DONE — 0.1.0 built + tested in this repo |
| 2 | Adopt in OtOpcUa AdminUI | OtOpcUa | High | S | Low | Already rail; fix latent font 404; cookie nav-state optional retain |
| 3 | Adopt in ScadaBridge CentralUI + Host | ScadaBridge | Med | M | Med | Sidebar class migration + MainLayout replace; scoped .razor.css unchanged |
| 4 | Adopt in MxAccessGateway Dashboard | MxAccessGateway | Low | L | High | Combined MainLayout migration; sidebar idiom change; largest UX-visible change — verify visually |
Sequencing: #2 first (lowest risk, validates the adoption pattern); #3 next (medium effort, no design change); #4 last (highest risk — verify dashboard UX thoroughly before merging). Each adoption is a per-repo PR, independent.
Open questions
- MxGateway login: No Blazor login page today. If one is added during adoption (#4),
use
<LoginCard>. If the server-redirect pattern is kept,<LoginCard>is not needed. - OtOpcUa cookie nav state: Decide whether to retain
otopcua_navcookie persistence (keep bespoke interactiveNavSectionalongsideThemeShell'sNavslot) or drop it (CSS-onlyNavRailSectionreplaces it, losing expand-state persistence across page loads). - ScadaBridge
AuthorizeViewpolicy gating in nav: Verify<NavRailSection>inside<AuthorizeView>renders + hides correctly with the canonical SSR rendering model.