From 0738ef50b0bf57c62dedee7229cf03a2520098a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sun, 17 May 2026 12:39:09 +0200 Subject: [PATCH] ux(about): drop manual update-check panel for an inline badge MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The dedicated "Updates" card carried a "Check for updates" button, a last-checked timestamp, and three result paragraphs covering up-to-date / newer-available / unknown. All of it surfaced an answer to one question — "is there a newer release?" — and the answer was overwhelmingly "no, you're current". The card was three full rem of chrome for that signal. The 24 h auto-check still runs on mount. When it returns `newer_available`, an ArrowUpCircle badge appears next to the version line; hover/tap exposes the latest tag via the title attribute. The link points at the release HTML URL when present. No bouncing, no badge counter, no manual recheck button. The settings/about route now reads as a tight identity card + external links list, matching how often the surface actually gets used. --- .../about-section-update-badge.test.tsx | 102 ++++++++++++ .../settings/__tests__/sections.test.tsx | 24 +-- src/components/settings/about-section.tsx | 155 +++++++----------- 3 files changed, 170 insertions(+), 111 deletions(-) create mode 100644 src/components/settings/__tests__/about-section-update-badge.test.tsx diff --git a/src/components/settings/__tests__/about-section-update-badge.test.tsx b/src/components/settings/__tests__/about-section-update-badge.test.tsx new file mode 100644 index 000000000..f40e79798 --- /dev/null +++ b/src/components/settings/__tests__/about-section-update-badge.test.tsx @@ -0,0 +1,102 @@ +import { describe, expect, it, vi, beforeEach, afterEach } from "vitest"; +import { renderToStaticMarkup } from "react-dom/server"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; + +import { I18nProvider } from "@/lib/i18n/context"; +import { AboutSection } from "../about-section"; + +/** + * v1.4.36 W4f — AboutSection update-badge contract. + * + * The dedicated "Updates" card with the manual "Check for updates" + * button is gone. The 24 h auto-check still runs on mount and + * surfaces a subtle ArrowUpCircle badge next to the version line + * ONLY when the check returns `{ status: "newer_available", ... }`. + * + * These SSR smoke tests pin the badge presence by seeding the + * react-query cache with a fake version payload (so the version + * line paints synchronously) and exercising the path that decides + * whether the badge mounts. The badge itself owns its own + * `useState`, so its presence is gated on the runCheck branch — + * SSR renders the initial `null` state. The actual badge render + * is therefore covered by the runtime store-set + re-render in + * future Playwright surfaces; here we pin the *baseline* contract + * that the version line + Sources/docs panel still render and + * the legacy manual-check chrome is gone. + */ + +function makeClient() { + const client = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + staleTime: Number.POSITIVE_INFINITY, + refetchOnMount: false, + }, + }, + }); + // Seed the version query so the section paints inline rather than + // rendering its loader skeleton. + client.setQueryData(["api", "version"], { + version: "1.4.36", + buildSha: "abc1234", + builtAt: "2026-05-17T08:00:00.000Z", + license: "AGPL-3.0", + repository: "https://github.com/MBombeck/HealthLog", + changelog: "https://github.com/MBombeck/HealthLog/blob/main/CHANGELOG.md", + docs: "https://github.com/MBombeck/HealthLog/tree/main/docs", + }); + return client; +} + +function render(node: React.ReactNode, locale: "en" | "de" = "en") { + const client = makeClient(); + return renderToStaticMarkup( + + {node} + , + ); +} + +beforeEach(() => { + vi.clearAllMocks(); + // Fail the auto-check fetch so the badge stays in its initial + // (no-result) state. This lets the SSR smoke tests assert the + // "no badge" baseline; the badge-visible branch is unit-tested + // separately by toggling `updateResult` via a forced re-render + // in the future Playwright + RTL suite. + vi.stubGlobal( + "fetch", + vi.fn(async () => { + throw new Error("network disabled in tests"); + }), + ); +}); + +afterEach(() => { + vi.unstubAllGlobals(); +}); + +describe(" — update badge", () => { + it("renders the version line without a badge by default", () => { + const html = render(); + expect(html).toContain("v1.4.36"); + // No badge — ArrowUpCircle's lucide class name is the unique signal. + expect(html).not.toContain("lucide-arrow-up-circle"); + }); + + it("drops the manual 'Check for updates' button surface entirely", () => { + const html = render(); + // Pre-v1.4.36 the section carried a "Check for updates" CTA + // alongside a last-checked timestamp. Both are now gone — only + // the 24 h auto-check stays, surfacing through the badge above. + expect(html).not.toContain(">Check for updates<"); + expect(html).not.toContain(">Check now<"); + expect(html).not.toContain("last-checked-iso"); + }); + + it("preserves the Sources & docs panel", () => { + const html = render(); + expect(html).toContain("Sources & docs"); + }); +}); diff --git a/src/components/settings/__tests__/sections.test.tsx b/src/components/settings/__tests__/sections.test.tsx index 170f0c6f1..9c2bfc534 100644 --- a/src/components/settings/__tests__/sections.test.tsx +++ b/src/components/settings/__tests__/sections.test.tsx @@ -132,16 +132,17 @@ describe("settings sections — SSR smoke", () => { expect(html).not.toContain("onboarding.tour."); }); - it(" renders version + license + check-for-updates button", () => { + it(" renders version + license + sources/docs cards (no Updates panel)", () => { const html = render(); expect(html).toContain("About"); - // The new about.* keys resolve. v1.4.3 split the page into three - // titled cards (HealthLog identity, Sources & docs, Updates) and - // shortened the button label from "Check for updates" to "Check now" - // because the surrounding heading already names the section. - expect(html).toContain("Updates"); + // v1.4.36 W4f — the dedicated "Updates" card with its manual + // "Check now" button is gone. The 24 h auto-check still runs and + // surfaces a subtle ArrowUpCircle badge next to the version line + // when a newer release is available. The Sources & docs card + // stays put. expect(html).toContain("Sources & docs"); - expect(html).toContain("Check now"); + expect(html).not.toContain(">Check now<"); + expect(html).not.toContain(">Check for updates<"); expect(html).not.toContain("settings.about."); }); @@ -198,11 +199,12 @@ describe("settings sections — SSR smoke", () => { it(" resolves about.* keys in German", () => { const html = render(, "de"); - // v1.4.3: button label shortened to "Jetzt prüfen" because the - // section heading "Updates" already provides context. - expect(html).toContain("Updates"); - expect(html).toContain("Jetzt prüfen"); + // v1.4.36 W4f — "Updates" panel + "Jetzt prüfen" button removed; + // the surviving German copy is the section title + the Sources + // & docs heading + the version line. + expect(html).toContain("Über"); expect(html).toContain("Quellen & Dokumentation"); + expect(html).not.toContain(">Jetzt prüfen<"); }); it(" renders heading and embeds both editors (v1.4.34 IW-D merge)", () => { diff --git a/src/components/settings/about-section.tsx b/src/components/settings/about-section.tsx index 7831f9261..2f9a8a866 100644 --- a/src/components/settings/about-section.tsx +++ b/src/components/settings/about-section.tsx @@ -3,15 +3,13 @@ import { useEffect, useState } from "react"; import { useQuery } from "@tanstack/react-query"; import { + ArrowUpCircle, BookOpen, - CheckCircle2, ExternalLink, GitBranch, Info, Loader2, - RefreshCw, Sparkles, - XCircle, } from "lucide-react"; import { Button } from "@/components/ui/button"; @@ -85,16 +83,15 @@ export function AboutSection() { staleTime: Infinity, }); - const [checking, setChecking] = useState(false); + // v1.4.36 W4f — the explicit "Check for updates" button is gone; + // only the 24 h auto-check stays. When the auto-check reports + // `newer_available`, a subtle upward-arrow badge appears next to + // the version line with the latest tag in its tooltip. const [updateResult, setUpdateResult] = useState( null, ); - const [lastCheckedISO, setLastCheckedISO] = useState(() => - readLastCheckedISO(), - ); async function runCheck(): Promise { - setChecking(true); try { const res = await fetch("/api/version/check-updates"); if (!res.ok) { @@ -109,9 +106,7 @@ export function AboutSection() { const result = json.data as CheckUpdatesResult; setUpdateResult(result); if (result.status !== "unknown") { - const iso = new Date().toISOString(); - writeLastCheckedISO(iso); - setLastCheckedISO(iso); + writeLastCheckedISO(new Date().toISOString()); } } catch { setUpdateResult({ @@ -119,14 +114,12 @@ export function AboutSection() { current: version?.version ?? "", reason: "network_error", }); - } finally { - setChecking(false); } } // Auto-check on mount when the last successful check is older than the - // refresh interval (or has never run). Keeps the UI populated without - // requiring a click — but won't spam GitHub on every navigation. + // refresh interval (or has never run). Keeps the badge fresh without a + // click — but won't spam GitHub on every navigation. // The setState inside runCheck() is intentional: the check is the // whole point of the effect, and the staleness gate prevents a // cascading-render loop because the timestamp resets after the run. @@ -184,6 +177,43 @@ export function AboutSection() { {t("settings.about.version")}
v{version.version}
+ {/* v1.4.36 W4f — subtle upward-arrow badge that + appears only when the 24 h auto-check reports a + newer release. Hover/tap reveals the latest tag via + the title attribute (read by screen readers + as a + native tooltip on desktop, and announced inline as + the link copy on mobile). No bouncing, no badge + counter, no manual recheck button — the badge is + the entire surface. */} + {updateResult?.status === "newer_available" && + (updateResult.html_url ? ( + + + + ) : ( + + + + ))}
@@ -262,91 +292,16 @@ export function AboutSection() {
)} - {/* Updates — proper heading + last-check timestamp + manual button - that proxies through `/api/version/check-updates`. The v1.4.2 - version called `api.github.com` directly from the browser, which - the production CSP blocked silently — that's why "nothing - happened" when the user clicked the button. */} - {version && ( -
-
- -

- {t("settings.about.updatesHeading")} -

-
- -
- - -

- {lastCheckedISO - ? t("settings.about.lastChecked", { - time: fmt.dateTime(lastCheckedISO), - }) - : t("settings.about.lastCheckedNever")} -

-
- - {updateResult?.status === "up_to_date" && ( -

- - {t("settings.about.upToDate")} -

- )} - - {updateResult?.status === "newer_available" && ( -

- - {updateResult.html_url ? ( - - {t("settings.about.newerAvailable", { - tag: updateResult.latest_tag, - })} - - ) : ( - - {t("settings.about.newerAvailable", { - tag: updateResult.latest_tag, - })} - - )} -

- )} - - {updateResult?.status === "unknown" && ( -

- - {t("settings.about.checkFailed")} -

- )} -
- )} + {/* v1.4.36 W4f — the dedicated "Updates" card with the + manual "Check for updates" button is gone. The 24 h auto- + check still runs on mount and writes its result into the + local `updateResult` state; the consequence renders as the + subtle upward-arrow badge next to the version line above + rather than as a separate panel. The card-level last-check + timestamp, success / failure states, and the explicit + button were collectively never the point of the surface — + the only signal users ever cared about was "is a newer + release out?", and the badge answers that with no clicks. */} ); }