Skip to content

fix(ui): keep the update verdict beside the stamp it was reached with - #150

Merged
ArangoGutierrez merged 1 commit into
mainfrom
fix/a-update-verdict
Aug 19, 2026
Merged

fix(ui): keep the update verdict beside the stamp it was reached with#150
ArangoGutierrez merged 1 commit into
mainfrom
fix/a-update-verdict

Conversation

@ArangoGutierrez

Copy link
Copy Markdown
Owner

Closes #147.

After any relaunch the panel showed both of these, one under the other:

coffee-bar has not looked for a newer version yet.
Last checked: 2026-08-19 12:35.

It reported that it had never checked, and then reported when it last checked. Observed on the shipped v0.3.0 build.

Cause

The timestamp was persisted and the verdict was not. ServingModel wrote SettingsKey.lastUpdateCheck after each check and restored it at launch, but updateVerdict had no such write, so a relaunch held a real stamp beside a nil verdict and rendered neverCheckedLine. Confirmed on the maintainer's Mac: defaults read com.coffeebar.app carried exactly one update-related key and no verdict.

Not an edge case. It was the state of the panel after every launch until the next check fell due, which with a 24-hour interval is most of a day.

Approach

The verdict is persisted as a discriminator plus a minimal payload, and the sentence is rebuilt from current code rather than stored as prose, so an upgrade cannot resurrect wording written by an older build.

The alternative of not persisting cannotCompare was rejected with reason: the machine in the bug report was in a failed-check state, so restoring no verdict returns neverCheckedLine beside a real stamp, which is the reported defect unchanged. It is only safe if the stamp is suppressed too, and the stamp is the sole bound on the app's one outbound request.

Every existing install has a stamp and no stored verdict, so a third state was needed regardless. Those users see one honest sentence once, until their next check falls due, rather than a fabricated verdict.

The intent at UpdateCheck.swift is preserved: it still reports that nothing has happened rather than assuming "up to date", because silence read as good news is how a check that has been broken for a year goes unnoticed.

Testing done

  • swift test rc=0, 1243 tests in 12 suites pre-rebase, verified independently on a clean scratch path rather than taken from the report.
  • Three targeted mutations, each leaving the tree compiling and reverting exactly one behaviour, with RED and GREEN pasted. Reverting the status line reproduces the contradiction exactly; the never-checked control stays green under all three, so the guard discriminates the pair rather than banning a sentence.
  • A wholesale revert of the production change only yields a compile error, and was reported as not evidence rather than counted as a proof.

Breaking changes

None. One new settings key, absent-safe.

Issue #147. The panel restored WHEN coffee-bar last looked and nothing about
what it found, so every relaunch printed these two lines together:

    coffee-bar has not looked for a newer version yet.
    Last checked: 2026-08-19 12:35.

The stamp was persisted and the verdict was not. Read off a maintainer's
machine: `defaults read com.coffeebar.app` carried `lastUpdateCheck` and no
update-related key beside it.

A completed check now writes both, together, and `checkForUpdates` refuses to
write one without the other. What goes into `lastUpdateVerdict` is a TAG and,
where the case has one, the smallest payload that rebuilds the sentence. Never
the sentence. Every line in `UpdateCheck` is prose, prose is rewritten between
releases, and a stored one would be replayed by a build that no longer agrees
with it: the same defect as this one, a release later. `verdict(of:)` is the
single place a stored form becomes words, called by the check that reached it
and by the launch that restores it, so a relaunch cannot show a second spelling
of the same answer.

The tag is decided where the CAUSE is still in hand rather than derived
afterwards from the sentence the verdict carries, because deriving it would
mean matching prose, which is the thing being kept out of the store.

An absent or unreadable verdict restores NOTHING rather than a guess, and
`updateStatusLine` gained a third sentence for that state. It does not say
"up to date": a missing verdict is silence, and silence read as good news is
how a check broken for a year goes unnoticed, which is the argument
`neverCheckedLine` already makes. Every install made before this key existed
lands there once, and so does a value written by a newer build or left by a
key collision.

`cannotCompare(unstampedLine)` is deliberately not storable. It is reached
before the fetch and records no attempt, so there is no stamp for it to sit
beside.

GREEN, measured against this tree:
  swift test   rc=0
  Test run with 1243 tests in 12 suites passed
  baseline at b333f41: 1235 tests, so 8 are new

Guards mutation-proven. Reverting `updateStatusLine` to its two-state form
turns `aRestoredStampWithNoVerdictNeverSaysItHasNotLooked` red on the exact
contradiction; dropping the restore or the write turns
`aVerdictSurvivesARelaunchBesideItsStamp` red. The never-checked case stays
green under all three, so the guard discriminates the pair rather than the
sentence.

Signed-off-by: Carlos Eduardo Arango Gutierrez <eduardoa@nvidia.com>
@ArangoGutierrez
ArangoGutierrez merged commit c63f88e into main Aug 19, 2026
4 of 6 checks passed
@ArangoGutierrez
ArangoGutierrez deleted the fix/a-update-verdict branch August 19, 2026 17:28
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.

The panel says it has never checked for updates and shows when it last checked

1 participant