feat(ENG-849): surface shell (installer) updates as an in-app notice#453
Draft
pnewsam wants to merge 1 commit into
Draft
feat(ENG-849): surface shell (installer) updates as an in-app notice#453pnewsam wants to merge 1 commit into
pnewsam wants to merge 1 commit into
Conversation
The Electron shell (src/main, preload, runtime, native deps) isn't covered by
OTA — it only updates via a manual reinstall — so all we can do is notice a
newer published shell and point the user at the installer. Detection only; no
download/install (that's ENG-850).
Behavior
- The boot/periodic poll compares the installed shell CalVer against a new
`shellVersion` field in latest.json; if strictly newer it pushes a
`shell-available` status over the existing UI_UPDATE_STATUS channel.
- Sidebar: a dismissible "New version available — Download" banner opening the
installer URL; dismissal is per-version (localStorage) so it re-notifies when
a newer shell ships.
- Settings → Updates: an "App update available" card (current → latest +
Download), shown regardless of banner dismissal.
- Download targets the per-platform CloudFront installer (prod
mindshub-cowork-latest.{pkg,exe}).
Two deliberate decisions
- Prod-only: the manifest is prod-only and a non-prod build must never be sent
to a prod installer (ENG-676), so checkForShellUpdate self-gates to
buildKindStrict() === 'prod'. shellDownloadUrl still maps stable → -staging
(tested) so it stays correct if non-prod is ever enabled.
- Explicit shellVersion (not the manifest's UI-bundle `version`): publish-ui.yml
emits it ONLY on the auto-release path (via a new shell_version input from
release.yml), where a prod installer actually ships — so a UI-only re-publish
can't fabricate a phantom reinstall notice. Absent shellVersion → no notice.
- update-logic.ts: UIManifest.shellVersion + parse, shellUpdateIsNewer,
shellDownloadUrl (+ 11 unit tests).
- updater.ts: checkForShellUpdate() orchestrator + poll wiring.
- host.ts / global.d.ts: shell fields on the update-status payload.
- App.jsx / Sidebar.jsx / SettingsView.jsx: state, banner, and Settings card.
- publish-ui.yml / release.yml: emit shellVersion on the release path.
- docs/update-behavior.md: shell-notice section.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
The Electron shell (
src/main, preload, the runtime, native deps) isn't covered by OTA — it only updates when the user downloads and reinstalls a new installer, and today the app gives no signal that a newer shell exists. This matters now that UI OTA is prod-only (ENG-670): a user stranded on an old shell keeps missing renderer fixes with no explanation.This adds a detect + notice flow (no auto-install — that's ENG-850):
getAppDisplayVersion()) against a newshellVersionfield inlatest.json; if strictly newer, main pushes ashell-availablestatus over the existingUI_UPDATE_STATUSchannel.current → latest+ Download), shown regardless of banner dismissal (Settings is a deliberate visit).…/mac/mindshub-cowork-latest.pkg/…/windows/mindshub-cowork-latest.exe(macOS installer is a.pkg).Two deliberate design decisions
checkForShellUpdateself-gates tobuildKindStrict() === 'prod'; non-prod simply doesn't check.shellDownloadUrlstill mapsstable → -staging(tested) so it stays correct if non-prod is ever enabled.shellVersion, not the manifest'sversion.versionis the UI-bundle version;shellVersionis the installer version.publish-ui.ymlemitsshellVersiononly on the auto-release path (newshell_versioninput fromrelease.yml), where a prod installer actually ships — so a UI-only re-publish can't fabricate a phantom "reinstall" notice for an installer that doesn't exist. AbsentshellVersion→ no notice. Fails closed everywhere (unknown kind, no manifest, non-CalVer versions).Implementation
update-logic.ts—UIManifest.shellVersion+ parse (advisory-only: a malformed value is ignored, never rejects the manifest / breaks OTA); pureshellUpdateIsNewerandshellDownloadUrl. +11 unit tests.updater.ts—checkForShellUpdate()orchestrator + poll wiring (runs before the OTA early-return so the notice surfaces even when UI/server are current).host.ts/global.d.ts— shell fields on the update-status payload.App.jsx/Sidebar.jsx/SettingsView.jsx— state, per-version dismiss, banner, and the Settings card.publish-ui.yml/release.yml— emitshellVersionon the release path.docs/update-behavior.md— new shell-notice section.Fixes ENG-849.
Screenshots
Pending — see verification note below.
Type of change
docs/update-behavior.md)Checklist:
Verification
npm run typecheck(main + renderer + tests)npm test— 368 passed, incl. 11 new (parseUiManifestshellVersion,shellUpdateIsNewer,shellDownloadUrl)check:cowork-purity· ✅npm run build:renderer· ✅ both workflow YAMLs parseshellVersion(none published yet) — exercisable via theCOWORK_OTA_MANIFEST_URLQA seam pointing at a fixture manifest withshellVersion. Left as draft until that smoke + screenshots are attached.Notes for reviewers
fetchManifest/hasInternet/UI_UPDATE_STATUS/ the sidebar banner — no new IPC channel, snapshot untouched.renderUpdatesSection— different sections, trivial merge.🤖 Generated with Claude Code