From b021497bc84ef725b0e5abc8bdd8d3b790b8e929 Mon Sep 17 00:00:00 2001 From: Brooklyn Nicholson Date: Tue, 9 Jun 2026 18:26:54 -0500 Subject: [PATCH] fix(desktop): show a staging spinner in the edit composer while OS drops upload The message-edit composer staged dropped OS files asynchronously with no visible state, so confirming the edit before the upload resolved could send the message without the gateway-side ref (helix4u review note on #43109). Add a staging flag: while uploadOsDropRefs is in flight, show a small spinner pill in the bubble and block submit (disabled send button + submitEdit guard) so the edit can't outrace the ref insertion. New `attachingFile` i18n string across en/zh/zh-hant/ja. --- .../src/components/assistant-ui/thread.tsx | 30 ++++++++++++++----- apps/desktop/src/i18n/en.ts | 3 +- apps/desktop/src/i18n/ja.ts | 3 +- apps/desktop/src/i18n/types.ts | 1 + apps/desktop/src/i18n/zh-hant.ts | 3 +- apps/desktop/src/i18n/zh.ts | 3 +- 6 files changed, 32 insertions(+), 11 deletions(-) diff --git a/apps/desktop/src/components/assistant-ui/thread.tsx b/apps/desktop/src/components/assistant-ui/thread.tsx index ac0f277d700..90d97209f41 100644 --- a/apps/desktop/src/components/assistant-ui/thread.tsx +++ b/apps/desktop/src/components/assistant-ui/thread.tsx @@ -971,6 +971,10 @@ const UserEditComposer: FC = ({ cwd, gateway, sessionId } const [triggerPlacement, setTriggerPlacement] = useState<'bottom' | 'top'>('top') const [focusRequestId, setFocusRequestId] = useState(0) const [submitting, setSubmitting] = useState(false) + // True while OS-drop files are being staged/uploaded into the session. Blocks + // submit and shows a spinner so confirming the edit can't race the async + // upload and drop the gateway-side ref before it lands in the draft. + const [staging, setStaging] = useState(false) const expanded = draft.includes('\n') const canSubmit = draft.trim().length > 0 const at = useAtCompletions({ cwd, gateway, sessionId }) @@ -1324,11 +1328,14 @@ const UserEditComposer: FC = ({ cwd, gateway, sessionId } } if (osDrops.length) { - void uploadOsDropRefs(osDrops).then(refs => { - if (insertRefStrings(refs)) { - triggerHaptic('selection') - } - }) + setStaging(true) + void uploadOsDropRefs(osDrops) + .then(refs => { + if (insertRefStrings(refs)) { + triggerHaptic('selection') + } + }) + .finally(() => setStaging(false)) } } @@ -1360,7 +1367,7 @@ const UserEditComposer: FC = ({ cwd, gateway, sessionId } const submitEdit = (editor: HTMLDivElement) => { const nextDraft = syncDraftFromEditor(editor) - if (submitting || !nextDraft.trim()) { + if (submitting || staging || !nextDraft.trim()) { return } @@ -1517,10 +1524,19 @@ const UserEditComposer: FC = ({ cwd, gateway, sessionId } suppressContentEditableWarning /> + {staging && ( + + + {copy.attachingFile} + + )}