Repository navigation
fix: the update notice and Settings show the same latest version after a check - #332
Merged
Merged
Conversation
…r a check The Overview notice and the Settings update section each kept their own copy of the check result, and neither was shared. A check from Settings never reached the notice, and Settings' own check on opening read the pre-release switch from UI state that still held its default, so with the switch on it asked for stable releases only and fell back to the older release. UpdateTracker is now the one holder of the latest known release. Every check (app start, Settings opening, Check now, switch change) reads the switch when it runs and stores its result there; the notice, the sheet and Settings render from it. A result computed under the other pre-release setting is hidden at once, a newer check is never overwritten by an older slower one, and a failed check keeps the last good result. When several releases are newer than the installed one, the newest is offered. Closes #331 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…Settings Follow-ups of the review of #332. - A check no longer dies with the screen that asked for it. UpdateTracker runs its checks in an injected application scope (@ApplicationScope, provided by AppModule); a requester that goes away only stops waiting. Before, leaving Settings while the app-start check was in flight cancelled the newer run and the older run's answer was discarded. - Requests under the same pre-release setting share the running check (one network call). A request under a changed setting starts a new check; the older one still ends but only the newest started check stores. - UpdateStatus.checking is true while a check runs; the held result stays. Settings shows "Checking..." from it again, the Overview notice keeps the held result. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… pre-release switch - A check whose fetch or write of the check time throws no longer leaves the Settings row on "Checking..." and no longer reaches the screens that wait for it: the run ends, the failure is shown, the held result stays. - The tracker itself checks when "Include pre-releases" changes (debounced, so flipping the switch quickly makes one call), also when Settings was left right after the flip. The Settings view model only writes the setting. - Tests: the superseded check that ends first must not clear the flag of the newer one; the status is read by two separate collectors instead of comparing two reads of one flow; throwing checks; the setting reaction. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This was referenced Nov 13, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #331
What was wrong
Since 0.1.47 the update notice and its sheet are the only way users learn about updates (ADR-0046), and they could disagree with Settings. Causes, found in the code before changing it:
MainViewModelkept_updateState(Overview notice and sheet) andSettingsViewModelkept_checkState(Settings line and button). A check from Settings never reached the notice, and the notice's check ran only once when the app started.SettingsViewModel.initcallscheckNow(), which usedstate.value.includePrereleases. ThatStateFlowisstateIn(WhileSubscribed), so ininitnobody subscribes yet and it still holds its defaultfalse. With "Include pre-releases" on, opening Settings asked GitHub for stable releases only: Latest available fell back to v0.1.46 with that release's age ("54m ago") while "Last check" said "Just now". "Check now" later used the real value, hence v0.1.47-beta1 again.Deviation from the task's guess: there is no stored (DataStore) result and no throttle for the automatic check in the code. Only the time of the last check is stored. Both checks simply run each time (app start, Settings opening).
What changed
UpdateTracker(data/update, singleton) is the one holder of the latest known release. App start, Settings opening, "Check now" and the check after the switch all callcheck(), which reads the switch when it runs and stores its result there. It also records "Last check" as before.UpdateTracker.statusis what every surface renders: the Overview notice, the update sheet from the notice, the Settings "Latest available" line, the Settings button and the sheet from Settings.UpdateStatus.notice(dismissedTag)is the single decision for the notice.UpdateRepository.evaluatenow picks the newest release above the installed version instead of the first one in GitHub's list order (small related fix, with a test).No timers, retries or polling were added. The result is kept in memory only (a stored copy would have to be thrown away on every app update and every switch change, and the check at app start refreshes it).
Tests
UpdateTrackerTest(11, new) and one newUpdateRepositoryTestcase encode why: notice and Settings read one result; the check uses the switch as it is when it runs; switching never leaves the other setting's result on screen; stable-only users are never offered a pre-release; a result not newer than the installed version shows no notice; an older check never overrides a newer one; a failed check keeps the last good result; every check records its time.The old code cannot host these tests (the view models are not constructible on the JVM, see
ResetCertificateTest), so they could not be run against main directly. Instead each rule was checked by breaking the new code on purpose and watching the right tests fail: ignoring the setting tag (2 tests), dropping the run-number guard (1), letting a failure drop the good result (1), not reading the switch (7), and restoring the old "first newer release in list order" (1,theNewestReleaseWinsWhateverOrderGitHubListsThem, which is the only one that tests code that exists unchanged on main)../scripts/local-ci.sh: passed (lint, debug and release assemble, 267 unit tests, 0 skipped, 0 failed).Left out on purpose
.github/or store-flavor change; no installer code.Follow-ups after review (209504a)
Progress of a manual check restored.
UpdateStatus.checkingis true while a check runs; the held result stays. Settings shows "Checking…" in the "Latest available" row again (as on main; the "Check now" button was never disabled on main and still is not). The Overview notice and the "Update to" button keep the held result during a check. A failed check ends the in-progress state and keeps the last good result.A check no longer dies with the screen that asked.
UpdateTrackerruns its checks in an injected application scope (@ApplicationScope, provided byAppModule);check()only requests and waits, so cancelling the requester (leaving Settings) does not lose the answer. Requests under the same pre-release setting join the running check (one network call). A request under a changed setting starts a new check; the older one still ends, but only the newest started check stores its result.Tests: 4 new, 1 rewritten (
UpdateTrackerTestnow 15); each fails when its fix is reverted (checked by mutation)../scripts/local-ci.shpassed, 271 unit tests, 0 failed, 0 skipped. CHANGELOG unchanged (the user-visible behaviour is the same as described).Second review (5e1d512). A check that throws (fetch or the write of the check time) now ends the progress, shows the failure and keeps the held result instead of leaving "Checking…" or crashing the waiting screen; the tracker follows "Include pre-releases" itself (debounce 300 ms, so quick flipping makes one call;
collectLatestonly so a slow old check does not delay the new one), also when Settings is left right after the flip, and the Settings view model only writes the setting; two tests were added or sharpened (superseded check ending first, two collectors instead of two reads of one flow), 278 unit tests, each new test fails when its fix is reverted.🤖 Generated with Claude Code