Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
126 changes: 67 additions & 59 deletions apps/web/src/components/LegacySidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ import {
archiveSelectedThreadEntries,
buildMultiSelectThreadContextMenuItems,
getSidebarThreadIdsToPrewarm,
getVisibleThreadsForProject,
resolveAdjacentThreadId,
isContextMenuPointerDown,
isSidebarNestedLinkClick,
Expand All @@ -193,6 +194,7 @@ import {
} from "./Sidebar.logic";
import { sortThreads } from "../lib/threadSort";
import { SidebarChromeFooter, SidebarChromeHeader } from "./sidebar/SidebarChrome";
import { useSidebarActiveThreadScroll } from "./sidebar/useSidebarActiveThreadScroll";
import { useCopyToClipboard } from "~/hooks/useCopyToClipboard";
import { useIsMobile } from "~/hooks/useMediaQuery";
import { CommandDialogTrigger } from "./ui/command";
Expand Down Expand Up @@ -727,6 +729,7 @@ export const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThr
<SidebarMenuSubItem
className="w-full"
data-thread-item
data-sidebar-thread-key={threadKey}
onMouseLeave={handleMouseLeave}
onBlurCapture={handleBlurCapture}
>
Expand Down Expand Up @@ -1150,6 +1153,7 @@ interface SidebarProjectItemProps {
attachThreadListAutoAnimateRef: (node: HTMLElement | null) => void;
expandThreadListForProject: (projectKey: string) => void;
collapseThreadListForProject: (projectKey: string) => void;
markSidebarThreadNavigation: (threadKey: string) => void;
dragInProgressRef: React.RefObject<boolean>;
suppressProjectClickAfterDragRef: React.RefObject<boolean>;
suppressProjectClickForContextMenuRef: React.RefObject<boolean>;
Expand All @@ -1173,6 +1177,7 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec
attachThreadListAutoAnimateRef,
expandThreadListForProject,
collapseThreadListForProject,
markSidebarThreadNavigation,
dragInProgressRef,
suppressProjectClickAfterDragRef,
suppressProjectClickForContextMenuRef,
Expand Down Expand Up @@ -1357,19 +1362,6 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec
visibleProjectThreads,
};
}, [projectThreads, threadLastVisitedAts, threadSortOrder]);
const pinnedCollapsedThread = useMemo(() => {
const activeThreadKey = activeRouteThreadKey ?? undefined;
if (!activeThreadKey || projectExpanded) {
return null;
}
return (
visibleProjectThreads.find(
(thread) =>
scopedThreadKey(scopeThreadRef(thread.environmentId, thread.id)) === activeThreadKey,
) ?? null
);
}, [activeRouteThreadKey, projectExpanded, visibleProjectThreads]);

