From 71b3451b13d6d132713c08ce2a8809b7cec2ed8d Mon Sep 17 00:00:00 2001 From: larssand Date: Thu, 10 Sep 2026 14:51:46 +0200 Subject: [PATCH] Show update-available link in the sidebar, not only in Settings The update card lived inside Settings, easy to miss on an app people mostly leave on the Dashboard or Timing view. A small link now sits next to the version number in the sidebar footer, on every view, and appears automatically from the same startup update check Settings already used - no new endpoint. Clicking it jumps straight into Settings with the update ready to run. Verified with a real headless-browser run against a throwaway git clone: the link appears on its own without ever opening Settings, names the real new version, survives navigating to another view, and clicking it lands on Settings with the update already available there. Co-Authored-By: Claude Sonnet 5 --- CHANGELOG.md | 6 ++++++ ROADMAP.md | 3 +++ index.html | 1 + package-lock.json | 4 ++-- package.json | 2 +- src/app.js | 22 ++++++++++++++++++++++ src/styles.css | 18 ++++++++++++++++++ 7 files changed, 53 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9cfbb1a..d9b5305 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,12 @@ The application version is maintained in `package.json` and displayed in the adm No changes recorded yet. +## 1.10.1 - 2026-09-10 + +### Added + +- A new version is now flagged right in the sidebar, next to the current version number - not only inside Settings. Clicking it jumps straight into Settings with the update ready to run. + ## 1.10.0 - 2026-09-10 ### Added diff --git a/ROADMAP.md b/ROADMAP.md index 62e5949..b62d311 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -196,6 +196,9 @@ Implemented 2026-09-09, requested by the user: an easy way to see a new version - [x] Show whether a newer version is available, with the actual version number. `GET /api/update-status` shells out to the git binary already required for this deployment model (`git fetch`, `git rev-parse`, `git show origin/:package.json`) rather than any git-hosting-specific API, so it works the same against GitHub, GitLab or a self-hosted remote. Result is cached for 10 minutes (`force=1` bypasses it) so opening Settings repeatedly doesn't hammer the remote. Settings now shows the installed version, and - only when an update is genuinely available - the new version number read from the remote's `package.json`, not just a commit hash. - [x] One-click update, no git knowledge required. An "Uppdatera nu"/"Update now" button `POST`s to `/api/update/run`, which spawns the existing `scripts/serverctl.js` with a new `update` command: stop -> `git pull --ff-only` (refuses and restarts on the code already installed rather than merging/rewriting history if the local branch has diverged - a trackside laptop is not a place to leave a merge conflict for someone to resolve) -> `npm install` (warns and continues on failure rather than leaving the server down) -> start, every step logged to `logs/update.log`. The confirmation dialog before starting adds an explicit extra warning if a race/session is currently active, since the update stops the server. - [x] Auto-reload once the server is back. Deliberately built no new client-side polling for this: the app already re-polls `/api/app-version` every 5 seconds and reloads the page once it detects the running server's process identity changed (`revision:updatedAt`) - exactly what a restart-with-new-code produces - so the existing mechanism picks this up for free. +- [x] Surface it outside Settings too. Added 2026-09-10 after the user pointed out the update card was easy to miss buried in Settings: the sidebar, right next to the existing version label, now shows a small "Ny version vX.Y.Z - uppdatera" link whenever an update is available (reusing the same `updateStatus` already fetched at startup, no new endpoint) - visible on every view, not only Settings - and clicking it jumps straight into Settings with the update ready to run. + +Verified 2026-09-10 (sidebar link): a full headless-browser run against a throwaway git clone confirmed the sidebar link appears on its own from the app's automatic startup check, without ever visiting Settings first, that it correctly names the real new version, that clicking it lands on the actual Settings page with the update ready to run there, and that it stays visible after navigating back to another view (not just a one-off render inside Settings). Verified 2026-09-09: a new integration test (`tests/server_persistence.integration.test.js`) builds a throwaway local bare git repo plus two clones (standing in for "this installation" and "somewhere a newer commit was pushed"), confirms `available: false` initially, pushes a version bump from the second clone, confirms `available: true` with the correct `remoteVersion` read from its `package.json`, and confirms the 10-minute cache is respected until `force=1`. A full headless-browser run went further than any test this session before it: cloned the real project into a throwaway git working copy (own bare "remote", own temporary SQLite data dir - the real installation and its git history were never touched), started the real server from that clone, pushed a version bump to the throwaway remote from a second clone, then in a real browser opened Settings, saw "Version 9.9.9 finns tillgänglig", clicked "Uppdatera nu", accepted the confirmation dialog, and confirmed end-to-end: the server actually stopped, `git pull --ff-only` fast-forwarded, `npm install` ran, the server came back up on the same port serving the new code, and the open browser tab auto-reloaded on its own and displayed the new version - with zero uncaught JS exceptions throughout. diff --git a/index.html b/index.html index 7a41ab1..d0b36c2 100644 --- a/index.html +++ b/index.html @@ -32,6 +32,7 @@

