- Fix critical: outbox worker now copies file_rejected payload fields
(attachmentId, sourceFilename, rejectionReason, rejectionLimit,
affectedUserIds) so the reverse relay actually delivers them
- Fix: add sourceFilename to file_rejected event for reliable
multi-attachment matching on the sender side
- Fix: change text-accent-warning to text-accent-amber (valid class)
- Add Array.isArray guard on federationMeta parse
Both buildDmMessageWithUser and buildMessageWithUser manually map
attachment fields — add federationStatus and federationMeta so they
reach the frontend.
The leave-group-DM ConfirmDialog in the space/channel sidebar return
path was unreachable — group DMs only render in the DM view branch,
so setLeaveGroupDmId is never called from the space view.
The X button on group DMs now opens a ConfirmDialog instead of
soft-closing. The right-click 'Leave Group' context menu also
routes through the same confirmation. Prevents dead group DM data
from accumulating when users soft-close instead of leaving.
members.length > 2 caused group DMs to lose their identity when a
member left and only 2 remained. ownerId is the canonical marker set
at group creation and persists through the group's lifecycle.
The buildReadyPayload query fetched all dm_members rows without
checking the closed flag, causing closed DMs to reappear on every
page reload. The REST endpoint GET /api/dm already filtered correctly.
Federated 1-on-1 DMs showed the raw snowflake ID as the display name
and no avatar when the remote user had no pre-existing local record.
processCreateEvent used resolveLocalUser (find-only) instead of
resolveOrCreateReplicatedUser, and relay events carried no profile data
for participants.
- Add profile snapshot (displayName, avatar, avatarColor) to
FederationRelayParticipant and populate it in getDmParticipants
- Change processCreateEvent to auto-create replicated user stubs and
hydrate them with profile data from the relay event
- Fix hydrateReplicatedUserProfile URL resolution for homeInstance
values without protocol prefix
- Fix WelcomeHeader: return null while DM data is loading (eliminates
"unknown" flash on reload), use displayName for @mention text
Federated relay messages can have local snowflake IDs that don't match
chronological (createdAt) order — a message sent earlier on a remote
instance can arrive later and get a higher local ID. This caused a
permanent mismatch between the ready event's lastMessage (MAX id) and
the acked message (last in createdAt display order), making federated
DM channels appear unread after every server restart.
- Server: change ready event DM lastMessage query from MAX(id) to
ORDER BY created_at DESC (matching the DM REST API)
- Frontend: change ackChannel to ack MAX(id) among loaded messages
instead of last in display order (consistent with server comparison)
- Fix federated username display fallback in UserDiscoverCard
When converting a 1-on-1 DM to a group, the existing DM partner was
incorrectly required to be your friend. DMs don't require friendship,
so this check was over-strict. Added fromDmChannelId parameter to
createGroup — existing members of the source DM are exempt from the
friendship validation.
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.