diff --git a/src/components/insights/__tests__/chart-tooltip-observer.test.ts b/src/components/insights/__tests__/chart-tooltip-observer.test.ts
new file mode 100644
index 000000000..4a1bd3793
--- /dev/null
+++ b/src/components/insights/__tests__/chart-tooltip-observer.test.ts
@@ -0,0 +1,70 @@
+import { afterEach, beforeEach, describe, expect, it } from "vitest";
+
+import {
+ __resetChartTooltipObserverForTests,
+ getChartTooltipActive,
+ getChartTooltipActiveServer,
+ subscribeChartTooltipActive,
+} from "../chart-tooltip-observer";
+
+/**
+ * v1.4.33 (F15) — chart-tooltip observer contract tests.
+ *
+ * The full DOM-side behaviour (MutationObserver watching `[class*=
+ * "recharts-tooltip-wrapper"]` visibility flips) lives behind a guard
+ * that no-ops when `document` is undefined — which is the case in the
+ * default vitest node environment. These tests pin the SSR-safe path
+ * and the subscribe/snapshot contract that `useSyncExternalStore`
+ * leans on.
+ *
+ * The integration with a real Recharts tooltip is exercised via the
+ * Playwright suite (mobile insight chart hover → FAB fades).
+ */
+
+describe("chart-tooltip-observer", () => {
+ beforeEach(() => {
+ __resetChartTooltipObserverForTests();
+ });
+
+ afterEach(() => {
+ __resetChartTooltipObserverForTests();
+ });
+
+ it("getChartTooltipActiveServer always returns false", () => {
+ // The SSR snapshot must never claim a tooltip is active on the
+ // server — there's no DOM to inspect, and a stale `true` would
+ // render the FAB invisible on the initial pass.
+ expect(getChartTooltipActiveServer()).toBe(false);
+ });
+
+ it("getChartTooltipActive returns false when no wrappers exist", () => {
+ expect(getChartTooltipActive()).toBe(false);
+ });
+
+ it("subscribeChartTooltipActive returns an unsubscribe function", () => {
+ const unsubscribe = subscribeChartTooltipActive(() => undefined);
+ expect(typeof unsubscribe).toBe("function");
+ unsubscribe();
+ });
+
+ it("no-ops cleanly on a server-side render (no document available)", () => {
+ // Document is undefined in the vitest node environment — the
+ // module's `start()` early-returns. Subscribing should still
+ // succeed; the listener simply never fires.
+ let calls = 0;
+ const unsubscribe = subscribeChartTooltipActive(() => {
+ calls += 1;
+ });
+ expect(getChartTooltipActive()).toBe(false);
+ expect(calls).toBe(0);
+ unsubscribe();
+ });
+
+ it("supports multiple subscribers without throwing", () => {
+ const a = subscribeChartTooltipActive(() => undefined);
+ const b = subscribeChartTooltipActive(() => undefined);
+ expect(getChartTooltipActive()).toBe(false);
+ a();
+ b();
+ });
+});
diff --git a/src/components/insights/__tests__/layout-coach-fab.test.tsx b/src/components/insights/__tests__/layout-coach-fab.test.tsx
new file mode 100644
index 000000000..b8ff1d012
--- /dev/null
+++ b/src/components/insights/__tests__/layout-coach-fab.test.tsx
@@ -0,0 +1,106 @@
+import { describe, expect, it } from "vitest";
+import { renderToStaticMarkup } from "react-dom/server";
+
+import { I18nProvider } from "@/lib/i18n/context";
+import { CoachLaunchProvider } from "@/lib/insights/coach-launch-context";
+
+import { LayoutCoachFab } from "../layout-coach-fab";
+
+/**
+ * v1.4.33 (F15) — layout-level Coach FAB contract.
+ *
+ * The FAB sits at the mobile bottom-right. Before v1.4.33 it overlaid
+ * Recharts tooltips when the user tapped a data point in the chart's
+ * lower-right corner — the maintainer flagged this as F15 in the
+ * runtime audit. The fix wires the FAB to `useChartTooltipActive()`
+ * which reads a singleton MutationObserver of every
+ * `.recharts-tooltip-wrapper`; while a wrapper is visible the FAB
+ * fades and drops `pointer-events`.
+ *
+ * Project convention is SSR-only tests (no `@testing-library/react`),
+ * so we pin the initial-paint state — no tooltip can be active before
+ * hydration, so the FAB renders without the active-flag attribute
+ * and stays interactive on first paint. The end-to-end auto-hide
+ * behaviour is exercised in the Playwright mobile suite.
+ */
+
+function render(node: React.ReactNode, locale: "en" | "de" = "en") {
+ return renderToStaticMarkup(
+ {node},
+ );
+}
+
+describe("", () => {
+ it("returns nothing when no is mounted", () => {
+ // The hook returns `null` outside the provider; the FAB then
+ // renders nothing rather than crashing — same posture as
+ // ``.
+ const html = render();
+ expect(html).not.toContain('data-slot="coach-launch-fab"');
+ });
+
+ it("mounts the FAB with the launch-fab slot under the provider", () => {
+ const html = render(
+
+
+ ,
+ );
+ expect(html).toMatch(/data-slot="coach-launch-fab"/);
+ });
+
+ it("renders without the chart-tooltip-active flag on first paint", () => {
+ // SSR snapshot of `useChartTooltipActive()` is `false` so the
+ // FAB stays interactive on initial render. The active classes
+ // (opacity-0 + pointer-events-none) are applied client-side only
+ // once a Recharts tooltip wrapper flips its inline visibility.
+ const html = render(
+
+
+ ,
+ );
+ // The data attribute is `undefined` when inactive — React drops
+ // it from the DOM so the rendered markup never carries it.
+ expect(html).not.toContain('data-chart-tooltip-active="true"');
+ // The shadcn `