From 3fca7daab6d73f140735913801f9e93797da7c7f Mon Sep 17 00:00:00 2001 From: Marco Mendoza Date: Wed, 12 Aug 2026 23:13:13 +0200 Subject: [PATCH 1/3] fix(web): allow collapsing the settled threads tail Expanding "Show 25 more" had no way back; the paging row now offers a hover rollback while pages remain and collapses to "Show less" once fully expanded. --- apps/web/src/components/Sidebar.tsx | 52 +++++++++++++++++++++++------ 1 file changed, 42 insertions(+), 10 deletions(-) diff --git a/apps/web/src/components/Sidebar.tsx b/apps/web/src/components/Sidebar.tsx index 840e4918bfa..5fa4e98de28 100644 --- a/apps/web/src/components/Sidebar.tsx +++ b/apps/web/src/components/Sidebar.tsx @@ -2068,6 +2068,11 @@ export default function Sidebar() { () => setSettledVisibleCount((count) => count + SETTLED_TAIL_PAGE_COUNT), [], ); + const showLessSettled = useCallback(() => setSettledVisibleCount(SETTLED_TAIL_INITIAL_COUNT), []); + // One paging row serves both directions: it offers the next page while the + // tail has one, and collapses back to the initial page once fully expanded. + const settledPagingVisible = + hiddenSettledCount > 0 || settledVisibleCount > SETTLED_TAIL_INITIAL_COUNT; const [settledShelfExpanded, setSettledShelfExpanded] = useLocalStorage( SETTLED_SHELF_EXPANDED_KEY, true, @@ -3722,16 +3727,43 @@ export default function Sidebar() { } return items; })()} - {settledShelfExpanded && hiddenSettledCount > 0 ? ( -
  • - + {settledShelfExpanded && settledPagingVisible ? ( +
  • + {hiddenSettledCount > 0 ? ( + + ) : ( + + )} + {/* Collapsing the tail is the secondary action while pages + remain: it rides the row on hover like the un-settle + rollback, so paging stays one row at any depth. The + thread rows anchor that rollback inside their px-2.5 + content box, so offset by the same padding here to land + on the shared right-hand axis. */} + {hiddenSettledCount > 0 && settledVisibleCount > SETTLED_TAIL_INITIAL_COUNT ? ( + + ) : null}
  • ) : null} From eaa2fdc7e722ffff1b7466336b710a2774b6b0b4 Mon Sep 17 00:00:00 2001 From: Marco Mendoza Date: Wed, 12 Aug 2026 23:37:45 +0200 Subject: [PATCH 2/3] fix(web): hide the settled paging row once the tail no longer overflows settledVisibleCount only resets on a project-scope change, so un-settling, deleting, or reclassifying threads could shrink the settled list back to (or below) the initial page while leaving a stale, orphaned "Show less" row. --- apps/web/src/components/Sidebar.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/web/src/components/Sidebar.tsx b/apps/web/src/components/Sidebar.tsx index 5fa4e98de28..a3a341e3345 100644 --- a/apps/web/src/components/Sidebar.tsx +++ b/apps/web/src/components/Sidebar.tsx @@ -2071,8 +2071,13 @@ export default function Sidebar() { const showLessSettled = useCallback(() => setSettledVisibleCount(SETTLED_TAIL_INITIAL_COUNT), []); // One paging row serves both directions: it offers the next page while the // tail has one, and collapses back to the initial page once fully expanded. + // Gated on the tail still exceeding the initial page — un-settling or + // deleting threads can shrink the list back to (or below) that page without + // resetting a stale settledVisibleCount, and a "Show less" with nothing + // left to collapse would be an orphaned row. const settledPagingVisible = - hiddenSettledCount > 0 || settledVisibleCount > SETTLED_TAIL_INITIAL_COUNT; + settledThreads.length > SETTLED_TAIL_INITIAL_COUNT && + (hiddenSettledCount > 0 || settledVisibleCount > SETTLED_TAIL_INITIAL_COUNT); const [settledShelfExpanded, setSettledShelfExpanded] = useLocalStorage( SETTLED_SHELF_EXPANDED_KEY, true, From 77178e0a4ff8fbc0813888df05c6382e063ccffe Mon Sep 17 00:00:00 2001 From: Marco Mendoza Date: Thu, 13 Aug 2026 00:28:25 +0200 Subject: [PATCH 3/3] fix(web): don't offer Show less when a pinned route thread makes it a no-op The open-thread pin can already put the tail at the same row count that collapsing to the initial page would produce, so basing "expanded" on settledVisibleCount alone showed a Show less control that changed nothing when clicked. Co-Authored-By: Claude Sonnet 5 --- apps/web/src/components/Sidebar.tsx | 43 +++++++++++++++++++---------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/apps/web/src/components/Sidebar.tsx b/apps/web/src/components/Sidebar.tsx index edd233a11c5..35627a78f2e 100644 --- a/apps/web/src/components/Sidebar.tsx +++ b/apps/web/src/components/Sidebar.tsx @@ -2052,29 +2052,44 @@ export default function Sidebar() { lastSettledResetKeyRef.current = settledResetKey; setSettledVisibleCount(SETTLED_TAIL_INITIAL_COUNT); } + // The open thread must never hide under "Show more": navigating into a deep + // settled thread (search, deep link) pulls its row into the visible tail so + // the highlight and the un-settle affordance stay reachable, regardless of + // how many rows the current page would otherwise show. + const settledRouteThreadIndex = useMemo(() => { + if (routeThreadKey === null) return -1; + return settledThreads.findIndex( + (thread) => + scopedThreadKey(scopeThreadRef(thread.environmentId, thread.id)) === routeThreadKey, + ); + }, [routeThreadKey, settledThreads]); const visibleSettledThreads = useMemo(() => { if (settledThreads.length <= settledVisibleCount) return settledThreads; const visible = settledThreads.slice(0, settledVisibleCount); - // The open thread must never hide under "Show more": navigating into a - // deep settled thread (search, deep link) pulls its row into the visible - // tail so the highlight and the un-settle affordance stay reachable. - if (routeThreadKey !== null) { - const routeThread = settledThreads - .slice(settledVisibleCount) - .find( - (thread) => - scopedThreadKey(scopeThreadRef(thread.environmentId, thread.id)) === routeThreadKey, - ); - if (routeThread !== undefined) visible.push(routeThread); + if (settledRouteThreadIndex >= settledVisibleCount) { + visible.push(settledThreads[settledRouteThreadIndex]!); } return visible; - }, [routeThreadKey, settledThreads, settledVisibleCount]); + }, [settledRouteThreadIndex, settledThreads, settledVisibleCount]); const hiddenSettledCount = settledThreads.length - visibleSettledThreads.length; const showMoreSettled = useCallback( () => setSettledVisibleCount((count) => count + SETTLED_TAIL_PAGE_COUNT), [], ); const showLessSettled = useCallback(() => setSettledVisibleCount(SETTLED_TAIL_INITIAL_COUNT), []); + // How many rows would be visible at the initial page size alone — used to + // tell whether collapsing back to it would actually change anything. A + // pinned open thread beyond the initial page stays visible no matter what + // settledVisibleCount is, so comparing against settledVisibleCount directly + // (as opposed to this floor) could call a page "expanded" when "Show less" + // would be a no-op: the pinned row keeps rendering either way. + const settledFloorVisibleCount = + settledThreads.length <= SETTLED_TAIL_INITIAL_COUNT + ? settledThreads.length + : settledRouteThreadIndex >= SETTLED_TAIL_INITIAL_COUNT + ? SETTLED_TAIL_INITIAL_COUNT + 1 + : SETTLED_TAIL_INITIAL_COUNT; + const settledIsExpanded = visibleSettledThreads.length > settledFloorVisibleCount; // One paging row serves both directions: it offers the next page while the // tail has one, and collapses back to the initial page once fully expanded. // Gated on the tail still exceeding the initial page — un-settling or @@ -2083,7 +2098,7 @@ export default function Sidebar() { // left to collapse would be an orphaned row. const settledPagingVisible = settledThreads.length > SETTLED_TAIL_INITIAL_COUNT && - (hiddenSettledCount > 0 || settledVisibleCount > SETTLED_TAIL_INITIAL_COUNT); + (hiddenSettledCount > 0 || settledIsExpanded); const [settledShelfExpanded, setSettledShelfExpanded] = useLocalStorage( SETTLED_SHELF_EXPANDED_KEY, true, @@ -3766,7 +3781,7 @@ export default function Sidebar() { thread rows anchor that rollback inside their px-2.5 content box, so offset by the same padding here to land on the shared right-hand axis. */} - {hiddenSettledCount > 0 && settledVisibleCount > SETTLED_TAIL_INITIAL_COUNT ? ( + {hiddenSettledCount > 0 && settledIsExpanded ? (