Skip to content

Only mark Inbox items read on explicit action #3683

Description

@thevidarbob

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

  1. Open the Desktop Inbox with one or more unread items.
  2. Enable Show unread only.
  3. Select an unread row to preview its conversation in the detail pane.
  4. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions