diff --git a/src/app/(protected)/workorders/_components/list/table/wo-table-row-service-cells.tsx b/src/app/(protected)/workorders/_components/list/table/wo-table-row-service-cells.tsx
index ad995968..21731f67 100644
--- a/src/app/(protected)/workorders/_components/list/table/wo-table-row-service-cells.tsx
+++ b/src/app/(protected)/workorders/_components/list/table/wo-table-row-service-cells.tsx
@@ -114,6 +114,9 @@ export function WoTableRowServiceCells({
value={row.completedDate}
q={search}
onSave={(date) => onPatchRow({ completedDate: date })}
+ onClear={
+ row.completedDate && !viewOnly ? () => onPatchRow({ completedDate: "" }) : undefined
+ }
/>
e.stopPropagation()}>
diff --git a/src/test/app/(protected)/workorders/wo-table-completed-date-clear.test.tsx b/src/test/app/(protected)/workorders/wo-table-completed-date-clear.test.tsx
new file mode 100644
index 00000000..6e2c5ccd
--- /dev/null
+++ b/src/test/app/(protected)/workorders/wo-table-completed-date-clear.test.tsx
@@ -0,0 +1,128 @@
+import { fireEvent, screen } from "@testing-library/react";
+import { describe, expect, it, vi } from "vitest";
+import { WoTableRowServiceCells } from "@/app/(protected)/workorders/_components/list/table/wo-table-row-service-cells";
+import type { WoTableRowHandlers } from "@/app/(protected)/workorders/_components/list/table/wo-table-row";
+import type { WorkOrderTableRow } from "@/domain/work-orders/types/work-order-table-row";
+import { renderWithProviders } from "@/test/test-utils";
+
+function makeRow(overrides: Partial = {}): WorkOrderTableRow {
+ return {
+ id: 9,
+ woNumber: "24818000001",
+ site: "BK5",
+ type: "PM",
+ severity: null,
+ dispatcherId: "u1",
+ dispatcherName: "Alex",
+ scheduledOn: "2026-07-15",
+ completedDate: "2026-07-20",
+ targetWeek: "2026-07-15",
+ dayGroup: "07/15",
+ dayLabel: "Tue",
+ location: "Lobby",
+ locationId: "loc-1",
+ pm: "HVAC",
+ serviceNotes: "",
+ extraServices: [],
+ vendorId: "7",
+ company: "Acme",
+ tech: "Sam",
+ techPhone: "",
+ apptStart: "",
+ apptEnd: "",
+ apptTime: "09:00 – 10:00",
+ status: "Incomplete",
+ docStatus: "Yes",
+ pocName: "",
+ pocPhone: "",
+ pocNotes: "",
+ dueDate: "2026-07-22",
+ rescheduleCount: 0,
+ carriedOver: 0,
+ originalWeek: "",
+ originalDate: "",
+ isPastDue: false,
+ scheduleWeekOnly: false,
+ isAddOn: false,
+ mediaCount: 1,
+ title: "",
+ priority: "",
+ primaryDispatchId: null,
+ rowVersion: "",
+ dispatchRowVersion: "",
+ flagColor: null,
+ additionalContacts: [],
+ upliftSummary: { hasUplift: false, pendingCount: 0 },
+ ...overrides,
+ };
+}
+
+const handlers: WoTableRowHandlers = {
+ onRowClick: vi.fn(),
+ onView: vi.fn(),
+ onEdit: vi.fn(),
+ onOpenMedia: vi.fn(),
+ onPatch: vi.fn(),
+ onScheduleDate: vi.fn(),
+ onScheduleWeek: vi.fn(),
+ onScheduleRange: vi.fn(),
+ onOpenSlideOver: vi.fn(),
+ onOpenCompDoc: vi.fn(),
+ onOpenUplifts: vi.fn(),
+ onReorder: vi.fn(),
+ onDragStart: vi.fn(),
+ onDragOver: vi.fn(),
+ onDrop: vi.fn(),
+ onDragEnd: vi.fn(),
+};
+
+function renderCells(row: WorkOrderTableRow, viewOnly = false) {
+ const onPatchRow = vi.fn();
+ renderWithProviders(
+ ,
+ );
+ return onPatchRow;
+}
+
+describe("WoTableRowServiceCells completed date Clear (SH-390)", () => {
+ it("shows Clear in the completed-date editor and patches completedDate to empty", async () => {
+ const onPatchRow = renderCells(makeRow());
+
+ fireEvent.click(screen.getByRole("button", { name: /07-20/ }));
+
+ fireEvent.click(await screen.findByRole("button", { name: /clear/i }));
+
+ expect(onPatchRow).toHaveBeenCalledWith({ completedDate: "" });
+ });
+
+ it("offers no Clear when the completed date is already empty", async () => {
+ renderCells(makeRow({ completedDate: "" }));
+
+ fireEvent.click(screen.getByRole("button", { name: "—" }));
+
+ expect(await screen.findByRole("button", { name: "20" })).toBeInTheDocument();
+ expect(screen.queryByRole("button", { name: /clear/i })).not.toBeInTheDocument();
+ });
+
+ it("offers no Clear on a Completed (view-only) row", async () => {
+ renderCells(makeRow({ status: "Completed" }), true);
+
+ fireEvent.click(screen.getByRole("button", { name: /07-20/ }));
+
+ expect(await screen.findByRole("button", { name: "20" })).toBeInTheDocument();
+ expect(screen.queryByRole("button", { name: /clear/i })).not.toBeInTheDocument();
+ });
+});
diff --git a/src/test/app/(protected)/workorders/wo-table-row-completed-date-clear.test.tsx b/src/test/app/(protected)/workorders/wo-table-row-completed-date-clear.test.tsx
new file mode 100644
index 00000000..84275975
--- /dev/null
+++ b/src/test/app/(protected)/workorders/wo-table-row-completed-date-clear.test.tsx
@@ -0,0 +1,128 @@
+import { fireEvent, screen } from "@testing-library/react";
+import { describe, expect, it, vi } from "vitest";
+import { WoTableRow } from "@/app/(protected)/workorders/_components/list/table/wo-table-row";
+import type { WoTableRowHandlers } from "@/app/(protected)/workorders/_components/list/table/wo-table-row";
+import type { WorkOrderTableRow } from "@/domain/work-orders/types/work-order-table-row";
+import { renderWithProviders } from "@/test/test-utils";
+
+function makeRow(overrides: Partial = {}): WorkOrderTableRow {
+ return {
+ id: 9,
+ woNumber: "24818000001",
+ site: "BK5",
+ type: "PM",
+ severity: null,
+ dispatcherId: "u1",
+ dispatcherName: "Alex",
+ scheduledOn: "2026-07-15",
+ completedDate: "2026-07-20",
+ targetWeek: "2026-07-15",
+ dayGroup: "07/15",
+ dayLabel: "Tue",
+ location: "Lobby",
+ locationId: "loc-1",
+ pm: "HVAC",
+ serviceNotes: "",
+ extraServices: [],
+ vendorId: "7",
+ company: "Acme",
+ tech: "Sam",
+ techPhone: "",
+ apptStart: "",
+ apptEnd: "",
+ apptTime: "09:00 – 10:00",
+ status: "Incomplete",
+ docStatus: "Yes",
+ pocName: "",
+ pocPhone: "",
+ pocNotes: "",
+ dueDate: "2026-07-22",
+ rescheduleCount: 0,
+ carriedOver: 0,
+ originalWeek: "",
+ originalDate: "",
+ isPastDue: false,
+ scheduleWeekOnly: false,
+ isAddOn: false,
+ mediaCount: 1,
+ title: "",
+ priority: "",
+ primaryDispatchId: null,
+ rowVersion: "",
+ dispatchRowVersion: "",
+ flagColor: null,
+ additionalContacts: [],
+ upliftSummary: { hasUplift: false, pendingCount: 0 },
+ ...overrides,
+ };
+}
+
+function makeHandlers(): WoTableRowHandlers {
+ return {
+ onRowClick: vi.fn(),
+ onView: vi.fn(),
+ onEdit: vi.fn(),
+ onOpenMedia: vi.fn(),
+ onPatch: vi.fn(),
+ onScheduleDate: vi.fn(),
+ onScheduleWeek: vi.fn(),
+ onScheduleRange: vi.fn(),
+ onOpenSlideOver: vi.fn(),
+ onOpenCompDoc: vi.fn(),
+ onOpenUplifts: vi.fn(),
+ onReorder: vi.fn(),
+ onDragStart: vi.fn(),
+ onDragOver: vi.fn(),
+ onDrop: vi.fn(),
+ onDragEnd: vi.fn(),
+ };
+}
+
+function renderRow(row: WorkOrderTableRow, handlers: WoTableRowHandlers) {
+ renderWithProviders(
+ ,
+ { withAuth: false },
+ );
+}
+
+describe("WoTableRow completed date Clear (SH-390)", () => {
+ it("clears the completed date of an open row through the board patch", async () => {
+ const handlers = makeHandlers();
+ renderRow(makeRow(), handlers);
+
+ fireEvent.click(screen.getByRole("button", { name: /07-20/ }));
+ fireEvent.click(await screen.findByRole("button", { name: /clear/i }));
+
+ expect(handlers.onPatch).toHaveBeenCalledTimes(1);
+ expect(vi.mocked(handlers.onPatch).mock.calls[0].slice(0, 2)).toEqual([
+ 9,
+ { completedDate: "" },
+ ]);
+ });
+
+ it.each(["Completed", "Canceled"] as const)("offers no Clear on a %s row", async (status) => {
+ const handlers = makeHandlers();
+ renderRow(makeRow({ status }), handlers);
+
+ fireEvent.click(screen.getByRole("button", { name: /07-20/ }));
+
+ expect(await screen.findByRole("button", { name: "20" })).toBeInTheDocument();
+ expect(screen.queryByRole("button", { name: /clear/i })).not.toBeInTheDocument();
+ expect(handlers.onPatch).not.toHaveBeenCalled();
+ });
+});
diff --git a/src/test/domain/work-orders/mappers/completed-date-board-column.test.ts b/src/test/domain/work-orders/mappers/completed-date-board-column.test.ts
index 1820df72..d8a3c17d 100644
--- a/src/test/domain/work-orders/mappers/completed-date-board-column.test.ts
+++ b/src/test/domain/work-orders/mappers/completed-date-board-column.test.ts
@@ -16,6 +16,11 @@ describe("COMPLETED DATE board column wiring", () => {
expect(ops).toEqual([{ field: "completedDate", value: "2026-07-30" }]);
});
+ it("expands a clearing completedDate patch to an empty-string board field op (SH-390)", () => {
+ const ops = expandScalarBoardOps({ completedDate: "" });
+ expect(ops).toEqual([{ field: "completedDate", value: "" }]);
+ });
+
it("maps completedDate returned by a board GET row", () => {
const row = mapWorkOrderTableRow({
id: 42,
|