Replace global user search with socialStore friends list, fixing
federation ID mismatch that caused false 'not your friend' errors.
Add multi-select with chips, immediate friend list display, and
client-side search filtering.
Use parseFederatedUsername() consistently to show base username instead
of raw user@domain format. Replace inline domain text in Username
component with compact globe icon + tooltip. Add globe icons to DM
header and sidebar for federated users.
Bare filenames stored on replicated user stubs can't be resolved by
the home WS (normalizeUserAssets only runs for remote origins).
Now resolves avatar/banner to absolute URLs pointing to the user's
home instance so they render correctly without page refresh.
Also overwrites stale bare filenames from the prior deploy.
Replicated user stubs created by resolveOrCreateReplicatedUser had
null avatar/displayName, causing blank profiles in the UI until
page refresh. Friend relay events now carry profile snapshots
(displayName, avatar, avatarColor, banner, bio) so the receiving
instance can hydrate stubs with real data.
The friend_request_accepted and friend_removed WS handlers updated
socialStore but not discoverStore, so the discovery page buttons
stayed stale until page refresh. Decline/cancel already updated
both stores correctly.
Rename appendMutationLog and queueOutboxEvent params from DM-specific names
(dmMessageId/dmChannelId/messageId) to generic (entityId/contextId) with a new
contextType param defaulting to 'dm'. Update all internal schema column references
to match the renamed outbox/mutation-log schema columns. Add buildFriendContextId
and getFriendEventTargets helper functions for friend event relay routing.
Renames DM-specific columns in federation_outbox (dm_channel_id → context_id, message_id → entity_id) and federation_mutation_log (dm_message_id → entity_id, dm_channel_id → context_id) to generic names, adding context_type = 'dm' for all existing rows so the outbox can carry friend events too.
Adds migrateResetFederationSyncForLegacyDms which resets last_synced_at=0
on all active federation peers so the S2S sync worker re-pulls all mutation
log entries (including newly-backfilled legacy DMs) on next server startup.
A legacy_dm_sync_done flag on instance_settings ensures this runs exactly once.
Replace myUserId with full myUser object so isSelf() can handle cross-instance
identity matching when finding the other 1-on-1 DM member. Pass GroupDmUserIdentity
objects (id, homeUserId, homeInstance) to api.dm.createGroup instead of raw id strings.
Rewrite the group DM creation endpoint to accept identity objects
(GroupDmUserIdentity) instead of raw user ID strings. Each identity
is resolved to a local database user via resolveOrCreateReplicatedUser
for federated users or direct ID lookup with resolveLocalUser fallback
for local users. Dedup and caller-exclusion checks now operate on
resolved local IDs rather than input IDs.
Make both identity resolution helpers module-level exports so the group DM
endpoint can import and use them when resolving federated user identities
during group DM creation.
Replace flat userIds array with a structured users array of GroupDmUserIdentity
objects, enabling the group DM endpoint to accept federated identity hints
(homeUserId, homeInstance) alongside the local user ID.
addDmChannel must be called before navigate() so the route effect
finds the channel in the store immediately. Without this, the chat
view renders empty until the WS dm_channel_created event arrives.
Also show an error instead of silently returning when otherMember
cannot be determined.
- Add CreateGroupDmRequest to API client imports, type declaration, and implementation (POST /dm/group)
- Update AddDmMemberModal to branch on channel type: 1-on-1 DMs call createGroup and navigate to new channel, group DMs call addMember directly
- Remove legacy getApiForOrigin/channelOriginMap usage — S2S federation relay is now entirely server-side
- Hoist callerUser DB query above the federation block so it's fetched
once and reused for response building, federation ID assignment, and
relay payload construction (was fetched 3 times).
- Add homeInstance !== domainOrigin guard to finalTargets augmentation,
matching the existing pattern in POST /api/dm/:id/members.
Adds a dedicated endpoint for creating group DMs with 3-10 members.
Validates friendship, deduplication, and member caps. Includes federation
relay support for remote instance members.
- Add migrateFixOneOnOneOwnerIds migration to NULL-out ownerId on all
existing 1-on-1 DMs (those with exactly 2 members)
- Fix POST /api/dm to create 1-on-1 channels with ownerId=null instead
of the creator's ID
- Guard POST /api/dm/:id/members: reject with 400 if channel has no
owner (i.e. is a 1-on-1), directing callers to POST /api/dm/group
- Guard DELETE /api/dm/:id/members: replace member-count check with
ownerId check; remove now-duplicate dmChannel query in that handler
- Add CreateGroupDmRequest type to shared types
The CREATE INDEX on federated_id in createTables runs before migrations.
On existing installs the column is still canonical_pair_id, causing a crash.
Wrap in try-catch so the migration handles it instead.
Replace the hard-delete in the leave handler with a soft-delete (sets
deleted_at timestamp) when the last member leaves a group DM. A new
janitor sweep in the federation worker runs hourly and purges channels
whose grace period has expired, cascading through reactions, embeds,
attachments, messages, members, outbox/mutation-log/file-queue entries,
and finally the channel itself.
All client-facing dm_channels queries now filter on deleted_at IS NULL
to hide soft-deleted channels from the REST API and WebSocket ready
payload.
In processCreateEvent, branch on event.federatedId: group DM messages now
look up the pre-bootstrapped local channel by federatedId instead of
computing a pair hash from two participants. In queueDmRelay, fetch the
channel's federatedId and ownerId and include federatedId in the outgoing
relay payload for group DMs so receiving instances can route correctly.
Add three new event processors to the relay endpoint for handling
inbound group DM membership changes from peer instances:
- processMemberAddEvent: handles member additions with channel bootstrap
(creates the local channel from group metadata on first contact)
- processMemberRemoveEvent: handles member removals with read state
cleanup and soft-delete GC when no local members remain
- processOwnershipTransferEvent: updates local channel owner tracking
All three are wired into the relay endpoint's event dispatch switch.
getGroupDmTargetOrigins() was called after the leaving user's dm_members row
was deleted, causing their instance to be excluded from member_remove and
ownership_transfer relay fan-out if they were the only member from that peer.
Move the target origins and leaving user lookups to before the deletion.
Hook POST /api/dm/:id/members and DELETE /api/dm/:id/members to the
federation outbox so membership changes propagate to peer instances:
- Assign federatedId when a group DM first gains remote members
- Relay member_add events with group metadata and participant-aware routing
- Relay member_remove (leave) events to peer instances
- Relay ownership_transfer when the departing user was the group owner,
updating the federated owner columns accordingly
Extend FederationRelayEvent with member_add, member_remove, and
ownership_transfer event types, plus federatedId and the new optional
membership/ownership/group payload fields. Add FederationMembershipPayload,
FederationOwnershipPayload, and FederationGroupPayload interfaces.
Add federatedId to FederationSyncRequest for group DM sync filtering.
Remove federated_id from addMissingColumns so it no longer races with
migrateDmChannelsFederatedId on installs that have canonical_pair_id.
Wrap the table rebuild in PRAGMA foreign_keys = OFF/ON with a transaction
so the DROP TABLE succeeds even when dm_members/dm_messages hold FK refs.
Renames dm_channels.canonical_pair_id → federated_id and adds
owner_home_user_id, owner_home_instance, deleted_at columns to support
federated group DMs. Migration handles upgrade from existing installs
(full table rebuild to rename), partial states (ALTER TABLE), and fresh
installs (correct DDL from the start). Backfills federated_id for
existing 1-on-1 DM channels and owner federation identity for group DMs.
Uses messageHomeInstance to determine whether the target message
originated locally (lookup by ID) or was replicated from another
instance (lookup by sourceInstance + sourceMessageId). Supports
N-instance federation and falls back to relay-sender lookup for
backward compatibility with in-flight outbox entries.
Resolves canonical messageId (sourceMessageId for replicated messages,
local ID for originated messages) and includes messageHomeInstance so
the receiver can deterministically find the target message regardless
of which instance created it.
Consolidates 4 inline constructions of the instance origin URL into a
single shared function. Removes the private copy in federationWorker
and two ad-hoc domainOrigin variables in federationOutbox.
When Instance 2 receives a relay from Instance 1, it no longer
broadcasts dm_message_created to members whose home instance is Instance
1. Those users already have the original message via their home WS
connection. This prevents duplicate messages from appearing in the
sender's chat.