fix(desktop): remember Inbox unread-only choice (#7672)

## 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 <diem@squareup.com>
This commit is contained in:
Diem Nguyen
2026-09-22 15:55:55 -07:00
committed by GitHub
parent 7e96f91229
commit 312cf674fa
2 changed files with 37 additions and 2 deletions

View File

@@ -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<InboxFilter>("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}

View File

@@ -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,
}) => {