Skip to content

Commit 32632ed

Browse files
committed
fix(web): hide subagents from legacy sidebar history
1 parent 3e6b450 commit 32632ed

3 files changed

Lines changed: 65 additions & 15 deletions

File tree

‎apps/web/src/components/LegacySidebar.tsx‎

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ import {
190190
resolveThreadRowClassName,
191191
resolveThreadLastVisitedAt,
192192
resolveThreadStatusPill,
193+
filterSidebarSubagentThreads,
193194
orderItemsByPreferredIds,
194195
shouldClearThreadSelectionOnMouseDown,
195196
sortProjectsForSidebar,
@@ -1255,30 +1256,35 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec
12551256
},
12561257
});
12571258
const openPrLink = useOpenPrLink();
1258-
const sidebarThreads = useThreadShellsForProjectRefs(project.memberProjectRefs);
1259+
const projectThreads = useThreadShellsForProjectRefs(project.memberProjectRefs);
1260+
// Child agents stay addressable for project operations, but belong in the
1261+
// parent thread's Lineage rather than the legacy history list.
1262+
const sidebarThreads = useMemo(
1263+
() => filterSidebarSubagentThreads(projectThreads),
1264+
[projectThreads],
1265+
);
12591266
const sidebarThreadByKey = useMemo(
12601267
() =>
12611268
new Map(
1262-
sidebarThreads.map(
1269+
projectThreads.map(
12631270
(thread) =>
12641271
[scopedThreadKey(scopeThreadRef(thread.environmentId, thread.id)), thread] as const,
12651272
),
12661273
),
1267-
[sidebarThreads],
1274+
[projectThreads],
12681275
);
12691276
// Keep a ref so callbacks can read the latest map without appearing in
12701277
// dependency arrays (avoids invalidating every thread-row memo on each
12711278
// thread-list change).
12721279
const sidebarThreadByKeyRef = useRef(sidebarThreadByKey);
12731280
sidebarThreadByKeyRef.current = sidebarThreadByKey;
1274-
const projectThreads = sidebarThreads;
12751281
const projectPreferenceKeys = useMemo(() => projectExpansionPreferenceKeys(project), [project]);
12761282
const projectExpanded = useUiStateStore((state) =>
12771283
resolveProjectExpanded(state.projectExpandedById, projectPreferenceKeys),
12781284
);
12791285
const threadLastVisitedAts = useUiStateStore(
12801286
useShallow((state) =>
1281-
projectThreads.map(
1287+
sidebarThreads.map(
12821288
(thread) =>
12831289
state.threadLastVisitedAtById[
12841290
scopedThreadKey(scopeThreadRef(thread.environmentId, thread.id))
@@ -1329,7 +1335,7 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec
13291335

13301336
const { projectStatus, visibleProjectThreads, orderedProjectThreadKeys } = useMemo(() => {
13311337
const lastVisitedAtByThreadKey = new Map(
1332-
projectThreads.map((thread, index) => [
1338+
sidebarThreads.map((thread, index) => [
13331339
scopedThreadKey(scopeThreadRef(thread.environmentId, thread.id)),
13341340
resolveThreadLastVisitedAt(thread.lastVisitedAt, threadLastVisitedAts[index] ?? undefined),
13351341
]),
@@ -1346,7 +1352,7 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec
13461352
});
13471353
};
13481354
const visibleProjectThreads = sortThreads(
1349-
projectThreads.filter((thread) => thread.archivedAt === null),
1355+
sidebarThreads.filter((thread) => thread.archivedAt === null),
13501356
threadSortOrder,
13511357
);
13521358
const projectStatus = resolveProjectStatusIndicator(
@@ -1359,7 +1365,7 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec
13591365
projectStatus,
13601366
visibleProjectThreads,
13611367
};
1362-
}, [projectThreads, threadLastVisitedAts, threadSortOrder]);
1368+
}, [sidebarThreads, threadLastVisitedAts, threadSortOrder]);
13631369
const pinnedCollapsedThread = useMemo(() => {
13641370
const activeThreadKey = activeRouteThreadKey ?? undefined;
13651371
if (!activeThreadKey || projectExpanded) {
@@ -1381,7 +1387,7 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec
13811387
shouldShowThreadPanel,
13821388
} = useMemo(() => {
13831389
const lastVisitedAtByThreadKey = new Map(
1384-
projectThreads.map((thread, index) => [
1390+
sidebarThreads.map((thread, index) => [
13851391
scopedThreadKey(scopeThreadRef(thread.environmentId, thread.id)),
13861392
resolveThreadLastVisitedAt(thread.lastVisitedAt, threadLastVisitedAts[index] ?? undefined),
13871393
]),
@@ -1429,7 +1435,7 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec
14291435
isThreadListExpanded,
14301436
pinnedCollapsedThread,
14311437
projectExpanded,
1432-
projectThreads,
1438+
sidebarThreads,
14331439
sidebarThreadPreviewCount,
14341440
threadLastVisitedAts,
14351441
visibleProjectThreads,
@@ -1532,7 +1538,7 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec
15321538
const draftStore = useComposerDraftStore.getState();
15331539
releaseProjectDraftUploads(
15341540
memberProjectRef,
1535-
sidebarThreads
1541+
projectThreads
15361542
.filter(
15371543
(thread) =>
15381544
thread.environmentId === member.environmentId && thread.projectId === member.id,
@@ -1546,7 +1552,7 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec
15461552
draftStore.clearProjectDraftThreadId(memberProjectRef);
15471553
return result;
15481554
},
1549-
[deleteProject, sidebarThreads],
1555+
[deleteProject, projectThreads],
15501556
);
15511557

15521558
const handleRemoveProject = useCallback(
@@ -3138,7 +3144,13 @@ const SidebarProjectsContent = memo(function SidebarProjectsContent(
31383144

31393145
export default function LegacySidebar() {
31403146
const projects = useProjects();
3141-
const sidebarThreads = useThreadShells();
3147+
const allSidebarThreads = useThreadShells();
3148+
// Keep the raw shells for destructive actions; navigation derives a
3149+
// top-level-only set so Lineage remains the child-agent entry point.
3150+
const sidebarThreads = useMemo(
3151+
() => filterSidebarSubagentThreads(allSidebarThreads),
3152+
[allSidebarThreads],
3153+
);
31423154
const projectExpandedById = useUiStateStore((store) => store.projectExpandedById);
31433155
const projectOrder = useUiStateStore((store) => store.projectOrder);
31443156
const reorderProjects = useUiStateStore((store) => store.reorderProjects);
@@ -3272,12 +3284,12 @@ export default function LegacySidebar() {
32723284
const sidebarThreadByKey = useMemo(
32733285
() =>
32743286
new Map(
3275-
sidebarThreads.map(
3287+
allSidebarThreads.map(
32763288
(thread) =>
32773289
[scopedThreadKey(scopeThreadRef(thread.environmentId, thread.id)), thread] as const,
32783290
),
32793291
),
3280-
[sidebarThreads],
3292+
[allSidebarThreads],
32813293
);
32823294
// Resolve the active route's project key to a logical key so it matches the
32833295
// sidebar's grouped project entries.

‎apps/web/src/components/Sidebar.logic.test.ts‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
buildMultiSelectThreadContextMenuItems,
1414
createThreadJumpHintVisibilityController,
1515
deleteSelectedThreadEntries,
16+
filterSidebarSubagentThreads,
1617
filterSidebarProjectScopeItems,
1718
filterSidebarV2VisibleThreads,
1819
formatWorkingDurationLabel,
@@ -460,6 +461,37 @@ describe("sidebar thread lineage helpers", () => {
460461
expect(isSidebarSubagentThread(makeThreadFixture())).toBe(false);
461462
});
462463

464+
it("filters only subagent threads while preserving archived entries", () => {
465+
const parentId = ThreadId.make("thread-parent");
466+
const parent = makeThreadFixture({ id: parentId });
467+
const subagent = makeThreadFixture({
468+
id: ThreadId.make("thread-subagent"),
469+
lineage: {
470+
rootThreadId: parentId,
471+
parentThreadId: parentId,
472+
relationshipToParent: "subagent",
473+
},
474+
});
475+
const fork = makeThreadFixture({
476+
id: ThreadId.make("thread-fork"),
477+
lineage: {
478+
rootThreadId: parentId,
479+
parentThreadId: parentId,
480+
relationshipToParent: "fork",
481+
},
482+
});
483+
const archived = makeThreadFixture({
484+
id: ThreadId.make("thread-archived"),
485+
archivedAt: "2026-01-02T00:00:00.000Z",
486+
});
487+
488+
expect(filterSidebarSubagentThreads([parent, subagent, fork, archived])).toEqual([
489+
parent,
490+
fork,
491+
archived,
492+
]);
493+
});
494+
463495
it("resolves the parent thread for fork sidebar affordances", () => {
464496
const parentId = ThreadId.make("thread-parent");
465497
const fallbackParentId = ThreadId.make("thread-fallback-parent");

‎apps/web/src/components/Sidebar.logic.ts‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -561,6 +561,12 @@ export function isSidebarSubagentThread(thread: Pick<SidebarThreadSummary, "line
561561
return thread.lineage.relationshipToParent === "subagent";
562562
}
563563

564+
export function filterSidebarSubagentThreads<
565+
T extends Pick<SidebarThreadSummary, "lineage">,
566+
>(threads: readonly T[]): T[] {
567+
return threads.filter((thread) => !isSidebarSubagentThread(thread));
568+
}
569+
564570
export function filterSidebarV2VisibleThreads<
565571
T extends Pick<SidebarThreadSummary, "archivedAt" | "lineage"> & {
566572
environmentId: string;

0 commit comments

Comments
 (0)