From dba8934aa2fc8700a33b7adce6a6737f958ae22f Mon Sep 17 00:00:00 2001 From: Palash Debnath Date: Wed, 24 Jun 2026 12:48:51 +0530 Subject: [PATCH] =?UTF-8?q?feat(footer):=20clickable=20version=20badge=20?= =?UTF-8?q?=E2=86=92=20Updates,=20with=20an=20update-available=20indicator?= =?UTF-8?q?=20(#671)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The bottom status bar showed no version and had no quick path to updates. Add a small `v` badge next to the network/share icon; clicking it opens Settings → Updates. When an update is available (or downloaded and ready), the badge highlights and shows a pulsing notification dot, and its tooltip names the new version — so users can see at a glance that an update is waiting and one click takes them to install it. Mechanism: a one-shot `pendingSettingsTab` hand-off in the UI store (mirrors the existing `pendingProfileId` pattern) + an `openSettingsTab(tab)` convenience that sets the tab and navigates in one call. Settings consumes it as its initial tab and clears it (an effect covers the already-open case). The indicator reads the existing `updateStatus`/`updateVersion` from the updater slice — no new update plumbing. Version from the shared APP_VERSION constant; new strings via i18n. Test: openSettingsTab.test.js — the convenience sets mode=settings + the pending tab, and the value can be cleared after consumption. Co-authored-by: mergetest Co-authored-by: Claude Opus 4.8 (1M context) --- frontend/src/components/LogsFooter.css | 44 +++++++++++++++++++++++ frontend/src/components/LogsFooter.jsx | 19 ++++++++++ frontend/src/i18n/locales/en.json | 4 +++ frontend/src/pages/Settings.jsx | 16 ++++++++- frontend/src/store/uiSlice.ts | 13 +++++++ frontend/src/test/openSettingsTab.test.js | 24 +++++++++++++ 6 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 frontend/src/test/openSettingsTab.test.js diff --git a/frontend/src/components/LogsFooter.css b/frontend/src/components/LogsFooter.css index 7279088a..c9b10263 100644 --- a/frontend/src/components/LogsFooter.css +++ b/frontend/src/components/LogsFooter.css @@ -219,6 +219,50 @@ gap: 2px; } +/* Clickable app-version badge → Settings → Updates (sits by the network icon) */ +.logs-footer__version { + background: none; + border: none; + cursor: pointer; + flex-shrink: 0; + padding: 0 6px; + height: var(--chrome-icon-btn); + border-radius: 4px; + font-size: 11px; + font-variant-numeric: tabular-nums; + letter-spacing: 0.02em; + color: var(--chrome-fg, #ebdbb2); + opacity: 0.55; + transition: opacity 0.15s, color 0.15s; +} +.logs-footer__version:hover { + opacity: 1; + color: var(--chrome-accent, #b8bb26); + text-decoration: underline; +} +/* Update-available: highlight the badge + a pulsing notification dot. */ +.logs-footer__version--update { + position: relative; + opacity: 1; + color: var(--chrome-accent, #b8bb26); + font-weight: 600; +} +.logs-footer__version-dot { + display: inline-block; + width: 6px; + height: 6px; + margin-left: 5px; + border-radius: 50%; + background: var(--chrome-accent, #b8bb26); + box-shadow: 0 0 0 0 color-mix(in srgb, var(--chrome-accent, #b8bb26) 60%, transparent); + animation: version-dot-pulse 2s ease-in-out infinite; + vertical-align: middle; +} +@keyframes version-dot-pulse { + 0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--chrome-accent, #b8bb26) 55%, transparent); } + 50% { box-shadow: 0 0 0 4px color-mix(in srgb, var(--chrome-accent, #b8bb26) 0%, transparent); } +} + /* Discord button */ .logs-footer__discord { background: none; diff --git a/frontend/src/components/LogsFooter.jsx b/frontend/src/components/LogsFooter.jsx index 9728e150..33981476 100644 --- a/frontend/src/components/LogsFooter.jsx +++ b/frontend/src/components/LogsFooter.jsx @@ -12,6 +12,7 @@ import { getFrontendLogs, clearFrontendLogs } from '../utils/consoleBuffer'; import { useTranslation } from 'react-i18next'; import { useAppStore } from '../store'; import NetworkToggle from './NetworkToggle'; +import { APP_VERSION } from '../utils/appVersion'; import './LogsFooter.css'; /** @@ -142,6 +143,10 @@ export default function LogsFooter() { } const [collapsed, setCollapsed] = useState(true); const { t } = useTranslation(); + // Update availability drives the footer version badge's notification dot. + const updateStatus = useAppStore((s) => s.updateStatus); + const updateVersion = useAppStore((s) => s.updateVersion); + const updateReady = updateStatus === 'available' || updateStatus === 'ready'; const [height, setHeight] = useState(() => { const v = Number(localStorage.getItem(LS_HEIGHT)); return Number.isFinite(v) && v >= MIN_H && v <= MAX_H ? v : 300; @@ -412,6 +417,20 @@ export default function LogsFooter() { )} +