Skip to content

feat(desktop): update UX with dedicated Updates tab#567

Merged
skevetter merged 2 commits into
mainfrom
tiny-quail
Jul 1, 2026
Merged

feat(desktop): update UX with dedicated Updates tab#567
skevetter merged 2 commits into
mainfrom
tiny-quail

Conversation

@skevetter

@skevetter skevetter commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Summary

Re-imagines the desktop update and release-channel experience. Updates previously had no home — they were wedged into the General settings tab between proxy fields and a keyboard-shortcut table, and the same state was rendered across four disconnected surfaces.

Changes

  • Dedicated Updates settings tab as a single source of truth, with a status hero that gives an affirmative "up to date · last checked" resting state the app previously lacked.
  • Stable / Preview channel picker — reframes "Beta" as "Preview" with cadence + risk copy. This is a UI-only label mapped at the renderer boundary; the backend keeps stable/beta values and electron-updater's beta channel untouched. Switching Preview → Stable now confirms first (it's a potential downgrade).
  • Honest auto-update copy — renamed "Automatic Updates" to "Download updates automatically" to match the backend's autoDownload semantics (it downloads in the background but still needs a restart).
  • Redesigned UpdateDialog shares status/formatting helpers with the hero so the modal and settings tab speak the same language.
  • Periodic manifest polling — the app previously fetched the manifest only once, 10s after boot, so long-running sessions never discovered releases published after launch. It now re-checks every 6h, respects the autoDownload toggle, skips while a download is in flight, and clears the timer on quit.
  • Deletes the old inline UpdatesSection.

Verification

  • 36 update-related tests pass (renderer UI + main-process updater), including new coverage for channel label mapping, downgrade detection, status copy, and the boot + interval polling behavior.
  • svelte-check: 0 errors.

Backend updater.ts update logic and IPC are otherwise unchanged; no release-pipeline or feed changes required.

Summary by CodeRabbit

  • New Features

    • Added a dedicated Updates tab in Settings with update status, progress, release notes, and actions to check, download, and restart/install.
    • Included an Update Behavior toggle and release-channel selection (Stable/Preview), with downgrade confirmation.
  • Bug Fixes

    • Improved auto-update scheduling and shutdown behavior, including pausing background checks when an update is already downloading.
    • Refined update dialog copy and progress speed formatting, and updated the install CTA to Restart & Update.

Move updates into their own Settings tab as a single source of truth:
a status hero with an affirmative up-to-date resting state, a Stable/
Preview channel picker (UI-only rename over the backend beta channel)
with a downgrade confirmation, and honest 'Download updates
automatically' copy matching the autoDownload backend semantics.

Redesign UpdateDialog to share status/formatting helpers so the modal
and the settings hero speak the same language. Delete the old inline
UpdatesSection.

Add periodic manifest polling: the app previously fetched the update
manifest only once at boot, so long-running sessions never discovered
new releases. It now re-checks every 6h (respecting the autoDownload
toggle, skipping while a download is in flight) and clears the timer on
quit.
@netlify

netlify Bot commented Jul 1, 2026

Copy link
Copy Markdown

Deploy Preview for images-devsy-sh canceled.

Name Link
🔨 Latest commit b490a55
🔍 Latest deploy log https://app.netlify.com/projects/images-devsy-sh/deploys/6a452bcd35ea000008a4b446

@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The auto-updater now uses a stoppable initial delay plus recurring rechecks, and skips polling while an update is downloading or downloaded. The renderer adds shared update text/channel helpers, a new UpdatesPanel, UpdateDialog text updates, and SettingsPage wiring for a dedicated Updates tab.

Changes

Main process updater recheck scheduling

Layer / File(s) Summary
Recheck interval and stop control
desktop/src/main/updater.ts
Adds initial-delay and recheck timing constants, timer handles, a background check closure that skips active downloads, and an exported stopAutoUpdater().
Shutdown wiring and timer tests
desktop/src/main/index.ts, desktop/src/main/__tests__/updater.test.ts
Calls stopAutoUpdater() during before-quit; adds tests for initial and recurring check timing, stop behavior, and skipping checks during in-flight downloads.