const {
hasOverflowingThreads,
hiddenThreadStatus,
Expand All @@ -1394,37 +1386,37 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec
},
});
};
const hasOverflowingThreads = visibleProjectThreads.length > sidebarThreadPreviewCount;
const previewThreads =
isThreadListExpanded || !hasOverflowingThreads
? visibleProjectThreads
: visibleProjectThreads.slice(0, sidebarThreadPreviewCount);
const visibleThreadKeys = new Set(
[...previewThreads, ...(pinnedCollapsedThread ? [pinnedCollapsedThread] : [])].map((thread) =>
scopedThreadKey(scopeThreadRef(thread.environmentId, thread.id)),
),
);
const renderedThreads = pinnedCollapsedThread
? [pinnedCollapsedThread]
: visibleProjectThreads.filter((thread) =>
visibleThreadKeys.has(scopedThreadKey(scopeThreadRef(thread.environmentId, thread.id))),
);
const hiddenThreads = visibleProjectThreads.filter(
(thread) =>
!visibleThreadKeys.has(scopedThreadKey(scopeThreadRef(thread.environmentId, thread.id))),
);
const visibleThreadsResult = getVisibleThreadsForProject({
activeThreadId: activeRouteThreadKey ?? undefined,
getThreadId: (thread) => scopedThreadKey(scopeThreadRef(thread.environmentId, thread.id)),
isThreadListExpanded,
previewLimit: sidebarThreadPreviewCount,
threads: visibleProjectThreads,
});
const activeThread = activeRouteThreadKey
? (visibleThreadsResult.visibleThreads.find(
(thread) =>
scopedThreadKey(scopeThreadRef(thread.environmentId, thread.id)) ===
activeRouteThreadKey,
) ?? null)
: null;
const renderedThreads = projectExpanded
? visibleThreadsResult.visibleThreads
: activeThread
? [activeThread]
: [];
return {
hasOverflowingThreads,
hasOverflowingThreads: visibleThreadsResult.hasHiddenThreads,
hiddenThreadStatus: resolveProjectStatusIndicator(
hiddenThreads.map((thread) => resolveProjectThreadStatus(thread)),
visibleThreadsResult.hiddenThreads.map((thread) => resolveProjectThreadStatus(thread)),
),
renderedThreads,
showEmptyThreadState: projectExpanded && visibleProjectThreads.length === 0,
shouldShowThreadPanel: projectExpanded || pinnedCollapsedThread !== null,
shouldShowThreadPanel: projectExpanded || activeThread !== null,
};
}, [
activeRouteThreadKey,
isThreadListExpanded,
pinnedCollapsedThread,
projectExpanded,
projectThreads,
sidebarThreadPreviewCount,
Expand Down Expand Up @@ -1784,12 +1776,20 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec
if (isMobile) {
setOpenMobile(false);
}
markSidebarThreadNavigation(scopedThreadKey(threadRef));
void router.navigate({
to: "/$environmentId/$threadId",
params: buildThreadRouteParams(threadRef),
});
},
[clearSelection, isMobile, router, setOpenMobile, setSelectionAnchor],
[
clearSelection,
isMobile,
markSidebarThreadNavigation,
router,
setOpenMobile,
setSelectionAnchor,
],
);

