fix: federation avatar colors, cross-instance DMs, and instance persistence

- Fix avatar gradient colors in 7 UI sites by using homeUserId fallback
- Merge remote DM channels in populateFromReady (no longer home-only)
- Remove isHome guards from 8 DM WS event handlers for cross-instance DMs
- Route DM typing events to correct instance origin
- Fix autoConnectAll deleting disconnected instances from localStorage
- Distinguish network errors from auth errors in instance reconnection
- Start WebSocket retry for network-unreachable instances (auto-recovery)
- Add reconnectInstance method and Reconnect button in UI
This commit is contained in:
Jannis Braun
2026-03-04 18:46:16 +01:00
parent 6e44a4ef2f
commit 33ae79bae9
11 changed files with 133 additions and 43 deletions
@@ -122,7 +122,7 @@ export function AddDmMemberModal() {
disabled={isAdding}
className="w-full flex items-center gap-3 px-3 py-2 rounded-[4px] hover:bg-interactive-hover transition-colors text-left disabled:opacity-50"
>
<Avatar src={user.avatar} name={user.displayName ?? user.username} size={36} status={user.status as any} userId={user.id} />
<Avatar src={user.avatar} name={user.displayName ?? user.username} size={36} status={user.status as any} userId={user.homeUserId ?? user.id} />
<div className="flex-1 min-w-0">
<div className="text-[14px] font-medium text-txt-primary truncate">
{user.displayName ?? user.username}
@@ -265,6 +265,7 @@ function AddInstanceFlow({ onDone }: { onDone: () => void }) {
export function ConnectedInstances() {
const instances = useInstanceStore((s) => s.instances);
const removeInstance = useInstanceStore((s) => s.removeInstance);
const reconnectInstance = useInstanceStore((s) => s.reconnectInstance);
const [showAddForm, setShowAddForm] = useState(false);
return (
@@ -312,13 +313,24 @@ export function ConnectedInstances() {
)}
</div>
</div>
<button
onClick={() => removeInstance(inst.origin)}
className="px-2 py-1 text-xs text-txt-danger hover:bg-accent-rose/10 rounded transition-colors shrink-0 ml-2"
title="Disconnect"
>
Disconnect
</button>
<div className="flex items-center gap-1 shrink-0 ml-2">
{(inst.status === 'disconnected' || inst.status === 'error') && (
<button
onClick={() => reconnectInstance(inst.origin)}
className="px-2 py-1 text-xs text-accent-primary hover:bg-accent-primary/10 rounded transition-colors"
title="Reconnect"
>
Reconnect
</button>
)}
<button
onClick={() => removeInstance(inst.origin)}
className="px-2 py-1 text-xs text-txt-danger hover:bg-accent-rose/10 rounded transition-colors"
title="Disconnect"
>
Disconnect
</button>
</div>
</div>
))}
@@ -100,7 +100,7 @@ export function NewDmModal() {
onClick={() => handleSelectUser(user)}
className="w-full flex items-center gap-3 px-3 py-2 rounded-[4px] hover:bg-interactive-hover transition-colors text-left"
>
<Avatar src={user.avatar} name={user.displayName ?? user.username} size={36} status={user.status as any} userId={user.id} />
<Avatar src={user.avatar} name={user.displayName ?? user.username} size={36} status={user.status as any} userId={user.homeUserId ?? user.id} />
<div className="flex-1 min-w-0">
<div className="text-[14px] font-medium text-txt-primary truncate">
{user.displayName ?? user.username}
@@ -414,7 +414,7 @@ function JoinRequestsSection({ serverId }: { serverId: string }) {
src={user?.avatar}
name={displayName}
size={32}
userId={user?.id}
userId={user?.homeUserId ?? user?.id}
/>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-1.5">
@@ -65,7 +65,7 @@ export function UserSettingsModal() {
name={user.displayName ?? user.username}
size={64}
status={user.status}
userId={user.id}
userId={user.homeUserId ?? user.id}
/>
<div>
<div className="font-bold text-lg">{user.displayName ?? user.username}</div>