The modal read from the global spaceStore.members which only contains
members for the currently active space. Opening the modal via right-click
context menu on a space that hasn't been navigated to yet resulted in an
empty member list. Now fetches members independently via the dedicated
GET /api/spaces/:id/members endpoint with federation-aware API routing.
The modal was manually rendering avatars with a static bg-surface-input
background, bypassing the Avatar component's getAvatarGradient() logic.
Users without profile pictures got blank dark circles instead of their
hash-generated or user-set avatar color gradients.
- Remove tinted description box, use plain text
- Buttons are now flex-1 equal width side by side
- Cancel gets visible bg-interactive-hover background
- Larger padding, rounded-xl, wider max-w
- FriendItem/RequestItem: add plain wrapper div so divide-y border
lands on a non-rounded element (fixes curved separator lines)
- Add border-t to divide-y containers so first row also gets top line
- Remove uppercase from "Direct Messages" in DM sidebar
Add third group for instances the user explicitly disconnected via
the registry. These get a disconnected placeholder with preserved
token for instant reconnect, but no auto-connect on page load.
Aligns the ready payload query with the GET /api/dm approach:
get MAX(created_at) per channel first, then fetch the actual
message rows. Avoids issues with federated relay messages whose
local snowflake IDs don't match chronological order.
Extracts the inline DM list item rendering (~105 lines) from
ChannelSidebar.tsx into a standalone component. Applies cohesive
hover states, 6px border radius, 44px row height, and a selected-
state accent bar matching ChannelItem's pattern.
When the same person registers on multiple federated instances, separate
DM channels are created for each identity, causing duplicate sidebar
entries (e.g., "Nova" appearing twice — once for local, once for
federated account).
Add deduplicateDmChannels() safety net that detects matching counterparts
using two layers: canonicalUserMatch (same ID/homeUserId/username) and
replicatedInstances cross-check (local user has a replicated account on
the federated user's home instance with the same base username). Keeps
the DM with the most recent activity, applied in both setDmChannels and
populateFromReady.
Investigation: queried both live instances (nova.ddns.net, orbit)
and confirmed root cause is same-person-two-accounts, not a server-side
creation bug or identity resolution gap.
Introduces a lightweight DmLastMessagePreview interface for the ready
payload and GET /api/dm response, and widens DmChannel.lastMessage to
accept either DmLastMessagePreview or DmMessageWithUser so both the
static preview path and real-time WS events can coexist without casts.
- disconnectInstance: save cached tokens BEFORE filtering the instance out, so
reconnectInstance can restore disconnected entries from localStorage
- reconnectInstance: if instance is not in active instances array, attempt to
restore it from cached localStorage token before proceeding with reconnect
- reconnectInstance: update registry to unreachable/auth_expired on failure
- forceRemoveEntry: now tears down WS, removes from instances array, purges
token from localStorage, and cleans up space store (was registry-only before)
Adds persistent federation registry storage endpoints under
/api/users/@me/federation-registry. GET returns all registry entries
with the stored updatedAt timestamp. PUT replaces the full registry via
LWW (409 on stale write) in an atomic transaction.
removeChannelStates cleared messages and readStates but not hasMore.
When a closed DM was reopened with the same channel ID, loadMessages
saw the stale hasMore entry and skipped loading — showing an empty
chat. Now clears hasMore so messages reload on reopen.