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.
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.
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.
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
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.
When a user has federated WS connections, they can receive both the
original message (from their home instance) and the relay copy (from the
remote instance). The relay copy has a different message ID but carries
sourceMessageId pointing to the original. The dedup now checks both
directions: incoming relay copy vs existing original, and incoming
original vs existing relay copy.
When a relay-created dm_message_created arrives for an unknown channel,
check if an existing DM has the same member (by homeUserId). If so,
route the message to the existing channel instead of adding a duplicate
sidebar entry. Prevents the transient duplicate that required page
refresh to dedup.
Task 13: Hook server-to-server peering into connectToRemote (non-fatal)
and add federation API namespace to the client (initiate, peers, revoke).
Task 14: Show a transparency notice in the DM welcome header when the
other user is on a remote instance, informing that messages are stored
on both home instances and are not end-to-end encrypted.
Task 15: Add Federation section to the instance settings General panel
with DM relay toggle, TTL config, and a live peer list with revoke
buttons. Also extends InstanceAdminSettings type and the server settings
route to support federationRelayEnabled / federationRelayTtlDays.
The dist < 150 check caused the ResizeObserver and media load
listener to miss large layout shifts (e.g., GIF loading adding
400+ px of height). The isAtBottomRef gate already determines
whether to auto-scroll — the pixel threshold is redundant.
Remove isAtBottom state from scroll effect deps to break the
re-triggering feedback loop. Use isAtBottomRef (ref) instead.
Gate ResizeObserver and media load effects behind the ref so
they only auto-scroll after the initial snap completes.
Images now size naturally within max constraints instead of being
letterboxed into a 4/3 bg-surface-input container. Fixes dark bars
on GIFs and images whose aspect ratio doesn't match 4/3.
PNG conversion strips GIF animation. GIFs are now detected by URL
pattern (.gif extension or Tenor/Klipy CDN) and copied as URL text
instead, so pasting back into chat re-renders the animated GIF.
Add data-embed-thumbnail marker to VideoEmbed, GenericEmbed, and
RichEmbed thumbnail containers. Update detection logic to skip
these alongside avatars. Also remove redundant filename derivation
in Save Image menu item.
Detect right-clicked images in handleContextMenu and prepend
Save Image, Copy Image, and Open Original to the custom menu.
Avatars are excluded via the data-avatar semantic marker.
Implements image save/download (blob fetch + anchor trigger, fallback to window.open)
and clipboard copy (PNG write via ClipboardItem, fallback to URL text copy) with
toast feedback. Also adds ClipboardItem polyfill and Response.blob() content-type
fix to the jsdom test setup so the clipboard test suite runs correctly.
Refactor useGridLayout from RefObject to callback-ref API so the
ResizeObserver reattaches when the grid container remounts after
focus-mode toggle. Fixes tiles rendering at stale fullscreen dimensions
on 4:3 monitors.
Also clamp focus-mode participant strip height (max-h-[20vh] min-h-[80px])
to prevent it from crowding the focused stream on short viewports.
Lower minimum query length from 2 to 1 character so single-letter
searches return results.
Fix dedup to prefer native profiles (homeUserId=null) over replicated
ones. Previously the first-seen result won, which was usually the
local replicated profile (no instance badge, namespaced username).
Now when a native profile is found on the remote instance, it replaces
the replicated copy — showing the clean username with the instance badge.
Replicated profiles on remote instances have different local IDs but
share the same homeUserId as the native profile. Use homeUserId ?? id
as the dedup key so the same person only appears once. Home instance
is queried first, so the native profile (with clean username) wins.
Replace old "Add Friend" form tests (placeholder/button/inline messages) with
new tests for the Find People panel: search input, Direct Add row, toast feedback,
and searchUsers debounce. Also fix ancillary mocks (discoverStore, authStore,
activityStore, ConnectInstanceModal, findExistingDmForUser) required by the
rewritten component.
Replace the two separate inputs (direct add + discover search) with a
single unified search field. When the user types, results come from
socialStore.searchUsers() across all connected instances. When empty,
the discover grid is shown as before. A user@instance pattern surfaces
a Direct Add action row for sending friend requests to federated users.
Parent FriendsPage no longer owns add-friend state — it all lives inside
AddFriendTab now, keeping the component self-contained.
When a WS connection receives its ready event, push cached myActivities
to that instance so federated users see activities immediately without
waiting for the next debounce cycle.
Switch pushActivities and setShowActivity from wsSend (home-only) to
wsSendAll (all instances). Add myActivities cache for re-push on late
connections.