authorgravatar for me@pixp.ccAlex <me@pixp.cc> 2026-08-11 03:32:22+00:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-08-10 23:32:22-04:00
log59d0c922e9363b209bcb004444c05e70fa9fdd8b
treefe56b2bdaced47a2e179b3f9d609e1ae817f4cf2
parent2ea51bd31f494ec3214a0c9f9adc3262c71e6530
signature Signed by PGP key B5690EEEBB952194

fix(web): persist sidebar shelf collapse state (#5136)

Co-authored-by: Illia Panasenko <hello@ipanasenko.me>

1 files changed, 23 insertions(+), 4 deletions(-)

apps/web/src/components/Sidebar.tsx+23-4
......@@ -1,5 +1,6 @@
11import { autoAnimate } from "@formkit/auto-animate";
22import { useAtomValue } from "@effect/atom-react";
3import * as Schema from "effect/Schema";
34import {
45 DndContext,
56 PointerSensor,
......@@ -100,6 +101,7 @@ import { openCommandPalette } from "../commandPaletteBus";
100101import { startNewThreadFromContext } from "../lib/chatThreadActions";
101102import { useClientSettings } from "../hooks/useSettings";
102103import { useCopyToClipboard } from "../hooks/useCopyToClipboard";
104import { useLocalStorage } from "../hooks/useLocalStorage";
103105import { useNowMinute } from "../hooks/useNowMinute";
104106import { useEnvironments, usePrimaryEnvironmentId } from "../state/environments";
105107import { useProjects, useThreadShells } from "../state/entities";
......@@ -176,6 +178,9 @@ import {
176178// stays behind an explicit Show more.
177179const SETTLED_TAIL_INITIAL_COUNT = 10;
178180const SETTLED_TAIL_PAGE_COUNT = 25;
181// Keep the v2 key so existing preferences survive the v2-to-default rename.
182const SETTLED_SHELF_EXPANDED_KEY = "t3code:sidebar-v2:settled-expanded";
183const SNOOZED_SHELF_EXPANDED_KEY = "t3code:sidebar-v2:snoozed-expanded";
179184
180185function compactSidebarTimeLabel(label: string): string {
181186 if (label === "just now") return "now";
......@@ -2044,8 +2049,15 @@ export default function Sidebar() {
20442049 () => setSettledVisibleCount((count) => count + SETTLED_TAIL_PAGE_COUNT),
20452050 [],
20462051 );
2047 const [settledShelfExpanded, setSettledShelfExpanded] = useState(true);
2048 const toggleSettledShelf = useCallback(() => setSettledShelfExpanded((value) => !value), []);
2052 const [settledShelfExpanded, setSettledShelfExpanded] = useLocalStorage(
2053 SETTLED_SHELF_EXPANDED_KEY,
2054 true,
2055 Schema.Boolean,
2056 );
2057 const toggleSettledShelf = useCallback(
2058 () => setSettledShelfExpanded((value) => !value),
2059 [setSettledShelfExpanded],
2060 );
20492061 const renderedSettledThreads = useMemo(() => {
20502062 if (settledShelfExpanded) return visibleSettledThreads;
20512063 if (routeThreadKey === null) return [];
......@@ -2059,8 +2071,15 @@ export default function Sidebar() {
20592071 // The snoozed shelf is collapsed by default: out of the way, never gone.
20602072 // Collapsed threads don't render (and so don't participate in jump
20612073 // shortcuts or multi-select), matching the settled tail's paging model.
2062 const [snoozedShelfExpanded, setSnoozedShelfExpanded] = useState(false);
2063 const toggleSnoozedShelf = useCallback(() => setSnoozedShelfExpanded((value) => !value), []);
2074 const [snoozedShelfExpanded, setSnoozedShelfExpanded] = useLocalStorage(
2075 SNOOZED_SHELF_EXPANDED_KEY,
2076 false,
2077 Schema.Boolean,
2078 );
2079 const toggleSnoozedShelf = useCallback(
2080 () => setSnoozedShelfExpanded((value) => !value),
2081 [setSnoozedShelfExpanded],
2082 );
20642083 const visibleSnoozedThreads = useMemo(() => {
20652084 if (snoozedShelfExpanded) return snoozedThreads;
20662085 // The open thread must never vanish behind the collapsed shelf: a