diff --git a/apps/web/src/components/Sidebar.logic.ts b/apps/web/src/components/Sidebar.logic.ts index c4f3051292f..08eb6abc37d 100644 --- a/apps/web/src/components/Sidebar.logic.ts +++ b/apps/web/src/components/Sidebar.logic.ts @@ -63,7 +63,7 @@ export const THREAD_JUMP_HINT_SHOW_DELAY_MS = 100; export const SIDEBAR_THREAD_PREWARM_LIMIT = 10; // Settled-tail paging: recent history is the common lookup; the deep tail // stays behind an explicit Show more. Shared by SidebarV2, classic Recent -// (hide-settled shelf), and the board. +// (hide-settled shelf + recency headers), and the board. export const SETTLED_TAIL_INITIAL_COUNT = 10; export const SETTLED_TAIL_PAGE_COUNT = 25; export type SidebarNewThreadEnvMode = "local" | "worktree"; diff --git a/apps/web/src/components/Sidebar.tsx b/apps/web/src/components/Sidebar.tsx index c14acc75def..62f40211b07 100644 --- a/apps/web/src/components/Sidebar.tsx +++ b/apps/web/src/components/Sidebar.tsx @@ -218,6 +218,7 @@ import { orderItemsByPreferredIds, SETTLED_TAIL_INITIAL_COUNT, SETTLED_TAIL_PAGE_COUNT, + groupSettledThreadsByRecencyForSidebarV2, shouldClearThreadSelectionOnMouseDown, sortProjectsForSidebar, sortSettledThreadsForSidebarV2, @@ -235,6 +236,7 @@ import { useClientSettings, useUpdateClientSettings } from "~/hooks/useSettings" import { DEFAULT_HIDE_SETTLED_PROJECTS, DEFAULT_HIDE_SETTLED_RECENT, + DEFAULT_SIDEBAR_V2_SETTLED_RECENCY_HEADERS, DEFAULT_SIDEBAR_V2_SETTLED_SHELF_EXPANDED, DEFAULT_WEB_LIST_MODE, DEFAULT_WEB_THREAD_GROUPING, @@ -249,6 +251,7 @@ import { ListEnvironmentFilterSchema, ListHideSettledSchema, ListProjectFilterSchema, + SIDEBAR_V2_SETTLED_RECENCY_HEADERS_STORAGE_KEY, SIDEBAR_V2_SETTLED_SHELF_EXPANDED_STORAGE_KEY, WEB_LIST_MODE_LABELS, WEB_LIST_MODES, @@ -3674,7 +3677,13 @@ const SidebarRecentThreads = memo(function SidebarRecentThreads(props: { DEFAULT_SIDEBAR_V2_SETTLED_SHELF_EXPANDED, ListHideSettledSchema, ); + const [settledRecencyHeadersEnabled] = useLocalStorage( + SIDEBAR_V2_SETTLED_RECENCY_HEADERS_STORAGE_KEY, + DEFAULT_SIDEBAR_V2_SETTLED_RECENCY_HEADERS, + ListHideSettledSchema, + ); const [settledVisibleCount, setSettledVisibleCount] = useState(SETTLED_TAIL_INITIAL_COUNT); + const nowMinute = useNowMinute(); const { activeEntries, settledEntries } = useMemo(() => { if (!props.hideSettledThreads) { @@ -3773,6 +3782,32 @@ const SidebarRecentThreads = memo(function SidebarRecentThreads(props: { [setSettledShelfExpanded], ); + // Date headers under Settled (Last Hour / Earlier Today / …) — same helper + // and rules as Sidebar V2. Single-bucket pages omit headers; View menu can + // disable headers without changing settle-time sort order. + const settledRecencyLayout = useMemo(() => { + void nowMinute; + const layout = groupSettledThreadsByRecencyForSidebarV2( + renderedSettledEntries.map((entry) => entry.thread), + new Date(), + ); + if (!settledRecencyHeadersEnabled) { + return { groups: layout.groups, showHeaders: false }; + } + return layout; + }, [nowMinute, renderedSettledEntries, settledRecencyHeadersEnabled]); + + const settledEntryByThreadKey = useMemo( + () => + new Map( + renderedSettledEntries.map((entry) => [ + scopedThreadKey(scopeThreadRef(entry.thread.environmentId, entry.thread.id)), + entry, + ]), + ), + [renderedSettledEntries], + ); + // Multi-select range walks rendered rows only (collapsed shelf is out). const orderedRecentThreadKeys = useMemo( () => @@ -3811,6 +3846,38 @@ const SidebarRecentThreads = memo(function SidebarRecentThreads(props: { ); }; + const renderSettledRows = () => { + if (renderedSettledEntries.length === 0) { + return null; + } + if (settledRecencyLayout.showHeaders) { + return ( + <> + {settledRecencyLayout.groups.map((group) => ( +
+
+ {group.label} +
+ + {group.threads.flatMap((thread) => { + const entry = settledEntryByThreadKey.get( + scopedThreadKey(scopeThreadRef(thread.environmentId, thread.id)), + ); + return entry ? [renderThreadRow(entry)] : []; + })} + +
+ ))} + + ); + } + return ( + + {renderedSettledEntries.map(renderThreadRow)} + + ); + }; + const renderActiveList = () => { if (activeEntries.length === 0) { return null; @@ -3892,11 +3959,7 @@ const SidebarRecentThreads = memo(function SidebarRecentThreads(props: { )} /> - {renderedSettledEntries.length > 0 ? ( - - {renderedSettledEntries.map(renderThreadRow)} - - ) : null} + {renderSettledRows()} {settledShelfExpanded && hiddenSettledCount > 0 ? (