diff --git a/.gitignore b/.gitignore index 3a6602c..4cc4203 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# Brainstorming / design scratch (mockups, companion state) — durable specs live in docs/ +.superpowers/ + # Build outputs build/output/ build/cache/ diff --git a/windows/docs/superpowers/specs/2026-06-09-first-boot-branding-design.md b/windows/docs/superpowers/specs/2026-06-09-first-boot-branding-design.md new file mode 100644 index 0000000..7dca339 --- /dev/null +++ b/windows/docs/superpowers/specs/2026-06-09-first-boot-branding-design.md @@ -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 | -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=`. | 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.