diff --git a/src/components/medications/detail/medication-detail-tabs.tsx b/src/components/medications/detail/medication-detail-tabs.tsx
index 39a7a8b7b..9cefc459a 100644
--- a/src/components/medications/detail/medication-detail-tabs.tsx
+++ b/src/components/medications/detail/medication-detail-tabs.tsx
@@ -923,11 +923,7 @@ export function MedicationDetailTabs({
defaultOpen={false}
>
-
+
{/* v1.40 (#1024) — the courses: when it was taken, and a new
course once the last one has ended. */}
diff --git a/src/components/medications/sections/__tests__/destructive-zone-section.test.tsx b/src/components/medications/sections/__tests__/destructive-zone-section.test.tsx
new file mode 100644
index 000000000..5992803ea
--- /dev/null
+++ b/src/components/medications/sections/__tests__/destructive-zone-section.test.tsx
@@ -0,0 +1,93 @@
+import { describe, expect, it, vi } from "vitest";
+import { renderToStaticMarkup } from "react-dom/server";
+import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
+
+import { I18nProvider } from "@/lib/i18n/context";
+import { updateMedicationSchema } from "@/lib/validations/medication/create-update";
+
+import {
+ canEndCourse,
+ endCourseBody,
+ LifecycleManageBody,
+} from "../destructive-zone-section";
+
+vi.mock("@/hooks/use-auth", () => ({
+ useAuth: () => ({
+ user: { id: "test-user", username: "tester", role: "USER" },
+ isAuthenticated: true,
+ isLoading: false,
+ }),
+}));
+
+/**
+ * "End medication" sent `new Date().toISOString()`, a full instant, against an
+ * `endsOn` that only takes `YYYY-MM-DD`, so every press answered 422 and the
+ * toast said the medication could not be ended. The body now carries the
+ * person's own calendar day.
+ */
+describe("endCourseBody", () => {
+ it("is a date the update schema accepts", () => {
+ const body = endCourseBody(
+ new Date("2026-10-21T09:30:00.000Z"),
+ "Europe/Berlin",
+ );
+ expect(body).toEqual({ endsOn: "2026-10-21" });
+ expect(updateMedicationSchema.safeParse(body).success).toBe(true);
+ });
+
+ it("is the person's day, not the UTC one", () => {
+ // 23:30 UTC is already the next day in Warsaw and still the same day in
+ // New York.
+ const lateUtc = new Date("2026-10-20T23:30:00.000Z");
+ expect(endCourseBody(lateUtc, "Europe/Warsaw").endsOn).toBe("2026-10-21");
+ expect(endCourseBody(lateUtc, "America/New_York").endsOn).toBe(
+ "2026-10-20",
+ );
+ });
+
+ it("is what the update schema refused before: a full instant is not a date", () => {
+ expect(
+ updateMedicationSchema.safeParse({
+ endsOn: new Date("2026-10-21T09:30:00.000Z").toISOString(),
+ }).success,
+ ).toBe(false);
+ });
+});
+
+/**
+ * Ending writes today as the end of the latest course. On a course that has
+ * already ended that would reopen it up to today, and on one that has not
+ * started it would become a one-day course today, so End is only offered while
+ * the course runs. A medication with no dates is CURRENT.
+ */
+describe("End is offered only for a running course", () => {
+ function render(courseStatus?: "UPCOMING" | "CURRENT" | "ENDED") {
+ return renderToStaticMarkup(
+
+
+
+
+ ,
+ );
+ }
+
+ it.each([
+ ["CURRENT", true],
+ [undefined, true],
+ ["ENDED", false],
+ ["UPCOMING", false],
+ ] as const)("course %s → End shown: %s", (status, shown) => {
+ expect(canEndCourse(status)).toBe(shown);
+ const html = render(status);
+ expect(html.includes('data-slot="destructive-zone-end"')).toBe(shown);
+ // Pausing stays available whatever the course.
+ expect(html).toContain('id="medication-detail-pause-switch"');
+ });
+});
diff --git a/src/components/medications/sections/destructive-zone-section.tsx b/src/components/medications/sections/destructive-zone-section.tsx
index b8e247d76..ae1c193c2 100644
--- a/src/components/medications/sections/destructive-zone-section.tsx
+++ b/src/components/medications/sections/destructive-zone-section.tsx
@@ -13,7 +13,7 @@
* - Tier 1 Pausieren: PUT `{ active: false | true }`. Server derives
* `pausedAt`. Switch wraps in `