fix(versions): never show a 'latest release' older than the installed version

A stale 30-minute update-check cache could render a latest tag below the
installed version right after an update (e.g. 'latest v0.9.9' next to
'installed v0.9.10'), which reads backwards. The passive display now discards a
cached value lower than the installed version until a fresh check resolves it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat/v1-foundation v0.9.11
boban 2026-06-19 18:43:46 +02:00
parent 81a9e4d641
commit deea1a802c
4 changed files with 26 additions and 1 deletions

View File

@ -13,6 +13,15 @@ getaggte Releases (Kanal `stable`, optional `beta`) — niemals Entwicklungs-Bui
_Keine offenen Änderungen — der nächste Stand wird hier gesammelt und als `vX.Y.Z` getaggt._
## [0.9.11] - 2026-06-19
### Behoben
- **„Neuestes Release" zeigt nie mehr eine ältere Version als die installierte.** Direkt nach einem
Update konnte der 30-Minuten-Cache der Update-Prüfung noch den vorherigen Stand halten, sodass die
Karte „Installiert" z. B. „Neuestes Release v0.9.9" neben „installiert v0.9.10" zeigte (wirkte
rückwärts). Ist der gecachte Wert kleiner als die installierte Version, wird er jetzt verworfen
(Anzeige „—"), bis „Nach Updates suchen" den echten neuesten Tag auflöst.
## [0.9.10] - 2026-06-19
### Behoben

View File

@ -365,6 +365,12 @@ class Index extends Component
$releases = $this->releases();
// Passive (no network): cached remote result if a check has run, else local .git.
$latestTag = $this->resolveLatestTag($this->channel());
// A stale cache can hold a "latest" older than what's now installed (e.g. right after an
// update, before the next check). Never present yourself as behind a release you've
// already passed — hide it until a fresh check resolves the true latest.
if ($latestTag !== null && version_compare(ltrim($latestTag, 'vV'), ltrim((string) config('clusev.version'), 'vV'), '<')) {
$latestTag = null;
}
return view('livewire.versions.index', [
'version' => config('clusev.version'),

View File

@ -2,7 +2,7 @@
return [
// First tagged release is v0.1.0 (semantic, not -dev).
'version' => '0.9.10',
'version' => '0.9.11',
// Deployed commit + branch. install.sh bakes these into .env from the host's .git (the prod
// image ships no .git); the Versions page prefers them and falls back to a live .git read in

View File

@ -127,6 +127,16 @@ class VersionUpdateCheckTest extends TestCase
);
}
public function test_passive_latest_release_never_shows_older_than_installed(): void
{
// A stale cache from before an update must not render a "latest" below the installed
// version (which would look like the panel is behind a release it has already passed).
config()->set('clusev.version', '0.9.10');
Cache::put('clusev:latest-release:stable', '0.9.9', now()->addMinutes(30));
Livewire::test(Index::class)->assertViewHas('latestTag', null);
}
public function test_build_info_prefers_the_baked_config_over_a_git_read(): void
{
// Production bakes sha/branch into config via .env (no .git in the image).