Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 21 additions & 8 deletions apps/web/src/components/ChatView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,7 @@ import {
clearBackgroundDraftSubmissionByRef,
finalizePromotedDraftThreadByRef,
markPromotedDraftThreadByRef,
restoreFailedBackgroundDraftThread,
restoreFailedDraftThread,
composerDraftHasUserContent,
type ComposerFileAttachment,
type ComposerImageAttachment,
Expand Down Expand Up @@ -10023,8 +10023,16 @@ export default function ChatView(props: ChatViewProps) {
createdAt: messageCreatedAt,
},
});
// The composer is already cleared, so until the draft is marked promoting
// new-thread flows would treat it as an empty draft and reuse it while
// the worktree is still being prepared.
const sentDraftThreadRef = isLocalDraftThread
? scopeThreadRef(environmentId, threadIdForSend)
: null;
if (sentDraftThreadRef) {
markPromotedDraftThreadByRef(sentDraftThreadRef);
}
if (backgroundThreadRef) {
markPromotedDraftThreadByRef(backgroundThreadRef);
try {
backgroundDraftOpened = Boolean(
await handleNewThread(
Expand Down Expand Up @@ -10061,12 +10069,15 @@ export default function ChatView(props: ChatViewProps) {
releaseDraftAttachments(composerAttachmentsSnapshot);
}
acknowledgeActiveThreadWoke();
if (backgroundThreadRef) {
// A draft still on screen finalizes once its thread route takes over.
if (sentDraftThreadRef) {
if (backgroundDraftOpened || currentRouteThreadKeyRef.current !== routeThreadKey) {
finalizePromotedDraftThreadByRef(backgroundThreadRef);
finalizePromotedDraftThreadByRef(sentDraftThreadRef);
} else {
clearBackgroundDraftSubmissionByRef(backgroundThreadRef);
clearBackgroundDraftSubmissionByRef(sentDraftThreadRef);
}
}
if (backgroundThreadRef) {
if (backgroundDraftOpened) {
toastManager.add(
stackedThreadToast({
Expand Down Expand Up @@ -10096,11 +10107,13 @@ export default function ChatView(props: ChatViewProps) {
}

if (failure !== null) {
if (submissionIntent === "background" && draftId && draftThread) {
restoreFailedBackgroundDraftThread(
if (isLocalDraftThread && draftId && draftThread) {
restoreFailedDraftThread(
draftId,
draftThread,
wasBootstrapThreadDeleted(squashAtomCommandFailure(failure))
// Foreground sends rotate a deleted thread's id below, with a fresh createdAt.
submissionIntent === "background" &&
wasBootstrapThreadDeleted(squashAtomCommandFailure(failure))
? newThreadId()
: threadIdForSend,
);
Expand Down
5 changes: 3 additions & 2 deletions apps/web/src/components/ThreadRouteView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ export function ThreadRouteView({ target }: { target: ThreadRouteTarget }) {
);
const threadRefs = useThreadRefs();
// The server thread this view is about: the route's own ref, or the draft's
// reserved ref once the server knows it.
// reserved ref once the server knows it. A draft is marked promoting as soon
// as it sends, before the server has created its thread.
const inferredThreadRef = draftSession
? (threadRefs.find(
(ref) =>
Expand All @@ -54,7 +55,7 @@ export function ThreadRouteView({ target }: { target: ThreadRouteTarget }) {
) ?? null)
: null;
const serverThreadRef: ScopedThreadRef | null =
target.kind === "server" ? target.threadRef : (draftSession?.promotedTo ?? inferredThreadRef);
target.kind === "server" ? target.threadRef : inferredThreadRef;
const serverThread = useThreadShell(serverThreadRef);
const backgroundSubmissionPending = useBackgroundDraftSubmissionPending(
target.kind === "draft" ? serverThreadRef : null,
Expand Down
4 changes: 2 additions & 2 deletions apps/web/src/composerDraftStore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ import {
clearBackgroundDraftSubmissionByRef,
finalizePromotedDraftThreadByRef,
markPromotedDraftThreadByRef,
restoreFailedBackgroundDraftThread,
restoreFailedDraftThread,
type ComposerFileAttachment,
type ComposerImageAttachment,
composerFileNeedsReattach,
Expand Down Expand Up @@ -1671,7 +1671,7 @@ describe("composerDraftStore project draft thread mapping", () => {
const freshId = DraftId.make("fresh-background-draft");
store.setProjectDraftThreadId(projectRef, freshId, { threadId: ThreadId.make("fresh-thread") });
store.setPrompt(freshId, "new work");
restoreFailedBackgroundDraftThread(draftId, sentDraft, ThreadId.make("retry-thread"));
restoreFailedDraftThread(draftId, sentDraft, ThreadId.make("retry-thread"));
store.setPrompt(draftId, "retry work");
expect(useComposerDraftStore.getState().getDraftSession(draftId)?.promotedTo).toBeNull();
expect(useComposerDraftStore.getState().getDraftSession(draftId)?.threadId).toBe(
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/composerDraftStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4577,7 +4577,7 @@ export function markPromotedDraftThreadByRef(threadRef: ScopedThreadRef): void {
}
}

export function restoreFailedBackgroundDraftThread(
export function restoreFailedDraftThread(
draftId: DraftId,
draftThread: DraftThreadState,
threadId: ThreadId,
Expand Down
Loading