diff --git a/docs/redesign/phase-5/p5-15/p5-15-04/file-picker/README.md b/docs/redesign/phase-5/p5-15/p5-15-04/file-picker/README.md
new file mode 100644
index 0000000..b248e2e
--- /dev/null
+++ b/docs/redesign/phase-5/p5-15/p5-15-04/file-picker/README.md
@@ -0,0 +1,11 @@
+# P5-15-04-A11Y-01 — Native file-picker keyboard repair
+
+## Reproduction checkpoint
+
+From `f55c761ac229a69357a1c5b68a06e837cfa31ec0`, the unchanged native audit again reports six Enter/Space failures for Upload, Import JSON and Convert CSV to Import JSON; the three pointer comparisons succeed. The native suite's other failure is the separately owned CV source route. No files were uploaded by these chooser observations.
+
+The label-only MUI buttons have no click handler. MUI keyboard activation invokes a nonnative button's handler; it does not emulate the browser's label-to-input default activation. The same pattern exists in Add Job's four attachment buckets. It is not an API, permissions or file-validation failure.
+
+`src/file-picker-activation.test.tsx` adds eight real-component cases: seven keyboard/input activation boundaries (all fail with expected one input click/actual zero), plus file/Job association, uploading disabled state and post-upload selection reset. The latter also exposes that a label is not a native disabled button. Earlier test-fixture label/step mistakes were corrected before recording the final RED; they are not application defects.
+
+Affected contracts: PRES-W08 attachments and PRES-J10 import/export; Add Job same-pattern caller is covered without changing its domain workflow. Whole groups remain FAIL at this reproduction checkpoint. No implementation correction or fresh full-suite claim is made here. Production remains NO-GO; human screen-reader/device acceptance remains incomplete.
diff --git a/job-tracker-ui/src/file-picker-activation.test.tsx b/job-tracker-ui/src/file-picker-activation.test.tsx
new file mode 100644
index 0000000..26bf270
--- /dev/null
+++ b/job-tracker-ui/src/file-picker-activation.test.tsx
@@ -0,0 +1,93 @@
+import React from "react";
+import { act, fireEvent, render, screen, waitFor } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import Attachments from "./components/Attachments";
+import ImportExportJobs from "./components/ImportExportJobs";
+import AddJobModal from "./components/AddJobModal";
+import { ConfirmProvider } from "./confirm";
+import { PromptProvider } from "./prompt";
+import { I18nProvider } from "./i18n/I18nProvider";
+import { ToastProvider } from "./toast";
+import { api } from "./api";
+
+// These tests exercise file selection; the unrelated date-picker adapter is ESM.
+jest.mock("@mui/x-date-pickers/DatePicker", () => ({ DatePicker: ({ label }: { label: string }) =>
{label}
}));
+
+const mockedApi = api as jest.Mocked;
+
+function renderPicker(component: React.ReactNode) {
+ return render({component});
+}
+
+async function expectNativeInputActivation(button: HTMLElement, input: HTMLInputElement) {
+ const activated = jest.fn();
+ input.addEventListener("click", activated);
+ act(() => button.focus());
+ await userEvent.keyboard("{Enter}");
+ expect(activated).toHaveBeenCalledTimes(1);
+ await userEvent.keyboard(" ");
+ expect(activated).toHaveBeenCalledTimes(2);
+ userEvent.click(button);
+ expect(activated).toHaveBeenCalledTimes(3);
+ expect(button).toHaveFocus();
+}
+
+beforeEach(() => {
+ window.localStorage.clear();
+ jest.clearAllMocks();
+});
+
+test("attachment Upload activates the same native input from Enter, Space and pointer", async () => {
+ mockedApi.get.mockResolvedValue({ data: [] });
+ const { container } = renderPicker();
+ await screen.findByText(/No attachments yet\./);
+ await expectNativeInputActivation(screen.getByRole("button", { name: "Upload" }), container.querySelector('input[type="file"]')!);
+ expect(mockedApi.post).not.toHaveBeenCalled();
+});
+
+test.each([
+ ["Import JSON", "application/json"],
+ ["Convert CSV to Import JSON", ".csv,text/csv"],
+])("%s activates its own native input from Enter, Space and pointer", async (name, accept) => {
+ const { container } = renderPicker();
+ await expectNativeInputActivation(screen.getByRole("button", { name }), container.querySelector(`input[accept="${accept}"]`)!);
+ expect(mockedApi.post).not.toHaveBeenCalled();
+});
+
+test.each([0, 1, 2, 3])("Add Job attachment bucket %s retains keyboard selection and distinct pending files", async (bucket) => {
+ mockedApi.post.mockImplementation((url) => Promise.resolve({ data: url === "/jobimport/preview" ? { success: true, title: "Supported role", company: "Fixture company", description: "Known description" } : {} }));
+ renderPicker( {}} onCreated={() => {}} />);
+ await screen.findByDisplayValue("Supported role");
+ fireEvent.click(screen.getByRole("button", { name: "Continue" }));
+ for (let step = 0; step < bucket; step += 1) fireEvent.click(screen.getByRole("button", { name: "Skip and continue" }));
+ const input = document.querySelector('input[type="file"]')!;
+ expect(input.multiple).toBe(true);
+ expect(input.accept).toContain(".pdf,.doc,.docx,.txt,.md");
+ await expectNativeInputActivation(screen.getByRole("button", { name: "Choose files" }), input);
+ fireEvent.change(input, { target: { files: [new File(["fixture"], `bucket-${bucket}.txt`, { type: "text/plain" })] } });
+ expect(screen.getByText(`bucket-${bucket}.txt`)).toBeInTheDocument();
+ expect(mockedApi.post).not.toHaveBeenCalledWith("/attachments", expect.anything(), expect.anything());
+});
+
+test("attachment selection retains files and job ownership, disables activation while uploading and clears selection after completion", async () => {
+ mockedApi.get.mockResolvedValue({ data: [] });
+ let finishUpload!: (value: { data: object }) => void;
+ mockedApi.post.mockImplementationOnce(() => new Promise((resolve) => { finishUpload = resolve; }));
+ const { container } = renderPicker();
+ await screen.findByText(/No attachments yet\./);
+ const input = container.querySelector('input[type="file"]')!;
+ const files = [new File(["Known CV"], "cv.pdf", { type: "application/pdf" }), new File(["Søknad"], "søknad.txt", { type: "text/plain" })];
+ fireEvent.change(input, { target: { files } });
+ const uploading = await screen.findByRole("button", { name: "Uploading..." });
+ expect(uploading).toBeDisabled();
+ const body = mockedApi.post.mock.calls[0][1] as FormData;
+ expect(body.get("jobId")).toBe("42");
+ expect(body.getAll("files")).toEqual(files);
+ const activated = jest.fn();
+ input.addEventListener("click", activated);
+ userEvent.click(uploading);
+ expect(activated).not.toHaveBeenCalled();
+ finishUpload({ data: {} });
+ await waitFor(() => expect(screen.getByRole("button", { name: "Upload" })).toBeEnabled());
+ expect(input.value).toBe("");
+});