diff --git a/src/components/medications/__tests__/medication-card-glp1.test.tsx b/src/components/medications/__tests__/medication-card-glp1.test.tsx
new file mode 100644
index 000000000..086d422b1
--- /dev/null
+++ b/src/components/medications/__tests__/medication-card-glp1.test.tsx
@@ -0,0 +1,475 @@
+import { describe, expect, it, vi, beforeEach } from "vitest";
+import { renderToStaticMarkup } from "react-dom/server";
+import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
+
+import { I18nProvider } from "@/lib/i18n/context";
+import {
+ Glp1MedicationCard,
+ type Glp1Medication,
+} from "@/components/medications/glp1-medication-card";
+import { MedicationCard } from "@/components/medications/medication-card";
+
+/**
+ * v1.4.25 W4d-tests — RTL-style component coverage for the GLP-1
+ * medication-card variant deferred by phase W4d.
+ *
+ * The project never installed `@testing-library/react`; the convention
+ * is `renderToStaticMarkup` + assertions against the SSR string, with
+ * react-query data seeded via `QueryClient.setQueryData()` so the
+ * card's `useQuery` calls resolve synchronously. Interactive behaviour
+ * (button onClick → callback) is smoke-checked by invoking the
+ * supplied handler directly — SSR can't fire DOM events.
+ *
+ * Test cases (per phase-W4d-v1425-glp1-full-report.md "Tests deferred"):
+ * 1. Renders GLP-1 variant when treatmentClass === "GLP1"
+ * 2. Renders default MedicationCard when treatmentClass === null
+ * 3. Drug name + current dose ("Mounjaro · 7.5 mg") visible
+ * 4. Last + next injection labels with localised weekday
+ * 5. Inline dose-history disclosure (closed by default)
+ * 6. Injection-site rotation marker (last + recommended)
+ * 7. Pen-inventory line when data present
+ * 8. Side-effect quick-log button hands off the medication object
+ * (the parent wires this to MoodEntry with a pre-filled tag)
+ * 9. AI-disabled state: the GLP-1 Coach hand-off button (deferred —
+ * not yet implemented; if/when it lands it must respect the
+ * provider-configured gate). This test pins the current absence.
+ */
+
+const med7p5: Glp1Medication = {
+ id: "med-glp1-1",
+ name: "Mounjaro",
+ dose: "7.5 mg",
+ category: "OTHER",
+ treatmentClass: "GLP1",
+ dosesPerUnit: 4,
+ active: true,
+ notificationsEnabled: true,
+ pausedAt: null,
+ // ~3 days ago so "last injection" reads as a recent absolute date,
+ // not "today/yesterday" — keeps the SSR string deterministic-ish
+ // across local clocks while still exercising the with-site branch.
+ lastTakenAt: "2026-05-08T08:00:00.000Z",
+ schedules: [
+ {
+ id: "s1",
+ // Saturday only — one entry triggers the weekly-preset branch
+ // in `predictNextWeeklyDate()`.
+ windowStart: "08:00",
+ windowEnd: "20:00",
+ label: null,
+ dose: "7.5 mg",
+ daysOfWeek: "6",
+ },
+ ],
+};
+
+const defaultMed = {
+ id: "med-bp-1",
+ name: "Ramipril",
+ dose: "5 mg",
+ category: "BLOOD_PRESSURE",
+ treatmentClass: undefined as string | undefined,
+ active: true,
+ notificationsEnabled: true,
+ pausedAt: null,
+ lastTakenAt: null,
+ schedules: [
+ {
+ id: "s2",
+ windowStart: "07:30",
+ windowEnd: "09:30",
+ label: null,
+ dose: null,
+ daysOfWeek: null,
+ },
+ ],
+};
+
+function makeClient(): QueryClient {
+ return new QueryClient({
+ defaultOptions: { queries: { retry: false, gcTime: 0, staleTime: Infinity } },
+ });
+}
+
+/**
+ * Seed both queries the card consumes (compliance + glp1 details) so
+ * the first SSR pass already has data — TanStack Query returns
+ * cached data synchronously when present, even on the server.
+ */
+function seedGlp1Details(
+ client: QueryClient,
+ medId: string,
+ details: {
+ doseChanges?: Array<{
+ id: string;
+ effectiveFrom: string;
+ doseValue: number;
+ doseUnit: string;
+ note: string | null;
+ }>;
+ recentIntakes?: Array<{
+ takenAt: string | null;
+ injectionSite: string | null;
+ }>;
+ inventory?: {
+ pensRemaining: number | null;
+ dosesRemaining: number | null;
+ weeksOfSupply: number | null;
+ lowStock: boolean;
+ } | null;
+ },
+) {
+ client.setQueryData(["medications", medId, "glp1-details"], {
+ doseChanges: details.doseChanges ?? [],
+ recentIntakes: details.recentIntakes ?? [],
+ inventory: details.inventory ?? null,
+ });
+}
+
+function seedCompliance(
+ client: QueryClient,
+ medId: string,
+ payload: {
+ rate7?: number;
+ rate30?: number;
+ streak?: number;
+ } = {},
+) {
+ client.setQueryData(["medications", medId, "compliance"], {
+ compliance7: {
+ rate: payload.rate7 ?? 85,
+ streak: payload.streak ?? 0,
+ },
+ compliance30: { rate: payload.rate30 ?? 82 },
+ });
+}
+
+function render(
+ node: React.ReactNode,
+ client: QueryClient,
+ locale: "en" | "de" = "en",
+) {
+ return renderToStaticMarkup(
+
+ {node}
+ ,
+ );
+}
+
+beforeEach(() => {
+ vi.clearAllMocks();
+});
+
+describe(" — GLP-1 variant rendering", () => {
+ it("renders the GLP-1 variant when treatmentClass === 'GLP1' is active", () => {
+ // Headline differentiator: the GLP-1 card stamps the
+ // `medications.treatmentClassGlp1` badge ("GLP-1 injection")
+ // which the generic card never renders. Presence of the badge is
+ // the contract pin for "GLP-1 variant is showing".
+ const client = makeClient();
+ seedCompliance(client, med7p5.id);
+ seedGlp1Details(client, med7p5.id, {});
+
+ const html = render(
+ {}} />,
+ client,
+ );
+
+ expect(html).toContain("GLP-1 injection");
+ // The Syringe lucide icon renders an SVG with this class.
+ expect(html).toMatch(/lucide-syringe/i);
+ });
+
+ it("renders the default MedicationCard when treatmentClass is null/undefined (back-compat)", () => {
+ // The page dispatcher renders MedicationCard for everything that
+ // isn't `"GLP1"`. We verify the default card's output stays free
+ // of the GLP-1-specific badge so legacy mocks (no treatmentClass
+ // field) keep producing the v1.4.24 UI.
+ const client = makeClient();
+ seedCompliance(client, defaultMed.id);
+
+ const html = render(
+ {}} />,
+ client,
+ );
+
+ // Default card shows the localised category label, not the
+ // GLP-1 badge.
+ expect(html).toContain("Blood Pressure");
+ expect(html).not.toContain("GLP-1 injection");
+ expect(html).not.toMatch(/lucide-syringe/i);
+ // The dose-history disclosure is GLP-1-only; default card omits it.
+ expect(html).not.toContain("Dose history");
+ });
+
+ it("shows drug name + current dose ('Mounjaro · 7.5 mg')", () => {
+ const client = makeClient();
+ seedCompliance(client, med7p5.id);
+ seedGlp1Details(client, med7p5.id, {});
+
+ const html = render(
+ {}} />,
+ client,
+ );
+
+ expect(html).toContain("Mounjaro");
+ expect(html).toContain("7.5 mg");
+ // Card title joins the two with the middle-dot separator the
+ // glp1Headline copy uses.
+ expect(html).toContain("·");
+ });
+
+ it("shows last + next injection labels with localised weekday", () => {
+ const client = makeClient();
+ seedCompliance(client, med7p5.id);
+ seedGlp1Details(client, med7p5.id, {
+ recentIntakes: [
+ { takenAt: med7p5.lastTakenAt, injectionSite: "ABDOMEN_LEFT" },
+ ],
+ });
+
+ const html = render(
+ {}} />,
+ client,
+ );
+
+ // The with-site copy reads "Last: