From 115e3b5add728bf8dbdf18f8f6396d4f253ad2b7 Mon Sep 17 00:00:00 2001 From: Fabio Rehm Date: Tue, 15 Sep 2026 06:49:55 -0300 Subject: [PATCH 1/2] fix(app): confirm cascading thread archives --- .../dialogs/ThreadArchiveDialog.test.tsx | 83 ++++++++++++++++ .../dialogs/ThreadArchiveDialog.tsx | 97 +++++++++++++++++++ .../thread/ThreadActionsMenu.test.tsx | 2 +- .../components/thread/ThreadActionsMenu.tsx | 10 +- .../thread/ThreadActionsProvider.test.tsx | 50 +++++++--- .../thread/ThreadActionsProvider.tsx | 57 ++++++++++- .../thread/ThreadArchiveQuickAction.test.tsx | 12 +-- .../app/src/lib/plugin-sidebar-hooks.test.tsx | 2 +- apps/app/src/lib/plugin-sidebar-hooks.ts | 2 +- .../ThreadArchiveCommandHandler.test.tsx | 14 +-- .../ThreadArchiveCommandHandler.tsx | 4 +- packages/plugin-sdk/src/app-contract.ts | 2 +- .../references/frontend-registration.md | 8 +- 13 files changed, 297 insertions(+), 46 deletions(-) create mode 100644 apps/app/src/components/dialogs/ThreadArchiveDialog.test.tsx create mode 100644 apps/app/src/components/dialogs/ThreadArchiveDialog.tsx diff --git a/apps/app/src/components/dialogs/ThreadArchiveDialog.test.tsx b/apps/app/src/components/dialogs/ThreadArchiveDialog.test.tsx new file mode 100644 index 00000000000..e6950930eb1 --- /dev/null +++ b/apps/app/src/components/dialogs/ThreadArchiveDialog.test.tsx @@ -0,0 +1,83 @@ +// @vitest-environment jsdom + +import { cleanup, fireEvent, render, screen } from "@testing-library/react"; +import { makeThread } from "@bb/test-helpers/domain-fixtures"; +import { afterEach, describe, expect, it, vi } from "vitest"; +import { ThreadArchiveDialog } from "./ThreadArchiveDialog"; + +afterEach(() => { + cleanup(); +}); + +function renderDialog({ + childThreadCount, + status = "idle", +}: { + childThreadCount?: number; + status?: "idle" | "starting" | "active" | "stopping"; +} = {}) { + const onArchive = vi.fn(); + const onOpenChange = vi.fn(); + const thread = makeThread({ status }); + const view = render( + , + ); + return { onArchive, onOpenChange, thread, view }; +} + +describe("ThreadArchiveDialog", () => { + it("announces the cascade with singular and plural child counts", () => { + const { view } = renderDialog({ childThreadCount: 1 }); + expect( + screen.getByText(/1 child thread will be archived too\./), + ).toBeTruthy(); + + view.unmount(); + renderDialog({ childThreadCount: 3 }); + expect( + screen.getByText(/3 child threads will be archived too\./), + ).toBeTruthy(); + }); + + it("omits the cascade sentence when the thread has no children", () => { + renderDialog(); + + expect(screen.queryByText(/will be archived too/)).toBeNull(); + expect( + screen.getByText( + "Archived threads stay available and can be unarchived.", + ), + ).toBeTruthy(); + }); + + it.each(["starting", "active", "stopping"] as const)( + "warns that current work will stop for a %s thread", + (status) => { + renderDialog({ status }); + expect(screen.getByText(/This will stop current work\./)).toBeTruthy(); + }, + ); + + it("omits the active-work warning for an idle thread", () => { + renderDialog(); + expect(screen.queryByText(/This will stop current work\./)).toBeNull(); + }); + + it("archives only when confirmation is accepted", () => { + const { onArchive, onOpenChange, thread } = renderDialog({ + childThreadCount: 2, + }); + + fireEvent.click(screen.getByRole("button", { name: "Cancel" })); + expect(onArchive).not.toHaveBeenCalled(); + expect(onOpenChange).toHaveBeenCalledWith(false); + + fireEvent.click(screen.getByRole("button", { name: "Archive thread" })); + expect(onArchive).toHaveBeenCalledWith({ thread, childThreadCount: 2 }); + }); +}); diff --git a/apps/app/src/components/dialogs/ThreadArchiveDialog.tsx b/apps/app/src/components/dialogs/ThreadArchiveDialog.tsx new file mode 100644 index 00000000000..4874938a633 --- /dev/null +++ b/apps/app/src/components/dialogs/ThreadArchiveDialog.tsx @@ -0,0 +1,97 @@ +import type { Thread } from "@bb/domain"; +import { Button } from "@bb/shared-ui/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@bb/shared-ui/dialog"; + +export interface ThreadArchiveDialogTarget { + thread: Thread; + childThreadCount?: number; +} + +interface ThreadArchiveDialogProps { + target: ThreadArchiveDialogTarget | null; + pending: boolean; + onOpenChange: (open: boolean) => void; + onArchive: (target: ThreadArchiveDialogTarget) => void; +} + +export function ThreadArchiveDialog({ + target, + pending, + onOpenChange, + onArchive, +}: ThreadArchiveDialogProps) { + return ( + + + {target ? ( + + ) : null} + + + ); +} + +interface ThreadArchiveDialogContentProps { + target: ThreadArchiveDialogTarget; + pending: boolean; + onOpenChange: (open: boolean) => void; + onArchive: (target: ThreadArchiveDialogTarget) => void; +} + +export function ThreadArchiveDialogContent({ + target, + pending, + onOpenChange, + onArchive, +}: ThreadArchiveDialogContentProps) { + const childThreadCount = target.childThreadCount ?? 0; + const active = + target.thread.status === "starting" || + target.thread.status === "active" || + target.thread.status === "stopping"; + const sentences = [ + active ? "This will stop current work." : null, + childThreadCount > 0 + ? `${childThreadCount} child ${childThreadCount === 1 ? "thread" : "threads"} will be archived too.` + : null, + "Archived threads stay available and can be unarchived.", + ].filter((sentence): sentence is string => sentence !== null); + + return ( + <> + + Archive thread? + {sentences.join(" ")} + + + + + + + ); +} diff --git a/apps/app/src/components/thread/ThreadActionsMenu.test.tsx b/apps/app/src/components/thread/ThreadActionsMenu.test.tsx index 70e1506dd52..61158215314 100644 --- a/apps/app/src/components/thread/ThreadActionsMenu.test.tsx +++ b/apps/app/src/components/thread/ThreadActionsMenu.test.tsx @@ -30,7 +30,7 @@ import { useSidebarRename } from "../sidebar/SidebarInlineRename"; const moveThreadToSection = vi.hoisted(() => vi.fn()); const copyToClipboardWithToast = vi.hoisted(() => vi.fn()); const threadActions = vi.hoisted(() => ({ - archiveThreadAndChildren: vi.fn(), + requestArchive: vi.fn(), requestDelete: vi.fn(), requestRename: vi.fn(), togglePin: vi.fn(), diff --git a/apps/app/src/components/thread/ThreadActionsMenu.tsx b/apps/app/src/components/thread/ThreadActionsMenu.tsx index 9264c34b43a..1210b4887db 100644 --- a/apps/app/src/components/thread/ThreadActionsMenu.tsx +++ b/apps/app/src/components/thread/ThreadActionsMenu.tsx @@ -184,7 +184,7 @@ function ThreadActionsMenuItems({ surface, }: ThreadActionsMenuItemsProps) { const { - archiveThreadAndChildren, + requestArchive, requestRename, requestDelete, togglePin, @@ -307,7 +307,9 @@ function ThreadActionsMenuItems({ unarchiveThread(thread); return; } - archiveThreadAndChildren(thread); + window.setTimeout(() => { + requestArchive(thread); + }, 0); }} > {isArchived ? "Unarchive" : "Archive"} @@ -353,7 +355,7 @@ export function ThreadArchiveQuickAction({ className?: string; disabled?: boolean; }) { - const { archiveThreadAndChildren, unarchiveThread } = useThreadActions(); + const { requestArchive, unarchiveThread } = useThreadActions(); const isArchived = thread.archivedAt != null; const label = isArchived ? "Unarchive" : "Archive"; return ( @@ -373,7 +375,7 @@ export function ThreadArchiveQuickAction({ unarchiveThread(thread); return; } - archiveThreadAndChildren(thread); + requestArchive(thread); }} > ({ closePanesForThreads: vi.fn(), - dialogOnClose: vi.fn(), - dialogOnOpen: vi.fn(), - dialogOnOpenChange: vi.fn(), mutation: vi.fn(), navigate: vi.fn(), pathname: "/", @@ -94,15 +91,6 @@ vi.mock("@/lib/sdk", () => ({ }, })); -vi.mock("@/hooks/useDialogState", () => ({ - useDialogState: () => ({ - onClose: mocks.dialogOnClose, - onOpen: mocks.dialogOnOpen, - onOpenChange: mocks.dialogOnOpenChange, - target: null, - }), -})); - vi.mock("@/hooks/useRouteState", () => ({ useRouteState: () => ({ threadId: mocks.viewedThreadId }), })); @@ -121,9 +109,9 @@ function makeThread(overrides: Partial = {}): Thread { } function ArchiveButton({ thread }: { thread: Thread }) { - const { archiveThreadAndChildren } = useThreadActions(); + const { requestArchive } = useThreadActions(); return ( - ); @@ -152,6 +140,9 @@ beforeEach(() => { archivedThreadIds: ["thr_child", "thr_parent"], ok: true, }); + vi.mocked(sdk.threads.childSummary).mockResolvedValue({ + nonDeletedChildCount: 1, + }); vi.mocked(sdk.threads.unarchive).mockResolvedValue({ ok: true }); mocks.closePanesForThreads.mockReturnValue({ focusedRoute: null, @@ -164,11 +155,42 @@ afterEach(() => { vi.clearAllMocks(); }); +describe("ThreadActionsProvider archive confirmation", () => { + it("reports child threads and archives nothing before confirmation", async () => { + vi.mocked(sdk.threads.childSummary).mockResolvedValue({ + nonDeletedChildCount: 4, + }); + renderProvider(); + + fireEvent.click(screen.getByRole("button", { name: "Archive" })); + + expect( + await screen.findByText(/4 child threads will be archived too\./), + ).not.toBeNull(); + expect(sdk.threads.archiveAll).not.toHaveBeenCalled(); + }); + + it("leaves a thread unchanged when confirmation is cancelled", async () => { + renderProvider(); + + fireEvent.click(screen.getByRole("button", { name: "Archive" })); + fireEvent.click(await screen.findByRole("button", { name: "Cancel" })); + + expect( + screen.queryByRole("heading", { name: "Archive thread?" }), + ).toBeNull(); + expect(sdk.threads.archiveAll).not.toHaveBeenCalled(); + }); +}); + describe("ThreadActionsProvider archive feedback", () => { it("shows one archive toast whose Undo restores the parent and children", async () => { renderProvider(); fireEvent.click(screen.getByRole("button", { name: "Archive" })); + fireEvent.click( + await screen.findByRole("button", { name: "Archive thread" }), + ); await vi.waitFor(() => { expect(appToast.success).toHaveBeenCalledTimes(1); diff --git a/apps/app/src/components/thread/ThreadActionsProvider.tsx b/apps/app/src/components/thread/ThreadActionsProvider.tsx index a5f6ce489d3..a1b5ab69989 100644 --- a/apps/app/src/components/thread/ThreadActionsProvider.tsx +++ b/apps/app/src/components/thread/ThreadActionsProvider.tsx @@ -39,6 +39,10 @@ import { ThreadDeleteDialog, type ThreadDeleteDialogTarget, } from "@/components/dialogs/ThreadDeleteDialog"; +import { + ThreadArchiveDialog, + type ThreadArchiveDialogTarget, +} from "@/components/dialogs/ThreadArchiveDialog"; import { ArchivedThreadToastDescription } from "@/components/thread/ArchivedThreadToastDescription"; import { destroyPersistedBrowserViewsForThread } from "@/components/secondary-panel/browserViewVisibilityCoordinator"; import { getThreadReadToggleAction } from "@bb/client-core"; @@ -47,7 +51,7 @@ import { getDesktopBrowserApi } from "@/lib/bb-desktop"; import { useRouteNavigate } from "@/components/ui/app-route-anchor"; export interface ThreadActionsContextValue { - archiveThreadAndChildren: (thread: Thread) => void; + requestArchive: (thread: Thread) => void; renameThreadAsync: (threadId: string, title: string) => Promise; requestRename: (thread: Thread) => void; requestDelete: (thread: Thread) => void; @@ -74,6 +78,11 @@ interface ThreadActionsProviderProps { children: ReactNode; } +interface ArchiveThreadActionRequest { + closeDialog: () => void; + thread: Thread; +} + interface DeleteThreadActionRequest { childThreadsConfirmed: boolean; closeDialog: () => void; @@ -125,9 +134,12 @@ export function ThreadActionsProvider({ const renameDialog = useDialogState(); const deleteDialog = useDialogState(); + const archiveDialog = useDialogState(); const { onClose: closeRenameDialog, onOpen: openRenameDialog } = renameDialog; const { onClose: closeDeleteDialog, onOpen: openDeleteDialog } = deleteDialog; + const { onClose: closeArchiveDialog, onOpen: openArchiveDialog } = + archiveDialog; useEffect(() => { return () => { @@ -301,10 +313,11 @@ export function ThreadActionsProvider({ [unarchiveMutate], ); - const archiveThreadAndChildrenAction = useCallback( - (thread: Thread) => { + const performArchive = useCallback( + ({ closeDialog, thread }: ArchiveThreadActionRequest) => { archiveThreadAndChildrenMutateAsync({ id: thread.id }).then( (response) => { + closeDialog(); const viewedThreadId = viewedThreadIdRef.current; const archiveDisplacedThread = viewedThreadId === thread.id; const closeResult = closePanesForThreads( @@ -374,6 +387,7 @@ export function ThreadActionsProvider({ }); }, (error: unknown) => { + closeDialog(); showMutationErrorToast({ error, fallbackMessage: "Failed to archive thread and children", @@ -391,6 +405,33 @@ export function ThreadActionsProvider({ ], ); + const requestArchive = useCallback( + async (thread: Thread) => { + const controller = claimThreadActionContextAbortController(); + const context = await loadThreadActionContext(thread, controller.signal); + if (context === null || controller.signal.aborted) return; + if (threadActionContextAbortRef.current === controller) { + threadActionContextAbortRef.current = null; + } + openArchiveDialog(buildDialogTargetFromContext({ thread }, context)); + }, + [ + claimThreadActionContextAbortController, + loadThreadActionContext, + openArchiveDialog, + ], + ); + + const confirmArchive = useCallback( + (target: ThreadArchiveDialogTarget) => { + performArchive({ + closeDialog: closeArchiveDialog, + thread: target.thread, + }); + }, + [closeArchiveDialog, performArchive], + ); + const toggleRead = useCallback( (thread: Thread) => { if (getThreadReadToggleAction(thread) === "mark_unread") { @@ -437,15 +478,15 @@ export function ThreadActionsProvider({ () => ({ renameThreadAsync, requestRename, + requestArchive, requestDelete, - archiveThreadAndChildren: archiveThreadAndChildrenAction, unarchiveThread: unarchiveThreadAction, togglePin, toggleRead, }), [ - archiveThreadAndChildrenAction, renameThreadAsync, + requestArchive, requestRename, requestDelete, togglePin, @@ -469,6 +510,12 @@ export function ThreadActionsProvider({ onOpenChange={deleteDialog.onOpenChange} onDelete={confirmDelete} /> + ); } diff --git a/apps/app/src/components/thread/ThreadArchiveQuickAction.test.tsx b/apps/app/src/components/thread/ThreadArchiveQuickAction.test.tsx index 1375a8c8e91..23ff1a04752 100644 --- a/apps/app/src/components/thread/ThreadArchiveQuickAction.test.tsx +++ b/apps/app/src/components/thread/ThreadArchiveQuickAction.test.tsx @@ -7,25 +7,25 @@ import { afterEach, describe, expect, it, vi } from "vitest"; import { ThreadArchiveQuickAction } from "./ThreadActionsMenu"; const mocks = vi.hoisted(() => ({ - archiveThreadAndChildren: vi.fn(), + requestArchive: vi.fn(), unarchiveThread: vi.fn(), })); vi.mock("./ThreadActionsProvider", () => ({ useThreadActions: () => ({ - archiveThreadAndChildren: mocks.archiveThreadAndChildren, + requestArchive: mocks.requestArchive, unarchiveThread: mocks.unarchiveThread, }), })); afterEach(() => { cleanup(); - mocks.archiveThreadAndChildren.mockReset(); + mocks.requestArchive.mockReset(); mocks.unarchiveThread.mockReset(); }); describe("ThreadArchiveQuickAction", () => { - it("archives the thread on one click without bubbling to the row", () => { + it("requests archive confirmation on one click without bubbling to the row", () => { const onRowClick = vi.fn(); const thread = makeThread(); render( @@ -38,7 +38,7 @@ describe("ThreadArchiveQuickAction", () => { fireEvent.click(screen.getByRole("button", { name: "Archive thread" })); - expect(mocks.archiveThreadAndChildren).toHaveBeenCalledWith(thread); + expect(mocks.requestArchive).toHaveBeenCalledWith(thread); expect(mocks.unarchiveThread).not.toHaveBeenCalled(); expect(onRowClick).not.toHaveBeenCalled(); }); @@ -54,6 +54,6 @@ describe("ThreadArchiveQuickAction", () => { fireEvent.click(screen.getByRole("button", { name: "Unarchive thread" })); expect(mocks.unarchiveThread).toHaveBeenCalledWith(thread); - expect(mocks.archiveThreadAndChildren).not.toHaveBeenCalled(); + expect(mocks.requestArchive).not.toHaveBeenCalled(); }); }); diff --git a/apps/app/src/lib/plugin-sidebar-hooks.test.tsx b/apps/app/src/lib/plugin-sidebar-hooks.test.tsx index ea0ca56313c..374cfdb74dd 100644 --- a/apps/app/src/lib/plugin-sidebar-hooks.test.tsx +++ b/apps/app/src/lib/plugin-sidebar-hooks.test.tsx @@ -76,7 +76,7 @@ vi.mock("@/hooks/queries/host-queries", () => { vi.mock("@/components/thread/ThreadActionsProvider", () => ({ useThreadActions: () => ({ - archiveThreadAndChildren: vi.fn(), + requestArchive: vi.fn(), requestDelete: vi.fn(), togglePin: vi.fn(), toggleRead: vi.fn(), diff --git a/apps/app/src/lib/plugin-sidebar-hooks.ts b/apps/app/src/lib/plugin-sidebar-hooks.ts index 34b7e14a158..d1d0e9bfbfe 100644 --- a/apps/app/src/lib/plugin-sidebar-hooks.ts +++ b/apps/app/src/lib/plugin-sidebar-hooks.ts @@ -332,7 +332,7 @@ export function useSidebarThreadActions(): PluginSidebarThreadActions { await updateThreadAsync({ id: threadId, title }); }, archive(threadId) { - hostActions.archiveThreadAndChildren(requireEntry(threadId)); + hostActions.requestArchive(requireEntry(threadId)); }, requestDelete(threadId) { hostActions.requestDelete(requireEntry(threadId)); diff --git a/apps/app/src/views/thread-detail/ThreadArchiveCommandHandler.test.tsx b/apps/app/src/views/thread-detail/ThreadArchiveCommandHandler.test.tsx index fcacf91fb1e..15570fa8cbc 100644 --- a/apps/app/src/views/thread-detail/ThreadArchiveCommandHandler.test.tsx +++ b/apps/app/src/views/thread-detail/ThreadArchiveCommandHandler.test.tsx @@ -10,7 +10,7 @@ import { PaneContext, type PaneContextValue } from "./PaneContext"; import { ThreadArchiveCommandHandler } from "./ThreadArchiveCommandHandler"; const mocks = vi.hoisted(() => ({ - archiveThreadAndChildren: vi.fn(), + requestArchive: vi.fn(), })); const testState = vi.hoisted(() => ({ @@ -33,7 +33,7 @@ const testState = vi.hoisted(() => ({ vi.mock("@/components/thread/ThreadActionsProvider", () => ({ useThreadActions: () => ({ - archiveThreadAndChildren: mocks.archiveThreadAndChildren, + requestArchive: mocks.requestArchive, }), })); @@ -131,12 +131,12 @@ describe("ThreadArchiveCommandHandler", () => { ); pressArchiveShortcut(); - expect(mocks.archiveThreadAndChildren.mock.calls).toEqual([[firstThread]]); + expect(mocks.requestArchive.mock.calls).toEqual([[firstThread]]); - mocks.archiveThreadAndChildren.mockClear(); + mocks.requestArchive.mockClear(); view.rerender(); pressArchiveShortcut(); - expect(mocks.archiveThreadAndChildren.mock.calls).toEqual([[secondThread]]); + expect(mocks.requestArchive.mock.calls).toEqual([[secondThread]]); }); it("does nothing when no pane is focused", () => { @@ -144,7 +144,7 @@ describe("ThreadArchiveCommandHandler", () => { pressArchiveShortcut(); - expect(mocks.archiveThreadAndChildren).not.toHaveBeenCalled(); + expect(mocks.requestArchive).not.toHaveBeenCalled(); }); it("does nothing when the focused thread is archived", () => { @@ -158,6 +158,6 @@ describe("ThreadArchiveCommandHandler", () => { pressArchiveShortcut(); - expect(mocks.archiveThreadAndChildren).not.toHaveBeenCalled(); + expect(mocks.requestArchive).not.toHaveBeenCalled(); }); }); diff --git a/apps/app/src/views/thread-detail/ThreadArchiveCommandHandler.tsx b/apps/app/src/views/thread-detail/ThreadArchiveCommandHandler.tsx index dcb834d2129..38bb19f652a 100644 --- a/apps/app/src/views/thread-detail/ThreadArchiveCommandHandler.tsx +++ b/apps/app/src/views/thread-detail/ThreadArchiveCommandHandler.tsx @@ -5,11 +5,11 @@ import { usePaneContext } from "./PaneContext"; export function ThreadArchiveCommandHandler({ thread }: { thread: Thread }) { const { isFocused } = usePaneContext(); - const { archiveThreadAndChildren } = useThreadActions(); + const { requestArchive } = useThreadActions(); useAppCommandHandler("thread.archive", () => { if (!isFocused || thread.archivedAt !== null) return false; - archiveThreadAndChildren(thread); + requestArchive(thread); return true; }); diff --git a/packages/plugin-sdk/src/app-contract.ts b/packages/plugin-sdk/src/app-contract.ts index 1f444cdc9f5..eadc61880bc 100644 --- a/packages/plugin-sdk/src/app-contract.ts +++ b/packages/plugin-sdk/src/app-contract.ts @@ -1421,7 +1421,7 @@ export interface PluginSidebarThreadActions { setRead(threadId: string, read: boolean): Promise; /** Silent rename — no dialog. For inline editing in your own row. */ rename(threadId: string, title: string): Promise; - /** Archives the thread AND its children, closing any panes showing them. */ + /** Opens bb's confirmation before archiving the thread and its children. */ archive(threadId: string): void; /** * Opens bb's delete confirmation, which counts child threads first. Deletion diff --git a/plugins/bb-guide/skills/bb-plugin-authoring/references/frontend-registration.md b/plugins/bb-guide/skills/bb-plugin-authoring/references/frontend-registration.md index 37f0553377c..7da114b139b 100644 --- a/plugins/bb-guide/skills/bb-plugin-authoring/references/frontend-registration.md +++ b/plugins/bb-guide/skills/bb-plugin-authoring/references/frontend-registration.md @@ -419,13 +419,13 @@ actions.openNewThread({ projectId, environmentId }); // reuse an environment actions.setPinned(id, true); actions.setRead(id, false); actions.rename(id, "New title"); // silent; for inline editing -actions.archive(id); // archives children too, closes their panes +actions.archive(id); // opens bb's archive confirmation; cascades to children actions.requestDelete(id); // opens bb's delete confirmation ``` -Destructive actions deliberately route through the host's own flow, so there -is no silent `delete`: deletion is recursive, and only bb can show the -confirmation that counts the child threads. +Cascading actions deliberately route through the host's own flow, so there is +no silent `delete` or `archive`: both reach child threads, and only bb can show +the confirmation that counts them. Unit-test a list with `renderSlot(...)` from `@get-bb/plugin-sdk/testing/app`: seed rows with the `sidebarThreads` option (plus `sidebarDraftThreadIds`, From 1836194e35e1480008875e28da72329ce7bd315a Mon Sep 17 00:00:00 2001 From: Fabio Rehm Date: Tue, 22 Sep 2026 20:29:39 -0300 Subject: [PATCH 2/2] fix(app): confirm archive before navigation tests Bump the plugin SDK floor to 0.5.16 for the updated published app contract. --- .../src/components/thread/ThreadActionsProvider.test.tsx | 6 ++++++ packages/domain/src/plugin-sdk-version.ts | 2 +- packages/plugin-sdk/package.json | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/apps/app/src/components/thread/ThreadActionsProvider.test.tsx b/apps/app/src/components/thread/ThreadActionsProvider.test.tsx index 8ef341547d1..2ec908b0da7 100644 --- a/apps/app/src/components/thread/ThreadActionsProvider.test.tsx +++ b/apps/app/src/components/thread/ThreadActionsProvider.test.tsx @@ -233,6 +233,9 @@ describe("ThreadActionsProvider archive feedback", () => { const view = renderProvider(); fireEvent.click(screen.getByRole("button", { name: "Archive" })); + fireEvent.click( + await screen.findByRole("button", { name: "Archive thread" }), + ); await vi.waitFor(() => { expect(appToast.success).toHaveBeenCalledTimes(1); @@ -267,6 +270,9 @@ describe("ThreadActionsProvider archive feedback", () => { const view = renderProvider(); fireEvent.click(screen.getByRole("button", { name: "Archive" })); + fireEvent.click( + await screen.findByRole("button", { name: "Archive thread" }), + ); await vi.waitFor(() => { expect(appToast.success).toHaveBeenCalledTimes(1); diff --git a/packages/domain/src/plugin-sdk-version.ts b/packages/domain/src/plugin-sdk-version.ts index 7c8672dae18..e884f04e109 100644 --- a/packages/domain/src/plugin-sdk-version.ts +++ b/packages/domain/src/plugin-sdk-version.ts @@ -1,3 +1,3 @@ -export const PLUGIN_SDK_VERSION = "0.5.15"; +export const PLUGIN_SDK_VERSION = "0.5.16"; export const PLUGIN_SDK_MAJOR = Number(PLUGIN_SDK_VERSION.split(".", 1)[0]); diff --git a/packages/plugin-sdk/package.json b/packages/plugin-sdk/package.json index 154fa6ae8d2..fcf21a54097 100644 --- a/packages/plugin-sdk/package.json +++ b/packages/plugin-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@get-bb/plugin-sdk", - "version": "0.5.15", + "version": "0.5.16", "homepage": "https://github.com/get-bb/bb#readme", "bugs": { "url": "https://github.com/get-bb/bb/issues"