3070169e5d
Audit follow-up: the deferred 'dead .sidebar/.nav-link residual' was broader than logged (OtOpcUa's site.css duplicated and overrode the whole kit shell). Pruned across all 3 apps on chore/theme-css-prune branches (-167/-95/-106 lines, builds clean). Note the remaining deferred items (kit layout.css calc review; ScadaBridge Host transitive kit ref) and reconfirm the Theme 0.2.0 publish is genuine.
123 lines
8.1 KiB
Markdown
123 lines
8.1 KiB
Markdown
# 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): `<ThemeHead/>`/`<ThemeScripts/>`,
|
||
> thin `MainLayout` → `<ThemeShell>` + `NavRailItem`/`NavRailSection`, per-app `theme.css`/IBM-Plex fonts/
|
||
> `nav-state.js` deleted, `<LoginCard>` 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
|
||
> `<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>` `/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), then
|
||
> **fast-forward-merged into each repo's local default and PUSHED to origin (gitea) 2026-06-03** (in sync;
|
||
> `feat/*` kept locally): OtOpcUa `master`@`11de14d`, ScadaBridge `main`@`58352a6`, MxGateway `main`@`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-prune` per app).** An audit found each app's
|
||
> kept `site.css` still carried the old shell CSS the kit now owns — broader than first logged. Pruned:
|
||
> **OtOpcUa** shed a near-verbatim copy of the kit's `layout.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-toggle` block (−95), keeping `#reconnect-modal`/`.script-editor-modal`;
|
||
> **MxGateway** shed the dead `.sidebar` block + orphaned `.dashboard-login`/`.login-card` (−106), keeping
|
||
> `.app-bar` (still used by `/denied`) + the `.chip` override. 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-side `layout.css` `calc(100vh - 3.3rem)` review; and ScadaBridge's `Host` consumes the
|
||
> kit only **transitively via `CentralUI`** (no direct `PackageReference`) — builds green, but an implicit dependency.
|
||
>
|
||
> _Feed note: the same audit re-confirmed `ZB.MOM.WW.Theme 0.2.0` **is** genuinely on the Gitea feed (registration
|
||
> `count:1`, package base `versions:["0.2.0"]`, search `totalHits: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_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 `<NavRailSection>` inside
|
||
`<AuthorizeView>` renders + hides correctly with the canonical SSR rendering model.
|