Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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);
});
34 changes: 34 additions & 0 deletions desktop/src/features/home/lib/inboxUnreadOnlyPreference.ts
Original file line number Diff line number Diff line change
@@ -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<Storage, "getItem"> | 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<Storage, "setItem"> | 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.
}
}
15 changes: 13 additions & 2 deletions desktop/src/features/home/ui/HomeView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -107,7 +111,14 @@ export function HomeView({
homeInboxWidthPx > 0 &&
homeInboxWidthPx < INBOX_SINGLE_COLUMN_BREAKPOINT_PX;
const [filter, setFilter] = React.useState<InboxFilter>("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 —
Expand Down Expand Up @@ -697,7 +708,7 @@ export function HomeView({
handleUserSelectItem(null);
setSelectedReminderId(reminderId);
}}
onUnreadOnlyChange={setUnreadOnly}
onUnreadOnlyChange={handleUnreadOnlyChange}
reminderPubkey={currentPubkey}
reminders={pendingReminders}
selectedConversationId={selectedConversationId}
Expand Down