docs(ui-theme): current-state ×3 + GAPS adoption backlog

This commit is contained in:
Joseph Doherty
2026-06-01 05:15:38 -04:00
parent 95975d0754
commit 029ac0719b
4 changed files with 579 additions and 0 deletions
@@ -0,0 +1,163 @@
# UI Theme — current state: OtOpcUa
Repo: `~/Desktop/OtOpcUa` (Gitea `lmxopcua`). Stack: .NET 10, Blazor SSR.
UI surface: `src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/` (Razor Class Library).
All paths below are relative to the repo root. Verified against source on 2026-06-01.
**Summary:** OtOpcUa already uses a side-rail layout and the full Technical-Light token
set. Adoption is **lowest effort** of the three apps — the shell shape already matches the
canonical target. The one bug fixed by adoption: a latent font-path 404 that silently
falls back to system fonts today.
---
## 1. CSS / design tokens
**`theme.css`** — 379-line hand copy of the Technical-Light design system.
- Path: `src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/wwwroot/css/theme.css`
- Font path: `url('fonts/ibm-plex-sans-400.woff2')` (lines 24, 29, 34)
- **Bug:** the path is relative to the CSS file location (`wwwroot/css/`), so it resolves
as `wwwroot/css/fonts/…` — a 404. The browser silently falls back to system fonts. The
canonical RCL path `url('../fonts/…')` fixes this permanently.
- Wired in `App.razor` line 17:
`<link rel="stylesheet" href="_content/ZB.MOM.WW.OtOpcUa.AdminUI/css/theme.css"/>`.
**`site.css`** — 174 lines of per-app page layout (side-rail shell, login card layout,
page body padding, miscellaneous overrides).
- Path: `src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/wwwroot/css/site.css`
- Wired in `App.razor` line 18:
`<link rel="stylesheet" href="_content/ZB.MOM.WW.OtOpcUa.AdminUI/css/site.css"/>`.
- After adoption: the `.side-rail`, `.rail-*`, `.login-wrap`, `.login-title` rules are
superseded by the RCL's `layout.css`. The page-layout residuals (body padding, page-
specific overrides) stay in `site.css`.
---
## 2. IBM Plex fonts
Three `.woff2` files vendored into:
`src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/wwwroot/fonts/`
- `ibm-plex-sans-400.woff2`
- `ibm-plex-sans-600.woff2`
- `ibm-plex-mono-500.woff2`
After adoption: delete all three; the RCL serves them from
`_content/ZB.MOM.WW.Theme/fonts/`.
---
## 3. Layout shell
**`MainLayout.razor`** — 28-line static layout (no `@rendermode`). Renders `.app-shell`
flex row, hamburger toggle, `<NavSidebar/>` inside a Bootstrap collapse div, and
`<main class="page">`.
- Path: `src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Layout/MainLayout.razor`
- Structure: `.app-shell d-flex flex-column flex-lg-row` (line 8), hamburger (lines 1118),
`<div class="collapse d-lg-block" id="sidebar-collapse">` (line 21), `<NavSidebar />` (line 22),
`<main class="page">@Body</main>` (lines 2527).
**`NavSidebar.razor`** — 160-line interactive (`@rendermode InteractiveServer`) sidebar.
Hosts the collapsible `NavSection` groups and cookie-persisted expand state.
- Path: `src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Layout/NavSidebar.razor`
- Brand: `<div class="brand"><span class="mark">&#9646;</span> OtOpcUa</div>` (lines 1414).
- Nav sections: two `NavSection` groups ("Navigation", "Scripting", "Live", "Config")
with `<NavLink class="rail-link">` children.
- Rail foot (lines 4462): `<div class="rail-foot"><AuthorizeView>` — session info + sign-out
`<form method="post" action="/auth/logout">`.
- Nav expand state persisted in `otopcua_nav` cookie via
`wwwroot/js/nav-state.js` (cookie: `otopcua_nav=<comma-separated ids>`).
**`NavSection.razor`** — 36-line `NavSection` component (interactive; uses `EventCallback`
`OnToggle` for expand/collapse, not CSS `<details>`).
- Path: `src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Layout/NavSection.razor`
**`LoginLayout.razor`** — plain layout (no sidebar) used by the login page.
- Path: `src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Layout/LoginLayout.razor`
---
## 4. Login page
**`Login.razor`** — 50-line static login page. Uses `@layout LoginLayout`,
`@attribute [AllowAnonymous]`.
- Path: `src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Pages/Login.razor`
- Form: `<form method="post" action="/auth/login" data-enhance="false">` (line 21).
- Hidden `returnUrl` input (line 2225), username/password inputs, error notice panel.
- The form structure exactly matches what `<LoginCard>` emits; migration is direct.
---
## 5. StatusBadge component
**`StatusBadge.razor`** — thin wrapper over `.chip` classes.
- Path: `src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Shared/StatusBadge.razor`
- Parameters: `string Text`, `string CssClass` (default `chip-idle`).
- After adoption: replaced by `<StatusPill State="…">` — caller maps state to `StatusState`
enum rather than passing CSS class strings directly.
---
## 6. Divergences from spec
| Item | Current state | Spec |
|---|---|---|
| `theme.css` | Hand copy, 379 lines | Single canonical copy in RCL |
| Font-path `url()` | `url('fonts/…')`**latent 404** | `url('../fonts/…')` — correct |
| IBM Plex fonts | Vendored 3× in `wwwroot/fonts/` | Single copy in RCL `wwwroot/fonts/` |
| Shell layout | `.app-shell` + `NavSidebar` component (matches target shape) | `ThemeShell` + thin `MainLayout` |
| Nav items | `<NavLink class="rail-link">` inside interactive `NavSidebar` | `NavRailItem` inside `NavRailSection` |
| Nav expand state | Cookie-persisted via `otopcua_nav` + JS | CSS-only `<details>` in `NavRailSection` |
| Status chip | `StatusBadge` (string CSS class param) | `StatusPill` (`StatusState` enum param) |
| Login card | Inline markup in `Login.razor` | `<LoginCard>` |
---
## 7. Adoption plan
**Effort: Low.** The shell shape already matches the target. No layout migration needed.
**Steps:**
1. **Delete copies.** Remove `wwwroot/css/theme.css` and `wwwroot/fonts/ibm-plex-*.woff2`
from `ZB.MOM.WW.OtOpcUa.AdminUI`. This also fixes the latent font-path 404.
2. **Reference RCL.** Add `<PackageReference Include="ZB.MOM.WW.Theme" />` to
`ZB.MOM.WW.OtOpcUa.AdminUI.csproj`. Add `@using ZB.MOM.WW.Theme` to `_Imports.razor`.
3. **Wire `ThemeHead`.** In `App.razor` replace lines 1718:
```diff
- <link rel="stylesheet" href="_content/ZB.MOM.WW.OtOpcUa.AdminUI/css/theme.css"/>
- <link rel="stylesheet" href="_content/ZB.MOM.WW.OtOpcUa.AdminUI/css/site.css"/>
+ <ThemeHead />
+ <link rel="stylesheet" href="_content/ZB.MOM.WW.OtOpcUa.AdminUI/css/site.css"/>
```
(Keep `site.css` for the page-layout residuals.)
4. **Replace `MainLayout`.** Delete the current 28-line `MainLayout.razor`. Create a new
thin `MainLayout.razor` that delegates to `<ThemeShell Product="OtOpcUa Admin">` with
`Nav` and `RailFooter` slots (carry the session/sign-out block from `NavSidebar`'s
`.rail-foot` into `RailFooter`).
5. **Port nav.** Rebuild the `Nav` slot using `<NavRailSection>` + `<NavRailItem>`. The
four section groups ("Navigation", "Scripting", "Live", "Config") map directly to
`NavRailSection Title="…"` with `NavRailItem` children.
**Cookie nav state:** OtOpcUa's `otopcua_nav` cookie persistence requires JS and an
`InteractiveServer` component. If this feature is retained, keep a bespoke interactive
`NavSection` (the current `NavSection.razor` or a refactored version) alongside — it
is compatible with `ThemeShell`'s `Nav` slot. If cookie persistence is acceptable to
drop, `NavRailSection` (CSS-only `<details>`) is a drop-in replacement.
6. **Replace `StatusBadge`.** Find all usages of `<StatusBadge CssClass="chip-*">` and
replace with `<StatusPill State="StatusState.*">`. Delete `StatusBadge.razor`.
7. **Replace login card.** In `Login.razor`, replace the inline `<div class="login-wrap">
… </div>` block with `<LoginCard Product="OtOpcUa Admin" Action="/auth/login"
ReturnUrl="@ReturnUrl" Error="@Error"><AntiforgeryToken /></LoginCard>`. The code-behind
(`Error` / `ReturnUrl` supply-from-query properties) stays unchanged.
8. **Keep:** `site.css` page-layout residuals; scoped `.razor.css` files (none currently in
AdminUI); `LoginLayout.razor`; auth endpoints; all page components.
**Risk: Low** — layout shape already matches, no top-bar migration. Cookie nav state is
the only optional complexity (decide retain vs drop).