Skip to content

Commit 4451fd7

Browse files
feat: Group D — packaging, NSIS installer, branding fix & lock-safe auto-update (#33)
* docs(spec): Group D — packaging, NSIS installer, branding fix, auto-update Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(plan): Group D — packaging, installer, branding fix, auto-update Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(paths): unpackedAssetPath + use it for the packaged toast icon * feat(build): electron-builder NSIS config + dist scripts * feat(update): lock-safe electron-updater wiring + IPC + bridge * feat(about): lock-aware update-ready notice + restart button * fix(paths): unpack bundled chime + Firefox xpi paths for packaged build --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 8c11a3e commit 4451fd7

16 files changed

Lines changed: 4341 additions & 190 deletions

docs/superpowers/plans/2026-06-26-packaging-installer-autoupdate.md

Lines changed: 558 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
# Group D — Packaging, Installer, Branding Fix & Auto-update
2+
3+
**Date:** 2026-06-26
4+
**Status:** Design approved, ready for planning
5+
**Backlog:** App-shell lifecycle, Group D (#6 installer splash + the Windows
6+
toast/exe branding fix). The last group before the download web page.
7+
8+
## Goal
9+
10+
Stand up the Windows packaging pipeline (none exists yet) and ship a free,
11+
zero-cost distributable:
12+
13+
1. **Package** the Electron app into a branded, per-user **NSIS one-click installer**.
14+
2. **Fix the Windows toast & exe branding** so the app reads "Vitreous" (+ icon),
15+
not the raw AUMID / "electron.exe" — finishing decision #6 and completing the
16+
Group-A startup-entry branding.
17+
3. **Auto-update** via `electron-updater` + GitHub Releases, designed so an update
18+
can never interrupt a locked focus session.
19+
20+
## Distribution & cost decisions (locked)
21+
22+
- **Zero spend.** Every piece is free: electron-builder + NSIS (open-source),
23+
GitHub Releases (host), GitHub Pages (download page, next milestone).
24+
- **Unsigned, but signing-ready.** No paid certificate. The installer/app are
25+
unsigned → Windows SmartScreen shows a one-time "More info → Run anyway" until
26+
reputation builds; documented on the download page. The pipeline is structured so
27+
a signer can be added later with no rework.
28+
- **Free signing is a post-release follow-up, not part of Group D.** [SignPath
29+
Foundation](https://signpath.org/) gives free OV signing for qualifying OSS, but
30+
requires an existing released build + a download page first (so it cannot sign the
31+
first build). Pursue it after the download-page milestone. Publisher would then
32+
read "SignPath Foundation".
33+
- **No Microsoft Store / MSIX.** Store policy rejects apps that require elevation
34+
for *any* functionality — Vitreous requires it (hosts-file write via the elevated
35+
scheduled task, HKLM browser policies). MSIX virtualization would also break the
36+
enforcement core (elevated helpers, hosts edits, process kills). Direct download is
37+
the only viable channel for a system-level blocker.
38+
39+
## Section 1 — Packaging pipeline (electron-builder + NSIS)
40+
41+
**Tooling.** Add `electron-builder` (devDependency) layered on the existing
42+
`electron-vite build`. Config in a new **`electron-builder.yml`**. New scripts:
43+
- `"dist": "electron-vite build && electron-builder"` — full installer build.
44+
- `"dist:dir": "electron-vite build && electron-builder --dir"` — unpacked build for
45+
fast local testing (no installer).
46+
47+
**Identity.** `appId: com.vitreous.app` (matches `setAppUserModelId` and the toast
48+
AUMID). `productName: Vitreous` → binary is **`Vitreous.exe`** with "Vitreous"
49+
version metadata.
50+
51+
**Target.** `nsis`, **x64** only (arm64 deferrable). Output dir `release/`.
52+
53+
**Installer.** `oneClick: true`, `perMachine: false` → double-click installs per-user
54+
to `%LOCALAPPDATA%\Programs\Vitreous`, **no UAC for install**, auto-launches on
55+
finish. Branding: `assets/logo/icon.ico` for `installerIcon` / `uninstallerIcon` /
56+
`installerHeaderIcon`. NSIS auto-generates the uninstaller (also satisfies a future
57+
SignPath requirement).
58+
59+
**Package contents.** `files` includes built `out/**` plus the runtime assets not
60+
under `out/`: **`assets/**`** and **`extension/**`**. **`asarUnpack`** both so the
61+
path-sensitive files live on real disk (not inside `app.asar`):
62+
- `assets/**` — toast icon (`icon-300.png`) + bundled sounds (need real paths).
63+
- `extension/**` — the Firefox `.xpi` (Firefox installs from a `file://` path).
64+
65+
No change needed to `scheduler-task.js`/`taskPaths()` — it already resolves
66+
`Vitreous.exe` when `app.isPackaged`.
67+
68+
## Section 2 — Toast & exe branding fix (decision #6)
69+
70+
1. **Exe + Task Manager name** — free from `productName: Vitreous`. The Group-A
71+
startup entry and Task Manager → "Startup apps" now read **Vitreous** (resolving
72+
the "electron.exe" artifact seen during Group-A dev testing).
73+
2. **Start-Menu shortcut carries the AUMID** — Windows resolves a desktop app's toast
74+
name/icon from an installed shortcut whose `AppUserModelID` matches the running
75+
app. electron-builder NSIS stamps the shortcut AUMID from `appId`
76+
(`com.vitreous.app`), matching `setAppUserModelId('com.vitreous.app')`. With the
77+
already-shipped `registerToastAppId` HKCU registration, toasts attribute correctly
78+
in the **packaged** app.
79+
3. **Toast `IconUri` → real on-disk file.** `registerToastAppId` is currently passed
80+
`join(app.getAppPath(), 'assets','logo','icon-300.png')`, which resolves *inside*
81+
`app.asar` when packaged and won't render. Fix: a pure helper
82+
`unpackedAssetPath(appPath, rel)` returning the `app.asar.unpacked` twin when the
83+
path is inside an asar (exists because Section 1 unpacks `assets/**`), else the
84+
plain join. `index.js` passes its result to `registerToastAppId`. Unit-tested;
85+
rendering verified in the packaged smoke test.
86+
87+
## Section 3 — Auto-update (lock-aware)
88+
89+
**Mechanism.** Add `electron-updater` (dependency) + electron-builder GitHub publish
90+
provider: `publish: { provider: github, owner: subhasisbiswal012, repo: Vitreous }`.
91+
Build emits `latest.yml` + installer; publishing a GitHub Release makes it the feed.
92+
Unsigned auto-update works now; SignPath signing later just makes the publisher-match
93+
check pass — no rework.
94+
95+
**New module `src/main/updater.js`.** On app ready, **only when `app.isPackaged`** (a
96+
clean no-op in dev — never errors without `app-update.yml`):
97+
- `autoDownload = true`, `autoInstallOnAppQuit = true`.
98+
- `checkForUpdates()` ~15s after launch, then every 6h.
99+
- On `update-downloaded`: fire a toast ("A new version of Vitreous is ready") and
100+
broadcast status to the renderer.
101+
102+
**Lock-safety (falls out naturally).** We never call `quitAndInstall()` mid-session.
103+
With `autoInstallOnAppQuit = true`, the update installs **only when the app actually
104+
quits** — and quitting is already hard-blocked during any locked session
105+
(`shouldPreventQuit` in `before-quit`). So an update can never interrupt or end a
106+
focus session. The one explicit path, a "Restart to update now" button, is gated by
107+
the same `shouldPreventQuit`: locked → disabled with a note ("Vitreous will update
108+
when your session ends"); unlocked → set `isQuitting = true` then `quitAndInstall()`.
109+
110+
**UI (minimal).** IPC `update:getStatus` + `update:restartNow` on the bridge. The
111+
**About page** gains an update line — "Vitreous is up to date" / "Update ready —
112+
restart to apply" with a lock-aware Restart button — plus the one-time toast on
113+
download complete. No intrusive modal.
114+
115+
## Section 4 — Testing & packaging-gated verification
116+
117+
**Unit-testable (pure, `node:test`):**
118+
- `unpackedAssetPath(appPath, rel)` — asar → asar.unpacked mapping when packaged,
119+
plain join in dev.
120+
- A small test asserting `electron-builder.yml` parses and carries the critical keys
121+
(`appId`, `productName`, `nsis.oneClick`, `asarUnpack` covering `assets` +
122+
`extension`) so a careless edit can't silently break unpacking.
123+
- Auto-update's lock gate reuses the already-tested `shouldPreventQuit`; `updater.js`
124+
itself is electron-coupled and not unit-tested.
125+
126+
**Build-verified:** `npm run dist` produces `release/Vitreous Setup <version>.exe`
127+
(+ `latest.yml`) with no errors.
128+
129+
**Human-gated packaged smoke test** (needs the real installed app):
130+
1. One-click install → no UAC, lands in `%LOCALAPPDATA%\Programs\Vitreous`,
131+
auto-launches into the splash.
132+
2. Branding: session-start toast reads **Vitreous** + icon; Task Manager → Startup
133+
apps shows **Vitreous** after enabling run-at-startup; the real **hidden-to-tray
134+
login launch** is finally verifiable (the Group-A item not testable in dev).
135+
3. Firefox force-install works from the unpacked `.xpi`; bundled chime plays.
136+
4. Core enforcement intact in the packaged app: hosts blocking, the one-UAC scheduled
137+
task, app-kill, lockdown.
138+
5. Auto-update E2E once a second release exists (v0.1.0 → v0.1.1).
139+
140+
## Out of scope
141+
142+
- Code signing (free SignPath follow-up after the download page).
143+
- arm64 / 32-bit targets.
144+
- The download web page itself (next milestone; this group makes its artifacts).
145+
- macOS/Linux packaging (Windows-first).
146+
147+
## Files touched (anticipated)
148+
149+
- `electron-builder.yml` (new)
150+
- `package.json` (scripts, electron-builder devDep, electron-updater dep)
151+
- `src/main/paths.js` (new) — `unpackedAssetPath` pure helper
152+
- `src/main/updater.js` (new) — electron-updater wrapper, lock-aware
153+
- `src/main/index.js` — use `unpackedAssetPath` for the toast icon; init updater
154+
- `src/main/ipc.js``update:getStatus` / `update:restartNow` handlers
155+
- `src/preload/index.js``getUpdateStatus` / `restartToUpdate` bridge
156+
- `src/renderer/pages/About.jsx` — update line + lock-aware Restart button
157+
- `test/paths.test.js`, `test/electron-builder-config.test.js` (new)
158+
- `.gitignore` — ignore `release/`

electron-builder.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"$schema": "https://raw.githubusercontent.com/electron-userland/electron-builder/master/packages/app-builder-lib/scheme.json",
3+
"appId": "com.vitreous.app",
4+
"productName": "Vitreous",
5+
"directories": { "output": "release" },
6+
"files": ["out/**", "assets/**", "extension/**", "package.json"],
7+
"asarUnpack": ["assets/**", "extension/**"],
8+
"win": {
9+
"target": [{ "target": "nsis", "arch": ["x64"] }],
10+
"icon": "assets/logo/icon.ico"
11+
},
12+
"nsis": {
13+
"oneClick": true,
14+
"perMachine": false,
15+
"runAfterFinish": true,
16+
"installerIcon": "assets/logo/icon.ico",
17+
"uninstallerIcon": "assets/logo/icon.ico",
18+
"installerHeaderIcon": "assets/logo/icon.ico",
19+
"createDesktopShortcut": true,
20+
"createStartMenuShortcut": true,
21+
"shortcutName": "Vitreous"
22+
},
23+
"publish": { "provider": "github", "owner": "subhasisbiswal012", "repo": "Vitreous" }
24+
}

0 commit comments

Comments
 (0)