Updates UI panel and shared helpers

Layer / File(s) Summary
Status-copy formatting helpers
desktop/src/renderer/src/lib/components/update/status-copy.ts, desktop/src/renderer/src/lib/components/update/status-copy.test.ts
Adds fmtMBps, fmtTime, and statusHeadline with tests covering formatting and state-based headlines.
Channel metadata module
desktop/src/renderer/src/lib/components/update/channel.ts, desktop/src/renderer/src/lib/components/update/channel.test.ts
Adds ChannelMeta, CHANNELS, channelLabel, and isDowngrade with tests for labeling and downgrade detection.
UpdateDialog refactor to shared helpers
desktop/src/renderer/src/lib/components/update/UpdateDialog.svelte, desktop/src/renderer/src/lib/components/update/UpdateDialog.test.ts
Replaces local helpers with imports from status-copy, adjusts progress speed rendering, and renames the restart button to "Restart & Update".
New UpdatesPanel component
desktop/src/renderer/src/lib/components/update/UpdatesPanel.svelte, desktop/src/renderer/src/lib/components/update/UpdatesPanel.test.ts
Adds a full panel with status hero, release-channel selection with downgrade confirmation, update-behavior toggle, and footer, plus channel-switching tests.
SettingsPage wiring and UpdatesSection removal
desktop/src/renderer/src/pages/SettingsPage.svelte, desktop/src/renderer/src/lib/components/update/UpdatesSection.svelte
Swaps UpdatesSection for UpdatesPanel, adds a dedicated Updates tab, and deletes UpdatesSection.svelte.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Updater as updater.ts
  participant AutoUpdater as electron-updater
  participant Index as index.ts

  Updater->>Updater: schedule initial check after INITIAL_CHECK_DELAY_MS
  Updater->>AutoUpdater: checkForUpdates()
  loop every RECHECK_INTERVAL_MS
    Updater->>Updater: runBackgroundCheck()
    alt lastStatus is downloading or downloaded
      Updater-->>Updater: skip check
    else
      Updater->>AutoUpdater: checkForUpdates()
    end
  end
  Index->>Updater: stopAutoUpdater() on before-quit
  Updater->>Updater: clear scheduled timers
Loading
sequenceDiagram
  participant User
  participant UpdatesPanel
  participant ConfirmDialog
  participant IPC as Main Process

  User->>UpdatesPanel: select release channel
  UpdatesPanel->>UpdatesPanel: isDowngrade(current, target)?
  alt downgrade
    UpdatesPanel->>ConfirmDialog: open confirmation
    User->>ConfirmDialog: confirm
    ConfirmDialog->>UpdatesPanel: applyChannel(target)
  else not a downgrade
    UpdatesPanel->>UpdatesPanel: applyChannel(target)
  end
  UpdatesPanel->>IPC: setReleaseChannel()
  IPC-->>UpdatesPanel: success or failure
  UpdatesPanel-->>UpdatesPanel: rollback selection on failure
Loading

Possibly related PRs

  • devsy-org/devsy#390: Both PRs touch desktop/src/main/updater.ts in the update-check flow, including channel-aware update behavior.
  • devsy-org/devsy#459: Both PRs modify the updater flow and related status handling in desktop/src/main/updater.ts.
  • devsy-org/devsy#551: Both PRs change desktop/src/main/updater.ts and desktop/src/main/__tests__/updater.test.ts around update-check scheduling and behavior.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: a dedicated Updates tab and broader desktop update UX refresh.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@netlify

netlify Bot commented Jul 1, 2026

Copy link
Copy Markdown

Deploy Preview for devsydev canceled.

Name Link
🔨 Latest commit b490a55
🔍 Latest deploy log https://app.netlify.com/projects/devsydev/deploys/6a452bcdb326ef00081f6542

