# UI Theme — current state: ScadaBridge

Repo: `~/Desktop/ScadaBridge`. Stack: .NET 10, Blazor SSR (Akka.NET cluster + central UI).
UI surfaces: `src/ZB.MOM.WW.ScadaBridge.CentralUI/` (RCL) and
`src/ZB.MOM.WW.ScadaBridge.Host/` (the Blazor host that references it).
All paths below are relative to the repo root. Verified against source on 2026-06-01.

**Summary:** ScadaBridge uses a sidebar nav layout and the Technical-Light tokens, with the
correct font-path prefix. The sidebar uses `.sidebar` / `.nav-link` classes (same idiom as
MxGateway), not `.side-rail` / `.rail-link`. Adoption is **medium effort** — sidebar-class
migration + `MainLayout` replacement, no layout redesign. ScadaBridge has several
scoped `.razor.css` files that stay per-project.

---

## 1. CSS / design tokens

**`theme.css`** — 379-line hand copy of the Technical-Light design system.
- Path: `src/ZB.MOM.WW.ScadaBridge.CentralUI/wwwroot/css/theme.css`
- Font path: `url('../fonts/ibm-plex-sans-400.woff2')` (lines 24, 29, 34)
- **Correct path** — resolves from `wwwroot/css/` to `wwwroot/fonts/` without 404. This is
  the canonical `url('../fonts/…')` that the RCL also uses.
- Wired in the Host's `App.razor` line 9:
  `<link href="_content/ZB.MOM.WW.ScadaBridge.CentralUI/css/theme.css" rel="stylesheet" />`.

**`site.css`** — 128 lines of per-app page layout (sidebar shell, nav overrides).
- Path: `src/ZB.MOM.WW.ScadaBridge.CentralUI/wwwroot/css/site.css`
- Wired in the Host's `App.razor` line 11:
  `<link href="_content/ZB.MOM.WW.ScadaBridge.CentralUI/css/site.css" rel="stylesheet" />`.
- Contains: `.sidebar` layout block (~4–95), Bootstrap-icons integration for nav items.
- After adoption: the `.sidebar` layout section is superseded by RCL `layout.css`. The
  remaining rules (Bootstrap-icons, misc overrides) stay in `site.css`.

Note: ScadaBridge uses the `_content/ZB.MOM.WW.ScadaBridge.CentralUI/…` static-web-asset
path for its own CentralUI RCL assets — the same mechanism `ZB.MOM.WW.Theme` will use.

---

## 2. IBM Plex fonts

Three `.woff2` files vendored into:
`src/ZB.MOM.WW.ScadaBridge.CentralUI/wwwroot/fonts/`
- `ibm-plex-sans-400.woff2`
- `ibm-plex-sans-600.woff2`
- `ibm-plex-mono-500.woff2`

After adoption: delete all three from `CentralUI/wwwroot/fonts/`; the RCL serves them
from `_content/ZB.MOM.WW.Theme/fonts/`.

---

## 3. Layout shell

**`MainLayout.razor`** — 29-line static layout. `@inherits LayoutComponentBase`. No
`@rendermode` directive (static SSR).
- Path: `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Layout/MainLayout.razor`
- Root element: `<div class="d-flex flex-column flex-lg-row" style="min-height: 100vh;">`.
  No `.app-shell` class.
- Renders `<NavMenu />` inside a Bootstrap collapse div, `<main class="flex-grow-1 p-3">`,
  plus `<DialogHost />` and `<SessionExpiry />` at the bottom.

**`NavMenu.razor`** — 200+ line interactive sidebar component. `@implements IDisposable`.
- Path: `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Layout/NavMenu.razor`
- Brand: `<div class="brand"><span class="mark">&#9646;</span> ScadaBridge</div>` (lines ~9–9).
- Nav structure: `<nav class="sidebar d-flex flex-column">` with `<ul class="nav flex-column">`
  and `<NavSection>` groups ("Admin", "Data", "Audit", etc.) with `<NavLink class="nav-link">`
  children. Uses `AuthorizeView` + `AuthorizeView Policy="…"` to gate admin sections.
- Nav state: JS-based expand-state persistence (same pattern as OtOpcUa and MxGateway).

**`NavSection.razor`** (same name as OtOpcUa/MxGateway, independent per-project copy).
- Path: `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Layout/NavSection.razor`

---

## 4. Login page

**`Login.razor`** — 36-line static login page. `@layout LoginLayout`, `@attribute [AllowAnonymous]`.
- Path: `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Login.razor`
- Form: `<form method="post" action="/auth/login" data-enhance="false">` (line 16).
- Uses Bootstrap `.card` / `.card-body` markup — **not** the Technical-Light `.panel` /
  `.login-wrap` idiom used in OtOpcUa. Does not use a `<LoginCard>` yet.
- Error notice: Bootstrap `.alert alert-danger` (line 12) rather than `.panel.notice`.

