diff --git a/e2e/share-documents.spec.ts b/e2e/share-documents.spec.ts
index d6c81f481..518853a56 100644
--- a/e2e/share-documents.spec.ts
+++ b/e2e/share-documents.spec.ts
@@ -30,6 +30,7 @@ import { STORAGE_STATE_PATH } from "./setup/global-setup";
import {
ensureShareDocFixture,
ensureVaultFixture,
+ MRT_DOC_ID,
SHARE_DOC_PREFIX,
SHARE_JPEG_DOC_ID,
SHARE_PDF_DOC_ID,
@@ -233,6 +234,50 @@ test.describe("clinician document sharing", () => {
await clinician.context().close();
});
+ test("the document detail Share action opens the create flow with that document pre-attached", async ({
+ page,
+ }) => {
+ // Opens two stacked sheets (detail → share) and drives a real create with
+ // the one-time QR render — heavier than the default 30s budget on a loaded
+ // runner. Runs on the desktop and 390px mobile projects.
+ test.slow();
+
+ // Deep-link straight to the document detail sheet.
+ await page.goto(`/documents?doc=${MRT_DOC_ID}`);
+ const detail = page.getByRole("dialog").filter({ hasText: "MRT Knie" });
+ await expect(detail).toBeVisible();
+
+ // The Share action sits beside Download in the footer.
+ await detail.getByRole("button", { name: "Share" }).click();
+
+ // The share flow opens with the document already attached and its title
+ // pre-filled as the link label — no context switch to Settings.
+ const shareSheet = page
+ .getByRole("dialog")
+ .filter({ hasText: "Share this document" });
+ await expect(shareSheet).toBeVisible();
+ await expect(shareSheet.locator("#share-label")).toHaveValue("MRT Knie");
+ const chips = shareSheet
+ .getByTestId("share-attached-chips")
+ .getByRole("listitem");
+ await expect(chips).toHaveCount(1);
+ await expect(chips.first()).toContainText("MRT Knie");
+
+ // Create the link — the one-time reveal (with the scannable QR) lands in
+ // the same sheet, carrying the one attached document.
+ await shareSheet.getByRole("button", { name: "Create link" }).click();
+ const reveal = shareSheet.getByTestId("share-token-reveal");
+ await expect(reveal).toBeVisible();
+ await expect(
+ shareSheet.getByTestId("share-created-doc-count"),
+ ).toContainText("1");
+ const qrImg = shareSheet.getByTestId("share-qr-block").getByRole("img");
+ await expect(qrImg).toBeVisible();
+ const box = await qrImg.boundingBox();
+ expect(box, "QR image has a rendered box").not.toBeNull();
+ expect(box!.width).toBeGreaterThanOrEqual(160);
+ });
+
test("picker enforces the document cap at the limit", async ({ page }) => {
await page.goto("/settings/sharing");
await page.locator("#share-label").fill(`${SHARE_DOC_PREFIX} cap`);
diff --git a/src/components/settings/__tests__/share-link-create-form.test.tsx b/src/components/settings/__tests__/share-link-create-form.test.tsx
new file mode 100644
index 000000000..bbde00093
--- /dev/null
+++ b/src/components/settings/__tests__/share-link-create-form.test.tsx
@@ -0,0 +1,84 @@
+/**
+ * The shared clinician share-link CREATE flow. It is mounted from two places
+ * — Settings → Sharing and the document detail sheet's Share action — so the
+ * test pins the two behaviours the document entry point relies on:
+ * 1. Mounted bare (the Settings default) it renders the create form with the
+ * 90-day-capped expiry, the attach affordance, the frozen-set warning,
+ * and no pre-attached document.
+ * 2. Mounted with `initialDocuments` + `initialLabel` (the document flow) it
+ * pre-fills the label and seeds the attached-document chip, so sharing a
+ * document lands on a create form already scoped to it.
+ *
+ * SSR-static render only (no jsdom), matching the sibling settings tests. The
+ * TanStack hooks are mocked; the QR (`qrcode`) only ever renders in an effect
+ * after a create, so it never runs under `renderToStaticMarkup`.
+ */
+import { describe, expect, it, vi } from "vitest";
+import { renderToStaticMarkup } from "react-dom/server";
+
+vi.mock("@tanstack/react-query", () => ({
+ // The always-mounted ShareDocumentPicker reads via useQuery; the form
+ // itself creates via useMutation.
+ useQuery: () => ({ data: undefined, isPending: false, isError: false }),
+ useMutation: () => ({ mutate: vi.fn(), isPending: false }),
+ useQueryClient: () => ({ invalidateQueries: vi.fn() }),
+}));
+
+import { I18nProvider } from "@/lib/i18n/context";
+import { ShareLinkCreateForm } from "../share-link-create-form";
+import type { PickedDocument } from "../share-document-picker";
+
+function render(
+ props: {
+ initialDocuments?: PickedDocument[];
+ initialLabel?: string;
+ } = {},
+ locale: "en" | "de" = "en",
+) {
+ return renderToStaticMarkup(
+
+
+ ,
+ );
+}
+
+describe(" — shared create flow", () => {
+ it("renders the create form with the 90-day-capped expiry and attach affordance", () => {
+ const html = render();
+ expect(html).toContain('max="90"');
+ expect(html).toContain("Create link");
+ expect(html).toContain('data-testid="share-attach-open"');
+ expect(html).toContain("Choose documents");
+ });
+
+ it("surfaces the frozen-set warning so the write-once contract is explicit", () => {
+ const html = render();
+ expect(html).toContain("fixed once you create the link");
+ });
+
+ it("does not reveal a token before a create succeeds", () => {
+ const html = render();
+ expect(html).not.toContain('data-testid="share-token-reveal"');
+ });
+
+ it("mounts bare with no pre-attached document (the Settings default)", () => {
+ const html = render();
+ expect(html).not.toContain('data-testid="share-attached-chips"');
+ expect(html).toContain("0 of 50 selected");
+ });
+
+ it("seeds the label and the attached-document chip from the document flow", () => {
+ const html = render({
+ initialDocuments: [{ id: "doc-1", title: "Blood panel 2026" }],
+ initialLabel: "Blood panel 2026",
+ });
+ // Label pre-filled with the document title.
+ expect(html).toContain('value="Blood panel 2026"');
+ // The document rides in as a removable chip, and the count reflects it.
+ expect(html).toContain('data-testid="share-attached-chips"');
+ expect(html).toContain("Blood panel 2026");
+ expect(html).toContain("1 of 50 selected");
+ // A seeded set surfaces the EXIF note the picker path also shows.
+ expect(html).toContain("Camera metadata");
+ });
+});