fix(theme): shell fills the dynamic viewport — drop calc(100vh - 3.3rem) (scadaproj#10)
.app-shell min-height is now 100vh with a 100dvh override (the 3.3rem was a donor-app top-bar leftover; ThemeShell has no top bar) and the lg+ sticky rail is 100vh/100dvh. Source bumped to 0.5.3, UNPUBLISHED. Adds a static regression test and fixes the CS8602 in NavStateScriptTests (0 warnings). Browser-checked via Overview on local 0.5.2 vs 0.5.3 packages: at 390x844 a short page went from a 791px shell (53px gap) to 844px, no extra scroll; 1440x900 rail stays sticky full-height. Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
This commit is contained in:
@@ -160,7 +160,7 @@ each project's **code-verified current state**, and the **gaps** between. See
|
||||
| Component | Status | Goal | Design | Implementation |
|
||||
|---|---|---|---|---|
|
||||
| Auth (login / identity / authz) | **2026-11-09: source at `0.4.4`, UNPUBLISHED** (0.4.2 typed `ApiKeyPepperUnavailableException`; 0.4.3 Sqlite 10.0.13; 0.4.4 user-bind proves the bound account = the entry whose groups it returns (#40), SQL Server key ids case-exact, `ApiKeyFailure.StoreUnavailable`) — live-verify vs real AD before publishing (scadaproj#51). Adopted. Feed carries **`0.4.1`** (2026-10-01; 0.4.0 the same day; opt-in user bind) — **all four apps pin `0.4.1` on their default branches since 2026-11-09** (scadaproj#5: OtOpcUa `1ecd01d3`, ScadaBridge `37fcd296`, mxgw `ace12f81`, HistorianGateway `0d06483`; `BindMode` left at `ServiceAccount`; NOT deployed — wonder boxes still run `0.3.0` builds). History below refers to the earlier `0.3.0` pins. **All four apps pin `0.3.0`** (2026-09-03, all on their default branch and pushed — and all four are DEPLOYED at those tips on DEV vd03 and QA vq03/vq04 since 2026-09-10: OtOpcUa `b8b4c18f`, mxgw `4f931e4`, HistorianGateway `d8d890c`; ScadaBridge already had it on `main` via the `inbound-api-key-shared-store` work, `e45c4af9`). **0.4.0 (2026-10-01) — opt-in LDAP user bind:** adds `LdapOptions.BindMode` (`ServiceAccount` default | `User`), `UserBindTemplate` and `AcceptDomainQualifiedNames`; in `User` mode `LdapAuthService` binds as the signing-in user and reads their own groups on the same connection, so no service account is needed. Bind failures map to `BadCredentials` (bad password, unusable name, or bind result 49) or `GroupLookupFailed` (zero/multiple entries or no groups after a good bind), other bind/connect/search errors fail over to the next endpoint, and `ServiceAccount` stays the default so no existing consumer needs to change. **0.4.1** fixes the review findings: `Username` is canonicalised from the entry's `UserNameAttribute`, empty passwords are refused in both bind modes (a `ServiceAccount`-mode behaviour change), and the docs prefer the down-level `EXAMPLE\{0}` template over UPN. First consumer: BTDB Manager; ScadaBridge/HistorianGateway/OtOpcUa/MxAccessGateway stay on `0.3.0` until they move to AD. `0.2.1` is a functional fix, not housekeeping — `0.1.5` cannot log in against an AD domain root at all. `FallbackServers` (0.2.0) live-gated on the redundant local GLAuth pair. Version narrative, consumer matrix, `tools/LdapProbe`: [`ZB.MOM.WW.Auth/CLAUDE.md`](ZB.MOM.WW.Auth/CLAUDE.md). | Shared `ZB.MOM.WW.Auth` lib | [`components/auth/`](components/auth/) | [`ZB.MOM.WW.Auth/`](ZB.MOM.WW.Auth/) |
|
||||
| UI Theme (layout / tokens / components) | **2026-11-09: all five consumers pin `0.5.0` on their default branches (scadaproj#9; not deployed, no browser check yet); source at `0.5.2` unpublished (TechButton `disabled`, nav-state WeakSet, opt-in `ThemeShell ScrollNav`).** Adopted. Feed `0.2.0`–`0.4.1`; **five** consumers (the ignitionoee router joined), all on `0.4.1` and pushed (2026-08-11); Every deployed wonder box (DEV vd03 + QA vq03/vq04) runs its repo tip as of 2026-09-10, so the deployed Theme is 0.4.1 everywhere. **On bump, TRIM the local `.btn` block, don't delete it** — mxgw's carried more than sizing. Narrative: [`ZB.MOM.WW.Theme/CLAUDE.md`](ZB.MOM.WW.Theme/CLAUDE.md). | Shared `ZB.MOM.WW.Theme` RCL | [`components/ui-theme/`](components/ui-theme/) | [`ZB.MOM.WW.Theme/`](ZB.MOM.WW.Theme/) |
|
||||
| UI Theme (layout / tokens / components) | **2026-11-09: all five consumers pin `0.5.0` on their default branches (scadaproj#9; not deployed, no browser check yet); source at `0.5.3` unpublished (TechButton `disabled`, nav-state WeakSet, opt-in `ThemeShell ScrollNav`; 0.5.3 shell `100dvh` fill, #10).** Adopted. Feed `0.2.0`–`0.4.1`; **five** consumers (the ignitionoee router joined), all on `0.4.1` and pushed (2026-08-11); Every deployed wonder box (DEV vd03 + QA vq03/vq04) runs its repo tip as of 2026-09-10, so the deployed Theme is 0.4.1 everywhere. **On bump, TRIM the local `.btn` block, don't delete it** — mxgw's carried more than sizing. Narrative: [`ZB.MOM.WW.Theme/CLAUDE.md`](ZB.MOM.WW.Theme/CLAUDE.md). | Shared `ZB.MOM.WW.Theme` RCL | [`components/ui-theme/`](components/ui-theme/) | [`ZB.MOM.WW.Theme/`](ZB.MOM.WW.Theme/) |
|
||||
| Health (readiness / liveness / active-node) | **Adopted** — lib `0.3.0`, all 4 apps reference it + wire `MapZbHealth`. `0.2.1`/`0.3.0` changed the active tier's behaviour (standby now answers **503**; active = oldest `Up` member via `ClusterActiveNode`, not `RoleLeader` — the two diverge after any restart), and both Akka apps' private copies were deleted in its favour (merged + pushed). Narrative: [`ZB.MOM.WW.Health/CLAUDE.md`](ZB.MOM.WW.Health/CLAUDE.md). | Shared `ZB.MOM.WW.Health` lib | [`components/health/`](components/health/) | [`ZB.MOM.WW.Health/`](ZB.MOM.WW.Health/) |
|
||||
| Observability (metrics / traces / logs) | **Adopted** (libs `0.1.0`, on the feed; **all 4 apps** call `AddZbTelemetry`; `AddZbSerilog` in all but ScadaBridge, which keeps its own factory by design) | Shared `ZB.MOM.WW.Telemetry` lib + `.Serilog` | [`components/observability/`](components/observability/) | [`ZB.MOM.WW.Telemetry/`](ZB.MOM.WW.Telemetry/) |
|
||||
| Config + validation (options / startup validation) | Adopted (lib `0.1.0`, on the feed; **all 4 apps**, pushed — the "local only / not yet pushed" caveat was stale) | Shared `ZB.MOM.WW.Configuration` lib | [`components/configuration/`](components/configuration/) | [`ZB.MOM.WW.Configuration/`](ZB.MOM.WW.Configuration/) |
|
||||
|
||||
@@ -2,7 +2,22 @@
|
||||
|
||||
Shared UI kit (Razor Class Library — Technical-Light tokens, IBM Plex, side-rail shell, widgets) for the **ZB.MOM.WW SCADA family**; five consumers. Build/test from this directory: `dotnet test`. Component spec + gaps: `../components/ui-theme/`.
|
||||
|
||||
**Backlog / open work:** lives in Gitea issues on `dohertj2/scadaproj` (`tea issues list --repo dohertj2/scadaproj`) — currently #9 (roll `0.5.0` to the other consumers), #10 (`layout.css` `calc(100vh - 3.3rem)` review). The history below is a record, not a status tracker; file new follow-ups as issues.
|
||||
**Backlog / open work:** lives in Gitea issues on `dohertj2/scadaproj` (`tea issues list --repo dohertj2/scadaproj`) — currently #9 (roll `0.5.0` to the other consumers); #10 (`calc(100vh - 3.3rem)`) is fixed in 0.5.3 source. The history below is a record, not a status tracker; file new follow-ups as issues.
|
||||
|
||||
## 0.5.3 (2026-11-09, UNPUBLISHED)
|
||||
|
||||
Fixes scadaproj#10. `.app-shell` was `min-height: calc(100vh - 3.3rem)` — the 3.3rem is a donor-app top-bar
|
||||
leftover (ThemeShell renders no top bar), so below lg a short page stopped 53 px short of the viewport, and plain
|
||||
`100vh` overshoots on mobile browsers with collapsing toolbars. Now `min-height: 100vh; min-height: 100dvh;`
|
||||
(100vh = fallback), and the lg+ sticky `#theme-rail` is `height: 100vh; height: 100dvh;`. The column layout's
|
||||
`flex: 1` on the page was already supplied by `.app-shell .page`, so the page takes the height left under the
|
||||
hamburger row. Static test `Shell_fills_the_dynamic_viewport_with_no_topbar_offset`; also fixed the CS8602 in
|
||||
`NavStateScriptTests` (0 warnings). Browser check (Overview on local 0.5.2 vs 0.5.3 packages, iframe viewports):
|
||||
390x844 short page — before shell 791 px tall with a 53 px gap under the page, after shell/page reach 844,
|
||||
`scrollHeight == innerHeight` both times (no extra scroll); 1440x900 — rail sticky at 0–900 after scrolling a
|
||||
1133 px page, short page fills 900, before and after. Consumers: mxgw's `body.dashboard-body { min-height: 100vh }`
|
||||
and ScadaBridge's lg+ `#theme-rail > .side-rail { height: 100% }` don't conflict; neither app still has an inline
|
||||
`min-height:100vh` shell wrapper on its default branch. Version bumped in `Directory.Build.props`, not packed or pushed.
|
||||
|
||||
## 0.5.2 (2026-11-09, UNPUBLISHED)
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Version>0.5.2</Version>
|
||||
<Version>0.5.3</Version>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
||||
@@ -59,7 +59,7 @@ server-side** before redirecting (open-redirect risk):
|
||||
| Component | Parameters (key) | Notes |
|
||||
|---|---|---|
|
||||
| `ThemeHead` | — | Emits `<link>` tags for `theme.css` and `layout.css`. Place in `<head>` after Bootstrap. |
|
||||
| `ThemeShell` | `Product`*, `Accent`, `Logo`, `Nav`, `RailFooter`, `ChildContent`, `ScrollNav` | Side-rail chassis. Not a layout — delegated to from `MainLayout`. `Accent` overrides `--accent` token. `ScrollNav` (opt-in, default false; 0.5.2) wraps `Nav` in a `.rail-scroll` pane and fills the rail height on lg+ so a long nav scrolls while `RailFooter` stays pinned. |
|
||||
| `ThemeShell` | `Product`*, `Accent`, `Logo`, `Nav`, `RailFooter`, `ChildContent`, `ScrollNav` | Side-rail chassis. Not a layout — delegated to from `MainLayout`. The shell fills the viewport (`min-height: 100dvh`, `100vh` fallback; 0.5.3) and the lg+ sticky rail is `100dvh` tall, so no consumer needs its own `min-height: 100vh` wrapper. `Accent` overrides `--accent` token. `ScrollNav` (opt-in, default false; 0.5.2) wraps `Nav` in a `.rail-scroll` pane and fills the rail height on lg+ so a long nav scrolls while `RailFooter` stays pinned. |
|
||||
| `BrandBar` | `Product`*, `Logo` | Brand glyph + product name; rendered inside `ThemeShell`'s rail header. |
|
||||
| `NavRailItem` | `Href`*, `Text`*, `Icon`, `Match` | Wraps `<NavLink class="rail-link">`. |
|
||||
| `NavRailSection` | `Title`*, `Expanded` (default `true`), `ChildContent` | CSS-only collapsible `<details>` group; no JS, works in static SSR. |
|
||||
|
||||
@@ -60,9 +60,17 @@
|
||||
direction is supplied by Bootstrap utilities on the wrapper
|
||||
(`d-flex flex-column flex-lg-row`) so the hamburger row stacks above the rail
|
||||
on <lg viewports and the rail sits beside the page on lg+. */
|
||||
/* Fill the viewport exactly: ThemeShell renders no top bar, so the old
|
||||
`calc(100vh - 3.3rem)` (a donor-app leftover) under-filled short pages. `dvh`
|
||||
tracks the visible viewport on mobile browsers with collapsing toolbars, where
|
||||
plain `100vh` overshoots and forces a scroll; `100vh` is the fallback for
|
||||
browsers without `dvh`. Below lg the shell is a column (hamburger row + page)
|
||||
and `.app-shell .page { flex: 1 }` lets the page take the remaining height
|
||||
(scadaproj#10). */
|
||||
.app-shell {
|
||||
align-items: stretch;
|
||||
min-height: calc(100vh - 3.3rem);
|
||||
min-height: 100vh;
|
||||
min-height: 100dvh;
|
||||
}
|
||||
|
||||
/* A closed shell must hide navigation only, never the page. Modern Chromium
|
||||
@@ -117,13 +125,14 @@
|
||||
position: sticky;
|
||||
top: 0;
|
||||
height: 100vh;
|
||||
height: 100dvh;
|
||||
align-self: flex-start;
|
||||
z-index: 1020;
|
||||
}
|
||||
}
|
||||
|
||||
/* Opt-in rail nav scroll pane (ThemeShell ScrollNav="true"; ScadaBridge#61).
|
||||
The sticky #theme-rail is 100vh on lg+ but .side-rail never filled it, so a
|
||||
The sticky #theme-rail is 100dvh on lg+ but .side-rail never filled it, so a
|
||||
long nav grew past the viewport and carried .rail-foot off-screen. With the
|
||||
opt-in class the rail fills the rail and only .rail-scroll scrolls; the brand
|
||||
bar and .rail-foot stay put. Below lg the rail is inline and scrolls with
|
||||
|
||||
@@ -32,7 +32,8 @@ public class NavStateScriptTests
|
||||
return; // Node not available on this machine.
|
||||
}
|
||||
|
||||
using (node!)
|
||||
Assert.NotNull(node);
|
||||
using (node)
|
||||
{
|
||||
var output = node.StandardOutput.ReadToEnd() + node.StandardError.ReadToEnd();
|
||||
node.WaitForExit();
|
||||
|
||||
@@ -53,6 +53,19 @@ public class StaticAssetsTests
|
||||
Assert.Matches(@"\.tt-leaf\s*\{\s*visibility:\s*hidden", css);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Shell_fills_the_dynamic_viewport_with_no_topbar_offset()
|
||||
{
|
||||
// scadaproj#10: ThemeShell has no top bar, so the donor-app `calc(100vh - 3.3rem)` under-filled short pages,
|
||||
// and plain 100vh overshoots on mobile browsers with collapsing toolbars. 100vh stays as the fallback.
|
||||
var css = File.ReadAllText(Path.Combine(Wwwroot, "css", "layout.css"));
|
||||
css = Regex.Replace(css, @"/\*.*?\*/", "", RegexOptions.Singleline);
|
||||
Assert.DoesNotContain("3.3rem", css);
|
||||
Assert.Matches(@"\.app-shell\s*\{[^}]*min-height:\s*100vh;\s*min-height:\s*100dvh;", css);
|
||||
Assert.Matches(@"\.app-shell \.page\s*\{[^}]*flex:\s*1;", css);
|
||||
Assert.Matches(@"#theme-rail\s*\{[^}]*height:\s*100vh;\s*height:\s*100dvh;", css);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Closed_mobile_shell_hides_only_the_rail_and_keeps_main_content_visible()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user