const handleThreadClick = useCallback(
Expand Down Expand Up @@ -1831,6 +1831,7 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec
if (isMobile) {
setOpenMobile(false);
}
markSidebarThreadNavigation(threadKey);
void router.navigate({
to: "/$environmentId/$threadId",
params: buildThreadRouteParams(threadRef),
Expand All @@ -1839,6 +1840,7 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec
[
clearSelection,
isMobile,
markSidebarThreadNavigation,
rangeSelectTo,
router,
setOpenMobile,
Expand Down Expand Up @@ -2854,6 +2856,7 @@ interface SidebarProjectsContentProps {
attachThreadListAutoAnimateRef: (node: HTMLElement | null) => void;
expandThreadListForProject: (projectKey: string) => void;
collapseThreadListForProject: (projectKey: string) => void;
markSidebarThreadNavigation: (threadKey: string) => void;
dragInProgressRef: React.RefObject<boolean>;
suppressProjectClickAfterDragRef: React.RefObject<boolean>;
suppressProjectClickForContextMenuRef: React.RefObject<boolean>;
Expand Down Expand Up @@ -2898,6 +2901,7 @@ const SidebarProjectsContent = memo(function SidebarProjectsContent(
attachThreadListAutoAnimateRef,
expandThreadListForProject,
collapseThreadListForProject,
markSidebarThreadNavigation,
dragInProgressRef,
suppressProjectClickAfterDragRef,
suppressProjectClickForContextMenuRef,
Expand Down Expand Up @@ -3044,6 +3048,7 @@ const SidebarProjectsContent = memo(function SidebarProjectsContent(
attachThreadListAutoAnimateRef={attachThreadListAutoAnimateRef}
expandThreadListForProject={expandThreadListForProject}
collapseThreadListForProject={collapseThreadListForProject}
markSidebarThreadNavigation={markSidebarThreadNavigation}
dragInProgressRef={dragInProgressRef}
suppressProjectClickAfterDragRef={suppressProjectClickAfterDragRef}
suppressProjectClickForContextMenuRef={
Expand Down Expand Up @@ -3079,6 +3084,7 @@ const SidebarProjectsContent = memo(function SidebarProjectsContent(
attachThreadListAutoAnimateRef={attachThreadListAutoAnimateRef}
expandThreadListForProject={expandThreadListForProject}
collapseThreadListForProject={collapseThreadListForProject}
markSidebarThreadNavigation={markSidebarThreadNavigation}
dragInProgressRef={dragInProgressRef}
suppressProjectClickAfterDragRef={suppressProjectClickAfterDragRef}
suppressProjectClickForContextMenuRef={suppressProjectClickForContextMenuRef}
Expand Down Expand Up @@ -3126,6 +3132,10 @@ export default function LegacySidebar() {
const routeThreadKey = routeThreadRef ? scopedThreadKey(routeThreadRef) : null;
const routeThreadRefForShortcuts = useRef(routeThreadRef);
routeThreadRefForShortcuts.current = routeThreadRef;
const markSidebarThreadNavigation = useSidebarActiveThreadScroll({
hasThreadRoute: routeTarget !== null,
routeThreadKey,
});
const routeTerminalOpen = useTerminalUiStateStore((state) =>
routeThreadRef
? selectThreadTerminalUiState(state.terminalUiStateByThreadKey, routeThreadRef).terminalOpen
Expand Down Expand Up @@ -3431,29 +3441,26 @@ export default function LegacySidebar() {
projectExpandedById,
projectExpansionPreferenceKeys(project),
);
const activeThreadKey = routeThreadKey ?? undefined;
const pinnedCollapsedThread =
!projectExpanded && activeThreadKey
? (projectThreads.find(
(thread) =>
scopedThreadKey(scopeThreadRef(thread.environmentId, thread.id)) ===
activeThreadKey,
) ?? null)
: null;
const shouldShowThreadPanel = projectExpanded || pinnedCollapsedThread !== null;
if (!shouldShowThreadPanel) {
return [];
}
const isThreadListExpanded = expandedThreadListsByProject.has(project.projectKey);
const hasOverflowingThreads = projectThreads.length > sidebarThreadPreviewCount;
const previewThreads =
isThreadListExpanded || !hasOverflowingThreads
? projectThreads
: projectThreads.slice(0, sidebarThreadPreviewCount);
const renderedThreads = pinnedCollapsedThread ? [pinnedCollapsedThread] : previewThreads;
return renderedThreads.map((thread) =>
scopedThreadKey(scopeThreadRef(thread.environmentId, thread.id)),
);
const visibleThreadsResult = getVisibleThreadsForProject({
activeThreadId: routeThreadKey ?? undefined,
getThreadId: (thread) => scopedThreadKey(scopeThreadRef(thread.environmentId, thread.id)),
isThreadListExpanded,
previewLimit: sidebarThreadPreviewCount,
threads: projectThreads,
});
if (projectExpanded) {
return visibleThreadsResult.visibleThreads.map((thread) =>
scopedThreadKey(scopeThreadRef(thread.environmentId, thread.id)),
);
}
return routeThreadKey &&
visibleThreadsResult.visibleThreads.some(
(thread) =>
scopedThreadKey(scopeThreadRef(thread.environmentId, thread.id)) === routeThreadKey,
)
? [routeThreadKey]
: [];
}),
[
sidebarThreadSortOrder,
Expand Down Expand Up @@ -3778,6 +3785,7 @@ export default function LegacySidebar() {
attachThreadListAutoAnimateRef={attachThreadListAutoAnimateRef}
expandThreadListForProject={expandThreadListForProject}
collapseThreadListForProject={collapseThreadListForProject}
markSidebarThreadNavigation={markSidebarThreadNavigation}
dragInProgressRef={dragInProgressRef}
suppressProjectClickAfterDragRef={suppressProjectClickAfterDragRef}
suppressProjectClickForContextMenuRef={suppressProjectClickForContextMenuRef}
Expand Down
27 changes: 22 additions & 5 deletions apps/web/src/components/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ import {
} from "./ui/menu";
import { SidebarContent, SidebarGroup, SidebarMenuButton, useSidebar } from "./ui/sidebar";
import { SidebarChromeFooter, SidebarChromeHeader } from "./sidebar/SidebarChrome";
import { useSidebarActiveThreadScroll } from "./sidebar/useSidebarActiveThreadScroll";
import { Popover, PopoverPopup, PopoverTrigger } from "./ui/popover";
import { Tooltip, TooltipPopup, TooltipProvider, TooltipTrigger } from "./ui/tooltip";
import {
Expand Down Expand Up @@ -1363,6 +1364,7 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: {
return (
<li
data-thread-item
data-sidebar-thread-key={threadKey}
className="list-none [content-visibility:auto] [contain-intrinsic-size:auto_34px]"
>
<Tooltip>
Expand Down Expand Up @@ -1513,6 +1515,7 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: {
return (
<li
data-thread-item
data-sidebar-thread-key={threadKey}
ref={sortable?.setNodeRef}
style={
sortable
Expand Down Expand Up @@ -1795,8 +1798,9 @@ const SidebarSearchResultRow = memo(function SidebarSearchResultRow(props: {
threadId: thread.id,
});
const terminalStatus = terminalStatusFromRunningIds(runningTerminalIds);
const threadKey = scopedThreadKey(scopeThreadRef(thread.environmentId, thread.id));
return (
<li role="presentation" className="list-none">
<li role="presentation" className="list-none" data-sidebar-thread-key={threadKey}>
<Tooltip>
<TooltipTrigger
render={
Expand Down Expand Up @@ -2021,6 +2025,10 @@ export default function Sidebar() {
[routeDraftThread, routeTarget],
);
const routeThreadKey = routeThreadRef ? scopedThreadKey(routeThreadRef) : null;
const markSidebarThreadNavigation = useSidebarActiveThreadScroll({
hasThreadRoute: routeTarget !== null,
routeThreadKey,
});
const routeTargetRef = useRef(routeTarget);
routeTargetRef.current = routeTarget;
// Post-settle navigation validates against the CURRENT route, not the one
Expand Down Expand Up @@ -2226,7 +2234,8 @@ export default function Sidebar() {
(thread) =>
thread.archivedAt === null &&
(scopedProjectKeys === null ||
scopedProjectKeys.has(`${thread.environmentId}:${thread.projectId}`)),
scopedProjectKeys.has(`${thread.environmentId}:${thread.projectId}`) ||
scopedThreadKey(scopeThreadRef(thread.environmentId, thread.id)) === routeThreadKey),
);
const pinned: EnvironmentThreadShell[] = [];
const active: EnvironmentThreadShell[] = [];
Expand Down Expand Up @@ -2302,6 +2311,7 @@ export default function Sidebar() {
autoSettleOnMerge,
changeRequestSnapshotByKey,
nowMinute,
routeThreadKey,
scopedProjectKeys,
serverConfigs,
snoozeWakeTick,
Expand Down Expand Up @@ -2521,6 +2531,13 @@ export default function Sidebar() {
},
[clearSelection, isMobile, router, setOpenMobile, setSelectionAnchor],
);
const navigateToThreadFromSidebar = useCallback(
(threadRef: ScopedThreadRef) => {
markSidebarThreadNavigation(scopedThreadKey(threadRef));
navigateToThread(threadRef);
},
[markSidebarThreadNavigation, navigateToThread],
);

const navigateToDraft = useCallback(
(draftId: DraftId) => {
Expand Down Expand Up @@ -2642,9 +2659,9 @@ export default function Sidebar() {
if (isTrailingDoubleClick(event.detail)) {
return;
}
navigateToThread(threadRef);
navigateToThreadFromSidebar(threadRef);
},
[navigateToThread, rangeSelectTo, toggleThreadSelection],
[navigateToThreadFromSidebar, rangeSelectTo, toggleThreadSelection],
);

// A settle per thread at a time: double clicks and repeated menu picks
Expand Down Expand Up @@ -3960,7 +3977,7 @@ export default function Sidebar() {
timestampFormat={timestampFormat}
getCurrentShortcutContext={getCurrentSidebarShortcutContext}
onThreadClick={handleThreadClick}
onThreadActivate={navigateToThread}
onThreadActivate={navigateToThreadFromSidebar}
onStartRename={startThreadRename}
onRenameTitleChange={setRenamingTitle}
onCommitRename={commitThreadRename}
Expand Down
Loading
Loading