- 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
Unified · Split
| @@ -368,6 +368,7 @@ export default function ChatView({ threadId }: ChatViewProps) { · 367 unmodified lines | ||
| 368 | 368 | const textareaRef = useRef<HTMLTextAreaElement>(null); |
| 369 | 369 | const composerCommandInputRef = useRef<HTMLInputElement>(null); |
| 370 | 370 | const composerImagesRef = useRef<ComposerImageAttachment[]>([]); |
| 371 | + const sendInFlightRef = useRef(false); | |
| 371 | 372 | const dragDepthRef = useRef(0); |
| 372 | 373 | const terminalOpenByThreadRef = useRef<Record<string, boolean>>({}); |
| 373 | 374 | |
| @@ -1391,7 +1392,7 @@ export default function ChatView({ threadId }: ChatViewProps) { · 1017 unmodified lines | ||
| 1391 | 1392 | const onSend = async (e: React.SubmitEvent | React.KeyboardEvent) => { |
| 1392 | 1393 | e.preventDefault(); |
| 1393 | 1394 | const api = readNativeApi(); |
| 1394 | − if (!api || !activeThread || isSendBusy || isConnecting) return; | |
| 1395 | + if (!api || !activeThread || isSendBusy || isConnecting || sendInFlightRef.current) return; | |
| 1395 | 1396 | const trimmed = prompt.trim(); |
| 1396 | 1397 | if (!trimmed && composerImages.length === 0) return; |
| 1397 | 1398 | if (!activeProject) return; |
| @@ -1415,6 +1416,9 @@ export default function ChatView({ threadId }: ChatViewProps) { · 17 unmodified lines | ||
| 1415 | 1416 | return; |
| 1416 | 1417 | } |
| 1417 | 1418 | |
| 1419 | + sendInFlightRef.current = true; | |
| 1420 | + setSendPhase(baseBranchForWorktree ? "preparing-worktree" : "sending-turn"); | |
| 1421 | + | |
| 1418 | 1422 | const composerImagesSnapshot = [...composerImages]; |
| 1419 | 1423 | const messageIdForSend = newMessageId(); |
| 1420 | 1424 | const messageCreatedAt = new Date().toISOString(); |
| @@ -1463,9 +1467,17 @@ export default function ChatView({ threadId }: ChatViewProps) { · 42 unmodified lines | ||
| 1463 | 1467 | branch: result.worktree.branch, |
| 1464 | 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 | 1478 | const setupScript = setupProjectScript(activeProject.scripts); |
| 1467 | 1479 | if (setupScript) { |
| 1468 | − void runProjectScript(setupScript, { | |
| 1480 | + await runProjectScript(setupScript, { | |
| 1469 | 1481 | cwd: result.worktree.path, |
| 1470 | 1482 | worktreePath: result.worktree.path, |
| 1471 | 1483 | rememberAsLastInvoked: false, |
| @@ -1544,6 +1556,7 @@ export default function ChatView({ threadId }: ChatViewProps) { · 72 unmodified lines | ||
| 1544 | 1556 | err instanceof Error ? err.message : "Failed to send message.", |
| 1545 | 1557 | ); |
| 1546 | 1558 | } finally { |
| 1559 | + sendInFlightRef.current = false; | |
| 1547 | 1560 | setSendPhase("idle"); |
| 1548 | 1561 | } |
| 1549 | 1562 | }; |