@skevetter skevetter changed the title feat(desktop): re-imagine update UX with dedicated Updates tab feat(desktop): update UX with dedicated Updates tab Jul 1, 2026
@skevetter
skevetter marked this pull request as ready for review July 1, 2026 14:25

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
desktop/src/renderer/src/lib/components/update/status-copy.ts (1)

19-24: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Missing fallback for undefined version.

UpdateStatus.version is optional per the IPC contract, but statusHeadline interpolates s.version directly for available/downloading/downloaded, which would render as "Version undefined is available" if the field is ever omitted for these states.

💡 Proposed defensive fallback
     case "available":
-      return `Version ${s.version} is available`
+      return `Version ${s.version ?? "unknown"} is available`
     case "downloading":
-      return `Downloading v${s.version} · ${(s.progress?.percent ?? 0).toFixed(0)}%`
+      return `Downloading v${s.version ?? "?"} · ${(s.progress?.percent ?? 0).toFixed(0)}%`
     case "downloaded":
-      return `Version ${s.version} is ready to install`
+      return `Version ${s.version ?? "unknown"} is ready to install`
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@desktop/src/renderer/src/lib/components/update/status-copy.ts` around lines
19 - 24, The statusHeadline logic in status-copy.ts interpolates
UpdateStatus.version directly for the available, downloading, and downloaded
cases, which can surface “undefined” in the UI. Update the statusHeadline switch
to defensively handle missing version values by using a safe fallback or
conditional text for those states, and keep the formatting consistent with the
existing s.progress handling in the downloading branch.
desktop/src/renderer/src/lib/components/update/UpdatesPanel.svelte (2)

1-232: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Consider adding component tests for UpdatesPanel.

This new component owns non-trivial async logic — downgrade confirmation flow, optimistic channel switch with revert-on-error, and IPC error toasts — none of which appear covered by a dedicated test file (unlike UpdateDialog.test.ts). Given the PR's stated goal of comprehensive update-flow coverage, tests for requestChannel/applyChannel behavior (downgrade vs. upgrade paths, error revert) would guard this critical settings path.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@desktop/src/renderer/src/lib/components/update/UpdatesPanel.svelte` around
lines 1 - 232, Add a dedicated test file for UpdatesPanel to cover its async
channel-switch flow and IPC error handling. Focus on the requestChannel and
applyChannel paths: verify downgrade selections open ConfirmDialog instead of
switching immediately, verify normal upgrades call setReleaseChannelIpc and show
success toasts, and verify failures revert releaseChannel and emit the error
toast. Use the existing component behavior around markUserInitiated,
getReleaseChannel, and channelLabel to locate the logic under test.

168-189: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add ARIA semantics to the channel picker.

The channel cards act as an exclusive-choice control but expose no role/aria-checked state, so screen readers can't tell which channel is selected (only a visual border + checkmark indicate it).

♿ Proposed fix
-    <div class="grid grid-cols-2 gap-3">
+    <div class="grid grid-cols-2 gap-3" role="radiogroup" aria-label="Release Channel">
       {`#each` CHANNELS as c (c.value)}
         <button
+          type="button"
+          role="radio"
+          aria-checked={releaseChannel === c.value}
           class="rounded-lg border p-3 text-left transition-colors {releaseChannel === c.value
             ? 'border-primary bg-primary/5 ring-1 ring-primary'
             : 'border-border hover:border-muted-foreground/50'}"
           onclick={() => requestChannel(c.value)}
         >
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@desktop/src/renderer/src/lib/components/update/UpdatesPanel.svelte` around
lines 168 - 189, The channel picker buttons in UpdatesPanel.svelte are visually
exclusive but չուն’t expose selection state to assistive tech. Update the button
group rendered from CHANNELS to use proper ARIA semantics for a single-select
control, and add the selected state on each option based on releaseChannel ===
c.value. Keep the existing requestChannel(c.value) behavior, but make each card
announce its role and checked/selected status consistently with the current
visual state.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@desktop/src/main/updater.ts`:
- Around line 260-276: The initial background-check timeout in the updater flow
is not being cleaned up, so shutdown can still trigger a pending check. In the
runBackgroundCheck setup, store the handle returned by the initial setTimeout
alongside recheckTimer, and update stopAutoUpdater() to clear both timers before
nulling them out. Use the existing runBackgroundCheck and stopAutoUpdater
symbols to keep the fix localized.

