diff --git a/canvas/src/components/ApprovalBanner.tsx b/canvas/src/components/ApprovalBanner.tsx index e9baa776..03e5c521 100644 --- a/canvas/src/components/ApprovalBanner.tsx +++ b/canvas/src/components/ApprovalBanner.tsx @@ -16,6 +16,8 @@ interface PendingApproval { export function ApprovalBanner() { const [approvals, setApprovals] = useState([]); + // Guards double-click / double-keypress during in-flight POST. + const [pendingApprovalId, setPendingApprovalId] = useState(null); // Single endpoint — no N+1 per-workspace polling const pollApprovals = useCallback(async () => { @@ -35,6 +37,8 @@ export function ApprovalBanner() { }, [pollApprovals]); const handleDecide = async (approval: PendingApproval, decision: "approved" | "denied") => { + if (pendingApprovalId !== null) return; // guard double-submit + setPendingApprovalId(approval.id); try { await api.post(`/workspaces/${approval.workspace_id}/approvals/${approval.id}/decide`, { decision, @@ -44,6 +48,8 @@ export function ApprovalBanner() { setApprovals((prev) => prev.filter((a) => a.id !== approval.id)); } catch { showToast("Failed to submit decision", "error"); + } finally { + setPendingApprovalId(null); } }; @@ -72,22 +78,25 @@ export function ApprovalBanner() {
diff --git a/canvas/src/components/Toolbar.tsx b/canvas/src/components/Toolbar.tsx index c9b3f976..b2563aee 100644 --- a/canvas/src/components/Toolbar.tsx +++ b/canvas/src/components/Toolbar.tsx @@ -314,7 +314,7 @@ export function Toolbar() {