Skip to content
Closed
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
103 commits
Select commit Hold shift + click to select a range
d44ee17
Add archive controls to Sidebar V2
Quicksaver Jul 22, 2026
66063f9
Preserve sidebar archive outcomes
Quicksaver Jul 22, 2026
c328bf7
Harden sidebar archive actions
Quicksaver Jul 22, 2026
987a139
Document Sidebar V2 archive controls
Quicksaver Jul 23, 2026
696b21f
Merge remote-tracking branch 'upstream/main' into feat/sidebar-v2-arc…
Quicksaver Jul 23, 2026
5215305
refactor(web): extract Sidebar V2 archive controls
Quicksaver Jul 23, 2026
45755b4
Merge remote-tracking branch 'upstream/main' into feat/sidebar-v2-arc…
Quicksaver Jul 23, 2026
e5c2345
docs: describe Sidebar V2 archive ownership
Quicksaver Jul 23, 2026
ccf5348
Merge remote-tracking branch 'upstream/main' into feat/sidebar-v2-arc…
Quicksaver Jul 23, 2026
9a635a3
Merge remote-tracking branch 'upstream/main' into feat/sidebar-v2-arc…
Quicksaver Jul 23, 2026
9b48b89
Merge remote-tracking branch 'upstream/main' into feat/sidebar-v2-arc…
Quicksaver Jul 23, 2026
c9732fe
Guard Sidebar V2 archive actions
Quicksaver Jul 23, 2026
7274d11
Merge remote-tracking branch 'upstream/main' into feat/sidebar-v2-arc…
Quicksaver Jul 23, 2026
5957e66
Merge remote-tracking branch 'upstream/main' into feat/sidebar-v2-arc…
Quicksaver Jul 23, 2026
9c7c4b2
Merge remote-tracking branch 'upstream/main' into feat/sidebar-v2-arc…
Quicksaver Jul 23, 2026
8cef485
Guard all Sidebar V2 archive flows
Quicksaver Jul 23, 2026
02ea8f6
Coordinate overlapping Sidebar V2 archives
Quicksaver Jul 23, 2026
2cb81b7
Merge remote-tracking branch 'upstream/main' into feat/sidebar-v2-arc…
Quicksaver Jul 24, 2026
cd6bb2f
Fix overlapping Sidebar archive coordination
Quicksaver Jul 24, 2026
897af46
Merge remote-tracking branch 'upstream/main' into feat/sidebar-v2-arc…
Quicksaver Jul 24, 2026
d0ebbe5
Fix Sidebar archive-all feedback and pointer capture
Quicksaver Jul 24, 2026
0588f13
Merge remote-tracking branch 'refs/remotes/upstream/main' into feat/s…
Quicksaver Jul 24, 2026
6b2f4d0
Merge remote-tracking branch 'upstream/main' into feat/sidebar-v2-arc…
Quicksaver Jul 25, 2026
a682d5d
Skip un-settled threads in Archive all
Quicksaver Jul 26, 2026
920ac8e
Merge remote-tracking branch 'upstream/main' into feat/sidebar-v2-arc…
Quicksaver Jul 27, 2026
8cd4e37
Merge remote-tracking branch 'upstream/main' into feat/sidebar-v2-arc…
Quicksaver Jul 27, 2026
e4a2390
Merge remote-tracking branch 'upstream/main' into feat/sidebar-v2-arc…
Quicksaver Jul 27, 2026
c38cdc1
Merge remote-tracking branch 'upstream/main' into feat/sidebar-v2-arc…
Quicksaver Jul 27, 2026
107424b
Merge commit '32843c2551bd5832d436ef46bff5ab1198cdc39c' into feat/sid…
Quicksaver Jul 27, 2026
79454d7
Merge commit '887dd6e455bb969c1a0c9659a6bdf2baceac030d' into feat/sid…
Jul 28, 2026
569a69b
Prevent waiters from retrying skipped archives
Jul 28, 2026
2bcb5f1
Merge remote-tracking branch 'upstream/main' into feat/sidebar-v2-arc…
Jul 28, 2026
17ca865
Merge remote-tracking branch 'upstream/main' into feat/sidebar-v2-arc…
Jul 28, 2026
198e8fd
Merge remote-tracking branch 'upstream/main' into feat/sidebar-v2-arc…
Jul 28, 2026
2dd2142
Merge remote-tracking branch 'upstream/main' into feat/sidebar-v2-arc…
Jul 29, 2026
10c03a8
Merge remote-tracking branch 'upstream/main' into feat/sidebar-v2-arc…
Jul 29, 2026
9c6fcf2
Merge remote-tracking branch 'upstream/main' into feat/sidebar-v2-arc…
Jul 29, 2026
bdf2c71
Keep Archive all mounted during active batches
Jul 29, 2026
1d2309b
Publish archive skips before later failures
Jul 29, 2026
333cab4
Merge remote-tracking branch 'upstream/main' into feat/sidebar-v2-arc…
Jul 29, 2026
1d1eeec
Merge remote-tracking branch 'upstream/main' into feat/sidebar-v2-arc…
Jul 29, 2026
97264ac
Merge remote-tracking branch 'upstream/main' into feat/sidebar-v2-arc…
Jul 29, 2026
82abe07
Merge remote-tracking branch 'upstream/main' into feat/sidebar-v2-arc…
Jul 29, 2026
3e3d384
Merge remote-tracking branch 'upstream/main' into feat/sidebar-v2-arc…
Jul 29, 2026
4699d02
Clarify Sidebar archive skip warnings
Jul 29, 2026
6834c0a
Merge remote-tracking branch 'upstream/main' into feat/sidebar-v2-arc…
Jul 30, 2026
308213b
Merge remote-tracking branch 'upstream/main' into feat/sidebar-v2-arc…
Jul 30, 2026
94fa47c
Merge remote-tracking branch 'upstream/main' into feat/sidebar-v2-arc…
Jul 30, 2026
77114ce
Merge remote-tracking branch 'upstream/main' into feat/sidebar-v2-arc…
Jul 30, 2026
26861cc
refactor(web): compose sidebar thread menu with slots
Jul 30, 2026
b9408ed
Merge remote-tracking branch 'upstream/main' into feat/sidebar-v2-arc…
Jul 30, 2026
ad69188
Test sidebar menu slot composition
Jul 30, 2026
3887d2f
Merge remote-tracking branch 'upstream/main' into feat/sidebar-v2-arc…
Jul 31, 2026
6d40fe5
Merge remote-tracking branch 'upstream/main' into feat/sidebar-v2-arc…
Jul 31, 2026
96188bd
Merge remote-tracking branch 'upstream/main' into feat/sidebar-v2-arc…
Jul 31, 2026
8d3c9a4
Merge commit 'a041981276b4c789fed8132e3b8a320749bf25e8' into feat/sid…
Jul 31, 2026
f358480
Merge commit '916cff733cd00c85fe0419df5bcb9372a6384746' into feat/sid…
Jul 31, 2026
def1d77
Merge remote-tracking branch 'upstream/main' into feat/sidebar-v2-arc…
Aug 2, 2026
cce7ed8
Align settled controls with upstream focus behavior
Aug 2, 2026
19f45f8
Document Sidebar search lifecycle boundary
Aug 2, 2026
b64448e
Align settled control focus visibility with upstream
Aug 2, 2026
8594061
Merge remote-tracking branch 'upstream/main' into feat/sidebar-v2-arc…
Aug 3, 2026
1a04c22
Merge remote-tracking branch 'upstream/main' into feat/sidebar-v2-arc…
Aug 6, 2026
35b17a6
Merge remote-tracking branch 'upstream/main' into feat/sidebar-v2-arc…
Aug 6, 2026
18703d9
Merge remote-tracking branch 'upstream/main' into feat/sidebar-v2-arc…
Aug 6, 2026
89d2a15
fix(sidebar): block archive during background work
Aug 6, 2026
f851112
Harden sidebar archive guards
Aug 6, 2026
293f436
Merge remote-tracking branch 'upstream/main' into feat/sidebar-v2-arc…
Aug 7, 2026
8642aa2
docs: refresh sidebar archive branch details
Aug 7, 2026
22665ad
Merge remote-tracking branch 'upstream/main' into feat/sidebar-v2-arc…
Aug 7, 2026
7c761c9
Merge remote-tracking branch 'upstream/main' into feat/sidebar-v2-arc…
Aug 7, 2026
f91ac0c
Merge remote-tracking branch 'upstream/main' into feat/sidebar-v2-arc…
Aug 8, 2026
48fe52e
Merge remote-tracking branch 'upstream/main' into feat/sidebar-v2-arc…
Aug 8, 2026
02e541e
refactor(web): isolate default sidebar archive controls
Aug 8, 2026
49e8fa1
Share sidebar lifecycle button styles
Aug 8, 2026
065cf08
Merge remote-tracking branch 'upstream/main' into feat/sidebar-v2-arc…
Aug 8, 2026
c3cd5b9
Block archive while provider sessions start
Aug 8, 2026
669e4f3
Merge remote-tracking branch 'upstream/main' into feat/sidebar-v2-arc…
Aug 9, 2026
81475a4
Merge remote-tracking branch 'upstream/main' into feat/sidebar-v2-arc…
Aug 9, 2026
032c9ca
Merge remote-tracking branch 'upstream/main' into feat/sidebar-v2-arc…
Aug 9, 2026
beae486
Resolve sidebar archive upstream integration
Aug 9, 2026
f9d5121
Refactor sidebar archive logic ownership
Aug 9, 2026
453078d
Refine sidebar archive ownership seam
Aug 9, 2026
b147948
Merge remote-tracking branch 'upstream/main' into feat/sidebar-v2-arc…
Aug 10, 2026
1ee1174
Merge remote-tracking branch 'upstream/main' into feat/sidebar-v2-arc…
Aug 10, 2026
66118d1
Merge remote-tracking branch 'upstream/main' into feat/sidebar-v2-arc…
Aug 10, 2026
a7c9b5e
Merge remote-tracking branch 'upstream/main' into feat/sidebar-v2-arc…
Quicksaver Aug 16, 2026
35861a4
refactor(web): unify archive action coordination
Quicksaver Aug 16, 2026
512ddb5
Merge remote-tracking branch 'upstream/main' into feat/sidebar-v2-arc…
Quicksaver Aug 17, 2026
cd80ad6
fix(web): align archive control tooltip
Quicksaver Aug 17, 2026
0a3b638
docs: document archive tooltip ownership
Quicksaver Aug 17, 2026
651c4ab
fix(web): keep disabled archive tooltip interactive
Quicksaver Aug 17, 2026
a678727
fix(web): recheck legacy archive eligibility
Quicksaver Aug 17, 2026
d45fc99
fix(web): clarify blocked archive styling
Quicksaver Aug 17, 2026
df32c73
Merge remote-tracking branch 'upstream/main' into feat/sidebar-v2-arc…
Quicksaver Aug 18, 2026
c93f169
Merge remote-tracking branch 'upstream/main' into feat/sidebar-v2-arc…
Quicksaver Aug 18, 2026
50b0cc1
Merge remote-tracking branch 'upstream/main' into feat/sidebar-v2-arc…
Quicksaver Aug 18, 2026
5ca42ea
Merge remote-tracking branch 'upstream/main' into feat/sidebar-v2-arc…
Quicksaver Aug 19, 2026
3a19ef2
test(web): cover merged PR archive membership
Quicksaver Aug 19, 2026
1a990fe
fix(web): clarify disabled archive-all state
Quicksaver Aug 19, 2026
c035498
Merge remote-tracking branch 'upstream/main' into feat/sidebar-v2-arc…
Quicksaver Aug 19, 2026
2603f06
refactor(web): separate shared archive policy
Quicksaver Aug 20, 2026
5544d62
test(web): colocate shared archive policy coverage
Quicksaver Aug 20, 2026
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
19 changes: 19 additions & 0 deletions BRANCH_DETAILS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Sidebar V2 Archive Controls

