refactor(desktop): remove redundant handoff draft state

This commit is contained in:
John Choi
2026-10-21 15:26:04 -07:00
parent de80d1c99a
commit f0661278b6
5 changed files with 22 additions and 29 deletions
@@ -130,7 +130,7 @@ async function prepareCloudHandoff(
};
const cwd = readWorkspacePath(config) ?? readWorkspacePath(persisted);
if (!cwd) throw new Error("Cloud handoff requires a workspace path.");
return { sessionId, manager, config, cwd };
return { manager, config, cwd };
}
function assertHandoffAccount(
@@ -1410,7 +1410,6 @@ function ChatThreadPane({
setPendingAttachments,
clearPromptForSend,
promptDraft,
promptInputRef,
setPromptInput,
handlePromptInputChange,
} = usePromptDraft(promptDrafts, threadId);
@@ -2034,7 +2033,7 @@ function ChatThreadPane({
const preserveComposer =
historySession.origin === "cloud" &&
Boolean(
promptInputRef.current.trim() || draftRef.current.attachments.length,
draftRef.current.text.trim() || draftRef.current.attachments.length,
);
if (!preserveComposer) {
setPromptInput(initialPromptDraft ?? "");
@@ -2062,7 +2061,6 @@ function ChatThreadPane({
initialAttachments,
initialPromptDraft,
onInitialPromptDraftConsumed,
promptInputRef,
sessionId,
setPendingAttachments,
setPromptInput,
@@ -110,7 +110,7 @@ describe("usePromptDraft", () => {
send.resolve(false);
await send.finished;
});
expect(current.promptInputRef.current).toBe("");
expect(current.draftRef.current.text).toBe("");
expect(drafts.get("new-session")?.text).toBe("");
});
@@ -137,12 +137,12 @@ describe("usePromptDraft", () => {
first.resolve(false);
await first.finished;
});
expect(current.promptInputRef.current).toBe("");
expect(current.draftRef.current.text).toBe("");
await act(async () => {
second.resolve(false);
await second.finished;
});
expect(current.promptInputRef.current).toBe("Submitted prompt");
expect(current.draftRef.current.text).toBe("Submitted prompt");
});
it("does not let a retired pane's failed send overwrite a newer draft", async () => {
@@ -182,7 +182,7 @@ describe("usePromptDraft", () => {
await send.finished;
});
expect(drafts.has("new-session")).toBe(false);
expect(current.promptInputRef.current).toBe("");
expect(current.draftRef.current.text).toBe("");
});
it("preserves a newly started session's follow-up when reopened through history", async () => {
@@ -195,7 +195,7 @@ describe("usePromptDraft", () => {
act(() => current.handlePromptInputChange("Follow-up to my new session"));
// The same session ID on a different host must not reuse the local thread.
await openSession("remote");
expect(current.promptInputRef.current).toBe("");
expect(current.draftRef.current.text).toBe("");
act(() => current.handlePromptInputChange("Remote follow-up"));
await openSession("local");
expect(appState.navigation.current.activeThreadId).toBe("new-session");
@@ -212,10 +212,10 @@ describe("usePromptDraft", () => {
sessionId: "existing-session",
});
act(() => current.handlePromptInputChange("Unsent follow-up"));
const inputRef = current.promptInputRef;
const draftRef = current.draftRef;
await openSession();
expect(current.promptInputRef).toBe(inputRef);
expect(current.promptInputRef.current).toBe("Unsent follow-up");
expect(current.draftRef).toBe(draftRef);
expect(current.draftRef.current.text).toBe("Unsent follow-up");
expect(appState.threads).toHaveLength(1);
});
@@ -233,7 +233,7 @@ describe("usePromptDraft", () => {
await navigate({ type: "back" });
expect(current.promptDraft.value).toBe("Unsent prompt\nwith more detail");
expect(current.promptInputRef.current).toBe(current.promptDraft.value);
expect(current.draftRef.current.text).toBe(current.promptDraft.value);
await navigate({ type: "forward" });
expect(current.promptDraft.value).toBe("Follow-up draft");
});
@@ -317,7 +317,7 @@ describe("usePromptDraft", () => {
destination: { ...appState.navigation.current, view: "sessions" },
});
await openSession();
expect(current.promptInputRef.current).toBe("Edited follow-up");
expect(current.draftRef.current.text).toBe("Edited follow-up");
expect(current.pendingAttachments).toEqual([image]);
act(() => {
current.handlePromptInputChange("");
@@ -325,7 +325,7 @@ describe("usePromptDraft", () => {
});
await navigate({ type: "back" });
await openSession();
expect(current.promptInputRef.current).toBe("");
expect(current.draftRef.current.text).toBe("");
expect(current.pendingAttachments).toEqual([]);
});
@@ -23,7 +23,6 @@ export function usePromptDraft(
const draftRef = useRef<PromptDraft>(
drafts.get(threadId) ?? { text: "", attachments: [] },
);
const promptInputRef = useRef(draftRef.current.text);
const [pendingAttachments, setAttachments] = useState(
draftRef.current.attachments,
);
@@ -51,14 +50,13 @@ export function usePromptDraft(
);
const [promptDraft, setPromptDraft] = useState(() => ({
version: 0,
value: promptInputRef.current,
value: draftRef.current.text,
}));
const handlePromptInputChange = useCallback((value: string) => {
if (!mountedRef.current) return;
// The composer echoes external replacements. Those acknowledgements
// aren't new edits and must not invalidate a pending send's recovery.
if (promptInputRef.current !== value) revisionRef.current += 1;
promptInputRef.current = value;
if (draftRef.current.text !== value) revisionRef.current += 1;
draftRef.current.text = value;
}, []);
const setPromptInput = useCallback(
@@ -91,7 +89,6 @@ export function usePromptDraft(
draftRef,
pendingAttachments,
setPendingAttachments,
promptInputRef,
promptDraft,
setPromptInput,
handlePromptInputChange,
@@ -188,7 +188,7 @@ export function createHandoffLifecycle(effects: HandoffLifecycleEffects) {
},
async onRpcRejected(
sourceSessionId: string,
ctx: RpcContext & { error: unknown; silent?: boolean },
ctx: RpcContext & { error: unknown },
): Promise<void> {
const attempt = current(sourceSessionId, ctx.handoffAttemptId);
if (!attempt || attempt.completed) return;
@@ -214,14 +214,12 @@ export function createHandoffLifecycle(effects: HandoffLifecycleEffects) {
retryDraft: ctx.nextCommand,
retryAttachments: ctx.sourceAttachments,
});
if (!ctx.silent) {
effects.toast({
title: "Handoff failed",
description: message,
variant: "destructive",
connectUrl,
});
}
effects.toast({
title: "Handoff failed",
description: message,
variant: "destructive",
connectUrl,
});
},
};
}