---

## 5. Scoped `.razor.css` files (stays per-project)

ScadaBridge ships several component-scoped CSS files. These are **not shared** and stay
in the CentralUI RCL after adoption:

| File | Path |
|---|---|
| `MultiSelectDropdown.razor.css` | `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Shared/` |
| `TreeView.razor.css` | `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Shared/` |
| `AuditDrilldownDrawer.razor.css` | `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Audit/` |
| `AuditEventDetail.razor.css` | `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Audit/` |
| `ExecutionDetailModal.razor.css` | `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Audit/` |
| `ExecutionTree.razor.css` | `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Audit/` |
| `AuditResultsGrid.razor.css` | `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Audit/` |
| `NodeBrowserDialog.razor.css` | `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Dialogs/` |

These scoped styles are component-specific overrides and are unaffected by theme adoption.

---

## 6. Divergences from spec

| Item | Current state | Spec |
|---|---|---|
| `theme.css` | Hand copy, 379 lines | Single canonical copy in RCL |
| Font-path `url()` | `url('../fonts/…')` — **correct** | `url('../fonts/…')` — same |
| IBM Plex fonts | Vendored 3× in `wwwroot/fonts/` | Single copy in RCL `wwwroot/fonts/` |
| Shell class | `d-flex …` (no `.app-shell`) | `ThemeShell` + `.app-shell` |
| Nav class idiom | `.sidebar` + `.nav-link` + `<ul>/<li>` | `.side-rail` + `.rail-link` + `<a>` |
| Nav items | `<NavLink class="nav-link">` inside `<li>` | `<NavRailItem>` |
| Nav sections | `NavSection` (`EventCallback OnToggle` + JS) | `NavRailSection` (`<details>`, CSS-only) |
| Status chip | None (uses raw `.chip-*` classes inline) | `StatusPill` (`StatusState` enum) |
| Login card | Bootstrap `.card` markup (not Technical-Light `.panel`) | `<LoginCard>` |
| Scoped `.razor.css` | 8 component-scoped files | Stays per-project (no change) |

---

## 7. Adoption plan

**Effort: Medium. Risk: Medium.** The font path is already correct so no 404 fix needed.
The sidebar idiom migration (`.sidebar`→`.side-rail`, `.nav-link`→`.rail-link`) and the
`MainLayout` replacement are the main work. The scoped `.razor.css` files are unaffected.

**Steps:**

1. **Delete copies.** Remove `src/ZB.MOM.WW.ScadaBridge.CentralUI/wwwroot/css/theme.css`
   and `wwwroot/fonts/ibm-plex-*.woff2` from `CentralUI`.

2. **Reference RCL.** Add `<PackageReference Include="ZB.MOM.WW.Theme" />` to
   `ZB.MOM.WW.ScadaBridge.CentralUI.csproj`. Add `@using ZB.MOM.WW.Theme` to
   `CentralUI`'s `_Imports.razor`.

3. **Wire `ThemeHead`.** In `Host`'s `App.razor`, replace line 9
   (`<link href="_content/…/css/theme.css">`) with `<ThemeHead />` (which now resolves
   via `ZB.MOM.WW.Theme`). Keep the `site.css` link on line 11.

4. **Replace `MainLayout`.** Replace the 29-line `MainLayout.razor` with a thin wrapper
   around `<ThemeShell Product="ScadaBridge">`. Carry `<NavMenu />` into the `Nav` slot
   (or replace it — see step 5). Keep `<DialogHost />` and `<SessionExpiry />` below the
   `ThemeShell` or inside `ChildContent` as needed.

5. **Port nav.** Migrate `NavMenu.razor` from `nav.sidebar` + `<ul>/<li>` + `.nav-link`
   to `<NavRailSection>` + `<NavRailItem>`. The `AuthorizeView` policy gating on admin
   sections stays — wrap `<NavRailSection>` inside the appropriate `<AuthorizeView>` just
   as today.

6. **Clean `site.css`.** Remove the `.sidebar` layout block. Keep Bootstrap-icons
   integration and any domain-specific overrides.

7. **Replace login card.** In `Login.razor`, replace the Bootstrap `.card`/`.card-body`
   markup with `<LoginCard Product="ScadaBridge" Action="/auth/login" ReturnUrl="@ReturnUrl"
   Error="@ErrorMessage"><AntiforgeryToken /></LoginCard>`. Align error display with the
   Technical-Light `.panel.notice` style from `LoginCard`.

8. **Keep:** all scoped `.razor.css` files (8 files listed above); `site.css` domain rules;
   auth and session endpoints; all page components.

**Risk note:** ScadaBridge's `AuthorizeView` policy-gated nav sections require careful
testing — verify that `<NavRailSection>` inside `<AuthorizeView>` renders correctly and
that the section is fully hidden when the policy fails (not just collapsed).
