From 6e61713fc674f71be6ca4fd2c3e6c16c1fad3fed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Mon, 1 Jun 2026 22:49:13 +0200 Subject: [PATCH] fix(insights): move coach-ask to the header and de-emphasise the assessment border MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Coach launch sat at the foot of every category page; move it into the sub-page header as an icon-only button at heading height, top-right, so "ask the coach" reads as a header action and the page no longer ends on a stray button. The shared shell mounts it (gated on the Coach flag plus the per-user opt-out), so every category page picks it up and the foot instance is dropped. Also drop the purple left accent on the assessment ("Einschätzung") card — the coloured rule made it read as restless against the calmer surrounding cards. --- e2e/v1427-coach-launch.spec.ts | 41 ++++++++----------- src/app/insights/blood-pressure/page.tsx | 4 +- src/app/insights/bmi/page.tsx | 3 +- src/app/insights/medications/page.tsx | 3 +- src/app/insights/mood/page.tsx | 4 +- src/app/insights/pulse/page.tsx | 4 +- src/app/insights/sleep/page.tsx | 4 +- src/app/insights/weight/page.tsx | 4 +- src/app/insights/workouts/page.tsx | 7 +--- .../insights/coach-launch-button.tsx | 32 +++++++++++++++ .../insights/healthkit-metric-page.tsx | 8 ++-- .../insights/insight-status-card.tsx | 7 +++- 12 files changed, 68 insertions(+), 53 deletions(-) diff --git a/e2e/v1427-coach-launch.spec.ts b/e2e/v1427-coach-launch.spec.ts index 1baae5fb9..0cacdaaa8 100644 --- a/e2e/v1427-coach-launch.spec.ts +++ b/e2e/v1427-coach-launch.spec.ts @@ -8,13 +8,13 @@ import { STORAGE_STATE_PATH } from "./setup/global-setup"; * * Two contracts under test: * - * 1. Every routed Insights sub-page mounts a ``. - * The component renders BOTH a sticky-bottom FAB - * (`data-slot="coach-launch-fab"`, visible on `=sm` it mounts as a @@ -121,28 +121,23 @@ test.describe("v1.4.27 — Coach reachability on insights sub-pages", () => { .locator('[data-slot="coach-launch-fab"]') .first(); await expect(fab).toBeVisible({ timeout: 10_000 }); - - // The inline branch is in the DOM but hidden via `hidden lg:inline-flex`. - const inline = page.locator('[data-slot="coach-launch-inline"]').first(); - await expect(inline).toBeHidden(); }); - test(`/insights/${slug} mounts the Coach launch button on Desktop Chrome (inline branch)`, async ({ + test(`/insights/${slug} mounts the header Coach icon on Desktop Chrome`, async ({ page, }, testInfo) => { // Desktop Chrome viewport is 1280 — `lg` (1024) is matched so the - // inline branch wins; the FAB is hidden by `lg:hidden`. + // layout FAB is hidden by `lg:hidden`; the header icon the shell + // mounts is the Coach entry on the page itself. test.skip( testInfo.project.name !== "chromium-desktop", - "desktop inline branch", + "desktop header-icon branch", ); await page.goto(`/insights/${slug}`, { waitUntil: "domcontentloaded" }); - const inline = page - .locator('[data-slot="coach-launch-inline"]') - .first(); - await expect(inline).toBeVisible({ timeout: 10_000 }); + const icon = page.locator('[data-slot="coach-launch-icon"]').first(); + await expect(icon).toBeVisible({ timeout: 10_000 }); const fab = page.locator('[data-slot="coach-launch-fab"]').first(); await expect(fab).toBeHidden(); @@ -168,7 +163,7 @@ test.describe("v1.4.27 — Coach reachability on insights sub-pages", () => { await expect(drawer).toHaveAttribute("data-variant", "bottom-sheet"); }); - test("clicking the inline action opens the Coach drawer as a side-sheet on Desktop Chrome", async ({ + test("clicking the header Coach icon opens the Coach drawer as a side-sheet on Desktop Chrome", async ({ page, }, testInfo) => { test.skip( @@ -178,11 +173,9 @@ test.describe("v1.4.27 — Coach reachability on insights sub-pages", () => { await page.goto("/insights/blutdruck", { waitUntil: "domcontentloaded" }); - const inline = page - .locator('[data-slot="coach-launch-inline"]') - .first(); - await expect(inline).toBeVisible({ timeout: 10_000 }); - await inline.click(); + const icon = page.locator('[data-slot="coach-launch-icon"]').first(); + await expect(icon).toBeVisible({ timeout: 10_000 }); + await icon.click(); const drawer = page.locator('[data-slot="coach-drawer"]'); await expect(drawer).toBeVisible({ timeout: 10_000 }); diff --git a/src/app/insights/blood-pressure/page.tsx b/src/app/insights/blood-pressure/page.tsx index 391a9f83d..cd3e71194 100644 --- a/src/app/insights/blood-pressure/page.tsx +++ b/src/app/insights/blood-pressure/page.tsx @@ -10,7 +10,6 @@ import { useTranslations } from "@/lib/i18n/context"; import { useInsightsLayoutPrefs } from "@/hooks/use-insights-layout-prefs"; import { Button } from "@/components/ui/button"; import { HealthChartDynamic } from "@/components/charts/health-chart-dynamic"; -import { CoachLaunchButton } from "@/components/insights/coach-launch-button"; import { InsightStatusCard } from "@/components/insights/insight-status-card"; import { MetricEmptyState } from "@/components/insights/metric-empty-state"; import { MetricTargetSummary } from "@/components/insights/metric-target-summary"; @@ -101,6 +100,7 @@ export default function InsightsBlutdruckPage() { title={t("insights.bloodPressureSectionTitle")} description={t("insights.subPage.blutdruckDescription")} explainerMetric="bloodPressure" + coachLaunch > - - ); } diff --git a/src/app/insights/bmi/page.tsx b/src/app/insights/bmi/page.tsx index 46f5abdf4..d539f6a21 100644 --- a/src/app/insights/bmi/page.tsx +++ b/src/app/insights/bmi/page.tsx @@ -110,6 +110,7 @@ export default function InsightsBmiPage() { title={t("insights.bmiSectionTitle")} description={t("insights.subPage.bmiDescription")} explainerMetric="bmi" + coachLaunch > - - ); } diff --git a/src/app/insights/medications/page.tsx b/src/app/insights/medications/page.tsx index 0341e5aa4..57cb7cadd 100644 --- a/src/app/insights/medications/page.tsx +++ b/src/app/insights/medications/page.tsx @@ -187,6 +187,7 @@ export default function InsightsMedikamentePage() { title={t("insights.medicationCompliance")} description={t("insights.subPage.medikamenteDescription")} explainerMetric="medications" + coachLaunch >
- - ); } diff --git a/src/app/insights/mood/page.tsx b/src/app/insights/mood/page.tsx index ce89442ef..6e42bea95 100644 --- a/src/app/insights/mood/page.tsx +++ b/src/app/insights/mood/page.tsx @@ -12,7 +12,6 @@ import { useTranslations } from "@/lib/i18n/context"; import { useInsightsLayoutPrefs } from "@/hooks/use-insights-layout-prefs"; import { Button } from "@/components/ui/button"; import { ChartSkeleton } from "@/components/charts/chart-skeleton"; -import { CoachLaunchButton } from "@/components/insights/coach-launch-button"; import { InsightStatusCard } from "@/components/insights/insight-status-card"; import { MetricEmptyState } from "@/components/insights/metric-empty-state"; import { MetricTargetSummary } from "@/components/insights/metric-target-summary"; @@ -99,6 +98,7 @@ export default function InsightsStimmungPage() { title={t("insights.moodSectionTitle")} description={t("insights.subPage.stimmungDescription")} explainerMetric="mood" + coachLaunch > - - ); } diff --git a/src/app/insights/pulse/page.tsx b/src/app/insights/pulse/page.tsx index a36cb2a64..7eeb88f4c 100644 --- a/src/app/insights/pulse/page.tsx +++ b/src/app/insights/pulse/page.tsx @@ -10,7 +10,6 @@ import { useTranslations } from "@/lib/i18n/context"; import { useInsightsLayoutPrefs } from "@/hooks/use-insights-layout-prefs"; import { Button } from "@/components/ui/button"; import { HealthChartDynamic } from "@/components/charts/health-chart-dynamic"; -import { CoachLaunchButton } from "@/components/insights/coach-launch-button"; import { InsightStatusCard } from "@/components/insights/insight-status-card"; import { MetricEmptyState } from "@/components/insights/metric-empty-state"; import { MetricStatStrip } from "@/components/insights/metric-stat-strip"; @@ -129,6 +128,7 @@ export default function InsightsPulsPage() { timeZone={user?.timezone ?? undefined} /> } + coachLaunch showAllValuesType="PULSE" > - - ); } diff --git a/src/app/insights/sleep/page.tsx b/src/app/insights/sleep/page.tsx index 5777e8a67..b4ea865b6 100644 --- a/src/app/insights/sleep/page.tsx +++ b/src/app/insights/sleep/page.tsx @@ -6,7 +6,6 @@ import { Moon } from "lucide-react"; import { useInsightsAnalytics } from "@/hooks/use-insights-analytics"; import { useTranslations } from "@/lib/i18n/context"; import { Button } from "@/components/ui/button"; -import { CoachLaunchButton } from "@/components/insights/coach-launch-button"; import { MetricEmptyState } from "@/components/insights/metric-empty-state"; import { MetricTargetSummary } from "@/components/insights/metric-target-summary"; import { SleepOverview } from "@/components/insights/sleep-overview"; @@ -62,6 +61,7 @@ export default function InsightsSchlafPage() { title={t("insights.sleep.title")} description={t("insights.sleep.description")} explainerMetric="sleep" + coachLaunch > @@ -78,8 +78,6 @@ export default function InsightsSchlafPage() { structural slot waits for v1.5 where the route + hook key will wire in. */} - - ); } diff --git a/src/app/insights/weight/page.tsx b/src/app/insights/weight/page.tsx index 209622e39..c2d431799 100644 --- a/src/app/insights/weight/page.tsx +++ b/src/app/insights/weight/page.tsx @@ -10,7 +10,6 @@ import { useTranslations } from "@/lib/i18n/context"; import { useInsightsLayoutPrefs } from "@/hooks/use-insights-layout-prefs"; import { Button } from "@/components/ui/button"; import { HealthChartDynamic } from "@/components/charts/health-chart-dynamic"; -import { CoachLaunchButton } from "@/components/insights/coach-launch-button"; import { InsightStatusCard } from "@/components/insights/insight-status-card"; import { MetricEmptyState } from "@/components/insights/metric-empty-state"; import { MetricStatStrip } from "@/components/insights/metric-stat-strip"; @@ -86,6 +85,7 @@ export default function InsightsGewichtPage() { timeZone={user?.timezone ?? undefined} /> } + coachLaunch showAllValuesType="WEIGHT" > - - ); } diff --git a/src/app/insights/workouts/page.tsx b/src/app/insights/workouts/page.tsx index bb89f4867..014f2738a 100644 --- a/src/app/insights/workouts/page.tsx +++ b/src/app/insights/workouts/page.tsx @@ -4,7 +4,6 @@ import { Activity } from "lucide-react"; import { useTranslations } from "@/lib/i18n/context"; import { useWorkouts } from "@/hooks/use-workouts"; -import { CoachLaunchButton } from "@/components/insights/coach-launch-button"; import { MetricEmptyState } from "@/components/insights/metric-empty-state"; import { SubPageShell } from "@/components/insights/sub-page-shell"; import { WorkoutList } from "@/components/insights/workout-list"; @@ -36,6 +35,7 @@ export default function InsightsWorkoutsPage() { title={t("insights.workouts.title")} description={t("insights.workouts.description")} explainerMetric="workouts" + coachLaunch > {isLoading ? (
@@ -52,10 +52,7 @@ export default function InsightsWorkoutsPage() { coachPrefill="I haven't logged any workouts yet — why does tracking them matter, and what should I focus on first?" /> ) : data ? ( - <> - - - + ) : null} ); diff --git a/src/components/insights/coach-launch-button.tsx b/src/components/insights/coach-launch-button.tsx index d6e1dd7d4..bf9e1c3e5 100644 --- a/src/components/insights/coach-launch-button.tsx +++ b/src/components/insights/coach-launch-button.tsx @@ -33,12 +33,24 @@ export interface CoachLaunchButtonProps { prefill?: string; /** Optional className passthrough for inline overrides. */ className?: string; + /** + * v1.8.6 — render the launch as an icon-only affordance instead of the + * labelled pill. The sub-page shell mounts this variant at heading + * height in the page header so "Coach fragen" sits top-right, aligned + * with the title, rather than at the foot of the page. The accessible + * label still resolves the shared CTA copy via `aria-label`. The icon + * variant stays visible across breakpoints — it is the header's own + * Coach entry, not the `lg+`-only inline pill the old foot placement + * used (the mobile FAB covers ` launch.askCoach(prefill ?? null)} + className={cn("text-muted-foreground hover:text-foreground", className)} + > +