# UI Theme β gaps & adoption backlog
Divergence of each project from [`spec/SPEC.md`](spec/SPEC.md), and the ordered backlog to
reach adoption of the `ZB.MOM.WW.Theme` shared RCL. Status legend: β gap Β· π‘ partial Β· β matches.
> **β ADOPTED 2026-06-03 (local-only).** Backlog #2β#4 implemented across all three apps on each repo's
> **`feat/adopt-zb-theme`** branch β full canonical cutover (SPEC Β§7): ``/``,
> thin `MainLayout` β `` + `NavRailItem`/`NavRailSection`, per-app `theme.css`/IBM-Plex fonts/
> `nav-state.js` deleted, `` sign-in, and `StatusPill` (OtOpcUa's dead `StatusBadge` deleted;
> MxGateway's `StatusBadge` redirected to a thin `StatusPill` adapter; inline domain `.chip-*` kept as page
> content per Β§6). **Library first enhanced to `0.2.0`** β nav-expand persistence promoted INTO the kit
> (`NavRailSection.Key` β `data-nav-key` + a localStorage `nav-state.js` enhancer emitted by a new
> ``), 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 `` `/login` page** reusing its existing hardened
> `POST /login` endpoint (antiforgery + `SanitizeReturnUrl` + `SignInAsync` preserved). Every task spec+code
> reviewed (high-risk via serial specβcode; the MxGateway login via an Opus security review). Branch heads:
> OtOpcUa `11de14d`, ScadaBridge `58352a6`, MxGateway `73e54e2` β **committed local-only, not yet merged/pushed**.
> Plan: `docs/plans/2026-06-03-ui-theme-adoption*.md`. The β/π‘ cells below describe the PRE-adoption
> divergence (kept for history). Deferred follow-ups: prune now-dead `.sidebar`/`.nav-link` residual from each
> app's kept `site.css`; a kit-side `layout.css` `calc(100vh - 3.3rem)` review.
---
## 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 | β `` (NavLink) | β `