---

Nitpick comments:
In `@desktop/src/renderer/src/lib/components/update/status-copy.ts`:
- Around line 19-24: The statusHeadline logic in status-copy.ts interpolates
UpdateStatus.version directly for the available, downloading, and downloaded
cases, which can surface “undefined” in the UI. Update the statusHeadline switch
to defensively handle missing version values by using a safe fallback or
conditional text for those states, and keep the formatting consistent with the
existing s.progress handling in the downloading branch.

In `@desktop/src/renderer/src/lib/components/update/UpdatesPanel.svelte`:
- Around line 1-232: Add a dedicated test file for UpdatesPanel to cover its
async channel-switch flow and IPC error handling. Focus on the requestChannel
and applyChannel paths: verify downgrade selections open ConfirmDialog instead
of switching immediately, verify normal upgrades call setReleaseChannelIpc and
show success toasts, and verify failures revert releaseChannel and emit the
error toast. Use the existing component behavior around markUserInitiated,
getReleaseChannel, and channelLabel to locate the logic under test.
- Around line 168-189: The channel picker buttons in UpdatesPanel.svelte are
visually exclusive but չուն’t expose selection state to assistive tech. Update
the button group rendered from CHANNELS to use proper ARIA semantics for a
single-select control, and add the selected state on each option based on
releaseChannel === c.value. Keep the existing requestChannel(c.value) behavior,
but make each card announce its role and checked/selected status consistently
with the current visual state.
🪄 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: CHILL

Plan: Pro

Run ID: ba5155fe-a2d2-42e3-92ea-dbc9f3638cd3

📥 Commits

Reviewing files that changed from the base of the PR and between 85c1cef and bf1cfd9.

📒 Files selected for processing (12)
  • desktop/src/main/__tests__/updater.test.ts
  • desktop/src/main/index.ts
  • desktop/src/main/updater.ts
  • desktop/src/renderer/src/lib/components/update/UpdateDialog.svelte
  • desktop/src/renderer/src/lib/components/update/UpdateDialog.test.ts
  • desktop/src/renderer/src/lib/components/update/UpdatesPanel.svelte
  • desktop/src/renderer/src/lib/components/update/UpdatesSection.svelte
  • desktop/src/renderer/src/lib/components/update/channel.test.ts
  • desktop/src/renderer/src/lib/components/update/channel.ts
  • desktop/src/renderer/src/lib/components/update/status-copy.test.ts
  • desktop/src/renderer/src/lib/components/update/status-copy.ts
  • desktop/src/renderer/src/pages/SettingsPage.svelte
💤 Files with no reviewable changes (1)
  • desktop/src/renderer/src/lib/components/update/UpdatesSection.svelte

Comment thread desktop/src/main/updater.ts
@skevetter
skevetter marked this pull request as draft July 1, 2026 14:59
- Clear the pending initial-check setTimeout in stopAutoUpdater so a
  quit within the boot delay can't fire a check during teardown.
- Add radiogroup/radio ARIA semantics to the release-channel picker so
  the selected channel is exposed to assistive tech.
- Guard statusHeadline against a missing version field.
- Add UpdatesPanel tests covering the upgrade, downgrade-confirm, and
  IPC-failure-revert channel-switch paths, plus a boot-timer cleanup
  test for the updater.
@skevetter
skevetter marked this pull request as ready for review July 1, 2026 15:25

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
desktop/src/renderer/src/lib/components/update/UpdatesPanel.test.ts (1)

85-94: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Downgrade confirmation flow isn't tested end-to-end.

