fix(desktop): support channel-aware update checks#345
Conversation
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (7)
🚧 Files skipped from review as they are similar to previous changes (5)
📝 WalkthroughWalkthroughAdds channel-aware desktop auto-update (stable/beta/nightly): build/runtime wiring, feed resolution and diagnostics, IPC/menu trigger for manual checks, renderer UI/banner and hook updates for "up-to-date" state, a small runtime-model resolve tweak, docs, and unit tests. Changes
Sequence Diagram(s)sequenceDiagram
participant User as User/Menu
participant Main as Main Process
participant UpdateMgr as UpdateManager
participant Feed as Update Feed (R2/GitHub)
participant Renderer as Renderer
rect rgba(200,230,255,0.5)
User->>Main: Select "Check for Updates…"
Main->>Renderer: send host command `desktop:check-for-updates`
Renderer->>UpdateMgr: invoke update.check() (userInitiated)
UpdateMgr->>UpdateMgr: resolveUpdateFeedUrl(env/feed/channel)
UpdateMgr->>Feed: fetch remote info (version, releaseDate)
Feed-->>UpdateMgr: return remote metadata
UpdateMgr->>Main: emit diagnostic events (checking → available/up-to-date/error)
Main->>Renderer: forward updater events with diagnostics
Renderer->>Renderer: update banner UI (phase, message, auto-dismiss)
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (3)
.github/workflows/desktop-build.yml (1)
161-161: Invalid channel values silently default tostableat runtime.The
${{ inputs.channel }}value is embedded directly without validation. If a typo occurs (e.g.,"nightyl"), the runtime will silently fall back to"stable"(perreadUpdateChannel()inruntime-config.ts), potentially causing the app to check the wrong update feed with no CI error.Consider adding early validation in the workflow:
♻️ Optional: Add channel validation step
- name: Validate channel input shell: bash run: | case "${{ inputs.channel }}" in stable|beta|nightly) ;; *) echo "Invalid channel: ${{ inputs.channel }}" >&2; exit 1 ;; esac🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/desktop-build.yml at line 161, Validate the GitHub Actions input before embedding it into NEXU_DESKTOP_UPDATE_CHANNEL to fail CI on typos: add a workflow step that checks the inputs.channel value against allowed values (stable, beta, nightly) and exits non‑zero on mismatch so the runtime fallback in readUpdateChannel() (runtime-config.ts) cannot silently use "stable"; ensure the step references the inputs.channel variable and produces a clear error message on invalid input.apps/desktop/src/components/desktop-shell.tsx (1)
52-52: Dependency onupdatecauses unnecessary effect re-runs.The
useAutoUpdate()hook returns a new object literal on every render ({ ...state, check, ... }at line 161 ofuse-auto-update.ts), which isn't memoized. Addingupdateto this dependency array will tear down and re-register theonDesktopCommandlistener on every render cycle.Since only
update.checkis used inside the effect, consider depending on the stablecheckcallback directly:♻️ Suggested fix
+ const { check: checkForUpdates } = update; + useEffect(() => { return onDesktopCommand((command) => { if (command.type === "desktop:auth-session-restored") { setWebSurfaceVersion((current) => current + 1); return; } if (command.type === "desktop:check-for-updates") { - void update.check(); + void checkForUpdates(); return; } setActiveSurface(command.surface); setChromeMode(command.chromeMode); }); - }, [update]); + }, [checkForUpdates]);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/desktop/src/components/desktop-shell.tsx` at line 52, The effect in desktop-shell.tsx depends on the unstable update object from useAutoUpdate(), causing the onDesktopCommand listener to be torn down and re-registered on every render; change the dependency to the stable callback used inside the effect by importing/destructuring check from useAutoUpdate() (i.e., const { check } = useAutoUpdate()) and replace update in the effect dependency array with check so only the stable check function is tracked and the onDesktopCommand listener remains stable.apps/desktop/shared/host.ts (1)
285-292: MakeUpdateCheckDiagnosticthe single shared interface.This is now a cross-process contract used by updater IPC payloads. Defining it as an
interfacehere and importing it from main/renderer code avoids shape drift and matches the repo convention for object-shape contracts. As per coding guidelines, "Prefer interface for defining object shapes in TypeScript instead of type aliases".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/desktop/shared/host.ts` around lines 285 - 292, Change the exported UpdateCheckDiagnostic type alias into an exported interface named UpdateCheckDiagnostic (replace "export type UpdateCheckDiagnostic = { ... }" with "export interface UpdateCheckDiagnostic { ... }"), update any imports/usages across main/renderer/updater IPC code to import this shared interface, and ensure optional fields (remoteVersion, remoteReleaseDate) keep their optional markers; this centralizes the cross-process contract and prevents shape drift.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/desktop/main/index.ts`:
- Around line 272-291: The "Check for Updates…" app menu item currently always
binds to triggerUpdateCheck() even when UpdateManager hasn't been constructed,
so clicking can silently no-op; update the menu entry (the object with label
"Check for Updates…" in the appMenu submenu) to either (A) be disabled by
default (add enabled: false) and then enable it when the UpdateManager instance
is created, or (B) change its click handler to call the live manager directly
(e.g., updateManager?.checkNow()) instead of triggerUpdateCheck(); ensure you
reference the UpdateManager instance creation point and flip the menu item's
enabled state or rebind the click when UpdateManager is instantiated so the item
is only active when checkNow() is available.
In `@apps/desktop/main/updater/update-manager.ts`:
- Around line 95-117: getDiagnostic currently exposes this.currentFeedUrl which
may contain credentials or sensitive query params; sanitize the feed URL before
including it in the diagnostics object (e.g., parse the URL and strip username,
password and search/query params) so the returned UpdateCheckDiagnostic contains
only a safe feedUrl. Update getDiagnostic (and any place that constructs
diagnostics for emission) to use the sanitized value, and keep the full
currentFeedUrl only in main-process-only fields if needed for internal use;
ensure logCheck/writeDesktopMainLog and any send(...) that emits update:* events
use the sanitized diagnostic. Reference: getDiagnostic, currentFeedUrl,
UpdateCheckDiagnostic, logCheck, writeDesktopMainLog, send.
- Around line 184-203: Add a simple in-flight guard to serialize concurrent
calls to checkNow(): introduce a private field (e.g. this._checkNowPromise or
this._checking) on the UpdateManager and, at the top of checkNow(), if that
field is set return the existing Promise so callers share the in-flight result;
otherwise assign the Promise for the ongoing check to that field, run the
existing try/catch body, and clear the field in a finally block so subsequent
calls (from startPeriodicCheck() or manual checks) will execute a new check.
Ensure the assigned Promise resolves to the same { updateAvailable: boolean }
shape and that errors still trigger the existing logCheck behavior before
returning { updateAvailable: false }.
In `@apps/desktop/src/main.tsx`:
- Around line 1203-1212: DesktopShell currently renders UpdateBanner but never
the companion UpdateBadge, so once a user dismisses a ready update there is no
in-session affordance to bring the restart CTA back; import and render the
UpdateBadge component (from apps/desktop/src/components/update-banner.tsx) in
the DesktopShell UI and show it conditionally when the update has been dismissed
(e.g., when update.dismissed is true and update.phase === 'ready' or
update.version is present). Wire the badge's click handler to a method that
clears the dismissed state (call update.show() or
update.clearDismiss()/update.setDismiss(false) — add that method to the update
controller if it doesn't exist) so clicking the badge restores the
UpdateBanner/CTA. Ensure the UpdateBadge receives any necessary props
(version/percent/phase) and lives alongside the existing UpdateBanner render
block.
In `@tests/desktop/nexu-runtime-model-plugin.test.ts`:
- Around line 4-7: The test currently uses a machine-specific hardcoded absolute
path in stateModulePath which will fail on other machines or CI; change
stateModulePath to be computed dynamically (like pluginModulePath) by resolving
it relative to the test file using Node's path utilities (e.g.,
path.resolve/path.join with __dirname) so it points to the same
nexu-runtime-model.json used by the plugin; update the assignment of
stateModulePath (and ensure pluginModulePath uses a similar relative resolution
if needed) so both paths are built at runtime rather than hardcoded.
In `@tests/desktop/update-manager.test.ts`:
- Around line 3-26: The test currently defines its own resolveFeedUrl and
bypasses production logic; update the test to exercise the real
UpdateManager.configureFeedUrl (or extract a new pure function used by
UpdateManager) so environment overrides and source routing are validated: call
UpdateManager.configureFeedUrl (or the extracted pure resolveFeedUrl used by
UpdateManager) instead of the local stub, set process.env.NEXU_UPDATE_FEED_URL
in the test and restore it after, instantiate UpdateManager (or a minimal mock)
to verify provider selection for source "r2" vs "github" and that feedUrl/env
var precedence matches the production expression using
process.env.NEXU_UPDATE_FEED_URL ?? this.feedUrl.
---
Nitpick comments:
In @.github/workflows/desktop-build.yml:
- Line 161: Validate the GitHub Actions input before embedding it into
NEXU_DESKTOP_UPDATE_CHANNEL to fail CI on typos: add a workflow step that checks
the inputs.channel value against allowed values (stable, beta, nightly) and
exits non‑zero on mismatch so the runtime fallback in readUpdateChannel()
(runtime-config.ts) cannot silently use "stable"; ensure the step references the
inputs.channel variable and produces a clear error message on invalid input.
In `@apps/desktop/shared/host.ts`:
- Around line 285-292: Change the exported UpdateCheckDiagnostic type alias into
an exported interface named UpdateCheckDiagnostic (replace "export type
UpdateCheckDiagnostic = { ... }" with "export interface UpdateCheckDiagnostic {
... }"), update any imports/usages across main/renderer/updater IPC code to
import this shared interface, and ensure optional fields (remoteVersion,
remoteReleaseDate) keep their optional markers; this centralizes the
cross-process contract and prevents shape drift.
In `@apps/desktop/src/components/desktop-shell.tsx`:
- Line 52: The effect in desktop-shell.tsx depends on the unstable update object
from useAutoUpdate(), causing the onDesktopCommand listener to be torn down and
re-registered on every render; change the dependency to the stable callback used
inside the effect by importing/destructuring check from useAutoUpdate() (i.e.,
const { check } = useAutoUpdate()) and replace update in the effect dependency
array with check so only the stable check function is tracked and the
onDesktopCommand listener remains stable.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: c9a418f6-c34d-4a33-bc03-b9165ce8adfa
📒 Files selected for processing (16)
.github/workflows/desktop-build.ymlAGENTS.mdapps/controller/static/runtime-plugins/nexu-runtime-model/index.jsapps/desktop/main/index.tsapps/desktop/main/updater/update-manager.tsapps/desktop/shared/host.tsapps/desktop/shared/runtime-config.tsapps/desktop/src/components/desktop-shell.tsxapps/desktop/src/components/update-banner.tsxapps/desktop/src/hooks/use-auto-update.tsapps/desktop/src/main.tsxapps/desktop/src/runtime-page.cssapps/web/src/components/channel-setup/wechat-setup-view.tsxtests/desktop/nexu-runtime-model-plugin.test.tstests/desktop/runtime-config.test.tstests/desktop/update-manager.test.ts
Summary
stable,beta, andnightlyfeeds explicitlyversionandreleaseDatein updater events and desktop logsTesting
Summary by CodeRabbit
New Features
Bug Fixes
Documentation
Tests