authorgravatar for maria@kuuro.netmaria <maria@kuuro.net> 2026-09-02 22:36:35-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-02 22:36:35-04:00
log31eeb443305a4e11c8b20a7c8ff2b3f5e841eb0f
treee0f34f12a9f23b1dfef59e69c8639c1b37b55bed
parent9ebbeda5a03fd8fce7be147b65ea396e50721c68
signature Signed by PGP key B5690EEEBB952194

fix(sidebar): collapse settled and snoozed shelves by default (#9314)

Co-authored-by: maria-rcks <254055478+maria-rcks@users.noreply.github.com>

4 files changed, 31 insertions(+), 30 deletions(-)

apps/mobile/src/features/threads/use-thread-list-v2-shelf-preferences.ts+4-4
...@@ -14,9 +14,9 @@ export function useThreadListV2ShelfPreferences() {...@@ -14,9 +14,9 @@ export function useThreadListV2ShelfPreferences() {
14 const savePreferences = useAtomSet(updateMobilePreferencesAtom);14 const savePreferences = useAtomSet(updateMobilePreferencesAtom);
15 const loaded = AsyncResult.isSuccess(preferencesResult);15 const loaded = AsyncResult.isSuccess(preferencesResult);
16 const snoozedShelfExpanded =16 const snoozedShelfExpanded =
17 loaded && preferencesResult.value.threadListV2SnoozedShelfExpanded === true;17 loaded && preferencesResult.value.threadListSnoozedShelfExpanded === true;
18 const settledShelfExpanded =18 const settledShelfExpanded =
19 !loaded || preferencesResult.value.threadListV2SettledShelfExpanded !== false;19 loaded && preferencesResult.value.threadListSettledShelfExpanded === true;
20 const snoozedShelfExpandedRef = useRef(snoozedShelfExpanded);20 const snoozedShelfExpandedRef = useRef(snoozedShelfExpanded);
21 const settledShelfExpandedRef = useRef(settledShelfExpanded);21 const settledShelfExpandedRef = useRef(settledShelfExpanded);
22 snoozedShelfExpandedRef.current = snoozedShelfExpanded;22 snoozedShelfExpandedRef.current = snoozedShelfExpanded;
...@@ -26,13 +26,13 @@ export function useThreadListV2ShelfPreferences() {...@@ -26,13 +26,13 @@ export function useThreadListV2ShelfPreferences() {
26 if (!loaded) return;26 if (!loaded) return;
27 const expanded = !snoozedShelfExpandedRef.current;27 const expanded = !snoozedShelfExpandedRef.current;
28 snoozedShelfExpandedRef.current = expanded;28 snoozedShelfExpandedRef.current = expanded;
29 savePreferences({ threadListV2SnoozedShelfExpanded: expanded });29 savePreferences({ threadListSnoozedShelfExpanded: expanded });
30 }, [loaded, savePreferences]);30 }, [loaded, savePreferences]);
31 const toggleSettledShelf = useCallback(() => {31 const toggleSettledShelf = useCallback(() => {
32 if (!loaded) return;32 if (!loaded) return;
33 const expanded = !settledShelfExpandedRef.current;33 const expanded = !settledShelfExpandedRef.current;
34 settledShelfExpandedRef.current = expanded;34 settledShelfExpandedRef.current = expanded;
35 savePreferences({ threadListV2SettledShelfExpanded: expanded });35 savePreferences({ threadListSettledShelfExpanded: expanded });
36 }, [loaded, savePreferences]);36 }, [loaded, savePreferences]);
3737
38 return {38 return {
apps/mobile/src/lib/storage.test.ts+14-12
...@@ -213,33 +213,35 @@ describe("mobile connection storage", () => {...@@ -213,33 +213,35 @@ describe("mobile connection storage", () => {
213 expect(fallback.updatedAt).toEqual(expect.any(Number));213 expect(fallback.updatedAt).toEqual(expect.any(Number));
214 });214 });
215215
216 it("persists Thread List v2 shelf expansion preferences", async () => {216 it("persists thread list shelf expansion preferences", async () => {
217 await expect(217 await expect(
218 savePreferencesPatch({218 savePreferencesPatch({
219 threadListV2SettledShelfExpanded: false,219 threadListSettledShelfExpanded: false,
220 threadListV2SnoozedShelfExpanded: true,220 threadListSnoozedShelfExpanded: true,
221 }),221 }),
222 ).resolves.toEqual({222 ).resolves.toEqual({
223 threadListV2SettledShelfExpanded: false,223 threadListSettledShelfExpanded: false,
224 threadListV2SnoozedShelfExpanded: true,224 threadListSnoozedShelfExpanded: true,
225 });225 });
226226
227 await expect(loadPreferences()).resolves.toEqual({227 await expect(loadPreferences()).resolves.toEqual({
228 threadListV2SettledShelfExpanded: false,228 threadListSettledShelfExpanded: false,
229 threadListV2SnoozedShelfExpanded: true,229 threadListSnoozedShelfExpanded: true,
230 });230 });
231 expect(JSON.parse(mocks.getPreferencesJson() ?? "")).toEqual({231 expect(JSON.parse(mocks.getPreferencesJson() ?? "")).toEqual({
232 threadListV2SettledShelfExpanded: false,232 threadListSettledShelfExpanded: false,
233 threadListV2SnoozedShelfExpanded: true,233 threadListSnoozedShelfExpanded: true,
234 });234 });
235 });235 });
236236
237 it("ignores invalid Thread List v2 shelf expansion preference types", async () => {237 it("drops legacy and invalid thread list shelf expansion preferences", async () => {
238 mocks.setPreferencesJson(238 mocks.setPreferencesJson(
239 JSON.stringify({239 JSON.stringify({
240 baseFontSize: 17,240 baseFontSize: 17,
241 threadListV2SettledShelfExpanded: "false",241 threadListV2SettledShelfExpanded: true,
242 threadListV2SnoozedShelfExpanded: 1,242 threadListV2SnoozedShelfExpanded: true,
243 threadListSettledShelfExpanded: "false",
244 threadListSnoozedShelfExpanded: 1,
243 }),245 }),
244 10,246 10,
245 );247 );
apps/mobile/src/persistence/mobile-preferences.ts+9-10
...@@ -41,10 +41,9 @@ export interface Preferences {...@@ -41,10 +41,9 @@ export interface Preferences {
41 readonly legacyThreadListEnabled?: boolean;41 readonly legacyThreadListEnabled?: boolean;
42 /** Device-local counterpart of desktop's `planModeEnabled` legacy flag. */42 /** Device-local counterpart of desktop's `planModeEnabled` legacy flag. */
43 readonly planModeEnabled?: boolean;43 readonly planModeEnabled?: boolean;
44 /** Undefined preserves the default expanded Settled shelf. */44 /** Fresh keys reset both shelves to collapsed when users update. */
45 readonly threadListV2SettledShelfExpanded?: boolean;45 readonly threadListSettledShelfExpanded?: boolean;
46 /** Undefined preserves the default collapsed Snoozed shelf. */46 readonly threadListSnoozedShelfExpanded?: boolean;
47 readonly threadListV2SnoozedShelfExpanded?: boolean;
48}47}
4948
50export class MobilePreferencesLoadError extends Schema.TaggedErrorClass<MobilePreferencesLoadError>()(49export class MobilePreferencesLoadError extends Schema.TaggedErrorClass<MobilePreferencesLoadError>()(
...@@ -102,8 +101,8 @@ function sanitizePreferences(parsed: Preferences): Preferences {...@@ -102,8 +101,8 @@ function sanitizePreferences(parsed: Preferences): Preferences {
102 projectGroupingMode?: SidebarProjectGroupingMode;101 projectGroupingMode?: SidebarProjectGroupingMode;
103 legacyThreadListEnabled?: boolean;102 legacyThreadListEnabled?: boolean;
104 planModeEnabled?: boolean;103 planModeEnabled?: boolean;
105 threadListV2SettledShelfExpanded?: boolean;104 threadListSettledShelfExpanded?: boolean;
106 threadListV2SnoozedShelfExpanded?: boolean;105 threadListSnoozedShelfExpanded?: boolean;
107 } = {};106 } = {};
108107
109 if (typeof parsed.liveActivitiesEnabled === "boolean") {108 if (typeof parsed.liveActivitiesEnabled === "boolean") {
...@@ -171,11 +170,11 @@ function sanitizePreferences(parsed: Preferences): Preferences {...@@ -171,11 +170,11 @@ function sanitizePreferences(parsed: Preferences): Preferences {
171 if (typeof parsed.planModeEnabled === "boolean") {170 if (typeof parsed.planModeEnabled === "boolean") {
172 preferences.planModeEnabled = parsed.planModeEnabled;171 preferences.planModeEnabled = parsed.planModeEnabled;
173 }172 }
174 if (typeof parsed.threadListV2SettledShelfExpanded === "boolean") {173 if (typeof parsed.threadListSettledShelfExpanded === "boolean") {
175 preferences.threadListV2SettledShelfExpanded = parsed.threadListV2SettledShelfExpanded;174 preferences.threadListSettledShelfExpanded = parsed.threadListSettledShelfExpanded;
176 }175 }
177 if (typeof parsed.threadListV2SnoozedShelfExpanded === "boolean") {176 if (typeof parsed.threadListSnoozedShelfExpanded === "boolean") {
178 preferences.threadListV2SnoozedShelfExpanded = parsed.threadListV2SnoozedShelfExpanded;177 preferences.threadListSnoozedShelfExpanded = parsed.threadListSnoozedShelfExpanded;
179 }178 }
180 return preferences;179 return preferences;
181}180}
apps/web/src/components/Sidebar.tsx+4-4
...@@ -211,9 +211,9 @@ import {...@@ -211,9 +211,9 @@ import {
211// stays behind an explicit Show more.211// stays behind an explicit Show more.
212const SETTLED_TAIL_INITIAL_COUNT = 10;212const SETTLED_TAIL_INITIAL_COUNT = 10;
213const SETTLED_TAIL_PAGE_COUNT = 25;213const SETTLED_TAIL_PAGE_COUNT = 25;
214// Keep the v2 key so existing preferences survive the v2-to-default rename.214// Fresh keys deliberately reset both shelves to collapsed for existing users.
215const SETTLED_SHELF_EXPANDED_KEY = "t3code:sidebar-v2:settled-expanded";215const SETTLED_SHELF_EXPANDED_KEY = "t3code:sidebar:settled-expanded";
216const SNOOZED_SHELF_EXPANDED_KEY = "t3code:sidebar-v2:snoozed-expanded";216const SNOOZED_SHELF_EXPANDED_KEY = "t3code:sidebar:snoozed-expanded";
217217
218function compactSidebarTimeLabel(label: string): string {218function compactSidebarTimeLabel(label: string): string {
219 if (label === "just now") return "now";219 if (label === "just now") return "now";
...@@ -2274,7 +2274,7 @@ export default function Sidebar() {...@@ -2274,7 +2274,7 @@ export default function Sidebar() {
2274 );2274 );
2275 const [settledShelfExpanded, setSettledShelfExpanded] = useLocalStorage(2275 const [settledShelfExpanded, setSettledShelfExpanded] = useLocalStorage(
2276 SETTLED_SHELF_EXPANDED_KEY,2276 SETTLED_SHELF_EXPANDED_KEY,
2277 true,2277 false,
2278 Schema.Boolean,2278 Schema.Boolean,
2279 );2279 );
2280 const toggleSettledShelf = useCallback(2280 const toggleSettledShelf = useCallback(