diff --git a/src/app/hooks/useAutoDirectSync.ts b/src/app/hooks/useAutoDirectSync.ts index ecc8b047..75ffadd3 100644 --- a/src/app/hooks/useAutoDirectSync.ts +++ b/src/app/hooks/useAutoDirectSync.ts @@ -2,7 +2,7 @@ import { MatrixClient, Room, RoomEvent } from 'matrix-js-sdk'; import { useEffect } from 'react'; import { AccountDataEvent } from '../../types/matrix/accountData'; import { Membership } from '../../types/matrix/room'; -import { getAccountData, getMDirects } from '../utils/room'; +import { getAccountData, getMDirects, isBridgedRoom } from '../utils/room'; import { addRoomIdToMDirect } from '../utils/matrix'; export function useAutoDirectSync(mx: MatrixClient): void { @@ -17,6 +17,13 @@ export function useAutoDirectSync(mx: MatrixClient): void { const joinedAndInvited = room.getJoinedMemberCount() + room.getInvitedMemberCount(); if (joinedAndInvited > 2) return; + // Bridged portal rooms (mautrix-telegram/whatsapp/discord/…) live in the + // per-bridge personal filtering space (Channels tab) — don't pollute + // `m.direct` with them. `useDirectRooms` already filters bridged rooms + // out of the Direct tab visually; this prevents the underlying account + // data from accruing stale entries for other clients to misclassify. + if (isBridgedRoom(room)) return; + const mDirectEvent = getAccountData(mx, AccountDataEvent.Direct); if (mDirectEvent) { const directs = getMDirects(mDirectEvent); diff --git a/src/app/pages/client/direct/useDirectRooms.ts b/src/app/pages/client/direct/useDirectRooms.ts index 52b87adb..7a8f655a 100644 --- a/src/app/pages/client/direct/useDirectRooms.ts +++ b/src/app/pages/client/direct/useDirectRooms.ts @@ -8,12 +8,17 @@ import { useDirects, useOrphanRooms } from '../../../state/hooks/roomList'; import { roomToParentsAtom } from '../../../state/room/roomToParents'; import { useBotPresets } from '../../../features/bots/catalog'; import { isBridgeStateEvent, isCatalogBotControlRoom } from '../../../features/bots/room'; +import { isBridgedRoom } from '../../../utils/room'; // After P3c the Direct tab is universal: every joined non-space «orphan» -// room renders here, regardless of `m.direct`, except curated bot control DMs. -// Those belong to the Robots tab, while bridged Telegram portal rooms remain -// normal Direct candidates. Implementation = -// `useOrphanRooms ∪ useDirects`: +// room renders here, regardless of `m.direct`, except curated bot control DMs +// (Robots tab) and bridged portal rooms (mautrix-telegram/whatsapp/discord/…). +// Bridged rooms are filtered out here so they live exclusively in their +// per-bridge personal filtering space (Channels tab) — otherwise a 1:1 +// Telegram chat that the bridge tagged `m.direct` AND placed inside the +// Telegram space would appear twice. Implementation = +// `useOrphanRooms ∪ useDirects`, minus catalog bot control rooms and bridged +// portals: // // - `useOrphanRooms` = `isRoom && !mDirects.has && !roomToParents.has` → // non-space rooms that don't live inside any space and aren't m.direct- @@ -79,7 +84,10 @@ export const useDirectRooms = (): string[] => { const out: string[] = []; const isVisibleDirect = (id: string): boolean => { const room = mx.getRoom(id); - return !room || !isCatalogBotControlRoom(mx, room, bots); + if (!room) return true; + if (isCatalogBotControlRoom(mx, room, bots)) return false; + if (isBridgedRoom(room)) return false; + return true; }; orphanRooms.forEach((id) => {