History

Prevent duplicate sends and sync worktree branch state

- Guard `onSend` with an in-flight ref to block double submits - Set send phase before worktree prep and always clear in-flight state in `finally` - Sync thread branch/worktree path immediately and await setup script execution

Julius Marminge committed 8 months ago

Commit 7b0ae431 parent 69dbc5d1 file · +15 −2Browse files

Unified · Split

apps/web/src/components/ChatView.tsx+15−2
@@ -368,6 +368,7 @@ export default function ChatView({ threadId }: ChatViewProps) { · 367 unmodified lines
368 const textareaRef = useRef<HTMLTextAreaElement>(null);368 const textareaRef = useRef<HTMLTextAreaElement>(null);
369 const composerCommandInputRef = useRef<HTMLInputElement>(null);369 const composerCommandInputRef = useRef<HTMLInputElement>(null);
370 const composerImagesRef = useRef<ComposerImageAttachment[]>([]);370 const composerImagesRef = useRef<ComposerImageAttachment[]>([]);
371 const sendInFlightRef = useRef(false);
371 const dragDepthRef = useRef(0);372 const dragDepthRef = useRef(0);
372 const terminalOpenByThreadRef = useRef<Record<string, boolean>>({});373 const terminalOpenByThreadRef = useRef<Record<string, boolean>>({});
373 374
@@ -1391,7 +1392,7 @@ export default function ChatView({ threadId }: ChatViewProps) { · 1017 unmodified lines
1391 const onSend = async (e: React.SubmitEvent | React.KeyboardEvent) => {1392 const onSend = async (e: React.SubmitEvent | React.KeyboardEvent) => {
1392 e.preventDefault();1393 e.preventDefault();
1393 const api = readNativeApi();1394 const api = readNativeApi();
1394 if (!api || !activeThread || isSendBusy || isConnecting) return;1395 if (!api || !activeThread || isSendBusy || isConnecting || sendInFlightRef.current) return;
1395 const trimmed = prompt.trim();1396 const trimmed = prompt.trim();
1396 if (!trimmed && composerImages.length === 0) return;1397 if (!trimmed && composerImages.length === 0) return;
1397 if (!activeProject) return;1398 if (!activeProject) return;
@@ -1415,6 +1416,9 @@ export default function ChatView({ threadId }: ChatViewProps) { · 17 unmodified lines
1415 return;1416 return;
1416 }1417 }
1417 1418
1419 sendInFlightRef.current = true;
1420 setSendPhase(baseBranchForWorktree ? "preparing-worktree" : "sending-turn");
1421
1418 const composerImagesSnapshot = [...composerImages];1422 const composerImagesSnapshot = [...composerImages];
1419 const messageIdForSend = newMessageId();1423 const messageIdForSend = newMessageId();
1420 const messageCreatedAt = new Date().toISOString();1424 const messageCreatedAt = new Date().toISOString();
@@ -1463,9 +1467,17 @@ export default function ChatView({ threadId }: ChatViewProps) { · 42 unmodified lines
1463 branch: result.worktree.branch,1467 branch: result.worktree.branch,
1464 worktreePath: result.worktree.path,1468 worktreePath: result.worktree.path,
1465 });1469 });
1470 // Keep local thread state in sync immediately so terminal drawer opens
1471 // with the worktree cwd/env instead of briefly using the project root.
1472 dispatch({
1473 type: "SET_THREAD_BRANCH",
1474 threadId: threadIdForSend,
1475 branch: result.worktree.branch,
1476 worktreePath: result.worktree.path,
1477 });
1466 const setupScript = setupProjectScript(activeProject.scripts);1478 const setupScript = setupProjectScript(activeProject.scripts);
1467 if (setupScript) {1479 if (setupScript) {
1468 void runProjectScript(setupScript, {1480 await runProjectScript(setupScript, {
1469 cwd: result.worktree.path,1481 cwd: result.worktree.path,
1470 worktreePath: result.worktree.path,1482 worktreePath: result.worktree.path,
1471 rememberAsLastInvoked: false,1483 rememberAsLastInvoked: false,
@@ -1544,6 +1556,7 @@ export default function ChatView({ threadId }: ChatViewProps) { · 72 unmodified lines
1544 err instanceof Error ? err.message : "Failed to send message.",1556 err instanceof Error ? err.message : "Failed to send message.",
1545 );1557 );
1546 } finally {1558 } finally {
1559 sendInFlightRef.current = false;
1547 setSendPhase("idle");1560 setSendPhase("idle");
1548 }1561 }
1549 };1562 };