From a5dd2d1bb46a075f7f170f727a0f2bcf8cd7cc45 Mon Sep 17 00:00:00 2001 From: NanoRisk6 Date: Wed, 29 Jul 2026 23:57:32 -0400 Subject: [PATCH] fix(desktop): persist Inbox Show unread only preference (#3669) Store the toggle in localStorage so remounting Inbox or restarting Buzz restores the last choice. Default remains off when no preference is saved. Signed-off-by: NanoRisk6 --- .../lib/inboxUnreadOnlyPreference.test.mjs | 71 +++++++++++++++++++ .../home/lib/inboxUnreadOnlyPreference.ts | 34 +++++++++ desktop/src/features/home/ui/HomeView.tsx | 15 +++- 3 files changed, 118 insertions(+), 2 deletions(-) create mode 100644 desktop/src/features/home/lib/inboxUnreadOnlyPreference.test.mjs create mode 100644 desktop/src/features/home/lib/inboxUnreadOnlyPreference.ts diff --git a/desktop/src/features/home/lib/inboxUnreadOnlyPreference.test.mjs b/desktop/src/features/home/lib/inboxUnreadOnlyPreference.test.mjs new file mode 100644 index 0000000000..24e8fe5fbb --- /dev/null +++ b/desktop/src/features/home/lib/inboxUnreadOnlyPreference.test.mjs @@ -0,0 +1,71 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import { + INBOX_UNREAD_ONLY_STORAGE_KEY, + readInboxUnreadOnlyPreference, + writeInboxUnreadOnlyPreference, +} from "./inboxUnreadOnlyPreference.ts"; + +function memoryStorage(initial = {}) { + const map = new Map(Object.entries(initial)); + return { + getItem(key) { + return map.has(key) ? map.get(key) : null; + }, + setItem(key, value) { + map.set(key, String(value)); + }, + store: map, + }; +} + +test("readInboxUnreadOnlyPreference defaults to false when unset", () => { + const storage = memoryStorage(); + assert.equal(readInboxUnreadOnlyPreference(storage), false); +}); + +test("readInboxUnreadOnlyPreference returns true for true/1", () => { + assert.equal( + readInboxUnreadOnlyPreference( + memoryStorage({ [INBOX_UNREAD_ONLY_STORAGE_KEY]: "true" }), + ), + true, + ); + assert.equal( + readInboxUnreadOnlyPreference( + memoryStorage({ [INBOX_UNREAD_ONLY_STORAGE_KEY]: "1" }), + ), + true, + ); +}); + +test("readInboxUnreadOnlyPreference returns false for other values", () => { + assert.equal( + readInboxUnreadOnlyPreference( + memoryStorage({ [INBOX_UNREAD_ONLY_STORAGE_KEY]: "false" }), + ), + false, + ); + assert.equal( + readInboxUnreadOnlyPreference( + memoryStorage({ [INBOX_UNREAD_ONLY_STORAGE_KEY]: "yes" }), + ), + false, + ); +}); + +test("writeInboxUnreadOnlyPreference persists and round-trips", () => { + const storage = memoryStorage(); + writeInboxUnreadOnlyPreference(true, storage); + assert.equal(storage.getItem(INBOX_UNREAD_ONLY_STORAGE_KEY), "true"); + assert.equal(readInboxUnreadOnlyPreference(storage), true); + + writeInboxUnreadOnlyPreference(false, storage); + assert.equal(storage.getItem(INBOX_UNREAD_ONLY_STORAGE_KEY), "false"); + assert.equal(readInboxUnreadOnlyPreference(storage), false); +}); + +test("readInboxUnreadOnlyPreference returns false when storage is null", () => { + assert.equal(readInboxUnreadOnlyPreference(null), false); +}); diff --git a/desktop/src/features/home/lib/inboxUnreadOnlyPreference.ts b/desktop/src/features/home/lib/inboxUnreadOnlyPreference.ts new file mode 100644 index 0000000000..bd77ce5586 --- /dev/null +++ b/desktop/src/features/home/lib/inboxUnreadOnlyPreference.ts @@ -0,0 +1,34 @@ +/** Persist Inbox "Show unread only" across navigations and restarts (#3669). */ + +export const INBOX_UNREAD_ONLY_STORAGE_KEY = "buzz.desktop.inbox-unread-only.v1"; + +export function readInboxUnreadOnlyPreference( + storage: Pick | null | undefined = typeof window !== + "undefined" + ? window.localStorage + : null, +): boolean { + if (!storage) return false; + try { + const raw = storage.getItem(INBOX_UNREAD_ONLY_STORAGE_KEY); + if (raw === null) return false; + return raw === "true" || raw === "1"; + } catch { + return false; + } +} + +export function writeInboxUnreadOnlyPreference( + value: boolean, + storage: Pick | null | undefined = typeof window !== + "undefined" + ? window.localStorage + : null, +): void { + if (!storage) return; + try { + storage.setItem(INBOX_UNREAD_ONLY_STORAGE_KEY, value ? "true" : "false"); + } catch { + // Quota / private mode — preference is best-effort. + } +} diff --git a/desktop/src/features/home/ui/HomeView.tsx b/desktop/src/features/home/ui/HomeView.tsx index 0f7c851643..1eb8f6de6b 100644 --- a/desktop/src/features/home/ui/HomeView.tsx +++ b/desktop/src/features/home/ui/HomeView.tsx @@ -15,6 +15,10 @@ import { formatInboxFullTimestamp, getInboxItemConversationId, } from "@/features/home/lib/inbox"; +import { + readInboxUnreadOnlyPreference, + writeInboxUnreadOnlyPreference, +} from "@/features/home/lib/inboxUnreadOnlyPreference"; import { useInboxSelectionAnchor } from "@/features/home/useInboxSelectionAnchor"; import { useOwnedAgentPubkeys } from "@/features/home/useOwnedAgentPubkeys"; import { @@ -107,7 +111,14 @@ export function HomeView({ homeInboxWidthPx > 0 && homeInboxWidthPx < INBOX_SINGLE_COLUMN_BREAKPOINT_PX; const [filter, setFilter] = React.useState("all"); - const [unreadOnly, setUnreadOnly] = React.useState(false); + // Persist "Show unread only" across Inbox remounts and app restarts (#3669). + const [unreadOnly, setUnreadOnly] = React.useState(() => + readInboxUnreadOnlyPreference(), + ); + const handleUnreadOnlyChange = React.useCallback((checked: boolean) => { + setUnreadOnly(checked); + writeInboxUnreadOnlyPreference(checked); + }, []); // Explicit selections are mirrored to the URL (`?item=`), so back/forward // restores the detail pane each history entry was showing and reloads // restore it from the URL. Default/automatic selection stays local-only — @@ -697,7 +708,7 @@ export function HomeView({ handleUserSelectItem(null); setSelectedReminderId(reminderId); }} - onUnreadOnlyChange={setUnreadOnly} + onUnreadOnlyChange={handleUnreadOnlyChange} reminderPubkey={currentPubkey} reminders={pendingReminders} selectedConversationId={selectedConversationId}