fix: improve desktop menu updates and feedback#122
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds renderer→main desktop-context synchronization with retries, a status-aware updater controller and localized dialogs, localized menu/template/locale persistence, feedback/problem-report tooling, updater-metadata download+merge in CI, and propagates a feedback form URL through build and runtime. Changes
Sequence Diagram(s)sequenceDiagram
rect rgba(200,200,255,0.5)
participant Browser
end
rect rgba(200,255,200,0.5)
participant Renderer
participant Router
end
rect rgba(255,200,200,0.5)
participant Preload
participant Main
participant Store
end
Browser->>Router: navigate(route)
Router->>Renderer: route changed
Renderer->>Renderer: isSessionRoute? (skip if true)
alt non-session
Renderer->>Preload: window.api.setDesktopContext(context)
Preload->>Main: ipc invoke "set-desktop-context"
Main->>Store: store.set(windowId, context)
Main->>Main: if focused → resync menu locale / persist
Main-->>Preload: ack
Preload-->>Renderer: resolved
else session
Renderer-->>Router: no sync
end
sequenceDiagram
actor User
participant UI
participant Platform
participant Updater
participant Dialog
User->>UI: Click "Check for Updates"
UI->>Platform: platform.checkUpdate()
Platform->>Updater: controller.check()
alt busy
Updater-->>Platform: {status: "busy"}
Platform-->>UI: show "already checking" toast
else disabled
Updater-->>Platform: {status: "disabled"}
Platform-->>UI: show "updates disabled" toast
else failed
Updater-->>Platform: {status: "failed", message}
Platform-->>UI: show failure toast
else none
Updater-->>Platform: {status: "none"}
Platform-->>UI: show "up to date" message
else ready
Updater-->>Platform: {status: "ready", version}
Platform->>Dialog: show update dialog (localized)
Dialog->>User: prompt install
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Code Review
This pull request introduces a comprehensive feedback and update system, featuring desktop context synchronization between the renderer and main processes, localized application menus in English and Chinese, and a diagnostic reporting tool that generates truncated markdown reports. The update logic has been refactored into a dedicated controller with enhanced status handling and UI feedback. Additionally, the release workflow is updated with a script to merge updater metadata across architectures. Feedback was provided to improve the robustness of the metadata finalization script by handling scenarios where a release has not yet been created.
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.github/workflows/build.yml:
- Around line 334-345: The download_or_warn() helper currently treats any "not
found" text as benign which masks real failures; update its grep matcher (around
the gh release download call and the "$err" handling) to only consider the
specific benign messages used by the finalize-latest-yml contract (e.g., "no
assets match", "no matches found", "no matches", "no assets to download") and
remove broad terms like "not found" or "could not find" so HTTP 404 / "Not
Found" errors from the gh release download remain fatal; keep the same flow
using the err file and gh release download but replace the grep -qiE pattern
with a tighter case-insensitive regex matching only the allowed benign phrases.
In `@packages/desktop-electron/scripts/finalize-latest-yml.ts`:
- Around line 118-132: downloadExisting currently merges cached/live latest*.yml
without validating the embedded version; parse the cached and live YAML after
reading (or use existing parse helper used by mergeLatest), and if either file
exists but its top-level version field !== OPENCODE_VERSION, treat that file as
absent (do not pass it to mergeLatest) and log/ignore it so you only merge
metadata whose version matches OPENCODE_VERSION; update downloadExisting to
validate cached and live versions before calling mergeLatest and return the
merge result of only version-matching inputs (or undefined if none match).
In `@packages/desktop-electron/src/renderer/index.tsx`:
- Around line 35-38: The catch block on the promise chain that calls
window.api.initializeDesktopContext currently swallows errors (the .catch(() =>
undefined) after .then((locale) =>
window.api.initializeDesktopContext(locale))), losing failure context; modify
that catch to perform lightweight logging instead of silently returning
undefined — capture the caught error and call a minimal logger (e.g.,
console.warn or a processLogger) with a short message like "desktop context init
failed" plus the error, then return undefined so behavior is unchanged; update
the .catch handler attached to the initializeDesktopContext promise accordingly.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 0c1afcb1-d261-460e-9ef1-34c444b8be63
📒 Files selected for processing (40)
.github/workflows/build.ymlpackages/app/src/app.tsxpackages/app/src/components/settings-general.tsxpackages/app/src/context/platform.tsxpackages/app/src/i18n/en.tspackages/app/src/i18n/zh.tspackages/app/src/pages/error-update.test.tspackages/app/src/pages/error-update.tspackages/app/src/pages/error.tsxpackages/app/src/pages/session.tsxpackages/app/src/utils/desktop-context.test.tspackages/app/src/utils/desktop-context.tspackages/desktop-electron/electron.vite.config.tspackages/desktop-electron/scripts/finalize-latest-yml.tspackages/desktop-electron/scripts/release-metadata-contract.test.tspackages/desktop-electron/src/main/constants.tspackages/desktop-electron/src/main/desktop-context-store.test.tspackages/desktop-electron/src/main/desktop-context-store.tspackages/desktop-electron/src/main/error.tspackages/desktop-electron/src/main/feedback.test.tspackages/desktop-electron/src/main/feedback.tspackages/desktop-electron/src/main/index.tspackages/desktop-electron/src/main/ipc.tspackages/desktop-electron/src/main/logging.tspackages/desktop-electron/src/main/menu-i18n.tspackages/desktop-electron/src/main/menu-labels.test.tspackages/desktop-electron/src/main/menu-labels.tspackages/desktop-electron/src/main/menu-template.tspackages/desktop-electron/src/main/menu.test.tspackages/desktop-electron/src/main/menu.tspackages/desktop-electron/src/main/problem-report.test.tspackages/desktop-electron/src/main/problem-report.tspackages/desktop-electron/src/main/updater-dialog-labels.test.tspackages/desktop-electron/src/main/updater-dialog-labels.tspackages/desktop-electron/src/main/updater.test.tspackages/desktop-electron/src/main/updater.tspackages/desktop-electron/src/preload/index.tspackages/desktop-electron/src/preload/types.tspackages/desktop-electron/src/renderer/index.tsxpackages/opencode/test/github/build-workflow.test.ts
480db64 to
0e688df
Compare
Astro-Han
left a comment
There was a problem hiding this comment.
Code review for new modules. Minor issues worth addressing for consistency and correctness.
Astro-Han
left a comment
There was a problem hiding this comment.
Additional review for modified files.
Astro-Han
left a comment
There was a problem hiding this comment.
Review Summary
This PR adds menu localization, desktop context tracking, problem report integration, and updater refactoring. Overall the implementation is solid, but I have several nitpicks and minor concerns.
Key Issues
-
Cross-package imports in preload/types.ts - Imports from
packages/appcould create circular dependency risks. The preload package is typically a leaf in the dependency graph. -
Clipboard size limit - The 5MB default for problem reports is quite large for clipboard operations. Most clipboard implementations struggle with content above 1-2MB.
-
Silent error handling - Several places catch errors silently (
.catch(() => undefined)) which could hide important failures.
Minor Nitpicks
-
Unnecessary optional chaining in
menuLabel():labels[locale]?.[key] ?? labels.en[key]- sinceMenuLocaleis a union type"en" | "zh", both keys are guaranteed to exist. -
Missing debug logs for locale fallbacks -
feedbackDialogLabels()andupdaterDialogLabels()both have runtime fallbacks for unexpected locale values. Consider adding aconsole.warnor logger call to help debug cross-process boundary issues. -
Reference vs value comparison in
desktop-context-store.ts: Thedeletemethod comparesremoved === mostRecentby reference, not by value. Two context objects with identical values but different references would be treated as different. -
Hardcoded accelerators - Strings like
"Shift+Cmd+S"are hardcoded throughoutmenu-template.ts. These should be documented or extracted to a constants file. -
Retry without backoff in
session.tsx- ThesyncDesktopContextretry logic uses a fixed 250ms delay without exponential backoff, which could retry indefinitely for persistent errors.
Documentation Suggestions
-
The comment at
menu-i18n.ts:10says "Keep an explicit stored locale, including English" but could be clearer about the intent. -
The
mergeLatestfunction infinalize-latest-yml.tstakes the last item as metadata base - the reasoning should be documented more clearly.
Overall, the PR is well-structured with good test coverage. These are mostly nitpicks that could improve maintainability.
60b2c80 to
ef0341e
Compare
Astro-Han
left a comment
There was a problem hiding this comment.
Thanks for this comprehensive PR. Below are a few nitpicks and suggestions for improvement. Overall the implementation is solid and well-tested.
9fba49c to
fcac32f
Compare
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 5
♻️ Duplicate comments (2)
packages/desktop-electron/scripts/finalize-latest-yml.ts (1)
115-119:⚠️ Potential issue | 🟡 MinorAdd
no assets matchto theisMissingAssetErrorregex to match the workflow.The workflow (
.github/workflows/build.ymlline 341) includesno assets matchas a benign error pattern, but the script's regex on line 118 omits it. This discrepancy could cause the workflow step to succeed while the script subsequently fails on the same error condition.Update the regex to:
/no assets to download|no matches found|no assets match|could not find any assets/i🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/desktop-electron/scripts/finalize-latest-yml.ts` around lines 115 - 119, The isMissingAssetError function's regex is missing the "no assets match" pattern used by the workflow; update the regular expression inside isMissingAssetError to include the "no assets match" alternative (so it tests for "no assets to download|no matches found|no assets match|could not find any assets" case-insensitively) to ensure the script treats that message as a benign missing-asset error..github/workflows/build.yml (1)
324-347:⚠️ Potential issue | 🔴 CriticalHandle the first publish before trying to download prior release assets.
This step still runs before the first
--publish alwayspath in the job, at Line 669 for macOS and Line 776 for Windows. On the firstfinalize/fullrun for a version, the GitHub release may not exist yet, sogh release download "$tag"fails and the workflow exits before publication ever happens. Treat a missing release as benign, or probe for the release before downloading.🔧 Suggested guard
existing_dir="$RUNNER_TEMP/existing-latest-yml" mkdir -p "$existing_dir" tag="v${{ steps.package_version.outputs.version }}" + + if ! gh release view "$tag" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then + echo "Release $tag does not exist yet; skipping existing metadata download." + exit 0 + fi download_or_warn() { local pattern="$1" local err="$RUNNER_TEMP/${pattern}.err" if gh release download "$tag" --pattern "$pattern" --dir "$existing_dir" --repo "$GITHUB_REPOSITORY" 2>"$err"; then🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/build.yml around lines 324 - 347, The download step can fail when the GitHub release doesn't yet exist; update the download_or_warn logic to first probe for the release (e.g., with gh release view "$tag") and treat a non-existent release as benign, or catch the specific 404/no-release error from gh before attempting gh release download; update the function download_or_warn (and its callers that use tag/existing_dir) to skip downloads if the release is missing and only error for other failures so the workflow can proceed to publish on first-run.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/desktop-electron/src/main/ipc.ts`:
- Around line 142-145: The handler for "set-desktop-context" currently returns
undefined when BrowserWindow.fromWebContents(event.sender) is missing, which
resolves the IPC call as success; change it to explicitly reject/throw an error
in that case so the renderer sees a rejected promise and will retry. In the
ipcMain.handle("set-desktop-context", ...) callback check the result of
BrowserWindow.fromWebContents(event.sender) (used to call
deps.setDesktopContext(context, win)) and if it's falsy throw a descriptive
Error (or return a rejected Promise) indicating "missing sender window" so the
IPC caller can detect failure and retry.
In `@packages/desktop-electron/src/main/problem-report.ts`:
- Around line 21-24: SessionExport currently allows unknown values for info and
messages which are JSON.stringify'd in buildReport/markdown and can throw on
BigInt or circular structures; update the code to sanitize/normalize these
fields before serialization (either tighten SessionExport to only accept
JSON-serializable types or, preferably, add a sanitizer function used where
markdown()/JSON.stringify is called) that: 1) converts BigInt to strings, 2)
replaces circular references with a placeholder or pruned representation, and 3)
ensures arrays/objects are traversed to produce only JSON-safe primitives; apply
this sanitizer to sessionExport.info and each entry in sessionExport.messages
prior to calling markdown() and anywhere else (e.g., any code around
buildReport, markdown, or the functions that serialize sessionExport) so
JSON.stringify cannot throw.
In `@packages/desktop-electron/src/main/updater.ts`:
- Around line 73-78: The check() method currently returns a new resolved Promise
with { status: "busy" } when an update is inflight, causing later callers to
never observe the eventual result; instead, change check() to return the shared
inflight promise so concurrent callers join the same run() and receive the final
result (keep inflight = run().finally(...) behavior). Use the existing inflight
variable and run() invocation; if callers need immediate UI state, have them
call busy() separately rather than relying on check()'s return value.
In `@packages/desktop-electron/src/preload/types.ts`:
- Around line 84-85: The preload API type for installUpdate is out of sync:
update the declaration of installUpdate on ElectronAPI (in
packages/desktop-electron/src/preload/types.ts) to match the main-process
contract (installUpdate from packages/desktop-electron/src/main/ipc.ts) by
changing its return type from Promise<void> to Promise<boolean> | boolean (or
Promise<boolean | boolean?> exactly as used in main) so renderer callers can
detect "install started" vs "nothing to install"; ensure the exported
ElectronAPI type and any related references to installUpdate use the same union
return type.
In `@packages/opencode/test/github/build-workflow.test.ts`:
- Around line 159-160: The test uses packageNotarizedStep! in ordering
assertions without first asserting it exists, so indexOf(undefined) could yield
-1 and falsely pass; add an explicit expect(packageNotarizedStep).toBeDefined()
before the two indexOf comparisons (which reference
downloadExistingMetadataStep, packageNotarizedStep, and packageAppStep) so the
test fails if packageNotarizedStep is missing and the subsequent
expect(...).toBeLessThan(...) assertions are meaningful.
---
Duplicate comments:
In @.github/workflows/build.yml:
- Around line 324-347: The download step can fail when the GitHub release
doesn't yet exist; update the download_or_warn logic to first probe for the
release (e.g., with gh release view "$tag") and treat a non-existent release as
benign, or catch the specific 404/no-release error from gh before attempting gh
release download; update the function download_or_warn (and its callers that use
tag/existing_dir) to skip downloads if the release is missing and only error for
other failures so the workflow can proceed to publish on first-run.
In `@packages/desktop-electron/scripts/finalize-latest-yml.ts`:
- Around line 115-119: The isMissingAssetError function's regex is missing the
"no assets match" pattern used by the workflow; update the regular expression
inside isMissingAssetError to include the "no assets match" alternative (so it
tests for "no assets to download|no matches found|no assets match|could not find
any assets" case-insensitively) to ensure the script treats that message as a
benign missing-asset error.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 08a3fc00-87ff-48b7-bcb0-a900df4d26e1
📒 Files selected for processing (43)
.github/workflows/build.ymlpackages/app/src/app.tsxpackages/app/src/components/settings-general.tsxpackages/app/src/context/platform.tsxpackages/app/src/i18n/en.tspackages/app/src/i18n/zh.tspackages/app/src/pages/error-update.test.tspackages/app/src/pages/error-update.tspackages/app/src/pages/error.tsxpackages/app/src/pages/session.tsxpackages/app/src/utils/desktop-context.test.tspackages/app/src/utils/desktop-context.tspackages/desktop-electron/electron.vite.config.tspackages/desktop-electron/scripts/finalize-latest-yml.tspackages/desktop-electron/scripts/release-metadata-contract.test.tspackages/desktop-electron/src/main/constants.tspackages/desktop-electron/src/main/desktop-context-store.test.tspackages/desktop-electron/src/main/desktop-context-store.tspackages/desktop-electron/src/main/error.tspackages/desktop-electron/src/main/feedback.test.tspackages/desktop-electron/src/main/feedback.tspackages/desktop-electron/src/main/index.tspackages/desktop-electron/src/main/ipc.tspackages/desktop-electron/src/main/logging.tspackages/desktop-electron/src/main/menu-i18n.tspackages/desktop-electron/src/main/menu-labels.test.tspackages/desktop-electron/src/main/menu-labels.tspackages/desktop-electron/src/main/menu-template.tspackages/desktop-electron/src/main/menu.test.tspackages/desktop-electron/src/main/menu.tspackages/desktop-electron/src/main/problem-report.test.tspackages/desktop-electron/src/main/problem-report.tspackages/desktop-electron/src/main/support-links.test.tspackages/desktop-electron/src/main/support-links.tspackages/desktop-electron/src/main/updater-dialog-labels.test.tspackages/desktop-electron/src/main/updater-dialog-labels.tspackages/desktop-electron/src/main/updater.test.tspackages/desktop-electron/src/main/updater.tspackages/desktop-electron/src/preload/index.tspackages/desktop-electron/src/preload/types.tspackages/desktop-electron/src/renderer/index.tsxpackages/opencode/test/config/config.test.tspackages/opencode/test/github/build-workflow.test.ts
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/opencode/test/config/config.test.ts (1)
1331-1386:⚠️ Potential issue | 🟠 MajorTimeout bump is only a partial fix for the lock-contention flake.
Line 1385 increases this test’s budget, but CI still shows lock-wait timeouts in the same config-deps path. Please apply the same timeout policy to the other
withConfigDepsLock(...)test in this file so both lock-dependent paths have consistent headroom.Suggested follow-up patch
-test("service dependency loading uses config plugin package metadata", async () => { +test("service dependency loading uses config plugin package metadata", async () => { await using tmp = await tmpdir<string>({ init: async (dir) => { const cfg = path.join(dir, "configdir") await fs.mkdir(cfg, { recursive: true }) return cfg @@ await withConfigDepsLock(async () => { @@ }) -}) +}, 60_000)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/opencode/test/config/config.test.ts` around lines 1331 - 1386, The timeout bump was only applied to the "installs dependencies in writable OPENCODE_CONFIG_DIR" test while another test that also uses withConfigDepsLock still has the shorter timeout and flakes; find the other it.live test in this file that calls withConfigDepsLock and increase its timeout argument to match (60_000) so both lock-dependent tests have the same headroom (look for usages of withConfigDepsLock and the it.live test declarations to locate and update the timeout).
♻️ Duplicate comments (6)
packages/desktop-electron/scripts/finalize-latest-yml.ts (1)
115-118:⚠️ Potential issue | 🟠 MajorHandle the
gh“no assets match” variant here too.
gh release downloadcan emitno assets matchfor the benign missing-pattern case. This matcher will currently treat that as fatal, so finalize can fail instead of falling back to cached metadata.🔧 Proposed fix
- return /no assets to download|no matches found|could not find any assets/i.test(message) + return /no assets to download|no matches found|no assets match|could not find any assets/i.test(message)Run this read-only check to confirm the mismatch against the rest of the release flow:
#!/bin/bash set -euo pipefail printf 'Current matcher in packages/desktop-electron/scripts/finalize-latest-yml.ts:\n' sed -n '115,119p' packages/desktop-electron/scripts/finalize-latest-yml.ts printf '\nRepo references to benign gh download messages:\n' rg -n 'no assets match|no assets to download|no matches found|could not find any assets' \ .github/workflows/build.yml \ packages/desktop-electron/scripts/release-metadata-contract.test.ts🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/desktop-electron/scripts/finalize-latest-yml.ts` around lines 115 - 118, The isMissingAssetError function's regex omits the "no assets match" gh message so benign missing-asset cases are treated as fatal; update the regex in isMissingAssetError to also match "no assets match" (in addition to the existing "no assets to download", "no matches found", and "could not find any assets") so finalize-latest-yml will treat that variant as a non-fatal missing-asset case.packages/desktop-electron/src/main/problem-report.ts (1)
21-24:⚠️ Potential issue | 🟠 MajorMake
sessionExportJSON-safe before stringifying it.
infoandmessagesare still arbitraryunknownvalues, but Lines 55 and 65 serialize them directly. ABigIntor circular object will throw here and break the “Report a Problem” flow before truncation runs.Also applies to: 54-68
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/desktop-electron/src/main/problem-report.ts` around lines 21 - 24, The sessionExport object (type SessionExport) can contain non-JSON-safe values in its info and messages fields; before calling JSON.stringify on sessionExport (where sessionExport is constructed/serialized), sanitize info and messages with a JSON-safe serializer or replacer that handles BigInt (convert to string) and circular references (e.g., track seen objects and replace cycles with a placeholder) so stringify cannot throw; update the code paths that serialize sessionExport (the serialization around sessionExport) to use that safe serializer or a try/catch fallback that converts problematic values to string before truncation runs.packages/desktop-electron/src/main/ipc.ts (1)
142-145:⚠️ Potential issue | 🟠 MajorReject missing sender windows so desktop-context sync can retry.
Returning
undefinedhere resolvesipcRenderer.invoke()successfully. Inpackages/app/src/pages/session.tsx, retries only happen on rejected promises, so a missingBrowserWindowis treated as a successful sync and the stale context is never retried.♻️ Proposed fix
ipcMain.handle("set-desktop-context", (event: IpcMainInvokeEvent, context: DesktopContext) => { const win = BrowserWindow.fromWebContents(event.sender) - if (!win) return + if (!win) { + throw new Error("set-desktop-context: sender window not found") + } return deps.setDesktopContext(context, win) })Verification: inspect the handler and the renderer retry paths together. The current code should show that retries are only in
.catch(...), while the main handler has a success-return path for!win.#!/bin/bash set -e echo "=== main IPC handler ===" sed -n '139,146p' packages/desktop-electron/src/main/ipc.ts echo echo "=== session retry path ===" sed -n '349,381p' packages/app/src/pages/session.tsx echo echo "=== non-session route bridge ===" sed -n '140,150p' packages/app/src/app.tsx🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/desktop-electron/src/main/ipc.ts` around lines 142 - 145, The IPC handler "set-desktop-context" currently returns undefined when BrowserWindow.fromWebContents(event.sender) is missing, which resolves ipcRenderer.invoke() and prevents renderer-side retries; change the handler to reject the invocation instead when no sender window is found (e.g., throw a descriptive Error or return a rejected Promise) so that deps.setDesktopContext(context, win) is only called for valid windows and the renderer's retry logic (which retries only on rejected promises) will run on missing-window cases.packages/desktop-electron/src/preload/types.ts (1)
83-85:⚠️ Potential issue | 🟡 MinorKeep
installUpdatetyped in sync with the IPC contract.
packages/desktop-electron/src/main/ipc.tsexposesinstallUpdateas a boolean result, butElectronAPI.installUpdatestill erases that toPromise<void>. That makes the preload bridge inconsistent and prevents renderer code from detecting “install started” vs “nothing to install”.♻️ Proposed fix
- installUpdate: () => Promise<void> + installUpdate: () => Promise<boolean>Verification: compare the preload signature with the main-process contract and current call sites.
#!/bin/bash set -e echo "=== main IPC contract ===" sed -n '54,66p' packages/desktop-electron/src/main/ipc.ts echo echo "=== preload API contract ===" sed -n '79,87p' packages/desktop-electron/src/preload/types.ts echo echo "=== installUpdate usages ===" rg -n --type=ts --type=tsx '\binstallUpdate\b|\bupdate\?\(\): Promise<void>' packages/desktop-electron/src packages/app/src🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/desktop-electron/src/preload/types.ts` around lines 83 - 85, The preload API's installUpdate signature is out of sync with the main IPC contract: change installUpdate: () => Promise<void> in packages/desktop-electron/src/preload/types.ts to installUpdate: () => Promise<boolean> so the preload bridge returns the boolean "install started" result; then update any renderer call sites that assume void to handle the boolean result (e.g., code calling ElectronAPI.installUpdate or awaiting installUpdate) and adjust logic to detect true/false accordingly; use the existing symbols runUpdater and checkUpdate to locate the surrounding API surface when updating the type..github/workflows/build.yml (1)
324-346:⚠️ Potential issue | 🔴 CriticalSkip the pre-download when the versioned release does not exist yet.
This step still runs before the later
--publish alwayspackaging step createsv${{ steps.package_version.outputs.version }}. On a first publish for that version,gh release downloadfails with a missing-release error, and the current asset-only matcher treats it as fatal, so the job can stop before packaging starts. Guard the step with a release-exists check (or explicitly handle the missing-release case) before callingdownload_or_warn().🔧 Suggested fix
- name: Download existing updater metadata # macOS finalize can run after notarization as a separate phase; Windows finalizes during full packaging. if: ${{ (runner.os == 'macOS' && (inputs.phase == 'finalize' || inputs.phase == 'full') && inputs.arch == matrix.arch_label) || (runner.os == 'Windows' && inputs.phase == 'full') }} shell: bash run: | set -euo pipefail existing_dir="$RUNNER_TEMP/existing-latest-yml" mkdir -p "$existing_dir" tag="v${{ steps.package_version.outputs.version }}" + + release_err="$RUNNER_TEMP/release-view.err" + if ! gh release view "$tag" --repo "$GITHUB_REPOSITORY" > /dev/null 2>"$release_err"; then + if grep -qiE 'release not found|HTTP 404: Not Found' "$release_err"; then + echo "Release $tag does not exist yet; skipping existing metadata download." + exit 0 + fi + cat "$release_err" >&2 + exit 1 + fi download_or_warn() { local pattern="$1" local err="$RUNNER_TEMP/${pattern}.err" if gh release download "$tag" --pattern "$pattern" --dir "$existing_dir" --repo "$GITHUB_REPOSITORY" 2>"$err"; then🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/build.yml around lines 324 - 346, The step uses download_or_warn() with tag="v${{ steps.package_version.outputs.version }}" but doesn’t guard against the release itself being absent; add a pre-check that the GitHub release exists for tag before calling download_or_warn (or explicitly detect the "release not found" message and treat it like the asset-not-found case). Implement the check right after tag is set (e.g., call gh release view "$tag" and skip downloads if it returns a missing-release error) so download_or_warn() is only invoked when the release exists and the job won’t fail on first-time publishes.packages/desktop-electron/src/main/updater.ts (1)
73-79:⚠️ Potential issue | 🟠 MajorReintroduced concurrency behavior: concurrent callers lose the final update result.
At Line 74, returning
{ status: "busy" }for later callers means they never receive the eventualready/failedresolution of the in-flight check.Suggested fix
check() { - if (inflight) return Promise.resolve({ status: "busy" as const }) + if (inflight) return inflight inflight = run().finally(() => { inflight = undefined }) return inflight },🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/desktop-electron/src/main/updater.ts` around lines 73 - 79, The current check() implementation returns a static { status: "busy" } for concurrent callers so they never get the eventual result; change the early-return to return the existing inflight promise instead of Promise.resolve({ status: "busy" as const }) so all callers await the same run() result. Ensure inflight remains the Promise produced by run() (and is cleared in finally as shown) so subsequent callers receive the final resolved value from run() via the inflight variable used in check().
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/app/src/app.tsx`:
- Around line 140-150: The current createEffect in app.tsx swallows all
setDesktopContext() rejections; change it to perform a bounded retry (same
approach as in packages/app/src/pages/session.tsx) instead of a permanent no-op:
wrap the window.api.setDesktopContext(buildDesktopContext({...})) call in the
same retry helper or loop used by session.tsx (respecting max attempts and
backoff/delay), ensure you abort retries when the effect is cleaned up or when
isSessionRoute(location.pathname) becomes true, and propagate errors only after
exhausting retries; refer to the createEffect, window.api.setDesktopContext,
buildDesktopContext, and isSessionRoute symbols to locate and replace the
current .catch(() => undefined) with the retry logic.
In `@packages/app/src/components/settings-general.tsx`:
- Around line 133-137: The toast for failed updates can show an empty
description because it uses the nullish coalescing operator with result.message;
change the fallback to use a truthy check so empty strings fall back: in the
failing branch where showToast is called (referencing result.status ===
"failed", result.message and showToast in settings-general.tsx) replace the
`result.message ?? ...` usage with a truthy fallback (e.g., `result.message ||
language.t("settings.updates.toast.failed.description")`) so a blank message
will use the default description.
---
Outside diff comments:
In `@packages/opencode/test/config/config.test.ts`:
- Around line 1331-1386: The timeout bump was only applied to the "installs
dependencies in writable OPENCODE_CONFIG_DIR" test while another test that also
uses withConfigDepsLock still has the shorter timeout and flakes; find the other
it.live test in this file that calls withConfigDepsLock and increase its timeout
argument to match (60_000) so both lock-dependent tests have the same headroom
(look for usages of withConfigDepsLock and the it.live test declarations to
locate and update the timeout).
---
Duplicate comments:
In @.github/workflows/build.yml:
- Around line 324-346: The step uses download_or_warn() with tag="v${{
steps.package_version.outputs.version }}" but doesn’t guard against the release
itself being absent; add a pre-check that the GitHub release exists for tag
before calling download_or_warn (or explicitly detect the "release not found"
message and treat it like the asset-not-found case). Implement the check right
after tag is set (e.g., call gh release view "$tag" and skip downloads if it
returns a missing-release error) so download_or_warn() is only invoked when the
release exists and the job won’t fail on first-time publishes.
In `@packages/desktop-electron/scripts/finalize-latest-yml.ts`:
- Around line 115-118: The isMissingAssetError function's regex omits the "no
assets match" gh message so benign missing-asset cases are treated as fatal;
update the regex in isMissingAssetError to also match "no assets match" (in
addition to the existing "no assets to download", "no matches found", and "could
not find any assets") so finalize-latest-yml will treat that variant as a
non-fatal missing-asset case.
In `@packages/desktop-electron/src/main/ipc.ts`:
- Around line 142-145: The IPC handler "set-desktop-context" currently returns
undefined when BrowserWindow.fromWebContents(event.sender) is missing, which
resolves ipcRenderer.invoke() and prevents renderer-side retries; change the
handler to reject the invocation instead when no sender window is found (e.g.,
throw a descriptive Error or return a rejected Promise) so that
deps.setDesktopContext(context, win) is only called for valid windows and the
renderer's retry logic (which retries only on rejected promises) will run on
missing-window cases.
In `@packages/desktop-electron/src/main/problem-report.ts`:
- Around line 21-24: The sessionExport object (type SessionExport) can contain
non-JSON-safe values in its info and messages fields; before calling
JSON.stringify on sessionExport (where sessionExport is constructed/serialized),
sanitize info and messages with a JSON-safe serializer or replacer that handles
BigInt (convert to string) and circular references (e.g., track seen objects and
replace cycles with a placeholder) so stringify cannot throw; update the code
paths that serialize sessionExport (the serialization around sessionExport) to
use that safe serializer or a try/catch fallback that converts problematic
values to string before truncation runs.
In `@packages/desktop-electron/src/main/updater.ts`:
- Around line 73-79: The current check() implementation returns a static {
status: "busy" } for concurrent callers so they never get the eventual result;
change the early-return to return the existing inflight promise instead of
Promise.resolve({ status: "busy" as const }) so all callers await the same run()
result. Ensure inflight remains the Promise produced by run() (and is cleared in
finally as shown) so subsequent callers receive the final resolved value from
run() via the inflight variable used in check().
In `@packages/desktop-electron/src/preload/types.ts`:
- Around line 83-85: The preload API's installUpdate signature is out of sync
with the main IPC contract: change installUpdate: () => Promise<void> in
packages/desktop-electron/src/preload/types.ts to installUpdate: () =>
Promise<boolean> so the preload bridge returns the boolean "install started"
result; then update any renderer call sites that assume void to handle the
boolean result (e.g., code calling ElectronAPI.installUpdate or awaiting
installUpdate) and adjust logic to detect true/false accordingly; use the
existing symbols runUpdater and checkUpdate to locate the surrounding API
surface when updating the type.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: cbbeef89-c7ac-42a4-b4df-52c5e2059218
📒 Files selected for processing (43)
.github/workflows/build.ymlpackages/app/src/app.tsxpackages/app/src/components/settings-general.tsxpackages/app/src/context/platform.tsxpackages/app/src/i18n/en.tspackages/app/src/i18n/zh.tspackages/app/src/pages/error-update.test.tspackages/app/src/pages/error-update.tspackages/app/src/pages/error.tsxpackages/app/src/pages/session.tsxpackages/app/src/utils/desktop-context.test.tspackages/app/src/utils/desktop-context.tspackages/desktop-electron/electron.vite.config.tspackages/desktop-electron/scripts/finalize-latest-yml.tspackages/desktop-electron/scripts/release-metadata-contract.test.tspackages/desktop-electron/src/main/constants.tspackages/desktop-electron/src/main/desktop-context-store.test.tspackages/desktop-electron/src/main/desktop-context-store.tspackages/desktop-electron/src/main/error.tspackages/desktop-electron/src/main/feedback.test.tspackages/desktop-electron/src/main/feedback.tspackages/desktop-electron/src/main/index.tspackages/desktop-electron/src/main/ipc.tspackages/desktop-electron/src/main/logging.tspackages/desktop-electron/src/main/menu-i18n.tspackages/desktop-electron/src/main/menu-labels.test.tspackages/desktop-electron/src/main/menu-labels.tspackages/desktop-electron/src/main/menu-template.tspackages/desktop-electron/src/main/menu.test.tspackages/desktop-electron/src/main/menu.tspackages/desktop-electron/src/main/problem-report.test.tspackages/desktop-electron/src/main/problem-report.tspackages/desktop-electron/src/main/support-links.test.tspackages/desktop-electron/src/main/support-links.tspackages/desktop-electron/src/main/updater-dialog-labels.test.tspackages/desktop-electron/src/main/updater-dialog-labels.tspackages/desktop-electron/src/main/updater.test.tspackages/desktop-electron/src/main/updater.tspackages/desktop-electron/src/preload/index.tspackages/desktop-electron/src/preload/types.tspackages/desktop-electron/src/renderer/index.tsxpackages/opencode/test/config/config.test.tspackages/opencode/test/github/build-workflow.test.ts
f6c3f2e to
7ccef0f
Compare
Summary
PAWWORK_FEEDBACK_FORM_URL.Why
Fixes desktop menu cleanup and update feedback gaps from #84. Also gives users without GitHub accounts a lower-friction Feishu form based bug report path while preserving the GitHub issue entry for developer workflows.
This also addresses the real v0.2.5 release failure mode: the release published successfully, but
latest-mac.ymlended up containing onlypawwork-mac-x64.zip/.dmgand dropped the arm64 entries. The old workflow packaged each macOS architecture independently and did not run a final metadata merge/upload step. This PR adds that workflow step and hardensfinalize-latest-yml.tsso existing, live, and current-run updater metadata are merged beforelatest*.ymlis re-uploaded.Related Issue
Fixes #84
How To Verify
Manual/config checks:
latest-mac.ymlonly includes x64 metadata, validating the release metadata clobbering path this PR fixes.PawWork Problem Reportin theCENO&纯刻用户服务project knowledge space.shared=true,shared_limit=anyone_editable.PAWWORK_FEEDBACK_FORM_URL.Screenshots or Recordings
Not included. Changes are native menu/dialog flows plus release metadata behavior. The external Feishu form was created and configured separately, and its URL is supplied by repository variable at build time.
Checklist
dev, and my PR title and commit messages use Conventional Commits in EnglishSummary by CodeRabbit
New Features
Improvements
Tests