- 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 | 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 | }; |