Files
SilverMetal/windows/docs/superpowers/specs/2026-06-09-wizard-app-recipes-design.md
sysadmin 583ed4400c docs(welcome): role app-recipes design spec
Per-role app-install picker for the Welcome wizard: catalog.json + AppsStep + winget
install engine (phased, swappable source for a future curated mirror). Stack stays
auto-installed; picker adds role apps + privacy-trimmed essentials. Approved in
brainstorming. Next: writing-plans.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 23:58:37 +01:00

8.6 KiB

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 <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)

{
  "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<AppCatalogEntry> apps, IProgress<ApplyProgress>, 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 <source.winget> --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.