+ {/* Last-test line in the medication-card grammar: label left, the
+ relative day (today / yesterday / date) right-aligned. The severity
+ band is intentionally NOT shown here — the trend below carries it. */}
+
{last ? (
-
+ <>
{t("mentalHealth.lastResult")}
-
- {t(`mentalHealth.band.${instrument}.${last.severityBand}`)}
-
- {formatDate(last.takenAt)}
-
+
+ {relativeCalendarDate(last.takenAt, t, formatDate)}
+
+ >
) : (
{t("mentalHealth.noResultYet")}
)}
diff --git a/src/components/mental-health/mental-wellbeing.tsx b/src/components/mental-health/mental-wellbeing.tsx
index 14978714b..5b0483624 100644
--- a/src/components/mental-health/mental-wellbeing.tsx
+++ b/src/components/mental-health/mental-wellbeing.tsx
@@ -26,7 +26,6 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { useTranslations } from "@/lib/i18n/context";
import { apiGet, apiPost } from "@/lib/api/api-fetch";
import { queryKeys } from "@/lib/query-keys";
-import { InfoHint } from "@/components/ui/info-hint";
import { AssessmentHistory } from "./assessment-history";
import { AssessmentResult } from "./assessment-result";
@@ -99,26 +98,16 @@ export function MentalWellbeing() {
}
return (
-
+
{phase === "choose" && (
<>
-
-
- {t("mentalHealth.pageTitle")}
-
-
-
+ {/* Title kept for screen readers / document outline only — the
+ visible heading + its tooltip were removed (§ Marc v1.25.5). */}
+ {t("mentalHealth.pageTitle")}
{t("mentalHealth.pageDescription")}
- {/* The ONLY place the disclaimer renders as body text (§2). */}
-
- {t("mentalHealth.landing.disclaimer")}
-
diff --git a/src/lib/__tests__/geo.test.ts b/src/lib/__tests__/geo.test.ts
index 63af5bb27..172b2a799 100644
--- a/src/lib/__tests__/geo.test.ts
+++ b/src/lib/__tests__/geo.test.ts
@@ -84,14 +84,15 @@ describe("lookupIpLocation IP-geolocation HTTPS guard", () => {
);
}
- it("uses the ip-api.com HTTPS endpoint by default", async () => {
- // v1.18.11 (W3): default provider is ip-api.com (ipwho.is 403s on
- // datacentre egress). The wire URL must be the ip-api.com JSON path
- // and the parser must accept its `status`/`countryCode` shape.
+ it("uses the ipwho.is HTTPS endpoint by default", async () => {
+ // v1.25.5: ipwho.is is the default provider again. The wire URL must be
+ // the ipwho.is path and the parser must accept its `success`/`country_code`
+ // shape. (Operators whose egress ipwho.is rejects override IP_GEO_LOOKUP_URL
+ // to ip-api.com — the parser still accepts that shape; covered below.)
const fetchSpy = vi
.fn()
.mockResolvedValue(
- jsonOk({ status: "success", city: "Berlin", countryCode: "DE" }),
+ jsonOk({ success: true, city: "Berlin", country_code: "DE" }),
);
vi.stubGlobal("fetch", fetchSpy);
const { lookupIpLocation } = await import("../geo");
@@ -99,7 +100,7 @@ describe("lookupIpLocation IP-geolocation HTTPS guard", () => {
expect(await lookupIpLocation("8.8.8.8")).toBe("Berlin, DE");
const url = fetchSpy.mock.calls[0]?.[0] as string;
expect(url.startsWith("https://")).toBe(true);
- expect(url).toBe("https://ip-api.com/json/8.8.8.8");
+ expect(url).toBe("https://ipwho.is/8.8.8.8");
});
it("refuses to call non-HTTPS configured providers", async () => {
diff --git a/src/lib/geo.ts b/src/lib/geo.ts
index c3bfc95a9..1f15b8320 100644
--- a/src/lib/geo.ts
+++ b/src/lib/geo.ts
@@ -90,16 +90,16 @@ type GeoResponse = IpwhoIsResponse & IpApiProResponse;
const PRIVATE_IP =
/^(127\.|10\.|172\.(1[6-9]|2\d|3[01])\.|192\.168\.|::1|fc|fd|fe80|localhost|unknown)/;
-// v1.18.11 (W3) — the default online provider is ip-api.com, not ipwho.is.
-// ipwho.is rejects datacentre / free-plan server egress with a 403
-// ("CORS is not supported on the Free plan"), which silently produced the
-// "—" location on prod (every server-side lookup failed). The base is
-// `https://ip-api.com/json`; `buildLookupUrl` appends `/` so the wire
-// URL is `https://ip-api.com/json/`. The parser already accepts the
-// ip-api.com shape (`status:"success"` + `countryCode`). Operators who need
-// a different / keyed provider override via `IP_GEO_LOOKUP_URL`; the HTTPS
-// egress guard in `buildLookupUrl` still applies to any override.
-const DEFAULT_GEO_URL = "https://ip-api.com/json";
+// v1.25.5 — ipwho.is is the default online provider again (free, no key, no
+// per-minute cap on the standard endpoint). The base is `https://ipwho.is`;
+// `buildLookupUrl` appends `/` so the wire URL is `https://ipwho.is/`.
+// The parser accepts BOTH the ipwho.is shape (`success` + `country_code`) and
+// the ip-api.com shape (`status` + `countryCode`), so operators whose egress
+// ipwho.is rejects can point `IP_GEO_LOOKUP_URL` at `https://ip-api.com/json`
+// (or any keyed provider matching one of those shapes) with no code change.
+// The offline GeoLite2 MMDB tier stays OPTIONAL — consulted only when the
+// online lookup misses, and skipped entirely when the databases are absent.
+const DEFAULT_GEO_URL = "https://ipwho.is";
function geoLiteDir(): string {
return process.env.GEOLITE2_DIR ?? "/opt/geolite2";