Skip to content

feat: hide stale DM threads from the left rail (local-only) #261

@sanity

Description

@sanity

Problem

After #244 / #258, every DM thread the local user is party to shows in the left-rail "Direct Messages" section, forever. There's no way to declutter: a one-off back-and-forth with someone six months ago sits at the bottom of the list until one of you talks again.

This is not about deleting messages from the recipient's view — that already works on a per-DM basis via "Purge thread" / `riverctl dm purge`, and it's intentionally a recipient-only action.

What's missing is a purely LOCAL-VIEW affordance: "hide this thread from my rail; bring it back if either of us sends a new DM."

Proposed behaviour

  • Each `(room, peer)` thread gets a "Hide thread" action in the DM thread modal.
  • Triggering it records the timestamp of the most recent message in the thread at that moment, as a "hidden-at" cutoff for that pair.
  • `DmRailSection` filters the thread out as long as no message in the thread has `timestamp > hidden_at`. As soon as either side sends a new DM, the thread re-appears with an unread badge (the new DM's timestamp is > hidden_at).
  • Sending an outbound DM to a hidden peer (e.g. via "Send Direct Message" from the member info modal) also revives the thread for the next render — same rule.
  • Multi-device: the hide should ride the chat-delegate state so a hide on device A doesn't keep "rediscovering" on device B. Natural piggyback on feat: persist outbound DM plaintext in the chat delegate #256's outbound-DM store.

Storage shape (proposal)

In the existing `OutboundDmStore` proposed by #256, OR alongside it:

```rust
pub struct HiddenDmThreadsV1 {
// Sorted Vec rather than HashMap for serde_json round-trip.
pub hidden: Vec,
}
pub struct HiddenDmThreadEntry {
pub room_owner_vk: VerifyingKey,
pub peer: MemberId,
pub hidden_at_ts: u64,
}
```

Lookup: `DmRailSection` builds the per-peer max timestamp once per render and compares against the matching `HiddenDmThreadEntry.hidden_at_ts`. Cost is O(rooms × dms) which it already pays.

UX details

  • The action belongs in the DM thread modal header — small ✕-but-not-the-close-button. Don't confuse with "Purge thread" which is destructive across all peers.
  • Tooltip: "Hide this conversation from your sidebar. It will come back when either of you sends a new message."
  • No confirmation prompt; the action is fully reversible (just send/receive a DM).
  • Possibly: also expose "Hidden conversations" in a settings/menu corner so a user who hid an active thread by mistake can un-hide it. Not strictly required for v1.

Out of scope

  • Permanently deleting a thread (not what's wanted).
  • Affecting the recipient's view (this is local-only).
  • Time-based auto-hide (separate decision — and easy to layer on top).

Estimate

Half a day if it piggybacks on #256's delegate store. A few hours longer if shipped standalone with localStorage as the v1 persistence.

Related

[AI-assisted - Claude]

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