Skip to content

fix(auto-update): autoDownload=true + manual-check toast gate (Greg's #314 follow-up)#327

Merged
ghinkle merged 1 commit into
nimbalyst:mainfrom
ademczuk:fix/auto-update-policy-auto-download-followup-245
May 25, 2026
Merged

fix(auto-update): autoDownload=true + manual-check toast gate (Greg's #314 follow-up)#327
ghinkle merged 1 commit into
nimbalyst:mainfrom
ademczuk:fix/auto-update-policy-auto-download-followup-245

Conversation

@ademczuk
Copy link
Copy Markdown
Collaborator

Summary

Follow-up to PR #314 (closed-merged 2026-05-15). Greg signed off there with:

"I'll accept this to avoid the error conditions, but plan to move toward auto-download in the future."

This is that auto-download follow-up, marked DRAFT so it sits as an option you can pull in when ready rather than asking for review now.

What changes

Two-line behavioral change plus a small testable gate function:

  1. autoUpdater.autoDownload = false -> true in AutoUpdaterService constructor. With fix(updater): drop redundant checkForUpdates that tore down Squirrel.Mac proxy (#245) #314's Squirrel.Mac proxy race fixed, the safe-choice flag flips back to its electron-updater default.
  2. The update-toast:show-available send inside the update-available event handler is now gated on wasManualCheck. Background-poll updates surface only the post-download "Ready to install" toast; the manual "Check for Updates" menu item still confirms the find before bytes start arriving.
  3. Gating is a one-arg pure function shouldShowAvailableToast(wasManualCheck) in autoUpdaterUtils.ts, same pattern as the classifyUpdateError extraction from fix(updater): drop redundant checkForUpdates that tore down Squirrel.Mac proxy (#245) #314. Unit-tested without booting an Electron app global.

Flow under the new policy

background poll (auto)               manual "Check for Updates"
-----------------------              --------------------------
update-available                     update-toast:checking
  (no toast - gated)                 update-available
  auto-download starts                 update-toast:show-available
  download-progress                    auto-download starts
  update-downloaded                    download-progress
  update-toast:show-ready                (renderer: 'available'->'downloading')
                                       update-downloaded
                                       update-toast:show-ready

The renderer's updateListeners.ts already transitions 'available' -> 'downloading' on the first download-progress event (line 113-118), so the manual-check toast does not get stuck on "click Download" while the bytes are already coming in.

Tests

packages/electron/src/main/services/__tests__/autoUpdater.shouldShowAvailableToast.test.ts, 3 cases:

  • manual check -> returns true
  • background poll -> returns false
  • single boolean is the only state read (pin the contract so a future "factor in suppression" rework has to touch this test and the caller together)

Run locally, 3/3 pass. The existing autoUpdater.classifyUpdateError.test.ts (8 cases) still passes unchanged.

Why DRAFT

Two design questions I want to surface before this leaves DRAFT, both raised in my earlier reply on #314 that I never got to answer concretely:

1. Cancellation / retry hook if a newer release lands mid-download. electron-updater's internal state handles version-ahead detection on the next poll cycle automatically (4h interval), so a newer version landing during a silent download will be picked up. I did not add a separate hook because the existing poll loop covers it. Happy to add an explicit cancel-and-restart path inside AutoUpdaterService if you'd rather not rely on the poll loop alone.

2. The "Update Now" button on UpdateAvailableToast on the manual-check path. With autoDownload = true, when the user clicks "Check for Updates" and then clicks "Update Now" on the available toast, the toast button sends update-toast:download -> downloadUpdate() while electron-updater has already started the same download internally. electron-updater treats a repeat downloadUpdate() call as a no-op (returns the in-flight promise), so the user sees the toast immediately transition to 'downloading' as the existing progress events arrive. No double-download, no error. If you'd prefer the manual-check available toast to skip the "Update Now" button entirely (since the download is already happening), I can wire state === 'available' && autoDownloadInFlight to suppress the button in UpdateAvailableToast.tsx. That's a renderer-side polish so I left it out of this DRAFT.

Test plan

  • npx vitest run packages/electron/src/main/services/__tests__/autoUpdater.shouldShowAvailableToast.test.ts (3/3)
  • npx vitest run packages/electron/src/main/services/__tests__/autoUpdater.classifyUpdateError.test.ts (8/8 unchanged)
  • npx tsc --noEmit -p packages/electron/tsconfig.json clean for the auto-update files (pre-existing lexical type errors in packages/runtime are upstream and unrelated)
  • Manual smoke: trigger an alpha release, observe background poll fires only the "Ready to install" toast (no "Update Available" toast)
  • Manual smoke: click Help -> Check for Updates, observe checking toast -> available toast -> downloading toast -> ready toast progression
  • CI green

Refs #245 #314

@ademczuk ademczuk force-pushed the fix/auto-update-policy-auto-download-followup-245 branch from 8829e04 to c8a27dc Compare May 16, 2026 06:28
@ademczuk ademczuk force-pushed the fix/auto-update-policy-auto-download-followup-245 branch 2 times, most recently from 79123c1 to fd53769 Compare May 20, 2026 10:11
@ademczuk ademczuk marked this pull request as ready for review May 20, 2026 10:15
@ademczuk
Copy link
Copy Markdown
Collaborator Author

Rebased onto current main (was 37 behind) and marking ready for review. CI green across all three checks.

This is the auto-download follow-up you flagged when merging #314: "I'll accept this to avoid the error conditions, but plan to move toward auto-download in the future." With #314's Squirrel.Mac proxy race fixed, autoUpdater.autoDownload flips from false back to electron-updater's own default (true). To keep the background-poll path quiet, the update-toast:show-available toast is now gated on the manual-check path via a pure helper shouldShowAvailableToast(wasManualCheck) in autoUpdaterUtils.ts (3 unit tests). Background updates surface only the post-download "Ready to install" toast; the manual "Check for Updates" menu item still confirms the find first.

One thing worth your call: auto-download now happens on metered connections too, since that is the electron-updater default we are returning to. If you would rather gate auto-download on a connection-type or settings check, I can add that, but I kept this PR to the minimal flag-flip + toast-gate you described.

@ademczuk ademczuk marked this pull request as draft May 20, 2026 10:41
@ademczuk
Copy link
Copy Markdown
Collaborator Author

Putting this back to DRAFT after a closer look. With autoDownload = true, a manual "Check for Updates" both auto-starts the download (electron-updater) and shows the available toast with a live "Update now" button. The renderer only swaps that button out for the progress UI once the first download-progress event lands (update-toast:progress). In the gap between the toast appearing and the first progress event, a click on "Update now" routes through checkAndDownloadLatest() to a second downloadUpdate(), which is the same Squirrel.Mac proxy surface #245 was about.

It is likely mitigated (electron-updater returns the in-flight download promise on a re-entrant downloadUpdate(), and the button hides quickly), but I had not actually verified that interaction before marking this ready, so DRAFT is the honest state until it is either guarded or confirmed safe. Options I am weighing: gate the manual update-toast:download path on a "download already in flight" flag, or drop the manual download trigger entirely now that autoDownload covers it. Will follow up with one of those rather than leave the race unaddressed.

@ademczuk ademczuk force-pushed the fix/auto-update-policy-auto-download-followup-245 branch from fd53769 to 6f81c81 Compare May 20, 2026 18:13
@ademczuk ademczuk changed the title fix(auto-update): autoDownload=true + manual-check toast gate (Greg's #314 follow-up) [DRAFT] fix(auto-update): autoDownload=true + manual-check toast gate (Greg's #314 follow-up) May 20, 2026
@ghinkle
Copy link
Copy Markdown
Contributor

ghinkle commented May 21, 2026

It feels like Check for Update should just do the download and show the restart now toast. I think we can just get rid of the Update Available toast. I'd like to keep the manual trigger to check for download as I use that in testing frequently.

…nimbalyst#327)

Per maintainer direction on nimbalyst#327: with autoDownload=true both background
polls and the manual "Check for Updates" trigger download immediately and
surface only the "Ready to install" toast. The "Update Available" toast is
removed at the source (main no longer emits update-toast:show-available); the
renderer listener for it is now dormant. The manual menu trigger is unchanged.
@ademczuk ademczuk force-pushed the fix/auto-update-policy-auto-download-followup-245 branch from 6f81c81 to f57c0de Compare May 23, 2026 07:52
@ademczuk
Copy link
Copy Markdown
Collaborator Author

ademczuk commented May 23, 2026

@ghinkle, I have implemented finally your direction. autoDownload is on now, so both background checks and the manual "Check for Updates" trigger download immediately, and the "Update Available" toast is gone - the flow is download in the background, then the "Ready to install" toast. The manual menu trigger stays (it kicks off the check + download you use in testing).

Rebased onto current main as a single commit, CI green. One scoping note: I removed the toast at the source (main no longer emits update-toast:show-available), so the renderer UpdateAvailableToast is now dormant rather than deleted - happy to rip the dead component out in a follow-up if you'd prefer that cleanup in this PR too.

My appologies on the delayed response, due to speaking commitments this week.

@ademczuk ademczuk marked this pull request as ready for review May 23, 2026 07:56
@ghinkle ghinkle merged commit f53570a into nimbalyst:main May 25, 2026
3 checks passed
ghinkle added a commit that referenced this pull request May 27, 2026
- Non-markdown shared documents (Excalidraw, Mindmap, Mockup, etc.) now show the unified editor header bar with breadcrumb, View History, and local-source actions (Open Local, Relink, Clear).
- Editor header bar's "Shared to Team" dropdown now includes a link that jumps to the shared document, showing its name with the team-side folder path in subscript.
- Shared documents have revision history: Cmd/Ctrl+S saves a named version, auto snapshots run after idle, and any past revision can be restored from the History dialog.
- Custom shared-document editors can publish history controllers so the global History dialog can route to collaborative revisions.
- Extensions SDK permissions and backend modules
- Extension panels can run read-only SQL via `host.data.query()` against the local PGLite store when the manifest declares `nimbalyst-database-read` in `permissions.catalog`.
- Backend-module allowlist: only built-in extensions, curated marketplace ids, and dev-installed extensions with `NIMBALYST_ALLOW_DEV_BACKEND_MODULES=1` can ship native-code backends.
- Auto-update now downloads in the background and shows only the "Ready to install" toast; the redundant "Update Available" toast has been removed. (#327)
- Extension docs now cover all four markdown/transcript contribution surfaces in both the internal architecture doc and the public SDK docs, including declarative module exports, diff handlers, transcript renderer hooks, and the current `@nimbalyst/runtime` import surface.
- Backend-module consent prompt now leads with an explicit "this extension will run native code on your computer" banner; granular catalog ids only cover host-brokered services.
- Catalog permission ids `spawn-process`, `network-loopback`, `network-internet`, and `filesystem` were unenforceable inside a Node backend (the module can `require('child_process')` directly) and are no longer accepted. Manifests that still list them load with a non-fatal warning; the ids are silently dropped when computing effective permissions.
- CI `npm ci` no longer fails resolving `@nimbalyst/collab-adapters`; the workspace was missing from `package-lock.json` after the CollabContentAdapter commit.
- Claude Code sessions now flip to "ready" the moment the model's turn finishes instead of sitting on the last output for up to 30 s while the SDK stdin grace period expired.
- Claude Code sessions no longer reset mid-conversation when the agent spawns sub-agents. Sub-agent assistant chunks carry their own `session_id`; the transcript adapter was capturing that as the lead's, corrupting resume on the next turn. (#451, #456, #457)
- Project quick open now loads recent projects from stored recents instead of crawling every workspace on open.
- Rebuild Extensions submenu now lists buildable extensions alphabetically.
- Shared Excalidraw and mockup tabs no longer come back blank after restart or close+reopen — the document type is now persisted with each open tab and recovered server-side as a backstop.
- Tracker list, table, and kanban views now share the session-style `#tag` typeahead filter.
- Session history search bar no longer overlaps floating popovers (e.g. Claude Usage).
- Re-uploading a local source into a shared markdown document now waits for the collab write to be acknowledged before tearing down the headless sync client.
- Codex session-naming reminder no longer leaks into the chat transcript; its turn output is tagged so the transcript hides it. (#420)
- Mobile sync now picks up Codex sessions running in git worktrees by resolving worktree workspaceIds to the parent project path when matching against enabled sync projects. (#430) Thanks @stamkivi.
- Claude Code plugins installed at the project scope (via `<workspace>/.claude/settings.json` `enabledPlugins`) now appear in the Installed plugins list when the panel is opened from a project's settings, with badges showing the marketplace source and whether the plugin is user- or project-scoped.
- Slash-command typeahead now lists commands and skills from Claude CLI plugins (`~/.claude/plugins/`) without requiring the experimental "Agent Workflows" toggle, matching what the Claude Agent SDK auto-loads from `enabledPlugins`.
- Excalidraw "import mermaid" now registers the rendered diagram image, so it no longer shows as a broken thumbnail. (#428)
- Codex sessions now append actionable guidance when `~/.codex/config.toml` has a url-based MCP server the bundled Codex rejects, instead of an opaque config-load failure. (#424)
- Dollar signs in markdown no longer collapse currency text like `$7M ... $40M` as inline LaTeX math; the typing-time `$...$` shortcut is removed in favor of slash-menu "Math (inline)" / "Math (block)" entries. Pasting or loading markdown that already contains `$x$` still renders as math. (#447)
- Trackers panel now refetches when switching projects in the sidebar rail instead of staying pinned to the workspace that was active at app startup. (#441)
ghinkle added a commit that referenced this pull request May 27, 2026
- Non-markdown shared documents (Excalidraw, Mindmap, Mockup, etc.) now show the unified editor header bar with breadcrumb, View History, and local-source actions.
- Editor header bar's "Shared to Team" dropdown links to the shared document, showing its name with the team-side folder path in subscript.
- Shared documents have revision history: Cmd/Ctrl+S saves a named version, auto snapshots run after idle, and any past revision can be restored.
- Custom shared-document editors can publish history controllers so the global History dialog can route to collaborative revisions.
- Extensions SDK permissions and backend modules.
- Extension panels can run read-only SQL via `host.data.query()` against the local PGLite store when the manifest declares `nimbalyst-database-read`.
- Backend-module allowlist: only built-in extensions, curated marketplace ids, and dev-installed extensions with `NIMBALYST_ALLOW_DEV_BACKEND_MODULES=1` can ship native-code backends.
- Auto-update downloads in the background and shows only the "Ready to install" toast; the redundant "Update Available" toast is removed. (#327)
- Extension docs now cover all four markdown/transcript contribution surfaces in both the internal architecture doc and the public SDK docs.
- Backend-module consent prompt leads with an explicit "this extension will run native code on your computer" banner; granular catalog ids only cover host-brokered services.
- Claude Code sessions break out of the SDK iterator after the `result` chunk so the binary's task-list reminder hook can't emit a 14+ minute `tool_result(Stream closed)` flood that pins sendMessage open.
- Claude Code sessions flip to "ready" the moment the model's turn finishes instead of sitting on the last output for up to 30 s while the SDK stdin grace period expired.
- Claude Code sessions no longer reset mid-conversation when the agent spawns sub-agents. (#451, #456, #457)
- CI `npm ci` no longer fails resolving `@nimbalyst/collab-adapters`; the workspace was missing from `package-lock.json`.
- Project quick open loads recent projects from stored recents instead of crawling every workspace on open.
- Rebuild Extensions submenu lists buildable extensions alphabetically.
- Shared Excalidraw and mockup tabs no longer come back blank after restart or close+reopen.
- Tracker list, table, and kanban views share the session-style `#tag` typeahead filter.
- Session history search bar no longer overlaps floating popovers (e.g. Claude Usage).
- Re-uploading a local source into a shared markdown document waits for the collab write to be acknowledged before tearing down the headless sync client.
- Codex session-naming reminder no longer leaks into the chat transcript. (#420)
- Mobile sync picks up Codex sessions running in git worktrees by resolving worktree workspaceIds to the parent project path. (#430) Thanks @stamkivi.
- Claude Code plugins installed at the project scope appear in the Installed plugins list with marketplace and scope badges.
- Slash-command typeahead lists commands and skills from Claude CLI plugins without requiring the experimental "Agent Workflows" toggle.
- Excalidraw "import mermaid" registers the rendered diagram image, so it no longer shows as a broken thumbnail. (#428)
- Codex sessions append actionable guidance when `~/.codex/config.toml` has a url-based MCP server the bundled Codex rejects, instead of an opaque failure. (#424)
- Dollar signs in markdown no longer collapse currency text like `$7M ... $40M` as inline LaTeX; the typing-time `$...$` shortcut is replaced by slash-menu "Math (inline)" / "Math (block)". (#447)
- Trackers panel refetches when switching projects in the sidebar rail instead of staying pinned to the workspace that was active at app startup. (#441)
- Catalog permission ids `spawn-process`, `network-loopback`, `network-internet`, and `filesystem` are no longer accepted; they were unenforceable inside a Node backend. Manifests listing them load with a non-fatal warning.
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.

2 participants