Skip to content

Commit ce7166a

Browse files
DylanMerigaudclaude
andcommitted
#52 Stop locking the queue during a run; only hold the header steady on hover
Selecting another invoice (or opening a recent run) is now always allowed, even mid-run or while awaiting a decision: `select` already resets/aborts the stream and drops ?run=, so switching is safe, and the whole `locked` gate (dimmed, disabled, cursor-not-allowed rows + a disabled RecentRuns) is removed. The only thing kept: once a run is SHOWN on the pane (trace has events), hovering another queue row no longer swaps the header/document out from under it (previewId = (!runShown && hoveredId) || selectedId). At idle, hover still previews as before. Dropped the now-dead `disabled` prop from RecentRuns. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 3f4a294 commit ce7166a

2 files changed

Lines changed: 18 additions & 27 deletions

File tree

components/dashboard.tsx

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -195,12 +195,17 @@ export const Dashboard = ({
195195
// Lock the queue while a run is in flight, switching invoices mid-run would
196196
// abort the stream and is confusing. (Awaiting a human decision still locks:
197197
// resolve it with Approve/Reject first.)
198-
const locked = state.status === "running" || state.status === "awaiting";
199-
200-
// Hovering a row previews its PDF on the right (idle only). Falls back to the
201-
// selected row; ignored while locked so a hover can't replace a live run.
198+
// A run is on screen (live, awaiting a decision, or a replayed one) whenever the trace
199+
// has events. Selecting another invoice is always allowed, it aborts the stream and
200+
// starts clean; what we DON'T do while a run is shown is let a hover swap the pane out
201+
// from under it.
202+
const runShown = state.trace.length > 0;
203+
204+
// Hovering a row previews its PDF on the right, but only when NO run is shown (idle):
205+
// once a run is on the pane the header + document stay on the selected invoice, a hover
206+
// over another row doesn't override them. Falls back to the selected row.
202207
const [hoveredId, setHoveredId] = useState<string | null>(null);
203-
const previewId = (!locked && hoveredId) || selectedId;
208+
const previewId = (!runShown && hoveredId) || selectedId;
204209
// The trace-pane title follows whatever document is shown (hover preview or the
205210
// selected/running invoice) so the header never contradicts the PDF on screen.
206211
const previewItem = queue.find((q) => q.id === previewId) ?? selected;
@@ -350,7 +355,7 @@ export const Dashboard = ({
350355
);
351356

352357
const select = (id: string) => {
353-
if (id === selectedId || locked) return;
358+
if (id === selectedId) return;
354359
setSelectedId(id);
355360
setGateChoices({});
356361
setGateReasons({});
@@ -399,9 +404,6 @@ export const Dashboard = ({
399404
const outcome: Outcome = isSelected
400405
? state.outcome
401406
: "pending";
402-
// While a run is in flight the queue is locked: the active row stays
403-
// highlighted, the others dim and stop responding to clicks.
404-
const dimmed = locked && !isSelected;
405407
// Hovering a flagged row reveals WHY in plain English (a Radix tooltip),
406408
// so the queue explains itself before you run anything. Clean rows have
407409
// nothing to explain.
@@ -415,11 +417,9 @@ export const Dashboard = ({
415417
onMouseLeave={() =>
416418
setHoveredId((h) => (h === item.id ? null : h))
417419
}
418-
disabled={dimmed}
419-
aria-disabled={dimmed}
420420
className={`relative flex w-full items-start gap-3 px-4 py-3 text-left transition-colors ${
421421
isSelected ? "bg-accent-soft/50" : "hover:bg-subtle/70"
422-
} ${dimmed ? "cursor-not-allowed opacity-40" : ""}`}
422+
}`}
423423
>
424424
{isSelected && (
425425
<span
@@ -468,7 +468,7 @@ export const Dashboard = ({
468468
);
469469
return (
470470
<li key={item.id}>
471-
{explain && !locked ? (
471+
{explain ? (
472472
<Tooltip>
473473
<TooltipTrigger asChild>{row}</TooltipTrigger>
474474
<TooltipContent
@@ -510,7 +510,7 @@ export const Dashboard = ({
510510
{/* The audit trail: recent runs. Clicking one navigates to its `?run=<id>`,
511511
and the URL read-effect replays it, so a click, a refresh, and a shared
512512
link all take the exact same path. */}
513-
<RecentRuns onOpen={(id) => setRunUrl(id)} disabled={locked} />
513+
<RecentRuns onOpen={(id) => setRunUrl(id)} />
514514
</div>
515515

516516
{/* RIGHT: the workflow graph is the hero and owns the whole pane. No header,

components/recent-runs.tsx

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,11 @@ const timeAgo = (iso: string): string => {
4747

4848
export const RecentRuns = ({
4949
/** Open a stored run by navigating to its `?run=<id>`. The dashboard's URL effect
50-
does the actual fetch + replay, so a click here is just navigation. */
50+
does the actual fetch + replay, so a click here is just navigation. Opening one
51+
while a live run streams is fine: the replay aborts the stream. */
5152
onOpen,
52-
/** Disabled while a live run is in flight, opening one would clobber it. */
53-
disabled,
5453
}: {
5554
onOpen: (id: string) => void;
56-
disabled: boolean;
5755
}) => {
5856
const history = useQuery(orpc.history.queryOptions());
5957

@@ -83,10 +81,7 @@ export const RecentRuns = ({
8381
const moreCount =
8482
hiddenBelow > 0 ? Math.max(1, Math.round(hiddenBelow / ROW_PX)) : 0;
8583

86-
const open = (id: string) => {
87-
if (disabled) return;
88-
onOpen(id);
89-
};
84+
const open = (id: string) => onOpen(id);
9085

9186
return (
9287
<Card className="flex flex-col overflow-hidden">
@@ -116,11 +111,7 @@ export const RecentRuns = ({
116111
type="button"
117112
data-testid={`run-history-${r.id}`}
118113
onClick={() => open(r.id)}
119-
disabled={disabled}
120-
aria-disabled={disabled}
121-
className={`flex w-full items-center gap-2.5 px-4 py-2 text-left transition-colors hover:bg-subtle/70 ${
122-
disabled ? "cursor-not-allowed opacity-40" : ""
123-
}`}
114+
className="flex w-full items-center gap-2.5 px-4 py-2 text-left transition-colors hover:bg-subtle/70"
124115
>
125116
<span
126117
aria-hidden

0 commit comments

Comments
 (0)