Skip to content

feat: in-app auto-updater#35

Merged
hiskudin merged 1 commit into
mainfrom
feat/in-app-auto-updater
May 12, 2026
Merged

feat: in-app auto-updater#35
hiskudin merged 1 commit into
mainfrom
feat/in-app-auto-updater

Conversation

@hiskudin

@hiskudin hiskudin commented May 12, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds a complete in-panel auto-update flow for stack-nudge — no external installer required, no curl-pipe re-runs. Org members with gh CLI auth can update from any prior release to the latest with one keypress.

What lands

Surface Behaviour
Update detection UpdateChecker polls GitHub Releases on launch + every 6h. Falls back through gh api for private repos so authenticated org members get prompts transparently.
Settings tab indicator Dot badge next to "Settings" + a pinned "Update available · vX.Y.Z" row at the top of the list when a newer release exists.
Confirmation step Click the update row → release-notes markdown rendered with headings, bullets, inline links. Enter confirms, Esc cancels.
Install flow git clone --depth 1 of the latest tag, then install.sh runs in a detached session (Python fork + setsid) so it survives install.sh's pkill of the running panel mid-flight.
Progress UI Spinner + phase label (Cloning → Building → Voice engine → Launchd → Hooks → Done) driven by # STAGE: markers, with a heuristic fallback for older install.sh versions that lack the markers. Expandable "Show output" reveals the raw install log.
Auto-quit on done 2s after the install completes, the running panel quits gracefully so launchd brings up the freshly-installed bundle without two panels lingering.
Post-update welcome view First launch of the new bundle auto-opens the panel with a welcome-style "Updated to vX.Y.Z" screen showing the release notes for that specific tag (fetched via releases/tags/v…, same gh fallback). Enter or "Got it" dismisses.

How private-repo updates work

gh api repos/StackOneHQ/stack-nudge/releases/<latest|tags/vX.Y.Z> is invoked when the unauthenticated GitHub API returns 404. The gh binary lives at well-known homebrew paths so launchd's minimal PATH doesn't bite us. The user's existing gh auth (PAT / device flow) does the work — no token storage in stack-nudge.

Files

  • panel/UpdateChecker.swift (new) — release polling, version compare, gh fallback
  • panel/Updater.swift (new) — runner script, log tailing, phase parsing, PostUpdateView, UpdateConfirmView, UpdatingView, MarkdownNotesView
  • panel/Panel.swift — wire UpdateChecker + Updater, new modes in PanelContentView, keyboard handling for .updateConfirm / .updating / .postUpdate, handlePostUpdateStatus() on launch
  • panel/PanelNav.swift.updateConfirm / .updating / .postUpdate modes + state fields, updateRowOffset and beginUpdate / runUpdate actions
  • panel/Settings.swift — conditional update row at index 0, all subsequent rows shift via the offset
  • install.sh# STAGE: building|venv|launchd|hooks|done markers so the panel can show real progress
  • build.sh — compile the two new sources

Validated locally

  • ✅ Update check sees v1.4.2 against a v1.4.1 bundle (with gh fallback exercised on the private repo)
  • ✅ Confirmation view renders v1.4.2 release notes
  • ✅ Install runs end-to-end: clone, build, swap ~/Applications/stack-nudge.app, reload launchd, status file written
  • ✅ Phase UI advances through all stages thanks to STAGE markers + heuristic fallback
  • ✅ Auto-quit fires ~2s after Done
  • ✅ Post-update view auto-opens with v1.4.2 release notes (simulated via staged status file since v1.4.2 release itself predates this branch)
  • ✅ Multi-monitor fix from chore(main): release 1.4.0 #28 preserved post-rebase

Notes for reviewers

  • The runner's os.fork() + os.setsid() dance is required because Foundation.Process spawns children as process-group leaders, which makes setsid (and therefore session detachment) fail with EPERM. Forking once gives the grandchild a non-leader pid so setsid succeeds.
  • The post-update welcome view is built into this release but won't be visible to users yet — the v1.4.2 bundle they'd be updating from doesn't contain this code. From the first release after this lands, every subsequent update will display it.

Test plan

  • Merge → next release-please bump produces a vX.Y.Z that contains the auto-updater code
  • On a machine running that release, wait for the 6h check or launch fresh — Settings tab shows no dot (no newer release yet)
  • When a subsequent release lands, confirm the dot + update row appear
  • Click Update → confirm view → progress UI advances cleanly → app relaunches → PostUpdateView greets the user with the new release notes

🤖 Generated with Claude Code

Adds a complete in-panel auto-update flow for stack-nudge:

- UpdateChecker polls GitHub Releases on launch + every 6h with a
  graceful fallback through `gh api` so private-repo users with
  authenticated gh CLI still get update prompts.
- Settings tab surfaces a dot badge and a pinned "Update available
  · vX.Y.Z" row at the top of the list when a newer release exists.
- Confirmation step renders the release-notes markdown (parsed into
  headings + bullets + inline links) before kicking off the install.
- Install runs in a detached session (Python fork + setsid) so it
  survives install.sh's pkill of the running panel, with a phase
  progress UI driven by STAGE markers and a fallback heuristic for
  pre-marker install.sh versions.
- Welcome-style PostUpdateView auto-opens on the first launch after
  a successful update, showing exactly what shipped via a tag-specific
  release-notes fetch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@hiskudin hiskudin merged commit aa30bb9 into main May 12, 2026
4 checks passed
@hiskudin hiskudin deleted the feat/in-app-auto-updater branch May 12, 2026 14:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant