Problem
In the Desktop Inbox, opening or selecting an unread row immediately marks the item read. This makes browsing destructive to triage state: a user cannot preview several unread items without changing the queue, and an item can disappear from Show unread only merely because it was selected.
Current code couples the two actions in the Inbox selection handler: after selecting the item, it calls markItemRead(itemId).
- Selection handler:
|
onSelect={(itemId) => { |
|
const item = findInboxItemByEventId(inboxItems, itemId); |
|
setUnreadBoundary( |
|
item && !effectiveDoneSet.has(item.id) |
|
? { |
|
conversationId: item.conversationId, |
|
eventId: item.id, |
|
} |
|
: null, |
|
); |
|
setSelectedDraftKey(null); |
|
setSelectedReminderId(null); |
|
handleUserSelectItem(itemId); |
|
markItemRead(itemId); |
|
}} |
- Explicit row-level Mark as read action already exists:
|
<div className="pointer-events-none absolute right-3 top-2 z-10 flex items-center gap-0.5 rounded-full bg-[var(--inbox-row-highlight-bg)] p-1 opacity-0 transition-opacity duration-150 ease-out group-hover/inbox-item:pointer-events-auto group-hover/inbox-item:opacity-100 group-focus-within/inbox-item:pointer-events-auto group-focus-within/inbox-item:opacity-100"> |
|
{isDone ? ( |
|
<InboxRowActionButton |
|
label="Mark unread" |
|
onClick={() => onMarkUnread(item.id)} |
|
> |
|
<MailOpen className="!h-4 !w-4" /> |
|
</InboxRowActionButton> |
|
) : ( |
|
<InboxRowActionButton |
|
label="Mark as read" |
|
onClick={() => onMarkRead(item.id)} |
|
> |
|
<MailOpen className="!h-4 !w-4" /> |
|
</InboxRowActionButton> |
- Read action advances the shared channel/thread/message read markers:
|
const markItemRead = React.useCallback( |
|
(itemId: string) => { |
|
const item = itemById.get(itemId); |
|
const localUnreadIds = item ? getGroupedInboxItemIds(item) : [itemId]; |
|
for (const id of localUnreadIds) { |
|
undoUnreadLocal(id); |
|
} |
|
const threadRootId = item ? getInboxThreadRootId(item) : null; |
|
if (item && threadRootId) { |
|
const markedReplyIds = new Set<string>(); |
|
for (const reply of [item.item, ...item.groupItems]) { |
|
if (!isThreadReply(reply.tags) || markedReplyIds.has(reply.id)) { |
|
continue; |
|
} |
|
markedReplyIds.add(reply.id); |
|
markMessageRead(reply.id, reply.createdAt); |
|
} |
|
markThreadRead(threadRootId, item.latestActivityAt); |
|
const groupedChannelRead = getGroupedChannelReadTimestamp(item); |
|
if (groupedChannelRead) { |
|
markChannelRead( |
|
groupedChannelRead.channelId, |
|
new Date(groupedChannelRead.timestamp * 1_000).toISOString(), |
|
); |
|
} |
|
return; |
|
} |
|
|
|
const channelId = item?.item.channelId ?? null; |
|
if (item && channelId) { |
|
markChannelRead( |
|
channelId, |
|
new Date(item.latestActivityAt * 1_000).toISOString(), |
|
); |
|
return; |
|
} |
|
markDoneLocal(itemId); |
|
}, |
|
[ |
|
itemById, |
|
markChannelRead, |
|
markDoneLocal, |
|
markMessageRead, |
|
markThreadRead, |
|
undoUnreadLocal, |
|
], |
|
); |
Steps to reproduce
- Open the Desktop Inbox with one or more unread items.
- Enable Show unread only.
- Select an unread row to preview its conversation in the detail pane.
- Observe that selection marks it read; moving to other items drains or changes the unread queue.
Expected behavior
Use a Slack-style inbox triage model: selection previews an item, while read/completed state changes only through an explicit checkoff or Mark as read action. Selected and unread should remain separate states.
This request does not prescribe a storage or protocol implementation. The existing explicit read action and shared NIP-RS markers are relevant behavior to preserve.
Acceptance criteria
- Opening or previewing an unread Inbox item does not mark it read.
- Read state changes only through an explicit checkoff or mark-read action.
- Users can move through multiple unread items without losing their queue.
- Existing manual Mark unread behavior remains coherent with explicit completion.
- Keyboard and accessibility behavior are defined: preview/select and mark-read are distinct operable actions with clear focus and screen-reader labels.
- An explicit read-state change persists correctly across refresh and devices.
- Selected styling is distinguishable from unread styling.
- In Show unread only, previewing the selected row does not remove it from the list; explicit completion follows a predictable next-item behavior.
Related work reviewed
GitHub Discussions are disabled for this repository, so there was no discussion thread to match.
Problem
In the Desktop Inbox, opening or selecting an unread row immediately marks the item read. This makes browsing destructive to triage state: a user cannot preview several unread items without changing the queue, and an item can disappear from Show unread only merely because it was selected.
Current code couples the two actions in the Inbox selection handler: after selecting the item, it calls
markItemRead(itemId).buzz/desktop/src/features/home/ui/HomeView.tsx
Lines 673 to 687 in 63496cc
buzz/desktop/src/features/home/ui/InboxListPane.tsx
Lines 421 to 435 in 63496cc
buzz/desktop/src/features/home/useHomeInboxReadState.ts
Lines 181 to 227 in 63496cc
Steps to reproduce
Expected behavior
Use a Slack-style inbox triage model: selection previews an item, while read/completed state changes only through an explicit checkoff or Mark as read action. Selected and unread should remain separate states.
This request does not prescribe a storage or protocol implementation. The existing explicit read action and shared NIP-RS markers are relevant behavior to preserve.
Acceptance criteria
Related work reviewed
GitHub Discussions are disabled for this repository, so there was no discussion thread to match.