# 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. --- ## 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) | β›” `
  • ` inside `
      ` | β›” `
    • ` inside `
        ` | | 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` / `
        • `. 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`+`
        • ` + `NavSection` | β›” `NavLink`+`
        • ` + `NavSection` | | `ThemeShell` / thin `MainLayout` | β›” not yet | β›” not yet | β›” not yet | | `ThemeHead` | β›” manual `` tags | β›” manual `` tags | β›” manual `` tags | ### Β§5 Delivery | Item | OtOpcUa | MxAccessGateway | ScadaBridge | |---|---|---|---| | Asset via `_content/ZB.MOM.WW.Theme/…` | β›” `_content/…AdminUI/css/…` | β›” root-relative `/css/…` | β›” `_content/…CentralUI/css/…` | | `` in `` | β›” manual `` tags | β›” manual `` tags | β›” manual `` 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 ``. If the server-redirect pattern is kept, `` is not needed. - **OtOpcUa cookie nav state:** Decide whether to retain `otopcua_nav` cookie persistence (keep bespoke interactive `NavSection` alongside `ThemeShell`'s `Nav` slot) or drop it (CSS-only `NavRailSection` replaces it, losing expand-state persistence across page loads). - **ScadaBridge `AuthorizeView` policy gating in nav:** Verify `` inside `` renders + hides correctly with the canonical SSR rendering model.