diff --git a/apps/web/src/components/Sidebar.logic.test.ts b/apps/web/src/components/Sidebar.logic.test.ts index 669c805492bc..3e2e8be2ce18 100644 --- a/apps/web/src/components/Sidebar.logic.test.ts +++ b/apps/web/src/components/Sidebar.logic.test.ts @@ -624,10 +624,45 @@ describe("getFallbackThreadIdAfterDelete", () => { deletedThreadId: ThreadId.make("thread-active"), deletedThreadIds: new Set([ThreadId.make("thread-active"), ThreadId.make("thread-newest")]), sortOrder: "created_at", + now: "2026-03-09T10:10:00.000Z", }); expect(fallbackThreadId).toBe(ThreadId.make("thread-next")); }); + + it.each([ + [ + "snoozed", + { snoozedUntil: "2099-01-01T00:00:00.000Z", snoozedAt: "2026-03-09T10:06:00.000Z" }, + ], + ["settled", { settledOverride: "settled", settledAt: "2026-03-09T10:06:00.000Z" }], + ] as const)("skips a %s thread parked on its shelf", (_state, parked) => { + const fallbackThreadId = getFallbackThreadIdAfterDelete({ + threads: [ + makeThread({ + id: ThreadId.make("thread-visible"), + createdAt: "2026-03-09T10:01:00.000Z", + updatedAt: "2026-03-09T10:01:00.000Z", + }), + makeThread({ + id: ThreadId.make("thread-parked"), + createdAt: "2026-03-09T10:02:00.000Z", + updatedAt: "2026-03-09T10:06:00.000Z", + ...parked, + }), + makeThread({ + id: ThreadId.make("thread-active"), + createdAt: "2026-03-09T10:03:00.000Z", + updatedAt: "2026-03-09T10:03:00.000Z", + }), + ], + deletedThreadId: ThreadId.make("thread-active"), + sortOrder: "created_at", + now: "2026-03-09T10:10:00.000Z", + }); + + expect(fallbackThreadId).toBe(ThreadId.make("thread-visible")); + }); }); describe("sortScopedProjectsForSidebar", () => { diff --git a/apps/web/src/components/Sidebar.logic.ts b/apps/web/src/components/Sidebar.logic.ts index 55cf694c2b79..c20789e2a916 100644 --- a/apps/web/src/components/Sidebar.logic.ts +++ b/apps/web/src/components/Sidebar.logic.ts @@ -1211,14 +1211,17 @@ export function resolveThreadStatusPill(input: { } export function getFallbackThreadIdAfterDelete< - T extends Pick & ThreadSortInput, + T extends Pick & + ThreadSortInput & + ThreadSnoozeShell, >(input: { threads: readonly T[]; deletedThreadId: T["id"]; sortOrder: SidebarThreadSortOrder; + now: string; deletedThreadIds?: ReadonlySet; }): T["id"] | null { - const { deletedThreadId, deletedThreadIds, sortOrder, threads } = input; + const { deletedThreadId, deletedThreadIds, sortOrder, now, threads } = input; const deletedThread = threads.find((thread) => thread.id === deletedThreadId); if (!deletedThread) { return null; @@ -1230,7 +1233,9 @@ export function getFallbackThreadIdAfterDelete< (thread) => thread.projectId === deletedThread.projectId && thread.id !== deletedThreadId && - !deletedThreadIds?.has(thread.id), + !deletedThreadIds?.has(thread.id) && + thread.settledOverride !== "settled" && + !effectiveSnoozed(thread, { now }), ), sortOrder, )[0]?.id ?? null diff --git a/apps/web/src/hooks/useThreadActions.ts b/apps/web/src/hooks/useThreadActions.ts index e5eedffc1669..904b94ebfc5c 100644 --- a/apps/web/src/hooks/useThreadActions.ts +++ b/apps/web/src/hooks/useThreadActions.ts @@ -554,6 +554,7 @@ export function useThreadActions() { deletedThreadId: threadRef.threadId, deletedThreadIds, sortOrder: sidebarThreadSortOrder, + now: new Date().toISOString(), }); const deleteResult = await deleteThreadMutation({ environmentId: threadRef.environmentId,