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

@skevetterskevetter 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

netlifyBot commented Jul 1, 2026

Copy link
Copy Markdown

Deploy Preview for images-devsy-sh canceled.

NameLink
🔨 Latest commitb490a55
🔍 Latest deploy loghttps://app.netlify.com/projects/images-devsy-sh/deploys/6a452bcd35ea000008a4b446

@coderabbitai

coderabbitaiBot 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 nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring 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 nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly matches the main change: a dedicated Updates tab and broader desktop update UX refresh.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck 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

netlifyBot commented Jul 1, 2026

Copy link
Copy Markdown

Deploy Preview for devsydev canceled.

NameLink
🔨 Latest commitb490a55
🔍 Latest deploy loghttps://app.netlify.com/projects/devsydev/deploys/6a452bcdb326ef00081f6542

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

@coderabbitaicoderabbitaiBot 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 threaddesktop/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

@coderabbitaicoderabbitaiBot 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 mainJul 1, 2026
27 checks passed
@skevetter
skevetter deleted the tiny-quail branch July 1, 2026 15:38
Sign up for freeto 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

@skevetter