From cecf65a3a1277c87f5c9b824e635f2481c1cdcfc Mon Sep 17 00:00:00 2001 From: Jared Vititoe Date: Sat, 12 Sep 2026 14:48:35 -0400 Subject: [PATCH] fix(moderation): policy list viewer reads legacy Draupnir/Mjolnir rule types Only the stable m.policy.rule.* types were queried, so lists still writing org.matrix.mjolnir.rule.* showed as empty. Query both and de-duplicate by entity+recommendation. Fixes #22 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA --- .../room-settings/PolicyListViewer.tsx | 46 +++++++++++++++++-- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/src/app/features/room-settings/PolicyListViewer.tsx b/src/app/features/room-settings/PolicyListViewer.tsx index 2e4ea5973..5a120c8a3 100644 --- a/src/app/features/room-settings/PolicyListViewer.tsx +++ b/src/app/features/room-settings/PolicyListViewer.tsx @@ -24,6 +24,14 @@ const POLICY_USER_EVENT = 'm.policy.rule.user'; const POLICY_ROOM_EVENT = 'm.policy.rule.room'; const POLICY_SERVER_EVENT = 'm.policy.rule.server'; +// Legacy, unstable-prefixed event types still emitted by Draupnir/Mjolnir +// policy lists that predate MSC stabilization (or haven't migrated). Queried +// alongside the stable types and merged/de-duped so those lists don't show +// as falsely empty. +const LEGACY_POLICY_USER_EVENT = 'org.matrix.mjolnir.rule.user'; +const LEGACY_POLICY_ROOM_EVENT = 'org.matrix.mjolnir.rule.room'; +const LEGACY_POLICY_SERVER_EVENT = 'org.matrix.mjolnir.rule.server'; + type PolicyRuleContent = { entity?: string; reason?: string; @@ -76,6 +84,23 @@ function extractPolicyEntries(events: MatrixEvent[]): PolicyEntry[] { .filter((entry) => entry.entity !== ''); } +/** + * Merge policy entries from the stable and legacy event types for a rule + * kind, de-duplicating by entity+recommendation so a room that emits both a + * stable and a legacy rule for the same target isn't double-listed. + */ +export function dedupePolicyEntries(entries: PolicyEntry[]): PolicyEntry[] { + const seen = new Set(); + const result: PolicyEntry[] = []; + entries.forEach((entry) => { + const key = `${entry.entity} ${entry.recommendation}`; + if (seen.has(key)) return; + seen.add(key); + result.push(entry); + }); + return result; +} + // ── Entry row ───────────────────────────────────────────────────────────────── function PolicyEntryRow({ entry }: { entry: PolicyEntry }) { @@ -201,9 +226,24 @@ export function PolicyListViewer({ requestClose }: PolicyListViewerProps) { return; } - setUserEntries(extractPolicyEntries(getRoomPolicyEvents(room, POLICY_USER_EVENT))); - setRoomEntries(extractPolicyEntries(getRoomPolicyEvents(room, POLICY_ROOM_EVENT))); - setServerEntries(extractPolicyEntries(getRoomPolicyEvents(room, POLICY_SERVER_EVENT))); + setUserEntries( + dedupePolicyEntries([ + ...extractPolicyEntries(getRoomPolicyEvents(room, POLICY_USER_EVENT)), + ...extractPolicyEntries(getRoomPolicyEvents(room, LEGACY_POLICY_USER_EVENT)), + ]), + ); + setRoomEntries( + dedupePolicyEntries([ + ...extractPolicyEntries(getRoomPolicyEvents(room, POLICY_ROOM_EVENT)), + ...extractPolicyEntries(getRoomPolicyEvents(room, LEGACY_POLICY_ROOM_EVENT)), + ]), + ); + setServerEntries( + dedupePolicyEntries([ + ...extractPolicyEntries(getRoomPolicyEvents(room, POLICY_SERVER_EVENT)), + ...extractPolicyEntries(getRoomPolicyEvents(room, LEGACY_POLICY_SERVER_EVENT)), + ]), + ); setLoadedRoomId(roomId); setError(undefined); }, [mx, roomIdInput]);