# Desktop shell and packaging (Phases D–E)

The desktop app runs the portable engine in Electron's main process. The Angular
renderer loads from the secure `app://app/` origin and reaches the engine only
through the typed, context-isolated preload bridge. It does not start Fastify,
SQLite, or any listening socket.

## Run from source

```bash
npm run start:desktop
```

`npm run build:desktop` builds Angular into `public/` and compiles the desktop
and engine import graph into `.phase-d/`.

## Smoke checks

```bash
npm run smoke:desktop
npm run smoke:desktop -- --search-all=0986452041
npm run smoke:desktop -- --force-solve=trodo --exercise-solve
```

The cross-platform smoke process loads the real renderer, calls stores and plan IPC from it,
prints one JSON result, and exits. Reusing `--user-data-dir=<path>` across two
runs verifies that `purchase-plan.json` survives restart. `--hold` keeps the app
open for 15 seconds so `ss -ltnp` can be compared with the post-exit baseline.

`--no-sandbox` may be needed in a restricted development container. It is not a
production setting and is never added by the application itself.

## Distribution builds

```bash
npm run package:win:portable
npm run package:desktop:win
npm run package:desktop:linux
npm run package:desktop:mac
```

The three update-capable `package:desktop:*` commands require
`DESKTOP_UPDATE_REPOSITORY=owner/repo`. GitHub Actions supplies its equivalent
`GITHUB_REPOSITORY` automatically. Release packaging fails immediately when
neither is available, so it cannot silently produce an installer without an
update feed. `package:win:portable` deliberately uses the base, feed-free config.

The command bundles the main process and preload into a dependency-free staging
app before invoking electron-builder. This deliberately excludes the POC-only
Fastify, SQLite, and Puppeteer dependencies. The resulting unsigned x64 `.exe`
installer/portable executable, AppImage, or DMG/ZIP is written to `release/`.
Platform-native artifacts should be built on their matching operating system;
the tag workflow does this on Windows, Linux, and macOS runners.

Installed NSIS and AppImage builds check GitHub Releases in the background. An
available update downloads without interrupting a search and prompts for restart
when ready. Portable Windows builds cannot self-update. The v0 macOS build is
unsigned, so macOS auto-update is deliberately disabled until signing and
notarization are configured.

Pushing a tag named exactly `v<package.json version>` starts
`.github/workflows/release-desktop.yml`. It runs the full fixture-based gate,
builds all platform artifacts plus updater metadata, and creates the GitHub
Release. A mismatched tag fails before packaging.

The current signing decision is lean v0 distribution: Windows and Linux are
unsigned, and macOS is an unsigned DMG/ZIP. Expect Windows SmartScreen and macOS
Gatekeeper warnings. Signing should be added before presenting these artifacts
as frictionless consumer installs.

The deterministic solve switch only forces the first store fetch in a process
to return `needsSolve`; showing the store window clears the switch and emits the
same `solve:cleared` event as a real completed challenge.