Sidebar V2 preserves archive as a separate lifecycle from settle. Settled rows expose adjacent un-settle and archive buttons on hover or keyboard focus, while root conversation context menus expose archive for both settled and unsettled rows. Archive remains visible but disabled for a running conversation, and nested subagent rows continue to omit root lifecycle actions.

The `Settled` section header includes an `Archive all` action. It applies to the complete settled partition in the current project scope, including rows behind settled-tail pagination, and remains available when the list begins with settled conversations. Individual, selected, and all-settled archive actions honor the shared archive-confirmation setting, use the existing optimistic visibility and archived-snapshot refresh path, preserve already archived results if a later bulk mutation or post-archive navigation fails, remove archived rows from any active selection, and report failures without implying that completed archive work was rolled back. Repeated individual archive triggers for the same thread are ignored while its archive flow is in flight.

`SidebarV2.tsx` isolates the fork-owned integration in `SidebarV2SettledLifecycleControls` and `SidebarV2SettledDivider`. Shared lifecycle-button classes keep settle, un-settle, and archive affordances aligned with the upstream row surface tokens. Disabled archive controls retain pointer targeting so clicks cannot fall through to row navigation. The surrounding row structure, including tooltip wrapping, row sizing, filtering, and decorative environment/provider status semantics, remains upstream-owned. The settled divider remains an interactive list item rather than an aria-hidden separator because it contains `Archive all`.

