refactor(nav): surface Recovery as an Insights tab instead of a left-nav item

Recovery is a composite wearable surface (WHOOP / Polar / Oura recovery,
strain, ANS charge, cardio load), not a top-level tracking destination.
It moves off the left nav and onto the Insights tab strip as a pill at
/insights/recovery. The pill is always present (like Overview) since the
page self-gates every block and falls back to a calm empty note rather
than carrying its own availability signal. Adds the insights.navRecovery
label across all six locales.
This commit is contained in:
Marc-André Bombeck committed 2026-06-15 21:03:48 +02:00
1 parent aa27708ebc
commit 640793a3e8
10 files changed
+48 -13

No files matched your search

+1
View File
@@ -2784,6 +2784,7 @@
}
},
"navWorkouts": "Workouts",
"navRecovery": "Erholung",
"workouts": {
"title": "Workouts",
"description": "Letzte Läufe, Touren, Spaziergänge und Krafteinheiten — quellenübergreifend entdoppelt zwischen Apple Watch und Withings.",
+1
View File
@@ -2784,6 +2784,7 @@
}
},
"navWorkouts": "Workouts",
"navRecovery": "Recovery",
"workouts": {
"title": "Workouts",
"description": "Recent runs, rides, walks and strength sessions, deduped across Apple Watch and Withings sources.",
+1
View File
@@ -2784,6 +2784,7 @@
}
},
"navWorkouts": "Entrenamientos",
"navRecovery": "Recuperación",
"workouts": {
"title": "Entrenamientos",
"description": "Carreras, salidas en bici, paseos y sesiones de fuerza recientes, deduplicados entre Apple Watch y Withings.",
+1
View File
@@ -2784,6 +2784,7 @@
}
},
"navWorkouts": "Entraînements",
"navRecovery": "Récupération",
"workouts": {
"title": "Entraînements",
"description": "Courses, sorties à vélo, marches et séances de musculation récentes, dédupliquées entre Apple Watch et Withings.",
+1
View File
@@ -2784,6 +2784,7 @@
}
},
"navWorkouts": "Allenamenti",
"navRecovery": "Recupero",
"workouts": {
"title": "Allenamenti",
"description": "Corse, uscite in bici, camminate e sessioni di forza recenti, deduplicati tra Apple Watch e Withings.",
+1
View File
@@ -2784,6 +2784,7 @@
}
},
"navWorkouts": "Treningi",
"navRecovery": "Regeneracja",
"workouts": {
"title": "Treningi",
"description": "Ostatnie biegi, jazdy, marsze i treningi siłowe — zdeduplikowane między Apple Watch a Withings.",
@@ -94,6 +94,25 @@ describe("<InsightsTabStrip> — availability gating (v1.4.27 F19)", () => {
expect(html).toContain(">Overview<");
});
it("always renders the Recovery pill (composite wearable surface, self-gated page)", () => {
// v1.18.0 — Recovery left the left-nav for an Insights pill. It is a
// composite WHOOP / Polar / Oura surface, not a single MeasurementType,
// so it has no `summaries[METRIC].count` to gate on; the page itself
// data-gates each block and falls back to a calm empty note, so the
// pill is always present (like Overview) regardless of availability.
const noAvailability = render(<InsightsTabStrip />);
expect(noAvailability).toContain(">Recovery<");
expect(noAvailability).toContain('href="/insights/recovery"');
const emptyAvailability: InsightInputs = {
summaries: {},
hasMood: false,
hasMedication: false,
};
const html = render(<InsightsTabStrip availability={emptyAvailability} />);
expect(html).toContain(">Recovery<");
});
it("lights up Mood when hasMood flips to true", () => {
const availability: InsightInputs = {
summaries: {},
@@ -420,6 +420,18 @@ function buildTabs(
href: INSIGHTS_OVERVIEW_PATH,
labelKey: "insights.navOverview",
},
// v1.18.0 — Recovery is an Insights pill, no longer a left-nav home.
// It is a composite wearable surface (WHOOP / Polar / Oura recovery,
// strain, ANS charge, cardio load), not a single MeasurementType, so
// it has no `summaries[METRIC].count` to gate on. The page itself
// data-gates every block and falls back to a calm empty note, so the
// pill is always present like Overview rather than carrying its own
// availability signal.
{
kind: "link",
href: `${INSIGHTS_OVERVIEW_PATH}/recovery`,
labelKey: "insights.navRecovery",
},
];
// v1.4.34 IW-D — track which groups have already been emitted so the
@@ -53,11 +53,18 @@ describe("nav-model destination list", () => {
expect(hrefs).toContain("/vorsorge");
const vorsorge = NAV_DESTINATIONS.find((d) => d.href === "/vorsorge");
expect(vorsorge?.tKey).toBe("nav.vorsorge");
// Peer to Labs / Recovery: sits in the clinical spine, before Insights.
// Peer to Labs: sits in the clinical spine, before Insights.
expect(hrefs.indexOf("/labs")).toBeLessThan(hrefs.indexOf("/vorsorge"));
expect(hrefs.indexOf("/vorsorge")).toBeLessThan(hrefs.indexOf("/insights"));
});
it("does not list Recovery as a left-nav destination (it is an Insights pill)", () => {
// v1.18.0 — Recovery moved off the left nav and surfaces as an
// Insights tab-strip pill at `/insights/recovery` instead.
const hrefs = NAV_DESTINATIONS.map((d) => d.href);
expect(hrefs).not.toContain("/insights/recovery");
});
});
describe("nav-model utility tail (N-1 — one shared list)", () => {
+3 -12
View File
@@ -5,7 +5,6 @@ import {
Droplets,
Dumbbell,
FlaskConical,
HeartPulse,
Home,
Lightbulb,
MessagesSquare,
@@ -99,17 +98,9 @@ export const NAV_DESTINATIONS: ReadonlyArray<NavDestination> = [
icon: Dumbbell,
tourId: "nav-workouts",
},
// v1.17.1 — the Recovery surface gets a nav home alongside Workouts. It
// hosts the WHOOP / Polar device-native recovery + strain scores that were
// stored end-to-end but had no page. Like Workouts it is shown to every
// account (the page itself data-gates each block, so a non-wearable user
// lands on a calm empty note rather than a broken surface).
{
href: "/insights/recovery",
tKey: "nav.recovery",
icon: HeartPulse,
tourId: "nav-recovery",
},
// v1.18.0 — Recovery left the left-nav: it surfaces as an Insights pill
// (`/insights/recovery`) rather than a top-level destination, so it no
// longer appears in `NAV_DESTINATIONS`.
{
href: "/insights",
tKey: "nav.insights",