From 7c10aa3f1ee2652d2129405520490d85a4cd28cf Mon Sep 17 00:00:00 2001 From: Bryan Helmkamp Date: Sat, 23 May 2026 16:16:31 -0400 Subject: [PATCH] Add multi-select with bulk archive/unarchive to runs list view Adds a leading checkbox column with a tri-state select-all header and a fixed bottom action toolbar that surfaces selection count plus Archive and Unarchive buttons. Selection clears when pagination, sort, or filters change. Bulk actions fan out to the existing single- run endpoints via Promise.allSettled and report per-run outcomes. Co-Authored-By: Claude Opus 4.7 (1M context) --- apps/fabro-web/app/routes/runs.tsx | 228 ++++++++++++++++++++++++++++- 1 file changed, 222 insertions(+), 6 deletions(-) diff --git a/apps/fabro-web/app/routes/runs.tsx b/apps/fabro-web/app/routes/runs.tsx index a1cd5b441..b57c51fd1 100644 --- a/apps/fabro-web/app/routes/runs.tsx +++ b/apps/fabro-web/app/routes/runs.tsx @@ -1,6 +1,6 @@ import { useState, useCallback, useEffect, useMemo, useRef } from "react"; import { Link, useSearchParams } from "react-router"; -import { AdjustmentsHorizontalIcon, ArchiveBoxIcon, CheckIcon, ChevronDoubleLeftIcon, ChevronDoubleRightIcon, ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon, ChevronUpDownIcon, ChevronUpIcon, CommandLineIcon, MagnifyingGlassIcon, PlusCircleIcon } from "@heroicons/react/24/outline"; +import { AdjustmentsHorizontalIcon, ArchiveBoxIcon, ArrowUturnLeftIcon, CheckIcon, ChevronDoubleLeftIcon, ChevronDoubleRightIcon, ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon, ChevronUpDownIcon, ChevronUpIcon, CommandLineIcon, MagnifyingGlassIcon, PlusCircleIcon, XMarkIcon } from "@heroicons/react/24/outline"; import { EllipsisVerticalIcon } from "@heroicons/react/20/solid"; import { Listbox, ListboxButton, ListboxOption, ListboxOptions, Menu, MenuButton, MenuItem, MenuItems } from "@headlessui/react"; import { useSWRConfig } from "swr"; @@ -31,7 +31,7 @@ import { useToast } from "../components/toast"; import { mutateRunListCaches } from "../lib/board-cache"; import { shouldRefreshBoardForEvent, useBoardEvents } from "../lib/board-events"; import { useAllRuns, useAuthConfig, useRunsPage, useSystemInfo } from "../lib/queries"; -import { archiveRun, canArchive } from "../lib/run-actions"; +import { archiveRun, canArchive, canUnarchive, unarchiveRun } from "../lib/run-actions"; import type { BoardColumn, ListRunsDirectionEnum, @@ -738,16 +738,27 @@ export function RunRow({ run }: { run: RunWithStatus }) { function RunTableRow({ run, hiddenColumns, + selected, + onToggleSelected, }: { - run: RunWithStatus; - hiddenColumns: Set; + run: RunWithStatus; + hiddenColumns: Set; + selected: boolean; + onToggleSelected: (id: string) => void; }) { const lifecycleLabel = listLifecycleStatusLabel(run); const statusDisplay = columnStatusDisplay[run.status]; const show = (col: ToggleableColumn) => !hiddenColumns.has(col); return ( - + + + onToggleSelected(run.id)} + ariaLabel={selected ? `Deselect run ${run.title}` : `Select run ${run.title}`} + /> +