Decoder Offline

+ diff --git a/package-lock.json b/package-lock.json index 36a873b..87379fb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "rc-racecore", - "version": "1.10.0", + "version": "1.10.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "rc-racecore", - "version": "1.10.0", + "version": "1.10.1", "hasInstallScript": true, "dependencies": { "better-sqlite3": "^12.11.1", diff --git a/package.json b/package.json index 1276bea..fd96083 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "rc-racecore", - "version": "1.10.0", + "version": "1.10.1", "private": true, "description": "RC RaceCore live timing and race control with AMMC ingest and SQLite persistence", "engines": { diff --git a/src/app.js b/src/app.js index 30de99a..cc3e191 100644 --- a/src/app.js +++ b/src/app.js @@ -1489,6 +1489,7 @@ const TRANSLATIONS = { "settings.update_checking": "Kontrollerar om det finns en ny version...", "settings.update_up_to_date": "Du har den senaste versionen.", "settings.update_available": "Version {version} finns tillgänglig.", + "settings.update_available_sidebar": "Ny version v{version} - uppdatera", "settings.update_error": "Kunde inte kontrollera version: {msg}", "settings.update_check_now": "Kontrollera nu", "settings.update_run_now": "Uppdatera nu", @@ -2992,6 +2993,7 @@ const TRANSLATIONS = { "settings.update_checking": "Checking for a new version...", "settings.update_up_to_date": "You have the latest version.", "settings.update_available": "Version {version} is available.", + "settings.update_available_sidebar": "New version v{version} - update", "settings.update_error": "Could not check for updates: {msg}", "settings.update_check_now": "Check now", "settings.update_run_now": "Update now", @@ -3691,6 +3693,7 @@ async function init() { updateSyncStatus(); }); startAppVersionPolling(); + document.getElementById("appUpdateAvailable")?.addEventListener("click", navigateToSettings); setInterval(() => refreshUpdateStatus({ render: currentView === "settings" }), 20 * 60 * 1000); Promise.allSettled([hydrateFromBackend(), loadAmmcConfigFromBackend(), refreshServerBackups({ render: false }), refreshUpdateStatus({ render: false })]).finally(() => { @@ -4597,10 +4600,29 @@ async function refreshUpdateStatus({ render = true, force = false } = {}) { } updateStatusLoaded = true; updateCheckPending = false; + refreshSidebarUpdateLink(); if (render && currentView === "settings") renderView(); return updateStatus; } +function refreshSidebarUpdateLink() { + const link = document.getElementById("appUpdateAvailable"); + if (!link) return; + if (updateStatus?.available && updateStatus.remoteVersion) { + link.textContent = t("settings.update_available_sidebar", { version: updateStatus.remoteVersion }); + link.hidden = false; + } else { + link.hidden = true; + } +} + +function navigateToSettings() { + currentView = "settings"; + updateViewUrl(currentView); + renderNav(); + renderView(); +} + async function runUpdateNow() { const response = await fetch(`${getBackendUrl()}/api/update/run`, { method: "POST" }); const payload = await response.json().catch(() => ({})); diff --git a/src/styles.css b/src/styles.css index 5bb01db..4570b60 100644 --- a/src/styles.css +++ b/src/styles.css @@ -212,6 +212,24 @@ body { .app-version[hidden] { display: none; } +.app-update-link { + display: block; + margin-top: 4px; + padding: 0; + border: none; + background: none; + font: inherit; + font-size: 0.72rem; + font-weight: 600; + color: var(--accent); + cursor: pointer; + text-align: left; + text-decoration: underline; +} + +.app-update-link:hover { color: var(--accent-strong, var(--accent)); } +.app-update-link[hidden] { display: none; } + .sidebar-footer { position: static; margin-top: 14px;