Primary files:

- `apps/web/src/components/SidebarV2.tsx`
- `apps/web/src/components/Sidebar.logic.ts`
- `apps/web/src/components/Sidebar.logic.test.ts`
- `apps/web/src/hooks/useThreadActions.ts`

## Development Ports

- Web: `5743`
- Server/WebSocket: `13783`
77 changes: 77 additions & 0 deletions apps/web/src/components/Sidebar.logic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vite-plus/test"
import {
archiveSelectedThreadEntries,
buildMultiSelectThreadContextMenuItems,
buildSidebarV2ThreadContextMenuItems,
createThreadJumpHintVisibilityController,
filterArchivableSidebarThreads,
getSidebarThreadIdsToPrewarm,
getVisibleSidebarThreadIds,
resolveAdjacentThreadId,
Expand All @@ -11,6 +13,7 @@ import {
getProjectSortTimestamp,
hasUnseenCompletion,
isContextMenuPointerDown,
isThreadSessionRunning,
isTrailingDoubleClick,
orderItemsByPreferredIds,
resolveProjectStatusIndicator,
Expand All @@ -22,6 +25,7 @@ import {
resolveThreadStatusPill,
shouldNavigateAfterProjectRemoval,
shouldClearThreadSelectionOnMouseDown,
shouldShowSidebarV2SettledHeader,
sortLogicalProjectsForSidebar,
sortThreadsForSidebarV2,
sortProjectsForSidebar,
Expand Down Expand Up @@ -162,6 +166,79 @@ describe("buildMultiSelectThreadContextMenuItems", () => {
});
});

describe("buildSidebarV2ThreadContextMenuItems", () => {
it("offers archive for active and settled threads", () => {
const activeItems = buildSidebarV2ThreadContextMenuItems({
canUseLifecycleActions: true,
supportsSettlement: true,
isSettled: false,
isRunning: false,
});
const settledItems = buildSidebarV2ThreadContextMenuItems({
canUseLifecycleActions: true,
supportsSettlement: true,
isSettled: true,
isRunning: false,
});

expect(activeItems.map((item) => item.id)).toEqual([
"settle",
"archive",
"rename",
"mark-unread",
"delete",
]);
expect(settledItems.map((item) => item.id)).toEqual([
"unsettle",
"archive",
"rename",
"mark-unread",
"delete",
]);
});

it("keeps archive visible but disabled while a thread is running", () => {
const items = buildSidebarV2ThreadContextMenuItems({
canUseLifecycleActions: true,
supportsSettlement: true,
isSettled: false,
isRunning: true,
});

expect(items.find((item) => item.id === "archive")).toMatchObject({ disabled: true });
});
});

describe("shouldShowSidebarV2SettledHeader", () => {
it("shows above the first settled row even when the list starts settled", () => {
expect(shouldShowSidebarV2SettledHeader({ isSettled: true, previousIsSettled: false })).toBe(
true,
);
});

it("hides for active rows and later settled rows", () => {
expect(shouldShowSidebarV2SettledHeader({ isSettled: false, previousIsSettled: false })).toBe(
false,
);
expect(shouldShowSidebarV2SettledHeader({ isSettled: true, previousIsSettled: true })).toBe(
false,
);
});
});

describe("archive lifecycle guards", () => {
it("filters running threads from archive batches", () => {
const ready = { id: "ready", session: null };
const running = {
id: "running",
session: { status: "running", activeTurnId: "turn-running" },
};

expect(isThreadSessionRunning(running.session)).toBe(true);
expect(filterArchivableSidebarThreads([ready, running])).toEqual([ready]);
});
});

describe("resolveSidebarStageBadgeLabel", () => {
it("returns Nightly for nightly primary server versions", () => {
expect(
Expand Down
57 changes: 57 additions & 0 deletions apps/web/src/components/Sidebar.logic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,63 @@ export function buildMultiSelectThreadContextMenuItems(input: {
];
}

export type SidebarV2ThreadContextMenuItemId =
| "settle"
| "unsettle"
| "archive"
| "rename"
| "mark-unread"
| "delete";

export function buildSidebarV2ThreadContextMenuItems(input: {
canUseLifecycleActions: boolean;
supportsSettlement: boolean;
isSettled: boolean;
isRunning: boolean;
}): readonly ContextMenuItem<SidebarV2ThreadContextMenuItemId>[] {
return [
...(input.supportsSettlement
? [
input.isSettled
? ({ id: "unsettle", label: "Un-settle thread" } as const)
: ({ id: "settle", label: "Settle thread" } as const),
]
: []),
...(input.canUseLifecycleActions
? [{ id: "archive" as const, label: "Archive thread", disabled: input.isRunning }]
: []),
{ id: "rename", label: "Rename thread" },
{ id: "mark-unread", label: "Mark unread" },
...(input.canUseLifecycleActions
? [{ id: "delete" as const, label: "Delete", destructive: true, icon: "trash" as const }]
: []),
];
}

export function shouldShowSidebarV2SettledHeader(input: {
isSettled: boolean;
previousIsSettled: boolean;
}): boolean {
return input.isSettled && !input.previousIsSettled;
}

export function isThreadSessionRunning(
session: { readonly status: string; readonly activeTurnId?: unknown } | null | undefined,
): boolean {
return session?.status === "running" && session.activeTurnId != null;
}

export function filterArchivableSidebarThreads<
T extends {
readonly session?:
| { readonly status: string; readonly activeTurnId?: unknown }
| null
| undefined;
},
>(threads: readonly T[]): T[] {
return threads.filter((thread) => !isThreadSessionRunning(thread.session));
}

export interface ThreadStatusPill {
label:
| "Working"
Expand Down
Loading
Loading