diff --git a/windows/docs/superpowers/specs/2026-06-09-wizard-app-recipes-design.md b/windows/docs/superpowers/specs/2026-06-09-wizard-app-recipes-design.md new file mode 100644 index 0000000..11f5dc3 --- /dev/null +++ b/windows/docs/superpowers/specs/2026-06-09-wizard-app-recipes-design.md @@ -0,0 +1,166 @@ +# SilverOS Welcome — Role App Recipes + +> **Status**: design — 2026-06-09. Approved in brainstorming. Adds a per-role app-install +> picker to the first-logon Welcome wizard. Builds on the wizard in +> `windows/welcome/` and the flavour model in `windows/flavours/`. + +## 1. Goal + +Make the wizard's role/flavour selection *do something*: after choosing a role, the user +sees a grouped, pre-checked list of apps to install during onboarding — role-relevant tools +plus privacy-trimmed essentials — and the chosen apps are installed as part of Apply. + +## 2. Decisions (locked in brainstorming) + +- **Install engine: winget, phased.** v1 uses winget (`winget install --id --silent`). + IoT Enterprise LTSC ships *without* winget, so the engine bootstraps the App Installer at + apply time. Every catalog app has a winget id. The catalog's per-app `source` object is + **swappable** to a curated SilverMetal mirror later (the air-gap/privacy end-state) without + changing the UI or engine contract. +- **Stack stays auto-installed.** The SilverLABS Stack (`SilverBrowser`=ungoogled-chromium + rebrand, `SilverVPN`, `SilverKeys`, …) continues to install automatically via each flavour's + `appSet` and is **not** in the picker (shown as "included"). The picker adds **role apps** + plus **opt-in privacy-trimmed third-party** apps (incl. vanilla ungoogled-chromium and a + Thunderbird email option). +- **Per-role lists + Essentials defaults** as in §4 (operator-approved; editable in the JSON). +- **WDAC caveat**: Developer/Daily-Driver run app-control in *audit* (apps run); Privacy-Max/ + Journalist run *enforce* (third-party apps blocked until allow-listed). v1 installs anyway and + surfaces a clear note for enforce-mode roles; full WDAC allow-listing is a follow-up. + +## 3. Architecture (four small units) + +``` +windows/apps/catalog.json # the app catalog (staged into the image like flavours/) +SilverOS.Welcome.Core/Apps/ + AppCatalog.cs # record + loader (mirrors FlavourManifest) + AppCatalogEntry.cs + IAppInstaller.cs / AppInstaller.cs # winget bootstrap + per-app install + configure +SilverOS.Welcome.UI/Components/Steps/ + AppsStep.razor # new wizard step: grouped checkboxes +WizardState.cs # + SelectedApps (ids) +ApplyService.cs # calls IAppInstaller after the Stack/hardening +``` + +### 3a. Catalog schema (`catalog.json`) +```json +{ + "schemaVersion": 1, + "apps": [ + { + "id": "vscodium", + "name": "VSCodium", + "description": "Telemetry-free VS Code build.", + "source": { "winget": "VSCodium.VSCodium" }, + "group": "developer", + "roles": ["developer"], + "defaultFor": ["developer"], + "configure": null + }, + { + "id": "ungoogled-chromium", + "name": "ungoogled-chromium", + "description": "Chromium with Google integration stripped.", + "source": { "winget": "eloston.ungoogled-chromium" }, + "group": "essentials", + "roles": ["essentials"], + "defaultFor": [], + "configure": "ungoogled-chromium.ps1" + } + ] +} +``` +- `roles`: which roles are *offered* this app (`essentials` = all roles). +- `defaultFor`: roles where the checkbox is **pre-checked**. +- `source.winget`: the winget id (only `source` key in v1; a future `mirror` key is additive). +- `configure`: optional post-install script (relative to `windows/apps/configure/`), e.g. the + ungoogled-chromium policy that enables the Chrome Web Store + sets safe search/suggestions. + +### 3b. AppCatalog loader +Mirrors `FlavourManifest`/`IFlavourLoader`: `AppCatalogEntry` record + `AppCatalog.Load(dir)` +reading `catalog.json` with the same `JsonSerializerOptions` (case-insensitive, comments, +trailing commas). A pure function `AppsForRole(role)` returns the entries to display grouped, +and `DefaultSelectionForRole(role)` returns the pre-checked ids. + +### 3c. AppsStep (new wizard step, after Flavour) +- Renders **Essentials** group first, then the chosen role's group; each app a checkbox with + name + description; pre-checked from `DefaultSelectionForRole`. +- Writes the set of selected ids into `WizardState.SelectedApps`. +- Always valid (zero apps is allowed) — Next is never blocked. Notifies the host like the other + steps (so Next state is correct immediately — same `OnSelected`/`StateHasChanged` pattern as + the FlavourStep fix). +- `Routes.razor` gains a step between Flavour (1) and Account; step indices/titles shift by one. + +### 3d. AppInstaller (Apply-step engine) +`IAppInstaller.InstallAsync(IReadOnlyList apps, IProgress, ct)`: +1. **Bootstrap winget** if absent: install `Microsoft.DesktopAppInstaller` + deps + (`Microsoft.VCLibs…`, `Microsoft.UI.Xaml…`) via `Add-AppxProvisionedPackage`/staged msix. +2. **Per app**: `winget install --id --silent --accept-package-agreements + --accept-source-agreements`; capture exit code; **continue on failure** (a bad app must not + fail onboarding); run `configure` script if present and the install succeeded. +3. Return a per-app result list (id → installed/failed); ApplyService stows a summary for the + Done step ("Installed N of M apps; failed: …"). + +Runs **after** hardening + Stack + accounts, **before** BitLocker (so encryption is last), via a +new `progress.Report(new("Installing apps", …))` stage. + +## 4. Catalog contents (v1 — editable) + +| Group | App | winget id | default-checked for | +|---|---|---|---| +| essentials | Thunderbird | `Mozilla.Thunderbird` | all | +| essentials | VLC | `VideoLAN.VLC` | all | +| essentials | 7-Zip | `7zip.7zip` | all | +| essentials | LibreOffice | `TheDocumentFoundation.LibreOffice` | all | +| essentials | ungoogled-chromium | `eloston.ungoogled-chromium` | — (opt-in; configured) | +| essentials | KeePassXC | `KeePassXCTeam.KeePassXC` | — | +| developer | VSCodium | `VSCodium.VSCodium` | developer | +| developer | Git | `Git.Git` | developer | +| developer | .NET 9 SDK | `Microsoft.DotNet.SDK.9` | developer | +| developer | Node.js LTS | `OpenJS.NodeJS.LTS` | developer | +| developer | Windows Terminal | `Microsoft.WindowsTerminal` | developer | +| developer | PowerShell 7 | `Microsoft.PowerShell` | developer | +| developer | Claude Desktop | `Anthropic.Claude` | developer | +| developer | Visual Studio 2022 | `Microsoft.VisualStudio.2022.Community` | — | +| developer | JetBrains Rider | `JetBrains.Rider` | — | +| developer | Docker Desktop | `Docker.DockerDesktop` | — | +| developer | Claude Code (CLI) | *(npm `@anthropic-ai/claude-code`, needs Node)* | — | +| developer | Google Chrome | `Google.Chrome` | — | +| developer | PostgreSQL | `PostgreSQL.PostgreSQL` | — | +| developer | Bruno (API client) | `Bruno.Bruno` | — | +| journalist | VeraCrypt | `IDRIX.VeraCrypt` | journalist | +| journalist | KeePassXC | `KeePassXCTeam.KeePassXC` | journalist | +| journalist | Joplin | `Joplin.Joplin` | journalist | +| journalist | OBS Studio | `OBSProject.OBSStudio` | — | +| journalist | Standard Notes | `StandardNotes.StandardNotes` | — | +| journalist | Signal | `OpenWhisperSystems.Signal` | — | +| journalist | Tor Browser | `TorProject.TorBrowser` | — | +| daily-driver | Spotify | `Spotify.Spotify` | — | +| daily-driver | Zoom | `Zoom.Zoom` | — | +| daily-driver | Discord | `Discord.Discord` | — | +| privacy-max | VeraCrypt | `IDRIX.VeraCrypt` | — | + +> Claude Code (CLI) installs via npm, not winget — modelled with a `source.npm` variant the +> engine handles separately (and only if Node is selected/present). Listed but lower priority. + +## 5. Build wiring +- `build.ps1`: stage `windows/apps/` (catalog.json + configure/) into the image + (`C:\Program Files\SilverOS\Welcome\apps\` next to the flavours), same as flavours. +- The Welcome app reads the catalog from `AppContext.BaseDirectory\apps\catalog.json`. + +## 6. Error handling +- Catalog missing/!parse → the Apps step shows an empty/"no extra apps" state and onboarding + continues (never blocks). +- winget bootstrap fails (offline) → log it, skip the install stage with a Done-step note; + onboarding still completes. +- Per-app install failure → recorded, surfaced in the Done summary, never throws. + +## 7. Testing +- `AppCatalog` deserialization + `AppsForRole`/`DefaultSelectionForRole` unit tests (xUnit). +- `AppInstaller` against a fake `IProcessRunner`: asserts winget-bootstrap when absent, the exact + `winget install` invocation per selected app, continue-on-failure, and configure-script run. +- `AppsStep` selection/validity (bUnit, matching the existing step tests' style). + +## 8. Out of scope (follow-ups) +- Curated SilverMetal mirror (the `source.mirror` end-state) + signing for WDAC-enforce. +- WDAC allow-listing of installed apps for Privacy-Max/Journalist enforce mode. +- Per-app version pinning / update policy.