From 312cf674fad5563c24a66ec5350d306d192ed64a Mon Sep 17 00:00:00 2001 From: Diem Nguyen Date: Tue, 22 Sep 2026 15:55:55 -0700 Subject: [PATCH] fix(desktop): remember Inbox unread-only choice (#7672) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Inbox forgets the **Show unread only** choice after navigation or restart. Save the toggle with the existing safe storage helpers and restore it when Inbox opens. The preference stays on this installation and applies across accounts and communities. Fresh installations keep the current off default. If storage fails, the current view remains usable, but the choice may not survive navigation. ### Related issue Related report: #3669. Overlaps with #6105 and #3682. #6151 also persists category filters and pane widths. This draft keeps the change to the unread toggle and reuses the existing storage helpers. ### Testing The mock Desktop regression passed: enable the toggle, navigate to Agents and back, reload, then repeat with the toggle disabled. Both choices were restored. This exercises the UI with a synthetic fixture; a native app restart was not tested. The full local repository CI gate is pending. Keep this PR in draft until validation completes. ### Before and after behavior Same synthetic Desktop fixture in Chromium, 1280×720, on macOS. **Before:** production source from `41c5ace93740261ee5a5d962c2a17e8d846c2c1b`. **After:** PR head `b50c1a06d53fdc080e7aae6ee9aa2e9ad6b60a82`. The capture test asserted the displayed toggle state before taking each screenshot. 1. Enable **Show unread only**, open **Agents**, go back to **Inbox**, and reopen **Inbox options**. 2. Enable the toggle again, reload the page, and reopen **Inbox options**. Before the fix the toggle is off after both journeys; after the fix it stays on. These are mock-backed UI captures, not a native Desktop restart or live relay test. | Journey | Before: resets to off | After: stays on | | --- | --- | --- | | Inbox → Agents → back | ![before-navigation](https://raw.githubusercontent.com/block/buzz/8a4d4845a51ab096d573f96bd528aa0e7abb1dc9/pr-7672--before-navigation.png) | ![after-navigation](https://raw.githubusercontent.com/block/buzz/8a4d4845a51ab096d573f96bd528aa0e7abb1dc9/pr-7672--after-navigation.png) | | Enable → page reload | ![before-reload](https://raw.githubusercontent.com/block/buzz/8a4d4845a51ab096d573f96bd528aa0e7abb1dc9/pr-7672--before-reload.png) | ![after-reload](https://raw.githubusercontent.com/block/buzz/8a4d4845a51ab096d573f96bd528aa0e7abb1dc9/pr-7672--after-reload.png) | Generated with Codex Signed-off-by: Diem Nguyen --- desktop/src/features/home/ui/HomeView.tsx | 15 ++++++++++++-- desktop/tests/e2e/navigation.spec.ts | 24 +++++++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/desktop/src/features/home/ui/HomeView.tsx b/desktop/src/features/home/ui/HomeView.tsx index 0a16f27c4d..a803bc1485 100644 --- a/desktop/src/features/home/ui/HomeView.tsx +++ b/desktop/src/features/home/ui/HomeView.tsx @@ -66,6 +66,7 @@ import { KIND_REACTION } from "@/shared/constants/kinds"; import { topChromeInset } from "@/shared/layout/chromeLayout"; import { cn } from "@/shared/lib/cn"; import { normalizePubkey } from "@/shared/lib/pubkey"; +import { getStorageItem, setStorageItem } from "@/shared/lib/safeStorage"; import { useElementWidth } from "@/shared/hooks/use-mobile"; import { useThreadPanelWidth } from "@/shared/hooks/useThreadPanelWidth"; import { AUXILIARY_PANEL_SINGLE_COLUMN_BREAKPOINT_PX } from "@/shared/layout/AuxiliaryPanel"; @@ -81,6 +82,8 @@ const INBOX_SEARCH_KEYS = [ "profileView", ] as const; +const INBOX_UNREAD_ONLY_STORAGE_KEY = "buzz.desktop.inbox-unread-only"; + type HomeViewProps = { feed?: HomeFeedResponse; isLoading?: boolean; @@ -110,7 +113,15 @@ export function HomeView({ homeInboxWidthPx > 0 && homeInboxWidthPx < INBOX_SINGLE_COLUMN_BREAKPOINT_PX; const [filter, setFilter] = React.useState("all"); - const [unreadOnly, setUnreadOnly] = React.useState(false); + const [unreadOnly, setUnreadOnly] = React.useState( + () => getStorageItem(INBOX_UNREAD_ONLY_STORAGE_KEY) === "true", + ); + const handleUnreadOnlyChange = React.useCallback((value: boolean) => { + setUnreadOnly(value); + // Match other local preferences: storage failures keep the current view + // usable, while safeStorage records a warning if persistence is unavailable. + setStorageItem(INBOX_UNREAD_ONLY_STORAGE_KEY, String(value)); + }, []); // 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 — @@ -745,7 +756,7 @@ export function HomeView({ handleUserSelectItem(null); setSelectedReminderId(reminderId); }} - onUnreadOnlyChange={setUnreadOnly} + onUnreadOnlyChange={handleUnreadOnlyChange} reminderPubkey={currentPubkey} reminders={pendingReminders} selectedConversationId={selectedConversationId} diff --git a/desktop/tests/e2e/navigation.spec.ts b/desktop/tests/e2e/navigation.spec.ts index 6f6d1f2840..90ae8cc64f 100644 --- a/desktop/tests/e2e/navigation.spec.ts +++ b/desktop/tests/e2e/navigation.spec.ts @@ -277,6 +277,30 @@ test("open thread panels survive reload", async ({ page }) => { await expect(threadPanel).toBeVisible(); }); +test("inbox unread-only choice survives navigation and reload", async ({ + page, +}) => { + await page.goto("/"); + const options = page.getByRole("button", { name: "Inbox options" }); + const unreadOnly = page.getByRole("switch", { name: "Show unread only" }); + await options.click(); + await expect(unreadOnly).not.toBeChecked(); + + for (const enabled of [true, false]) { + await unreadOnly.setChecked(enabled); + await page.keyboard.press("Escape"); + await page.getByTestId("open-agents-view").click(); + await expect(page.getByTestId("home-inbox-list")).not.toBeVisible(); + await page.getByTestId("global-back").click(); + await options.click(); + await expect(unreadOnly).toBeChecked({ checked: enabled }); + + await page.reload(); + await options.click(); + await expect(unreadOnly).toBeChecked({ checked: enabled }); + } +}); + test("home inbox selection survives reload and back restores it", async ({ page, }) => {