This test only checks that the confirm dialog appears and setReleaseChannel hasn't fired yet. There's no coverage for clicking through the confirmation (verifying setReleaseChannel("stable") eventually fires) or canceling it (verifying the selection stays on Preview and no IPC call is made). Given the PR explicitly calls out downgrade-confirm as a reviewed path, closing this gap would meaningfully raise confidence in the new confirmation UX.

Suggested additional coverage
   it("opens a confirmation instead of switching on a downgrade", async () => {
     await renderPanel("beta")

     cardButton(/Stable/)?.click()
     await tick()

     // Downgrade must wait for confirmation — no IPC call yet.
     expect(setReleaseChannel).not.toHaveBeenCalled()
     expect(document.body.textContent).toMatch(/switch to the stable channel\?/i)
   })
+
+  it("confirms and switches on downgrade confirmation", async () => {
+    await renderPanel("beta")
+    cardButton(/Stable/)?.click()
+    await tick()
+
+    // Click through the confirmation button (adjust selector to match component).
+    const confirmBtn = Array.from(document.querySelectorAll("button")).find((b) =>
+      /confirm/i.test(b.textContent ?? ""),
+    )
+    confirmBtn?.click()
+    await tick()
+    await Promise.resolve()
+
+    expect(setReleaseChannel).toHaveBeenCalledWith("stable")
+  })
+
+  it("cancels the downgrade confirmation without switching", async () => {
+    await renderPanel("beta")
+    cardButton(/Stable/)?.click()
+    await tick()
+
+    const cancelBtn = Array.from(document.querySelectorAll("button")).find((b) =>
+      /cancel/i.test(b.textContent ?? ""),
+    )
+    cancelBtn?.click()
+    await tick()
+
+    expect(setReleaseChannel).not.toHaveBeenCalled()
+    expect(cardButton(/Preview/)?.getAttribute("aria-checked")).toBe("true")
+  })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@desktop/src/renderer/src/lib/components/update/UpdatesPanel.test.ts` around
lines 85 - 94, The downgrade confirmation path in UpdatesPanel is only partially
covered by the existing test in UpdatesPanel.test.ts. Extend the test around
renderPanel, cardButton, and setReleaseChannel to cover the full confirmation
flow: one case should click through the dialog and assert
setReleaseChannel("stable") is eventually called, and another should cancel the
dialog and assert the selected channel remains Preview with no IPC call. Keep
the coverage anchored to the same confirmation UI text and release channel
switching behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@desktop/src/renderer/src/lib/components/update/UpdatesPanel.test.ts`:
- Around line 85-94: The downgrade confirmation path in UpdatesPanel is only
partially covered by the existing test in UpdatesPanel.test.ts. Extend the test
around renderPanel, cardButton, and setReleaseChannel to cover the full
confirmation flow: one case should click through the dialog and assert
setReleaseChannel("stable") is eventually called, and another should cancel the
dialog and assert the selected channel remains Preview with no IPC call. Keep
the coverage anchored to the same confirmation UI text and release channel
switching behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: bd98d84a-1f4f-4fb8-b674-e9771035dbc4

📥 Commits

Reviewing files that changed from the base of the PR and between bf1cfd9 and b490a55.

📒 Files selected for processing (5)
  • desktop/src/main/__tests__/updater.test.ts
  • desktop/src/main/updater.ts
  • desktop/src/renderer/src/lib/components/update/UpdatesPanel.svelte
  • desktop/src/renderer/src/lib/components/update/UpdatesPanel.test.ts
  • desktop/src/renderer/src/lib/components/update/status-copy.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • desktop/src/renderer/src/lib/components/update/status-copy.ts
  • desktop/src/renderer/src/lib/components/update/UpdatesPanel.svelte
  • desktop/src/main/updater.ts

@skevetter
skevetter merged commit af7f4ba into main Jul 1, 2026
27 checks passed
@skevetter
skevetter deleted the tiny-quail branch July 1, 2026 15:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant