From ad1cbcf79269a82b87a3c1c1bf4e611f25ab26d2 Mon Sep 17 00:00:00 2001 From: Jared Vititoe Date: Sun, 20 Sep 2026 15:18:08 -0400 Subject: [PATCH] feat(room-settings): one-line "who can see this" under the room name (#133) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Room Settings → General now reads e.g. "Encrypted · Invite only · History visible to members since they joined" under the name — derived from m.room.encryption, m.room.join_rules and m.room.history_visibility with the same words the preview-card chips and the history setting use. One Text line, no card, no icons, no controls; spec defaults (invite-only, shared history) when a state event is absent. Unit-tested. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA --- .../common-settings/general/RoomProfile.tsx | 13 +++++ src/app/utils/roomVisibilityLine.test.ts | 27 ++++++++++ src/app/utils/roomVisibilityLine.ts | 49 +++++++++++++++++++ 3 files changed, 89 insertions(+) create mode 100644 src/app/utils/roomVisibilityLine.test.ts create mode 100644 src/app/utils/roomVisibilityLine.ts diff --git a/src/app/features/common-settings/general/RoomProfile.tsx b/src/app/features/common-settings/general/RoomProfile.tsx index a48a65307..4279e8325 100644 --- a/src/app/features/common-settings/general/RoomProfile.tsx +++ b/src/app/features/common-settings/general/RoomProfile.tsx @@ -40,6 +40,8 @@ import { useMatrixClient } from '../../../hooks/useMatrixClient'; import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication'; import { StateEvent } from '../../../../types/matrix/room'; import { sendStateEvent } from '../../../utils/room'; +import { describeRoomVisibility } from '../../../utils/roomVisibilityLine'; +import { useStateEvent } from '../../../hooks/useStateEvent'; import { CompactUploadCardRenderer } from '../../../components/upload-card'; import { useObjectURL } from '../../../hooks/useObjectURL'; import { createUploadAtom, UploadSuccess } from '../../../state/upload'; @@ -434,6 +436,14 @@ export function RoomProfile({ permissions }: RoomProfileProps) { const name = useRoomName(room); const topic = useRoomTopic(room); const joinRule = useRoomJoinRule(room); + // [Gitea #133] One line, no controls: encryption · join rule · history. + const historyVisibilityEvent = useStateEvent(room, StateEvent.RoomHistoryVisibility); + const visibilityLine = describeRoomVisibility({ + encrypted: room.hasEncryptionStateEvent(), + joinRule: joinRule?.join_rule, + historyVisibility: historyVisibilityEvent?.getContent<{ history_visibility?: string }>() + .history_visibility, + }); const canEditAvatar = permissions.stateEvent(StateEvent.RoomAvatar, mx.getSafeUserId()); const canEditName = permissions.stateEvent(StateEvent.RoomName, mx.getSafeUserId()); @@ -474,6 +484,9 @@ export function RoomProfile({ permissions }: RoomProfileProps) { {name ?? 'Unknown'} + + {visibilityLine} + {topic && ( {topic.format === 'org.matrix.custom.html' && diff --git a/src/app/utils/roomVisibilityLine.test.ts b/src/app/utils/roomVisibilityLine.test.ts new file mode 100644 index 000000000..fef65e505 --- /dev/null +++ b/src/app/utils/roomVisibilityLine.test.ts @@ -0,0 +1,27 @@ +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import { describeRoomVisibility } from './roomVisibilityLine'; + +test('describeRoomVisibility composes the three facts', () => { + assert.equal( + describeRoomVisibility({ encrypted: true, joinRule: 'invite', historyVisibility: 'joined' }), + 'Encrypted · Invite only · History visible to members since they joined', + ); + assert.equal( + describeRoomVisibility({ encrypted: false, joinRule: 'public', historyVisibility: 'shared' }), + 'Not encrypted · Anyone can join · History visible to members, including before they joined', + ); + assert.equal( + describeRoomVisibility({ + encrypted: false, + joinRule: 'knock', + historyVisibility: 'world_readable', + }), + 'Not encrypted · Ask to join · History visible to anyone, even guests', + ); + // Missing state: spec defaults are invite-only and "shared". + assert.equal( + describeRoomVisibility({ encrypted: true }), + 'Encrypted · Invite only · History visible to members, including before they joined', + ); +}); diff --git a/src/app/utils/roomVisibilityLine.ts b/src/app/utils/roomVisibilityLine.ts new file mode 100644 index 000000000..9fc8fa5dc --- /dev/null +++ b/src/app/utils/roomVisibilityLine.ts @@ -0,0 +1,49 @@ +import { HistoryVisibility, JoinRule } from 'matrix-js-sdk'; + +/** + * [Gitea #133] One sentence for Room Settings → General, under the name: + * "Encrypted · Invite only · History visible to members since they joined". + * Wording mirrors the room preview card chips and the history-visibility + * setting so the same words mean the same thing everywhere. + */ +export function describeRoomVisibility(input: { + encrypted: boolean; + joinRule?: string; + historyVisibility?: string; +}): string { + const parts: string[] = [input.encrypted ? 'Encrypted' : 'Not encrypted']; + switch (input.joinRule) { + case JoinRule.Public: + parts.push('Anyone can join'); + break; + case JoinRule.Knock: + case 'knock_restricted': + parts.push('Ask to join'); + break; + case JoinRule.Restricted: + parts.push('Restricted'); + break; + case JoinRule.Private: + parts.push('Private'); + break; + case JoinRule.Invite: + default: + parts.push('Invite only'); + } + switch (input.historyVisibility) { + case HistoryVisibility.WorldReadable: + parts.push('History visible to anyone, even guests'); + break; + case HistoryVisibility.Invited: + parts.push('History visible to members since they were invited'); + break; + case HistoryVisibility.Joined: + parts.push('History visible to members since they joined'); + break; + case HistoryVisibility.Shared: + default: + // "shared" is also the spec default when the state event is absent. + parts.push('History visible to members, including before they joined'); + } + return parts.join(' · '); +}