docs(windows): first-boot experience & branding design spec

Design for SilverMetal Windows first-boot: declarative branding build
(4 layers baked offline into the WIM, shared dual-mode module), hardened
onboarding kiosk (Shell Launcher v2 + Keyboard Filter for the one-time
sm-bootstrap session), and the Hybrid fullscreen glass-card presentation
for the Welcome app. Fills the empty Invoke-Brand stub (M4 branding).

Approved in brainstorming. Next: writing-plans.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
sysadmin
2026-06-09 13:53:58 +01:00
parent 394804f379
commit 66e7fd4ae8
2 changed files with 183 additions and 0 deletions

3
.gitignore vendored
View File

@@ -1,3 +1,6 @@
# Brainstorming / design scratch (mockups, companion state) — durable specs live in docs/
.superpowers/
# Build outputs
build/output/
build/cache/

View File

@@ -0,0 +1,180 @@
# SilverMetal Windows — First-Boot Experience & Branding
> **Status**: design — 2026-06-09. Approved in brainstorming. Fills the `Invoke-Brand`
> stub in [`installer/build.ps1`](../../../installer/build.ps1) (M4 branding milestone) and
> adds the hardened onboarding kiosk + branded first-boot presentation.
> Bound by [`../../../iso-builder.md`](../../../iso-builder.md), [`../../../hardening-spec.md`](../../../hardening-spec.md),
> and the product principles in [`../../../../docs/`](../../../../docs).
## 1. Goal
Give SilverMetal Windows one cohesive, declaratively-built identity across every surface a
user sees from power-on to desktop, and present the existing SilverOS Welcome onboarding
wizard as a hardened, escape-proof, branded first-boot experience.
Everything is **baked declaratively** — offline registry/file servicing of the WIM in
`build.ps1` plus a SYSTEM configuration step at end-of-setup. **No VM capture / golden-image**
(that idea remains parked).
## 2. Scope — three components, one initiative
| # | Component | Where it runs |
|---|---|---|
| **A** | **Declarative branding** — the four brand layers | Offline (WIM) *and* online (self-apply) — shared module |
| **B** | **Hardened kiosk** — Shell Launcher v2 + Keyboard Filter for the one-time `sm-bootstrap` session | Build / OOBE path only |
| **C** | **First-boot presentation** — Welcome app as fullscreen Hybrid glass card | MAUI Welcome app |
Out of scope: renaming the `SilverOS.*` app/namespace/paths to SilverMetal (tracked as a
separate follow-up); a graphical OEM pre-boot/boot-logo splash (Secure Boot — out, per
earlier brainstorm); bit-identical reproducibility (non-goal per `iso-builder.md §5`).
## 3. Decisions locked in brainstorming
- **Presentation**: Hybrid — fullscreen branded backdrop + centered frosted-glass card.
- **Kiosk**: hardened via Shell Launcher v2 (per-user → only `sm-bootstrap`), escapes disabled.
- **Branding**: all four layers — BitLocker pre-boot message, lock/sign-in, desktop
wallpaper+theme, OEM About. Custom bootloader / firmware logo OUT (Secure Boot).
- **Build**: declarative (`build.ps1` + offline registry). VM used only to design/verify visuals.
- **Aesthetic**: dark "void" canvas, cyan (`#00d4ff`) core mark, teal-green (`#00e5a0`)
secondary. Mockup: [`.superpowers/mockups/02-branding-layers.html`](../../../../.superpowers/mockups/02-branding-layers.html)
and `01-presentation-model.html`.
- **Name shown to users**: **SilverMetal Windows** on every branding surface. (The Enhanced
line is hardened Windows, not our own OS, so "SilverOS" would overclaim. The `SilverOS.*`
app strings are working-title leftovers → separate rename follow-up.)
- **Content**: support URL = `https://silverlabs.uk` (until a dedicated domain is locked);
OEM Model = generic `SilverMetal Windows`; BitLocker recovery message = minimal, URL-only.
- **Code structure**: split — shared dual-mode **branding** module + build-only **kiosk**.
## 4. Component A — Declarative branding (`windows/branding/`)
A shared, dual-mode module, mirroring the `hardening/` "write once, used by ISO + self-apply"
pattern.
```
windows/branding/
├── Apply-Branding.ps1 # -Mode Offline -MountPath <wim mount> | -Mode Online
├── branding.manifest.json # all strings (names, URLs, OEM fields) — single source of truth
├── assets/
│ ├── lockscreen.jpg
│ ├── wallpaper.jpg
│ ├── oemlogo.bmp # ~120x120 OEM About logo
│ └── SilverMetal.theme # dark + cyan accent .theme
└── README.md
```
- **Offline mode**: `reg load` the mounted image's `SOFTWARE` and `C:\Users\Default\NTUSER.DAT`
hives, write values, `reg unload` (with the `[gc]::Collect()` + sleep guard already used
elsewhere in `build.ps1`). Stage asset files into the mounted image.
- **Online mode**: write live `HKLM` / default-user hive and copy assets to the running system.
Same value set both ways.
### Layers, mechanism, lock policy
| Layer | Registry / file | Locked? |
|---|---|---|
| **1 · BitLocker pre-boot** | `SOFTWARE\Policies\Microsoft\FVE` — pre-boot recovery message + URL policy values. Message ≈ "SilverMetal Windows. Locked out? silverlabs.uk". | n/a (firmware) |
| **2 · Lock / sign-in** | `SOFTWARE\…\PersonalizationCSP` lock-screen image (per-device reliable path) **and** `SOFTWARE\Policies\Microsoft\Windows\Personalization\NoChangingLockScreen=1`. Stage `lockscreen.jpg` to `C:\Windows\Web\Screen\SilverMetal\`. | **Locked** |
| **3 · Wallpaper + theme** | default-user `NTUSER.DAT`: `Control Panel\Desktop\WallPaper` (+ `WallpaperStyle`), dark mode (`…\Themes\Personalize\AppsUseLightTheme=0`, `SystemUsesLightTheme=0`), cyan accent. Stage `wallpaper.jpg` + `SilverMetal.theme`. Applies to **every new account** incl. the real end user. | **Changeable** |
| **4 · OEM About** | `SOFTWARE\Microsoft\Windows\CurrentVersion\OEMInformation`: `Manufacturer=SilverLABS`, `Model=SilverMetal Windows`, `SupportURL=https://silverlabs.uk`, `Logo=<oemlogo.bmp path>`. | n/a |
### Honest limitation — BitLocker pre-boot (Layer 1)
Only the BitLocker **recovery** screen's message + URL are customizable. The normal
**PIN-entry** screen text ("Enter the PIN to unlock this drive") is fixed Windows UI and
**cannot** be branded. The mockup's branded PIN title is aspirational; Layer 1's real
deliverable is the recovery message + URL only. Exact `FVE` value names are pinned during
implementation (the M1 hardening `02-data-at-rest.ps1` already touches `FVE` for PIN enrolment).
## 5. Component B — Hardened kiosk (build-only)
Locks the ephemeral `sm-bootstrap` onboarding session so the user cannot escape the wizard.
The `sm-bootstrap` account, AutoLogon, and teardown already exist
([`autounattend.xml`](../../../installer/autounattend/autounattend.xml),
[`SetupComplete.cmd`](../../../installer/oem/SetupComplete.cmd), the Welcome app's `ApplyService`).
### Offline (in `build.ps1`)
- `DISM /Enable-Feature /All` for `Client-EmbeddedShellLauncher` (Shell Launcher v2) and
`Client-KeyboardFilter`, applied to the mounted WIM. (Both ship in IoT Enterprise LTSC.)
- Stage `windows/installer/oem/Configure-Kiosk.ps1` into `C:\Windows\Setup\Scripts\`.
### At end-of-setup (`SetupComplete.cmd`, runs as SYSTEM, after accounts exist, before first logon)
`Configure-Kiosk.ps1`:
- **Shell Launcher v2** (WMI `WESL_UserSetting`, online-only — hence configured here, not
offline): default shell = `explorer.exe`; `sm-bootstrap`'s shell = a small launcher that
starts the Welcome app **elevated** (reuses the baked UAC auto-approve:
`ConsentPromptBehaviorAdmin=0`). With no Explorer in that session there is **no taskbar and
no Start menu** — the escape the operator saw is structurally gone.
- **Keyboard Filter** (WMI `WEKF_PredefinedKey` / `WEKF_Settings`): block Win, Win+L,
Ctrl+Esc, and similar shell hotkeys; `DisableKeyboardFilterForAdministrators=false`.
- **Security-screen / escape policies**: `DisableTaskMgr=1`, `DisableLockWorkstation=1`,
hide fast-user-switching and Log off. Applied scoped to the `sm-bootstrap` session and
reverted at teardown (so the real user is unaffected).
### Interaction with the existing flow
- The `autounattend.xml` `FirstLogonCommands` app-launch is now **redundant and removed**
Shell Launcher launches the Welcome app as the session shell.
- `SetupComplete.cmd` keeps its existing "defer hardening to Welcome when the app is present"
branch; it gains the `Configure-Kiosk.ps1` call.
### Teardown (Welcome app `ApplyService`, on wizard success)
Already deletes `sm-bootstrap` + removes AutoLogon. **Adds**: remove the `sm-bootstrap` WESL
custom-shell entry, revert the escape policies, (optionally clear Keyboard Filter rules). The
features remain enabled but inert. The real end-user account then logs in to a normal,
branded Explorer desktop.
## 6. Component C — First-boot presentation (MAUI Welcome app)
The Welcome app
([`windows/welcome/src/SilverOS.Welcome.App`](../../../welcome/src/SilverOS.Welcome.App)) is
MAUI Blazor (WebView2). Today its window is the plain default and `MainLayout` is the stock
template.
### Native — window chrome
In the Windows lifecycle handler, customize the WinUI `AppWindow`:
- `OverlappedPresenter` with border + title bar off, not resizable / minimizable / maximizable;
use the FullScreen presenter so it covers the whole display.
- Non-closable (suppress/ignore close); Alt+F4 is additionally blocked by the Keyboard Filter.
This is the only native requirement — it removes the title bar and makes the app own the screen.
### Visual — Blazor + CSS only
The Hybrid look (full-bleed branded backdrop + centered frosted-glass card) is rendered
**entirely in the WebView with CSS** (`backdrop-filter: blur(...)` over the in-WebView wall),
exactly as the mockup demonstrates. **No OS-level Mica/Acrylic** — in a Shell-Launcher kiosk
there is no desktop behind the app to blur, so OS backdrop buys nothing.
Work: restyle `MainLayout` and the wizard step shell from the stock MAUI template to the brand
identity — branded backdrop, centered glass card, step rail, cyan/teal accents, the type and
motion direction from the SilverLABS aesthetic. The step components' logic is unchanged.
## 7. Build-flow wiring — what changes
1. `installer/build.ps1` `Invoke-Brand` → call `branding\Apply-Branding.ps1 -Mode Offline -MountPath $mount` and stage assets (inside the existing WIM-mounted block).
2. `installer/build.ps1` → new offline step: enable Shell Launcher + Keyboard Filter features; stage `Configure-Kiosk.ps1`.
3. `installer/oem/SetupComplete.cmd` → invoke `Configure-Kiosk.ps1` before first logon.
4. `installer/autounattend/autounattend.xml` → remove the `FirstLogonCommands` Welcome launch.
5. `welcome/...` → fullscreen borderless window + Hybrid CSS shell; `ApplyService` → kiosk teardown.
## 8. Testing
- **Branding module**: Pester tests for `Apply-Branding.ps1` — offline (load a throwaway hive
into a temp mount, apply, assert each value + asset staged) and online (apply, read back,
revert). Runs on the Windows runner; no hardware needed.
- **Kiosk + presentation**: the existing VM e2e harness (SLAB01 VM 102, `_stageiso.py` /
`_pverun.py` / `_shot.py`). Boot the built ISO → assert: no taskbar / Start in the bootstrap
session, Task Manager / Win+L / Ctrl+Alt+Del options blocked, the glass card is fullscreen,
the wizard completes → real user logs into a branded Explorer desktop with wallpaper, accent,
locked lock-screen, and correct OEM About.
- **Honest scope**: per `iso-builder.md §5`, "reproducible" = pinned inputs + recorded tool
versions + output SHA-256 + SBOM; bit-identical rebuild stays a documented stretch goal.
## 9. Open items (resolve during implementation, not blocking)
1. Pin exact `FVE` pre-boot recovery-message value names against the base media.
2. Confirm `PersonalizationCSP` vs `Policies\…\Personalization\LockScreenImage` reliability on
IoT Enterprise LTSC 24H2/25H2; pick the one that survives a clean OOBE.
3. Decide whether `Configure-Kiosk.ps1` sets the `sm-bootstrap` shell to the app directly
(with `requireAdministrator` manifest) or to an elevating launcher script — pick the
robust one during the elevation spike.
4. Final logo asset (`oemlogo.bmp`, lock-screen, wallpaper) — placeholder void/cyan mark used
until brand identity is finalized (`shared/branding/README.md` is still "to be defined").
5. Separate follow-up: rename `SilverOS.*` app / namespace / install path to SilverMetal.