Skip to content

Merge DM and Group Chat into one unified Messages app (improved UI/UX) #412

Description

@KruGoL

Goal

Merge the two separate desktop apps — dm ("Messages", NIP-04 via sphere.communications) and group-chat (NIP-29 via sphere.groupChat) — into one unified Messages app with improved UI/UX: a single conversation list mixing DMs and groups, one chat window, one unread badge.

Current state (~5,400 lines under src/components/chat/, two near mirror-image stacks)

Registered as two apps in src/config/activities.ts, mounted by DesktopLayout.renderTabContent() as DMChatSection / GroupChatSection. Duplication between the stacks:

  • Section shells dm/DMChatSection.tsx (278 ln) vs group/GroupChatSection.tsx (391 ln) — ~70% identical (layout, sidebar collapse, URL params, autofocus, mention handler).
  • Sidebars DMConversationList vs GroupList — line-for-line the same markup; rows DMConversationItem vs GroupItem — same anatomy.
  • Message panes DMMessageList vs GroupMessageList — date grouping, "Load earlier", scroll-restoration and the orbit loader are copy-pasted.
  • Bubbles DMMessageBubble vs GroupMessageBubble — same styling base.
  • Data hooks hooks/useChat.ts (382 ln) vs hooks/useGroupChat.ts (691 ln) — mirrored structure (address-scoped keys, localStorage selection, 20/+20 pagination, mark-as-read hardcoded to its own tab id).
  • useDmUnreadCount / useGroupUnreadCount + three duplicated dual-badge call sites (DesktopShortcuts, Sidebar, TabBar).
  • Display helpers duplicated wholesale: data/chatTypes.ts vs utils/groupChatHelpers.ts.
  • Mini-chat (mini/) re-implements DM querying/sending/mark-as-read a third time outside useChat.
  • Legacy ChatSection.tsx (dm/global mode toggle) is effectively dead code.

Feature asymmetry a unified UI must respect: groups add roles/moderation/write-restrictions (canWriteToGroup), invites + ?join= deep link, discovery, reply threading, member list, relay connection lifecycle (ServicesProvider auto-connect + pinned groups); DMs add E2E, read receipts (SENT/DELIVERED/READ), typing indicators, welcome-DM filtering, mini-chat.

Proposed architecture

  1. One app: replace dm + group-chat in activities.ts with a single messages app; keep /agents/dm and /agents/group-chat as aliases opening the right thread (preserves @mention navigation, ?nametag=/?join= deep links, Connect dm intent, marketplace links).
  2. Thread abstraction in data/: ChatThread = { kind: 'dm' | 'group', id, … } + two adapters (over sphere.communications / sphere.groupChat) normalizing to one DisplayMessage shape.
  3. Capability flags instead of component forks: adapters expose canWrite, canReply, canDeleteMessage, canKick, showReadReceipts, showTypingIndicator, isConnected; single ChatWindow/MessageList/MessageBubble render conditionally (DMChatInput is already shared by both — proof it works).
  4. Single conversation list: DMs + groups sorted by last message, pinned groups on top, kind icons, All/DMs/Groups filter chips, one "+" menu (New DM / Join group / Create group).
  5. Hooks: merge into useThreads() + useThreadMessages(threadRef); unified query keys; one useTotalUnreadCount → single desktop badge (removes the triple badge wiring).
  6. Events: normalize message:dm and groupchat:* into one thread-message event in useSphereEvents.
  7. Mini-chat: key windows by thread id (dm:<pubkey> / group:<groupId>) so groups can pop out too; refactor MiniChatWindow onto useThreadMessages.
  8. Cleanup: delete legacy ChatSection + CHAT_MODE storage key; merge duplicated display helpers; adopt SDK getMessagesPage for group pagination; consider exposing communications.deleteConversation in the thread menu.

UX improvements to fold in

  • Unified search across DMs and groups.
  • Consistent reply UX (groups have it, DMs don't — decide whether to add for DMs or keep per-capability).
  • Clickable nametag in group bubbles currently cross-navigates to the DM app — becomes an in-app thread switch.
  • Surface relay connection state only for group threads.

Acceptance criteria

  • One desktop icon/app, one conversation list containing both DMs and groups.
  • All existing deep links keep working: /agents/dm?nametag=…, /agents/group-chat?join=…, Connect dm intent.
  • Feature parity: read receipts + typing (DM), roles/moderation/invites/discovery (groups).
  • One combined unread badge; per-thread unread preserved.
  • Per-address storage scoping (buildAddressId) unchanged (no lost local state).
  • Unit tests for adapters/hooks; legacy ChatSection removed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    Status
    Backlog

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions