Skip to content

Add self-updater: in-app update check and one-click updates - #96

Merged
kelchm merged 11 commits into
mainfrom
feat/self-updater
Aug 8, 2026
Merged

Add self-updater: in-app update check and one-click updates#96
kelchm merged 11 commits into
mainfrom
feat/self-updater

Conversation

@kelchm

@kelchm kelchm commented Aug 7, 2026

Copy link
Copy Markdown
Owner

Implements the self-updater tracked in #95.

What

  • On SimHub startup, FanaBridge checks the GitHub Releases API for a newer version (one request per launch, opt-out checkbox in the About section; a manual "Check for updates now" link works regardless).
  • A banner on the settings page offers one-click Update: download the release zip, verify it against the SHA-256 digest GitHub publishes for the asset, swap FanaBridge.dll + DevicesLogos\ in place, then prompt to restart SimHub.
  • When one-click isn't possible (missing digest, unwritable install dir), the banner degrades to a notify-only / manual-install message with a release-page link. No UAC prompts.

Design notes

  • New src\FanaBridge.Updater project — compile-time module only; ILRepack still ships the single merged FanaBridge.dll. Keeps the network + file-swap code behind a hard project boundary with no reference to Core or the plugin.
  • Crash-safe commit: the staged DLL is fully written as FanaBridge.dll.new first, then committed with two renames (live → .old, .new → live), so the crash window is two metadata operations and a failure leaves recoverable files. .old survives until the next launch proves the new build initializes, then is cleaned up.
  • The update state machine is serialized and ReadyToRestart is terminal — no re-check can destroy the rollback copy after a swap. Zip extraction is whitelist-only (FanaBridge.dll + DevicesLogos/*.png) with streaming size caps; user profiles under FanaBridge\ are never touched.
  • No release-pipeline changes needed: verification uses the immutable per-asset digest the GitHub API already exposes (present on v0.6.0's asset).

Testing

  • 678 tests green, including 55 updater tests (version compare, feed parsing, whitelist extraction incl. zip-slip/duplicate/size-cap cases, swap rollback and access-denied classification, state-machine re-entrancy/cancellation/debounce).
  • Three-assembly merge verified via -p:MergePlugin=true.

Remaining before merge (why draft)

  • Real-feed dry run: local build with VersionPrefix=0.0.1, confirm banner offers v0.6.0 and the full update → restart path works against a live SimHub
  • Banner state persists across a game change (plugin-manager restart)
  • Opt-out checkbox suppresses the startup request (log check)

Closes #95

kelchm added 2 commits August 6, 2026 22:58
New FanaBridge.Updater project (merged into the single shipped DLL):
release feed parsing with availability/installability split, whitelist
zip extraction, crash-safe two-rename swap with rollback, serialized
state machine with terminal ReadyToRestart. Plugin wiring runs one
startup check (opt-out setting) and the settings UI offers the update
banner with notify-only and access-denied fallbacks.
- Honor cancellation up to the commit point: re-check the token after
  extraction and clean the staging dir when a cancel lands mid-download.
- Never report a committed swap as failed: the cosmetic logo step after
  rename 2 is now fully non-throwing (a Failed state on a live new DLL
  would let a retry destroy the .old rollback copy).
- One updater-lifetime CTS covering manual checks/applies from the UI,
  not just the startup check, so FinalizePlugin cancels those too.
- Move the post-update restart-prompt guard to the plugin (a fresh
  SettingsControl is created per page open).
- Classify access-denied from the staged-DLL version read; parse asset
  "size" defensively (non-numeric JSON no longer throws).
- Tests: real duplicate-DLL zip entries, 50 MB total-cap enforcement,
  IOException HRESULT access-denied classification, cancel-after-
  extraction, and post-commit logo failure still succeeding.
@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@kelchm, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 53 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

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.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 928754a9-3170-48a0-816a-d0eb13794406

📥 Commits

Reviewing files that changed from the base of the PR and between e41cd63 and a39e121.

📒 Files selected for processing (1)
  • CHANGELOG.md
📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added automatic and manual checks for new FanaBridge releases.
    • Added settings to enable or disable automatic update checks.
    • Added an in-app update banner with release notes, progress, errors, and restart prompts.
    • Added options to install updates, open release pages, and restart SimHub.
    • Added secure package verification and rollback-supported installation.
  • Bug Fixes

    • Improved stale update-file cleanup.
    • Preserved user data and improved handling of installation permission errors.

Walkthrough

The PR adds a .NET Framework updater module. It parses GitHub releases, validates and extracts packages, replaces assemblies safely, integrates update state into the plugin lifecycle, and adds settings-page controls.

Changes

Self-updater

Layer / File(s) Summary
Updater project and packaging
FanaBridge.sln, src/FanaBridge.Updater/..., src/FanaBridge/FanaBridge.csproj, Directory.Build.targets, Directory.Build.props, tests/FanaBridge.Tests/FanaBridge.Tests.csproj
Adds the updater project, solution configurations, assembly merging, version-prefix update, stale-artifact cleanup, and test references.
Release metadata and version contracts
src/FanaBridge.Updater/UpdateVersion.cs, src/FanaBridge.Updater/ReleaseFeed.cs, tests/FanaBridge.Tests/Updater/UpdateVersionTests.cs, tests/FanaBridge.Tests/Updater/ReleaseFeedTests.cs
Adds version parsing and comparison, release parsing, asset selection, digest validation, notify-only results, and tests.
Package extraction and file replacement
src/FanaBridge.Updater/UpdatePackage.cs, src/FanaBridge.Updater/UpdateFileSwapper.cs, tests/FanaBridge.Tests/Updater/UpdatePackageTests.cs, tests/FanaBridge.Tests/Updater/UpdateFileSwapperTests.cs
Adds constrained ZIP extraction, SHA-256 verification, staged DLL replacement, rollback, access-denied classification, logo copying, cleanup, and tests.
Update service orchestration
src/FanaBridge.Updater/UpdateService.cs, src/FanaBridge/Updates/GitHubHttpClient.cs, tests/FanaBridge.Tests/Updater/UpdateServiceTests.cs
Adds serialized checks and applications, debouncing, cancellation handling, phase snapshots, HTTP fetching, failure reporting, and tests.
Plugin lifecycle and settings UI
src/FanaBridge/FanatecPlugin.cs, src/FanaBridge/FanatecPluginSettings.cs, src/FanaBridge/UI/SettingsControl.xaml, src/FanaBridge/UI/SettingsControl.xaml.cs
Adds startup and manual update actions, lifecycle cancellation, update preferences, status rendering, release links, apply actions, and restart prompts.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant SettingsControl
  participant FanatecPlugin
  participant UpdateService
  participant GitHubHttpClient
  participant UpdatePackage
  participant UpdateFileSwapper
  SettingsControl->>FanatecPlugin: check or apply update
  FanatecPlugin->>UpdateService: CheckAsync or DownloadAndApplyAsync
  UpdateService->>GitHubHttpClient: fetch release metadata and package
  UpdateService->>UpdatePackage: verify and extract package
  UpdateService->>UpdateFileSwapper: apply staged update
  UpdateService-->>FanatecPlugin: publish update snapshot
  FanatecPlugin-->>SettingsControl: raise UpdateStateChanged
Loading

Possibly related issues

Possibly related PRs

  • kelchm/FanaBridge#67: Both PRs modify the assembly-merging pipeline and stale satellite-DLL cleanup.
  • kelchm/FanaBridge#71: Both PRs modify Directory.Build.props version configuration in opposite directions.
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The change from product version prefix 0.6.0 to 0.5.0 is not related to the self-updater objectives. Remove the unrelated product version change, or document why changing Directory.Build.props from 0.6.0 to 0.5.0 is required.
Docstring Coverage ⚠️ Warning Docstring coverage is 7.89% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The implementation covers the linked issue objectives for update checks, verification, safe replacement, restart prompting, fallback guidance, and single-DLL packaging [#95].
Title check ✅ Passed The title clearly summarizes the main change: adding an in-app self-updater with update checks and one-click updates.
Description check ✅ Passed The description directly explains the self-updater behavior, design, testing, and remaining validation work.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch

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.

@kelchm
kelchm marked this pull request as ready for review August 7, 2026 03:21

@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

🧹 Nitpick comments (4)
tests/FanaBridge.Tests/Updater/UpdatePackageTests.cs (1)

148-149: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Mark the total-size cap test as Slow for consistency.

This test builds three 20 MB entries and writes about 40 MB to disk before the cap trips. ExtractToStaging_PerEntrySizeCap_Throws at line 196 carries [Trait("Category", "Slow")] for comparable work. Apply the same trait so both heavy tests are filtered together.

♻️ Proposed change
         [Fact]
+        [Trait("Category", "Slow")]
         public void ExtractToStaging_TotalSizeCap_Throws()
🤖 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 `@tests/FanaBridge.Tests/Updater/UpdatePackageTests.cs` around lines 148 - 149,
Add the [Trait("Category", "Slow")] attribute to
ExtractToStaging_TotalSizeCap_Throws, matching
ExtractToStaging_PerEntrySizeCap_Throws so both heavy tests are categorized
consistently.
src/FanaBridge.Updater/UpdateFileSwapper.cs (1)

216-219: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove the staged .new file when Apply fails before the commit.

Line 158 writes FanaBridge.dll.new before any live file is touched. If step 4 (_move(liveDll, oldDll)) throws, control reaches this outer catch and returns immediately, so .new stays in the SimHub install directory until the next Apply call or the next CleanupStaleArtifacts run at startup.

The cleanup is safe here. Every path that reaches this catch after the copy has not yet renamed the live DLL: rename 2 failures are handled by the inner catch at lines 180-208, and CopyLogosBestEffort never throws.

♻️ Proposed change
             catch (Exception ex)
             {
+                // Pre-commit failure only: the live DLL is still in place, so the
+                // staged .new copy is an orphan.
+                TryDelete(newDll);
                 return FailClosed(ex.Message, ex);
             }

Also extend Apply_MoveAccessDeniedIoException_ClassifiedAccessDenied in tests/FanaBridge.Tests/Updater/UpdateFileSwapperTests.cs (lines 90-95) to assert that FanaBridge.dll.new no longer exists after the failure.

🤖 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 `@src/FanaBridge.Updater/UpdateFileSwapper.cs` around lines 216 - 219, Update
the outer catch in UpdateFileSwapper.Apply to delete the staged
FanaBridge.dll.new file before returning FailClosed, while preserving the
existing exception reporting. Extend
Apply_MoveAccessDeniedIoException_ClassifiedAccessDenied to assert that the
staged .new file is absent after the failed apply.
src/FanaBridge.Updater/UpdatePackage.cs (1)

139-174: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Align the DLL name comparison with the rest of the whitelist, and reject invalid file-name characters.

Two small hardening points in TryMapWhitelist:

  1. Line 152 matches the DLL name with StringComparison.Ordinal, but the .png suffix check at line 160, the seen set at line 66, and the sawDll check at line 114 all use OrdinalIgnoreCase. A zip that stores fanabridge.dll is ignored and the extraction then fails with "missing root entry". The behavior fails closed, so this is a consistency fix, not a defect.
  2. The logo file name is checked only for separators and dot segments. A name such as a:b.png still reaches Path.Combine and creates an NTFS alternate data stream inside the staging directory. The write stays inside stagingDir, so there is no traversal, but rejecting invalid characters avoids the surprise.
♻️ Proposed change
-            if (string.Equals(name, DllName, StringComparison.Ordinal))
+            if (string.Equals(name, DllName, StringComparison.OrdinalIgnoreCase))
             {
                 relativePath = DllName;
                 return true;
             }
 
             string prefix = LogosDirName + "/";
-            if (name.StartsWith(prefix, StringComparison.Ordinal)
+            if (name.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)
                 && name.EndsWith(".png", StringComparison.OrdinalIgnoreCase))
             {
                 string file = name.Substring(prefix.Length);
                 // Single level only — reject empty, nested, or traversal segments.
                 if (file.Length == 0 || file.IndexOf('/') >= 0 || file.IndexOf('\\') >= 0)
                     return false;
                 if (file == "." || file == "..")
                     return false;
+                if (file.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0)
+                    return false;
🤖 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 `@src/FanaBridge.Updater/UpdatePackage.cs` around lines 139 - 174, Update
TryMapWhitelist to compare DllName using StringComparison.OrdinalIgnoreCase,
matching the existing whitelist and seen/sawDll handling. Before accepting a
logo filename, reject any invalid platform filename characters in addition to
separators and dot segments, using the framework’s standard invalid-character
validation, while preserving valid single-level .png mappings.
tests/FanaBridge.Tests/Updater/UpdateServiceTests.cs (1)

194-217: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Make the concurrency test deterministic.

The test depends on await Task.Delay(50) to guarantee that the first CheckAsync has entered _fetchText. On a loaded CI agent the first task may not have acquired the gate yet. The second call then acquires it and fetches becomes 2, so the test fails intermittently. Signal entry from inside the fetch delegate instead.

🧪 Proposed fix
             var tcs = new TaskCompletionSource<string>();
+            var entered = new TaskCompletionSource<bool>();
             int fetches = 0;
             var svc = CreateService(
                 currentVersion: "0.6.0",
                 fetchText: async (_, ct) =>
                 {
                     Interlocked.Increment(ref fetches);
+                    entered.TrySetResult(true);
                     return await tcs.Task.ConfigureAwait(false);
                 });
 
             Task first = svc.CheckAsync();
-            // Allow first to enter fetch.
-            await Task.Delay(50);
+            // Deterministic: the first call is inside the gate and inside fetch.
+            await entered.Task;
             Task second = svc.CheckAsync();
             await second;
🤖 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 `@tests/FanaBridge.Tests/Updater/UpdateServiceTests.cs` around lines 194 - 217,
Make CheckAsync_ConcurrentSecondCall_IsNoOp deterministic by replacing the
timing-based Task.Delay with a TaskCompletionSource used as an entry signal
inside the fetchText delegate. Await that signal before starting the second
svc.CheckAsync call, while preserving the existing fetch count, completion, and
final phase assertions.
🤖 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 `@src/FanaBridge.Updater/ReleaseFeed.cs`:
- Around line 116-125: Trim tagName before assigning and processing the version
in the release-feed flow, while preserving the existing single leading v/V
removal. Use the normalized value consistently for UpdateVersion.TryParse and
subsequent asset-name matching, UI version output, and error reporting in the
surrounding release handling.

In `@src/FanaBridge.Updater/UpdateService.cs`:
- Around line 290-295: Update the generic Exception catch in the update-apply
flow to call TryDeleteDir(staging), matching the InvalidDataException and
swap-failure paths. Ensure cleanup occurs before publishing the failed
UpdateSnapshot, while preserving the existing logging and failure reporting.
- Around line 180-184: Distinguish timeout cancellations from caller-requested
cancellations in both OperationCanceledException handlers in UpdateService:
src/FanaBridge.Updater/UpdateService.cs lines 180-184 and 283-289. Only restore
the previous phase silently when the caller’s cancellation token was explicitly
requested; route timeout cancellations through the existing failed check/apply
reporting path at both sites.

In `@src/FanaBridge.Updater/UpdateVersion.cs`:
- Around line 118-124: Update the public UpdateVersion value-type paths that
depend on Numeric so default-constructed instances remain safe: ensure Normalize
handles a null Numeric value, and apply equivalent null-safe behavior in
CompareTo, Equals, GetHashCode, and ToString. Preserve existing version
comparison, equality, hashing, and string formatting for initialized instances.

In `@src/FanaBridge/UI/SettingsControl.xaml.cs`:
- Around line 1260-1271: Validate snapshot.Release.HtmlUrl in OpenReleasePage
before Process.Start, allowing only absolute http or https URLs; otherwise fall
back to the existing GitHub releases URL. Preserve the current UseShellExecute
launch and warning behavior for valid URLs and launch failures.

---

Nitpick comments:
In `@src/FanaBridge.Updater/UpdateFileSwapper.cs`:
- Around line 216-219: Update the outer catch in UpdateFileSwapper.Apply to
delete the staged FanaBridge.dll.new file before returning FailClosed, while
preserving the existing exception reporting. Extend
Apply_MoveAccessDeniedIoException_ClassifiedAccessDenied to assert that the
staged .new file is absent after the failed apply.

In `@src/FanaBridge.Updater/UpdatePackage.cs`:
- Around line 139-174: Update TryMapWhitelist to compare DllName using
StringComparison.OrdinalIgnoreCase, matching the existing whitelist and
seen/sawDll handling. Before accepting a logo filename, reject any invalid
platform filename characters in addition to separators and dot segments, using
the framework’s standard invalid-character validation, while preserving valid
single-level .png mappings.

In `@tests/FanaBridge.Tests/Updater/UpdatePackageTests.cs`:
- Around line 148-149: Add the [Trait("Category", "Slow")] attribute to
ExtractToStaging_TotalSizeCap_Throws, matching
ExtractToStaging_PerEntrySizeCap_Throws so both heavy tests are categorized
consistently.

In `@tests/FanaBridge.Tests/Updater/UpdateServiceTests.cs`:
- Around line 194-217: Make CheckAsync_ConcurrentSecondCall_IsNoOp deterministic
by replacing the timing-based Task.Delay with a TaskCompletionSource used as an
entry signal inside the fetchText delegate. Await that signal before starting
the second svc.CheckAsync call, while preserving the existing fetch count,
completion, and final phase assertions.
🪄 Autofix

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 Plus

Run ID: 6b810ae7-8d7b-4398-a15f-188d2b87531b

📥 Commits

Reviewing files that changed from the base of the PR and between ee204e7 and a7898fb.

📒 Files selected for processing (20)
  • Directory.Build.targets
  • FanaBridge.sln
  • src/FanaBridge.Updater/FanaBridge.Updater.csproj
  • src/FanaBridge.Updater/ReleaseFeed.cs
  • src/FanaBridge.Updater/UpdateFileSwapper.cs
  • src/FanaBridge.Updater/UpdatePackage.cs
  • src/FanaBridge.Updater/UpdateService.cs
  • src/FanaBridge.Updater/UpdateVersion.cs
  • src/FanaBridge/FanaBridge.csproj
  • src/FanaBridge/FanatecPlugin.cs
  • src/FanaBridge/FanatecPluginSettings.cs
  • src/FanaBridge/UI/SettingsControl.xaml
  • src/FanaBridge/UI/SettingsControl.xaml.cs
  • src/FanaBridge/Updates/GitHubHttpClient.cs
  • tests/FanaBridge.Tests/FanaBridge.Tests.csproj
  • tests/FanaBridge.Tests/Updater/ReleaseFeedTests.cs
  • tests/FanaBridge.Tests/Updater/UpdateFileSwapperTests.cs
  • tests/FanaBridge.Tests/Updater/UpdatePackageTests.cs
  • tests/FanaBridge.Tests/Updater/UpdateServiceTests.cs
  • tests/FanaBridge.Tests/Updater/UpdateVersionTests.cs

Comment thread src/FanaBridge.Updater/ReleaseFeed.cs
Comment thread src/FanaBridge.Updater/UpdateService.cs
Comment thread src/FanaBridge.Updater/UpdateService.cs
Comment thread src/FanaBridge.Updater/UpdateVersion.cs Outdated
Comment thread src/FanaBridge/UI/SettingsControl.xaml.cs
- Distinguish HttpClient timeouts from caller cancellation in both
  UpdateService command handlers (a timed-out manual check now reports
  CheckFailed instead of silently restoring the previous phase).
- Clean the staging dir in the generic apply catch, matching the other
  failure paths; sweep the half-staged .new in the swapper outer catch.
- Trim tag_name before deriving the version/asset name; null-safe
  UpdateVersion for default-constructed instances.
- Only shell-launch http(s) release URLs; fall back to the releases page.
- Reject invalid Windows filename characters in logo entries explicitly.
- Tests: deterministic concurrency test (entry signal instead of delay),
  Slow trait on the total-cap test, .new-absent assertion.
Copilot AI balanced review requested due to automatic review settings August 7, 2026 21:35

Copilot AI 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.

Pull request overview

Adds an in-app self-updater with release checks, verified downloads, update UI, and rollback-oriented file replacement.

Changes:

  • Adds updater state, release parsing, verification, extraction, and swapping.
  • Integrates startup/manual checks and update controls into the plugin UI.
  • Adds updater packaging configuration and comprehensive tests.

Reviewed changes

Copilot reviewed 20 out of 20 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/FanaBridge.Tests/Updater/UpdateVersionTests.cs Tests version parsing and ordering.
tests/FanaBridge.Tests/Updater/UpdateServiceTests.cs Tests updater state transitions.
tests/FanaBridge.Tests/Updater/UpdatePackageTests.cs Tests secure package extraction.
tests/FanaBridge.Tests/Updater/UpdateFileSwapperTests.cs Tests swapping and rollback behavior.
tests/FanaBridge.Tests/Updater/ReleaseFeedTests.cs Tests release metadata parsing.
tests/FanaBridge.Tests/FanaBridge.Tests.csproj References the updater project.
src/FanaBridge/Updates/GitHubHttpClient.cs Implements GitHub HTTP requests.
src/FanaBridge/UI/SettingsControl.xaml.cs Drives updater UI behavior.
src/FanaBridge/UI/SettingsControl.xaml Adds update banner and controls.
src/FanaBridge/FanatecPluginSettings.cs Adds startup-check preference.
src/FanaBridge/FanatecPlugin.cs Integrates updater lifecycle.
src/FanaBridge/FanaBridge.csproj Merges updater into the plugin.
src/FanaBridge.Updater/UpdateVersion.cs Implements version comparison.
src/FanaBridge.Updater/UpdateService.cs Implements the update state machine.
src/FanaBridge.Updater/UpdatePackage.cs Verifies and extracts packages.
src/FanaBridge.Updater/UpdateFileSwapper.cs Performs in-place file replacement.
src/FanaBridge.Updater/ReleaseFeed.cs Parses GitHub release responses.
src/FanaBridge.Updater/FanaBridge.Updater.csproj Defines the updater assembly.
FanaBridge.sln Adds the updater project.
Directory.Build.targets Cleans stale updater assemblies.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/FanaBridge.Updater/UpdateFileSwapper.cs
Comment thread src/FanaBridge/FanatecPlugin.cs
Comment thread src/FanaBridge/UI/SettingsControl.xaml.cs
Copilot AI review requested due to automatic review settings August 8, 2026 15:55

This comment was marked as off-topic.

- Defer updater init (and the .old rollback-copy sweep) from the end of
  InitializeCore to the end of the first full Init, so the rollback copy
  outlives the per-manager registrations that could still fail a fresh
  build''s load.
- Keep the About-section status line in step with the banner for the
  Downloading/Applying, ReadyToRestart, and Failed phases.
Copilot AI review requested due to automatic review settings August 8, 2026 16:00

This comment was marked as off-topic.

Copilot AI review requested due to automatic review settings August 8, 2026 16:14
Replace the left-hugging width-capped callout card with a bar that spans
the section content width: one row of headline (inline release-notes
link) plus a right-aligned action button, and a muted detail line shown
only for states that need one (installing, notify-only, failure).
Drops the glyph column — the blue/amber border and tint carry the state.

This comment was marked as off-topic.

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/FanaBridge/FanatecPlugin.cs (1)

93-93: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Make the updater’s nullable contract match its behavior.

Updates documents that updater initialization can leave the value null, but the getter still returns non-nullable UpdateService. Return UpdateService? so callers are required to handle the unavailable-updater case before dereferencing Snapshot.

🤖 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 `@src/FanaBridge/FanatecPlugin.cs` at line 93, Update the Updates property in
FanatecPlugin to return nullable UpdateService? instead of non-nullable
UpdateService, matching the possible uninitialized _updateService state and
requiring callers to handle null before accessing Snapshot.
🤖 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.

Outside diff comments:
In `@src/FanaBridge/FanatecPlugin.cs`:
- Line 93: Update the Updates property in FanatecPlugin to return nullable
UpdateService? instead of non-nullable UpdateService, matching the possible
uninitialized _updateService state and requiring callers to handle null before
accessing Snapshot.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 3a62931c-41f6-4fa7-87a2-0da5475436ca

📥 Commits

Reviewing files that changed from the base of the PR and between a7898fb and 3303bbe.

📒 Files selected for processing (12)
  • src/FanaBridge.Updater/ReleaseFeed.cs
  • src/FanaBridge.Updater/UpdateFileSwapper.cs
  • src/FanaBridge.Updater/UpdatePackage.cs
  • src/FanaBridge.Updater/UpdateService.cs
  • src/FanaBridge.Updater/UpdateVersion.cs
  • src/FanaBridge/FanatecPlugin.cs
  • src/FanaBridge/UI/SettingsControl.xaml
  • src/FanaBridge/UI/SettingsControl.xaml.cs
  • tests/FanaBridge.Tests/FanaBridge.Tests.csproj
  • tests/FanaBridge.Tests/Updater/UpdateFileSwapperTests.cs
  • tests/FanaBridge.Tests/Updater/UpdatePackageTests.cs
  • tests/FanaBridge.Tests/Updater/UpdateServiceTests.cs
🚧 Files skipped from review as they are similar to previous changes (8)
  • src/FanaBridge/UI/SettingsControl.xaml
  • src/FanaBridge.Updater/ReleaseFeed.cs
  • src/FanaBridge.Updater/UpdatePackage.cs
  • src/FanaBridge.Updater/UpdateFileSwapper.cs
  • tests/FanaBridge.Tests/Updater/UpdatePackageTests.cs
  • src/FanaBridge.Updater/UpdateService.cs
  • src/FanaBridge/UI/SettingsControl.xaml.cs
  • tests/FanaBridge.Tests/FanaBridge.Tests.csproj

Copilot AI review requested due to automatic review settings August 8, 2026 16:44

This comment was marked as off-topic.

Copilot AI review requested due to automatic review settings August 8, 2026 16:52

This comment was marked as off-topic.

Copilot AI review requested due to automatic review settings August 8, 2026 16:53

This comment was marked as off-topic.

Copilot AI review requested due to automatic review settings August 8, 2026 18:38

This comment was marked as off-topic.

@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

🤖 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 `@Directory.Build.props`:
- Line 11: Update the VersionPrefix property in Directory.Build.props from 0.5.0
to 0.6.0 or the intended next version, ensuring the derived Version,
AssemblyVersion, and FileVersion are not lower than the released product
version.
🪄 Autofix

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 Plus

Run ID: 3860bc8e-c7aa-4b2d-b45e-24da385cdc49

📥 Commits

Reviewing files that changed from the base of the PR and between 3303bbe and e41cd63.

📒 Files selected for processing (5)
  • Directory.Build.props
  • src/FanaBridge/FanatecPlugin.cs
  • src/FanaBridge/FanatecPluginSettings.cs
  • src/FanaBridge/UI/SettingsControl.xaml
  • src/FanaBridge/UI/SettingsControl.xaml.cs
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/FanaBridge/UI/SettingsControl.xaml.cs
  • src/FanaBridge/FanatecPlugin.cs

Comment thread Directory.Build.props Outdated
Rigs can leave SimHub running for weeks, so a launch-only check goes
stale. A 24 h timer re-runs the check in-session; the handler re-reads
EnableUpdateCheck so the About checkbox now takes effect live, and the
timer is torn down (and its checks cancelled) in FinalizePlugin.
Checkbox relabeled "Check for updates automatically".
kelchm added 3 commits August 8, 2026 14:41
Drop the redundant "you have x.y.z" from the banner headline (the
installed version is already printed in About), and make the About
status line report only manual-check outcomes: an available update says
"see above" instead of duplicating the banner, and the in-progress /
installed / failed states the banner owns leave it empty.
Everything update-related now shares one surface: installed version,
the banner (offer / progress / restart / failure), the auto-check
preference, and the manual check link. Accepted trade-off: on small
windows the banner can sit below the fold. The "see above" pointer in
the manual-check line is gone — the banner is adjacent.
50:50 grid split — About is short and the device chain does not need
full width, and this keeps the update banner above the fold instead of
at the bottom of the page. Experimental Features is now the last
section, so it drops its trailing separator.

Within About, the banner leads the section — directly under the heading
and ahead of the version identity — so it opens with the actionable
state when there is one.

The row's own dividers replace the per-section separators: both
sections set ShowSeparator="False", a vertical rule sits in the gutter
between them, and one continuous horizontal rule runs under the whole
row. Both rules copy SHSection's separator geometry (1px, 30% opacity,
20px template inset) and bind their brush to a section's SeparatorBrush
so they track the SimHub theme.
Copilot AI review requested due to automatic review settings August 8, 2026 18:41
@kelchm
kelchm force-pushed the feat/self-updater branch from e41cd63 to bd97a6e Compare August 8, 2026 18:41

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI review requested due to automatic review settings August 8, 2026 18:44

This comment was marked as off-topic.

@kelchm
kelchm merged commit 5a555c5 into main Aug 8, 2026
2 checks passed
@kelchm
kelchm deleted the feat/self-updater branch August 8, 2026 18:47
@coderabbitai coderabbitai Bot mentioned this pull request Aug 8, 2026
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.

Self-updater: in-app update check and one-click updates

2 participants