Skip to content

Add updater dialog and preferences (#443, #449)#731

Merged
fernandotonon merged 3 commits into
masterfrom
feat/updater-dialog-443-449
Jun 17, 2026
Merged

Add updater dialog and preferences (#443, #449)#731
fernandotonon merged 3 commits into
masterfrom
feat/updater-dialog-443-449

Conversation

@fernandotonon

@fernandotonon fernandotonon commented Jun 17, 2026

Copy link
Copy Markdown
Owner

Summary

  • Adds UpdaterDialog.qml with state-driven UX for unknown install confirmation, package-manager guidance, update available (release notes + actions), up-to-date, and error states — replacing the old QMessageBox flow.
  • Adds Preferences → Updates tab (UpdaterSettingsPanel.qml) with stable/beta channel, check-on-startup, auto-download opt-in, and Check now (opens the dialog).
  • Introduces ENABLE_AUTO_UPDATER (default ON) to omit in-app updater UI from package-managed builds; wires Help → Check for Updates… through MainWindow::showUpdaterDialog().

Test plan

  • ./build_local/bin/UnitTests --gtest_filter="UpdaterController*:GitHubReleaseParser*:UpdateVersion*" (21 tests pass)
  • Help → Check for Updates… opens dialog and runs GitHub check (portable/dev install)
  • Homebrew/Snap/Debian install shows package-manager command panel (no download)
  • Edit → Preferences → Updates tab: channel/toggles persist; Check now opens dialog
  • Skip this version / Remind me later dismiss dialog; skipped version treated as up-to-date on next check

Closes #443, #449

Made with Cursor

Summary by CodeRabbit

  • New Features
    • Added an in-app auto-updater with a full “Check for Updates” dialog (includes update progress, changelog preview, release page access, and last-checked info).
    • Added an “Updates” section in Preferences with stable/beta selection, check-on-startup, and auto-download options; the tab and updater UI appear only when supported.
    • Supports skipping versions and copying the suggested update command.
  • Bug Fixes
    • Improved updater state handling, error messaging, and persistence of updater preferences.

Replace the QMessageBox update flow with a QML dialog that handles install-flavor states, package-manager guidance, and skip/remind actions. Add an Updates preferences tab with channel and startup settings, guarded by ENABLE_AUTO_UPDATER for package-managed builds.

Co-authored-by: Cursor <cursoragent@cursor.com>
@coderabbitai

coderabbitai Bot commented Jun 17, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@fernandotonon, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 44 minutes and 1 second. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 074386ca-ee2f-46c0-9094-8e8a94b734a9

📥 Commits

Reviewing files that changed from the base of the PR and between e767450 and ac46770.

📒 Files selected for processing (2)
  • qml/UpdaterSettingsPanel.qml
  • src/mainwindow.cpp
📝 Walkthrough

Walkthrough

Adds a fully gated in-app auto-updater feature behind a new ENABLE_AUTO_UPDATER CMake option. UpdaterController is expanded with settings persistence, new UnknownInstall/PackageManaged states, and a broader QML-exposed API. Two new QML components (UpdaterDialog.qml, UpdaterSettingsPanel.qml) provide the check-for-updates window and preferences panel. MainWindow wires the controller signal to a QML-engine-based dialog launcher, and the Preferences dialog conditionally shows an "Updates" tab.

Changes

In-app Auto-updater

Layer / File(s) Summary
Build flag, settings keys, and feature-detection bridge
CMakeLists.txt, src/AppSettingsKeys.h, src/PropertiesPanelController.h, src/PropertiesPanelController.cpp
Introduces the ENABLE_AUTO_UPDATER CMake option and compile definition, adds five Updater/* QSettings key accessors, and exposes autoUpdaterEnabled as a QML constant property on PropertiesPanelController.
UpdaterController contract: new states, properties, and signals
src/updater/UpdaterController.h
Adds UnknownInstall and PackageManaged enum states, eight new QML-exposed properties with inline getters and change signals, showDialogRequested(bool) signal, kill() method, new invokable actions (remindLater, skipThisVersion, openReleasePage, copyUpdateCommand), and updated private member layout.
UpdaterController implementation: persistence, breadcrumbs, and check flow
src/updater/UpdaterController.cpp
Implements kill(), loadSettings(), saveSettings(), recordLastChecked(), isVersionSkipped(), logDialogStateBreadcrumb(), and stateToString() helper; extends setChannel/setCheckOnStartup/setAutoDownload to persist via saveSettings(); rewrites checkForUpdates() with direct state-setting and Sentry breadcrumbs; adds confirmUnknownInstall(); updates applyCheckResult() to track m_publishedAt and treat skipped versions as up-to-date; and aligns all user action handlers with consistent breadcrumb logging.
UpdaterController persistence tests
src/updater/UpdaterController_test.cpp
Adds a GoogleTest suite verifying default channel is stable, setCheckOnStartup/setAutoDownload persistence to QSettings, skipThisVersion() writing the skipped-version key and transitioning state to UpToDate, and pre-populated QSettings values loading without reset.
UpdaterDialog QML: state-driven check-for-updates window
qml/UpdaterDialog.qml, src/qml_resources.qrc
Adds a QML Window with open(runCheck) method, Esc-to-dismiss key handler, reusable InspectorButton component, and six mutually exclusive UpdaterController.state-driven UI sections (busy indicator, UnknownInstall confirm, PackageManaged command display, UpdateAvailable changelog+actions, UpToDate, and Error), plus a Downloading progress bar. Registers both QML files under the /UpdaterDialog resource prefix.
UpdaterSettingsPanel QML and Preferences integration
qml/UpdaterSettingsPanel.qml, qml/PreferencesDialog.qml
Creates UpdaterSettingsPanel.qml with flavor display, channel selector, startup/auto-download toggles, last-checked display, and a "Check now" button. Updates PreferencesDialog.qml to build tabs dynamically via tabLabels and conditionally load UpdaterSettingsPanel when autoUpdaterEnabled and currentTab === 3.
MainWindow: updater dialog wiring and menu integration
src/mainwindow.h, src/mainwindow.cpp
Guards the UpdaterController include under ENABLE_AUTO_UPDATER; registers the controller as a QML singleton; connects showDialogRequested to showUpdaterDialog; conditionally sets or hides the "Check for Updates" menu action; and implements showUpdaterDialog() to reuse or create a QQmlApplicationEngine loading UpdaterDialog.qml with software rendering and window-hidden cleanup. Also schedules startup check if checkOnStartup is enabled.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant MainWindow
    participant QQmlEngine as QQmlApplicationEngine
    participant UpdaterDialog
    participant UpdaterController

    User->>MainWindow: click "Check for Updates..."
    MainWindow->>MainWindow: showUpdaterDialog(true)
    alt m_updaterWindow exists
        MainWindow->>UpdaterDialog: invokeMethod("open", runCheck=true)
    else first open
        MainWindow->>QQmlEngine: new engine + register singletons
        QQmlEngine->>UpdaterDialog: load qrc:/UpdaterDialog/UpdaterDialog.qml
        MainWindow->>UpdaterDialog: force software rendering
        MainWindow->>UpdaterDialog: open(runCheck=true)
    end
    UpdaterDialog->>UpdaterController: checkForUpdates()
    UpdaterController->>UpdaterController: setState(Checking) + breadcrumb
    UpdaterController-->>UpdaterDialog: state = Checking → BusyIndicator
    UpdaterController->>UpdaterController: worker completes
    alt update available
        UpdaterController-->>UpdaterDialog: state = UpdateAvailable
        UpdaterDialog->>User: show changelog + actions
        User->>UpdaterDialog: "Download & Install" (wired but disabled)
    else already latest
        UpdaterController-->>UpdaterDialog: state = UpToDate
        User->>UpdaterDialog: close
    else package managed
        UpdaterController-->>UpdaterDialog: state = PackageManaged
        UpdaterDialog->>User: show update command + copy
    else unknown install
        UpdaterController-->>UpdaterDialog: state = UnknownInstall
        User->>UpdaterDialog: "Continue" or "Cancel"
    else error
        UpdaterController-->>UpdaterDialog: state = Error
        UpdaterDialog->>User: show error message
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

  • Epic: Auto Updater #439 – This PR implements the in-app auto-updater epic, adding ENABLE_AUTO_UPDATER build gating, UpdaterDialog.qml, UpdaterSettingsPanel.qml, and expanded UpdaterController as described in the epic's phased implementation.

  • Update: UpdaterDialog QML — states, changelog viewer, channel picker #449 – The PR directly delivers the UpdaterDialog.qml state-driven UI, UpdaterSettingsPanel.qml in preferences, AppSettingsKeys persistence, expanded UpdaterController properties/actions, and Sentry breadcrumb logging that this issue specifies.

  • Update: Install-flavor detection + package-manager passthrough UX #443 – The PR implements install-flavor detection passthrough UX including PackageManaged and UnknownInstall states, updater.flavor.detected Sentry breadcrumb, per-flavor update command hints displayed in UpdaterDialog, confirmUnknownInstall() for unknown portable installs, and ENABLE_AUTO_UPDATER=OFF compilation gating.

Possibly related PRs

  • fernandotonon/QtMeshEditor#279: Both PRs modify qml/PreferencesDialog.qml tab UI — this PR adds a conditional "Updates" tab via tabLabels and autoUpdaterEnabled, directly extending the tab structure.

  • fernandotonon/QtMeshEditor#532: Both PRs modify the "Check for Updates" entry point in src/mainwindow.cpp; this PR replaces the action handler with a showUpdaterDialog() QML-engine flow that routes through the new auto-updater dialog.

Poem

🐇 Hoppy news from the warren below,
A shiny update button starts to glow!
The controller tracks each channel and state,
"Stable" or "beta" — the rabbit won't wait.
With breadcrumbs and settings all snug in their place,
New versions arrive with a hop and a grace! 🌟

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 9.09% 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
Title check ✅ Passed The PR title 'Add updater dialog and preferences (#443, #449)' directly and concisely describes the main changes: introducing updater dialog and preferences UI components.
Description check ✅ Passed The PR description includes Summary, Technical Details (via component summaries), and Test plan sections, but is missing the required template sections like PS1 runtime rip checklist and features/bugfixes categorization.
Linked Issues check ✅ Passed The PR implements all major objectives from #443 and #449: install-flavor detection, package-manager command guidance, unknown install confirmation, ENABLE_AUTO_UPDATER flag, and dialog/preferences UI with proper state handling.
Out of Scope Changes check ✅ Passed All changes align with the linked issue objectives. New components (UpdaterDialog, UpdaterSettingsPanel), settings persistence, build-time flags, and MainWindow integration are all within scope of implementing the updater dialog and preferences features.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/updater-dialog-443-449

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 and usage tips.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6fbfe637bd

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/updater/UpdaterController.cpp Outdated
Comment thread qml/UpdaterSettingsPanel.qml Outdated
Comment thread qml/UpdaterSettingsPanel.qml Outdated

@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: 5

🤖 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 `@qml/UpdaterDialog.qml`:
- Around line 36-42: The Keys.onPressed handler calls
UpdaterController.dismiss() which only removes UI state without canceling active
update operations like Checking, Downloading, or Verifying. This leaves
background work running and causes issues when the dialog is reopened. Replace
or augment the UpdaterController.dismiss() call with the appropriate method that
actively cancels ongoing update work (such as a cancel() or abort() method) to
ensure any active requests are properly stopped when the Escape key is pressed.

In `@qml/UpdaterSettingsPanel.qml`:
- Around line 42-140: Replace the custom mouse-only controls throughout
UpdaterSettingsPanel.qml with proper Qt Quick Controls that support keyboard
accessibility. Specifically: convert the channel selection section (the Repeater
with Rectangle and MouseArea for selecting between "stable" and "beta") to use
RadioButton components within a ButtonGroup; replace the two checkbox
implementations (the "Check for updates on startup" and "Auto-download in
background" Rows containing custom Rectangle and MouseArea) with CheckBox
controls; and replace the "Check now" button (the Rectangle with MouseArea for
triggering requestCheckDialog()) with a Button control. These Qt Quick Controls
provide built-in keyboard navigation, focus handling, and accessibility features
that the custom implementations lack.

In `@src/updater/UpdaterController_test.cpp`:
- Around line 13-26: The SetUp() method clears QSettings but leaves the
singleton alive with stale in-memory values, causing test order-dependency
issues. Before removing the settings entries in SetUp(), call the
UpdaterController singleton's kill() method on the main thread to destroy the
existing singleton instance, ensuring each test gets a fresh state and forces
the singleton to reload settings from the cleared QSettings during
initialization.

In `@src/updater/UpdaterController.cpp`:
- Around line 128-134: The loadSettings() method calls setChannel() before
loading the remaining settings (m_checkOnStartup, m_autoDownload,
m_lastCheckedAt), and since setChannel() now calls saveSettings(), it writes the
uninitialized member values back to QSettings before they are loaded,
overwriting the actual saved preferences. To fix this, either load all settings
into their respective member variables first before calling setChannel(), or
directly assign the channel value to the m_channel member variable without
calling setChannel() to avoid triggering saveSettings() prematurely.

In `@src/updater/UpdaterController.h`:
- Around line 82-83: The requestCheckDialog() method is a user-facing action
entrypoint but lacks breadcrumb logging for telemetry. Add a call to
SentryReporter::addBreadcrumb() with category "ui.action" in the implementation
of requestCheckDialog() before or alongside the existing
showDialogRequested(true) emission to ensure consistent action telemetry across
all user-facing actions, following the established coding guidelines for Sentry
breadcrumb logging.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: e7e67a94-94c1-49d3-b51c-7db49f972ee3

📥 Commits

Reviewing files that changed from the base of the PR and between 86678f1 and 6fbfe63.

📒 Files selected for processing (13)
  • CMakeLists.txt
  • qml/PreferencesDialog.qml
  • qml/UpdaterDialog.qml
  • qml/UpdaterSettingsPanel.qml
  • src/AppSettingsKeys.h
  • src/PropertiesPanelController.cpp
  • src/PropertiesPanelController.h
  • src/mainwindow.cpp
  • src/mainwindow.h
  • src/qml_resources.qrc
  • src/updater/UpdaterController.cpp
  • src/updater/UpdaterController.h
  • src/updater/UpdaterController_test.cpp

Comment thread qml/UpdaterDialog.qml
Comment thread qml/UpdaterSettingsPanel.qml
Comment thread src/updater/UpdaterController_test.cpp
Comment thread src/updater/UpdaterController.cpp
Comment thread src/updater/UpdaterController.h
Load all updater QSettings before persisting to avoid resetting toggles when the saved channel differs from the default. Wire check-on-startup, cancel in-flight work on Esc, use CheckBox/Button controls, reset singleton in tests, and add regression coverage for the load path.

Co-authored-by: Cursor <cursoragent@cursor.com>

@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)
qml/UpdaterSettingsPanel.qml (1)

62-91: Use explicit id on CheckBox instances for consistency with codebase patterns.

Lines 62 and 94 reference control.* in custom CheckBox delegates (indicator and contentItem). While Qt Quick Controls 2 implicitly provides the control variable in delegate scopes, the codebase establishes an explicit pattern in ThemedCheckBox.qml (which defines id: control). For consistency and clarity, add explicit ids to these CheckBox instances and update references accordingly.

Suggested fix
     CheckBox {
+        id: checkOnStartupCheckBox
         width: parent.width
         text: "Check for updates on startup"
         checked: UpdaterController.checkOnStartup
         onToggled: UpdaterController.checkOnStartup = checked
         font.pixelSize: 12
         indicator: Rectangle {
             implicitWidth: 14
             implicitHeight: 14
-            x: control.leftPadding
+            x: checkOnStartupCheckBox.leftPadding
             y: parent.height / 2 - height / 2
             radius: 2
             border.color: borderColor
             border.width: 1
-            color: control.checked ? highlightColor : "transparent"
+            color: checkOnStartupCheckBox.checked ? highlightColor : "transparent"
             Text {
                 anchors.centerIn: parent
-                visible: control.checked
+                visible: checkOnStartupCheckBox.checked
                 text: "\u2713"
                 color: "white"
                 font.pixelSize: 10
             }
         }
         contentItem: Text {
-            text: control.text
-            font: control.font
+            text: checkOnStartupCheckBox.text
+            font: checkOnStartupCheckBox.font
             color: textColor
             verticalAlignment: Text.AlignVCenter
-            leftPadding: control.indicator.width + control.spacing
+            leftPadding: checkOnStartupCheckBox.indicator.width + checkOnStartupCheckBox.spacing
         }
     }

     CheckBox {
+        id: autoDownloadCheckBox
         width: parent.width
         text: "Auto-download in background (opt-in)"
         checked: UpdaterController.autoDownload
         onToggled: UpdaterController.autoDownload = checked
         font.pixelSize: 12
         indicator: Rectangle {
             implicitWidth: 14
             implicitHeight: 14
-            x: control.leftPadding
+            x: autoDownloadCheckBox.leftPadding
             y: parent.height / 2 - height / 2
             radius: 2
             border.color: borderColor
             border.width: 1
-            color: control.checked ? highlightColor : "transparent"
+            color: autoDownloadCheckBox.checked ? highlightColor : "transparent"
             Text {
                 anchors.centerIn: parent
-                visible: control.checked
+                visible: autoDownloadCheckBox.checked
                 text: "\u2713"
                 color: "white"
                 font.pixelSize: 10
             }
         }
         contentItem: Text {
-            text: control.text
-            font: control.font
+            text: autoDownloadCheckBox.text
+            font: autoDownloadCheckBox.font
             color: textColor
             verticalAlignment: Text.AlignVCenter
-            leftPadding: control.indicator.width + control.spacing
+            leftPadding: autoDownloadCheckBox.indicator.width + autoDownloadCheckBox.spacing
         }
     }
🤖 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 `@qml/UpdaterSettingsPanel.qml` around lines 62 - 91, The CheckBox instances on
lines 62 and 94 reference `control.*` properties in their indicator and
contentItem delegates using the implicit control variable, but lack an explicit
`id: control` definition. To maintain consistency with the codebase pattern
established in ThemedCheckBox.qml, add an explicit `id: control` property to
each CheckBox element. This makes the pattern explicit and clear while all
existing references to control properties will continue to work correctly.
🤖 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 `@qml/UpdaterSettingsPanel.qml`:
- Around line 62-91: The CheckBox instances on lines 62 and 94 reference
`control.*` properties in their indicator and contentItem delegates using the
implicit control variable, but lack an explicit `id: control` definition. To
maintain consistency with the codebase pattern established in
ThemedCheckBox.qml, add an explicit `id: control` property to each CheckBox
element. This makes the pattern explicit and clear while all existing references
to control properties will continue to work correctly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 03214ec1-4dc0-4478-86e0-8de8af74782e

📥 Commits

Reviewing files that changed from the base of the PR and between 6fbfe63 and e767450.

📒 Files selected for processing (5)
  • qml/UpdaterDialog.qml
  • qml/UpdaterSettingsPanel.qml
  • src/mainwindow.cpp
  • src/updater/UpdaterController.cpp
  • src/updater/UpdaterController_test.cpp
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/updater/UpdaterController_test.cpp
  • qml/UpdaterDialog.qml
  • src/mainwindow.cpp

MainWindow tests construct many instances; firing GitHub update checks from each one added network load and contributed to the 45-minute unit-tests-linux timeout. Also name CheckBox ids explicitly per review.

Co-authored-by: Cursor <cursoragent@cursor.com>
@sonarqubecloud

Copy link
Copy Markdown

@fernandotonon fernandotonon merged commit 1f1ab9c into master Jun 17, 2026
21 checks passed
@fernandotonon fernandotonon deleted the feat/updater-dialog-443-449 branch June 17, 2026 05:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Update: Install-flavor detection + package-manager passthrough UX

1 participant