diff --git a/apps/web/src/components/PhaseGroupedSidebar.tsx b/apps/web/src/components/PhaseGroupedSidebar.tsx index b5a5c8bb0440d..a184a06a673f3 100644 --- a/apps/web/src/components/PhaseGroupedSidebar.tsx +++ b/apps/web/src/components/PhaseGroupedSidebar.tsx @@ -244,6 +244,7 @@ import { buildPhaseSidebarSections, isPhaseSidebarSectionCollapsed, listPhaseSidebarCustomGroups, + PHASE_SIDEBAR_RUNNING_SHELF_SECTION, PHASE_SIDEBAR_SHELF_SECTIONS, PHASE_SIDEBAR_UNGROUPED_ID, phaseSidebarCustomGroupIdForRow, @@ -256,6 +257,7 @@ import { } from "@t3tools/client-runtime/state/phase-sidebar-grouping"; import { usePhaseSidebarGroupingStore } from "../phaseSidebarGroupingStore"; import { PhaseSidebarGroupByPopover } from "./sidebar/PhaseSidebarGroupByPopover"; +import { PhaseSidebarRunningShelfSwitch } from "./sidebar/PhaseSidebarRunningShelfSwitch"; import { PhaseSidebarGroupNameDialog } from "./sidebar/PhaseSidebarGroupNameDialog"; import { PHASE_SIDEBAR_SECTION_ACTION_BUTTON_CLASS_NAME, @@ -2888,7 +2890,7 @@ export function PhaseGroupedSidebar() { null, [environments], ); - const { sections, forcedExpansionKeys } = useMemo( + const { sections, forcedExpansionKeys, runningNodes } = useMemo( () => buildPhaseSidebarSections({ rows: activeRows, @@ -3040,8 +3042,10 @@ export function PhaseGroupedSidebar() { for (const child of node.children) visit(child); }; for (const group of groups) for (const node of group.nodes) visit(node); + // T3-CUSTOM(expbkt3): the Running shelf's trees are no longer in a group. + for (const node of runningNodes) visit(node); return keys; - }, [groups]); + }, [groups, runningNodes]); // T3-CUSTOM(expbkt3): END // T3-CUSTOM(expbkt3): Separate idle lifecycle groups from live agent work. // Only meaningful when the sections ARE lifecycle phases. @@ -3114,12 +3118,42 @@ export function PhaseGroupedSidebar() { }), [routeThreadKey, settledRows, settledShelfCollapsed, settledVisibleCount], ); + // The Running shelf (custom mode, switched on) holds live session trees, so + // an open shelf flattens them like any section for traversal. + const runningShelfCollapsed = isPhaseSidebarSectionCollapsed( + PHASE_SIDEBAR_RUNNING_SHELF_SECTION, + collapsedSectionKeys, + ); + const toggleRunningShelf = useCallback( + () => toggleSectionCollapsed(PHASE_SIDEBAR_RUNNING_SHELF_SECTION.key), + [toggleSectionCollapsed], + ); + const runningShelfRows = useMemo( + () => + runningShelfCollapsed + ? [] + : flattenPhaseSidebarTree(runningNodes, isTreeKeyExpanded).map((node) => node.row), + [isTreeKeyExpanded, runningNodes, runningShelfCollapsed], + ); + const runningShelfHoldsRoutedThread = useMemo( + () => + routeThreadKey !== null && + flattenPhaseSidebarTree(runningNodes, () => true).some( + (node) => phaseSidebarRowKey(node.row) === routeThreadKey, + ), + [routeThreadKey, runningNodes], + ); // T3-CUSTOM(expbkt3): END // Traversal, jump labels and shift-range selection operate on what is // actually on screen, in screen order: the list, then the docked shelves. const visibleRows = useMemo( - () => [...activeVisibleRows, ...snoozedShelf.rendered, ...settledShelf.rendered], - [activeVisibleRows, snoozedShelf.rendered, settledShelf.rendered], + () => [ + ...activeVisibleRows, + ...runningShelfRows, + ...snoozedShelf.rendered, + ...settledShelf.rendered, + ], + [activeVisibleRows, runningShelfRows, snoozedShelf.rendered, settledShelf.rendered], ); // T3-CUSTOM(expbkt3): BEGIN — environment markers are conditional on what is // actually rendered, not on how many environments are configured. With one @@ -3257,6 +3291,7 @@ export function PhaseGroupedSidebar() { !visibleRowByKey.has(routeThreadKey) && // T3-CUSTOM(expbkt3): a thread folded into a collapsed section or shelf is // not hidden by the filters; it is one header click away. + !runningShelfHoldsRoutedThread && !snoozedShelf.containsRoutedThread && !settledShelf.containsRoutedThread && !sections.some( @@ -4024,6 +4059,8 @@ export function PhaseGroupedSidebar() { // clean while silently leaving every treeXxx prop undefined, which reads as // "the feature is off" rather than as a build error. tree?: PhaseThreadRowTreeProps, + // T3-CUSTOM(expbkt3): Running shelf rows left their group, so they name it. + showGroup = false, ) => { const key = scopedThreadKey(scopeThreadRef(row.thread.environmentId, row.thread.id)); const project = @@ -4078,7 +4115,9 @@ export function PhaseGroupedSidebar() { // T3-CUSTOM(expbkt3): custom groups. {...(groupActions ? { groupActions } : {})} customGroupId={phaseSidebarCustomGroupIdForRow(row)} - customGroupLabel={grouping.groupBy === "custom" ? null : (row.customGroup ?? null)} + customGroupLabel={ + grouping.groupBy === "custom" && !showGroup ? null : (row.customGroup ?? null) + } customGroupColor={customGroupColors.get(phaseSidebarCustomGroupIdForRow(row) ?? "") ?? null} linearIssueStatuses={linearStatusesForRow(row)} {...(tree ?? {})} @@ -4088,22 +4127,27 @@ export function PhaseGroupedSidebar() { // T3-CUSTOM(expbkt3): BEGIN — a session tree renders as nested
No threads match these filters
@@ -4244,6 +4291,19 @@ export function PhaseGroupedSidebar() { just above the footer, so their headers stay on screen and only the list above gives up height when anything else in the sidebar grows. */}