From 1fe5131033f8c6a94d9a44b80539f1ef1eaef762 Mon Sep 17 00:00:00 2001 From: Skye Elliot Date: Thu, 18 Dec 2025 17:55:00 +0000 Subject: [PATCH 1/5] feat: Update algorithm for history visible banner. - The banner now only shows for rooms with `shared` or `worldReadable` history visibility. - The banner does not show in rooms in which the current user cannot send messages. --- .../views/composer/HistoryVisibleBanner.tsx | 3 + .../views/rooms/MessageComposer.tsx | 6 +- .../HistoryVisibleBannerViewModel.tsx | 30 +++++++--- .../HistoryVisibleBannerViewModel-test.tsx | 58 +++++++++++++++++-- 4 files changed, 81 insertions(+), 16 deletions(-) diff --git a/src/components/views/composer/HistoryVisibleBanner.tsx b/src/components/views/composer/HistoryVisibleBanner.tsx index 09286ae8e11..85a6bd7fb99 100644 --- a/src/components/views/composer/HistoryVisibleBanner.tsx +++ b/src/components/views/composer/HistoryVisibleBanner.tsx @@ -16,6 +16,9 @@ export const HistoryVisibleBanner: React.FC<{ /** The room instance associated with this banner view model. */ room: Room; + /** Whether the current user can send messages in the room. */ + canSendMessages: boolean; + /** * If not null, specifies the ID of the thread currently being viewed in the thread timeline side view, * where the banner view is displayed as a child of the message composer. diff --git a/src/components/views/rooms/MessageComposer.tsx b/src/components/views/rooms/MessageComposer.tsx index b2546b02a36..c23bc7917db 100644 --- a/src/components/views/rooms/MessageComposer.tsx +++ b/src/components/views/rooms/MessageComposer.tsx @@ -675,7 +675,11 @@ export class MessageComposer extends React.Component { return (
- +
{ + private static readonly computeSnapshot = ({ + room, + canSendMessages, + threadId, + }: Props): HistoryVisibleBannerViewSnapshot => { const featureEnabled = SettingsStore.getValue("feature_share_history_on_invite"); const acknowledged = SettingsStore.getValue("acknowledgedHistoryVisibility", room.roomId); + const isHistoryVisible = BANNER_VISIBLE_LEVELS.includes(room.getHistoryVisibility()); return { visible: featureEnabled && + canSendMessages && !threadId && room.hasEncryptionStateEvent() && - room.getHistoryVisibility() !== HistoryVisibility.Joined && + isHistoryVisible && !acknowledged, }; }; @@ -92,7 +104,7 @@ export class HistoryVisibleBannerViewModel * @param props - Properties for this view model. See {@link Props}. */ public constructor(props: Props) { - super(props, HistoryVisibleBannerViewModel.computeSnapshot(props.room, props.threadId)); + super(props, HistoryVisibleBannerViewModel.computeSnapshot(props)); this.disposables.trackListener(props.room, RoomStateEvent.Update, () => this.setSnapshot()); @@ -126,7 +138,7 @@ export class HistoryVisibleBannerViewModel ); } - this.snapshot.set(HistoryVisibleBannerViewModel.computeSnapshot(this.props.room, this.props.threadId)); + this.snapshot.set(HistoryVisibleBannerViewModel.computeSnapshot(this.props)); } /** diff --git a/test/unit-tests/components/viewmodels/composer/HistoryVisibleBannerViewModel-test.tsx b/test/unit-tests/components/viewmodels/composer/HistoryVisibleBannerViewModel-test.tsx index 764376aba32..b1ab6ef7ff8 100644 --- a/test/unit-tests/components/viewmodels/composer/HistoryVisibleBannerViewModel-test.tsx +++ b/test/unit-tests/components/viewmodels/composer/HistoryVisibleBannerViewModel-test.tsx @@ -54,7 +54,7 @@ describe("HistoryVisibleBannerViewModel", () => { }); it("should not show the banner in unencrypted rooms", () => { - const vm = new HistoryVisibleBannerViewModel({ room, threadId: null }); + const vm = new HistoryVisibleBannerViewModel({ room, canSendMessages: true, threadId: null }); expect(vm.getSnapshot().visible).toBe(false); }); @@ -76,7 +76,7 @@ describe("HistoryVisibleBannerViewModel", () => { }), ]); - const vm = new HistoryVisibleBannerViewModel({ room, threadId: null }); + const vm = new HistoryVisibleBannerViewModel({ room, canSendMessages: true, threadId: null }); expect(vm.getSnapshot().visible).toBe(false); }); @@ -99,7 +99,7 @@ describe("HistoryVisibleBannerViewModel", () => { }), ]); - const vm = new HistoryVisibleBannerViewModel({ room, threadId: null }); + const vm = new HistoryVisibleBannerViewModel({ room, canSendMessages: true, threadId: null }); expect(vm.getSnapshot().visible).toBe(false); vm.dispose(); }); @@ -122,12 +122,12 @@ describe("HistoryVisibleBannerViewModel", () => { }), ]); - const vm = new HistoryVisibleBannerViewModel({ room, threadId: "some thread ID" }); + const vm = new HistoryVisibleBannerViewModel({ room, canSendMessages: true, threadId: "some thread ID" }); expect(vm.getSnapshot().visible).toBe(false); vm.dispose(); }); - it("should show the banner in encrypted rooms with non-joined history visibility", async () => { + it("should not show the banner if the user cannot send messages", () => { upsertRoomStateEvents(room, [ mkEvent({ event: true, @@ -145,7 +145,53 @@ describe("HistoryVisibleBannerViewModel", () => { }), ]); - const vm = new HistoryVisibleBannerViewModel({ room, threadId: null }); + const vm = new HistoryVisibleBannerViewModel({ room, canSendMessages: false, threadId: null }); + expect(vm.getSnapshot().visible).toBe(false); + vm.dispose(); + }); + + it("should not show the banner if history visibility is `invited`", () => { + upsertRoomStateEvents(room, [ + mkEvent({ + event: true, + type: "m.room.encryption", + user: "@user1:server", + content: {}, + }), + mkEvent({ + event: true, + type: "m.room.history_visibility", + user: "@user1:server", + content: { + history_visibility: "invited", + }, + }), + ]); + + const vm = new HistoryVisibleBannerViewModel({ room, canSendMessages: true, threadId: null }); + expect(vm.getSnapshot().visible).toBe(false); + vm.dispose(); + }); + + it("should show the banner in encrypted rooms with shared history visibility", async () => { + upsertRoomStateEvents(room, [ + mkEvent({ + event: true, + type: "m.room.encryption", + user: "@user1:server", + content: {}, + }), + mkEvent({ + event: true, + type: "m.room.history_visibility", + user: "@user1:server", + content: { + history_visibility: "shared", + }, + }), + ]); + + const vm = new HistoryVisibleBannerViewModel({ room, canSendMessages: true, threadId: null }); expect(vm.getSnapshot().visible).toBe(true); await vm.onClose(); expect(vm.getSnapshot().visible).toBe(false); From 6604d2b2c11a7aeeeacafbe415349fd6c062ad90 Mon Sep 17 00:00:00 2001 From: Skye Elliot Date: Thu, 18 Dec 2025 18:18:21 +0000 Subject: [PATCH 2/5] tests: Add `getHistoryVisibility` to stub room. --- test/test-utils/test-utils.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/test/test-utils/test-utils.ts b/test/test-utils/test-utils.ts index 525f2e4ed83..9757905882c 100644 --- a/test/test-utils/test-utils.ts +++ b/test/test-utils/test-utils.ts @@ -677,6 +677,7 @@ export function mkStubRoom( getCanonicalAlias: jest.fn(), getDMInviter: jest.fn(), getEventReadUpTo: jest.fn(() => null), + getHistoryVisibility: jest.fn().mockReturnValue(HistoryVisibility.Joined), getInvitedAndJoinedMemberCount: jest.fn().mockReturnValue(1), getJoinRule: jest.fn().mockReturnValue("invite"), getJoinedMemberCount: jest.fn().mockReturnValue(1), From 1ab3184f2dd535723a2b8ada8a4d549e4535aacb Mon Sep 17 00:00:00 2001 From: Skye Elliot Date: Fri, 19 Dec 2025 11:01:16 +0000 Subject: [PATCH 3/5] docs: Add description to `visible` condition check. --- packages/shared-components/jest-sonar.xml | 147 ++++++++++++++++++ .../HistoryVisibleBannerViewModel.tsx | 12 +- 2 files changed, 157 insertions(+), 2 deletions(-) create mode 100644 packages/shared-components/jest-sonar.xml diff --git a/packages/shared-components/jest-sonar.xml b/packages/shared-components/jest-sonar.xml new file mode 100644 index 00000000000..c1dd77b1692 --- /dev/null +++ b/packages/shared-components/jest-sonar.xml @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/viewmodels/composer/HistoryVisibleBannerViewModel.tsx b/src/viewmodels/composer/HistoryVisibleBannerViewModel.tsx index 158a326f4d3..f48076a6a6c 100644 --- a/src/viewmodels/composer/HistoryVisibleBannerViewModel.tsx +++ b/src/viewmodels/composer/HistoryVisibleBannerViewModel.tsx @@ -88,13 +88,21 @@ export class HistoryVisibleBannerViewModel const acknowledged = SettingsStore.getValue("acknowledgedHistoryVisibility", room.roomId); const isHistoryVisible = BANNER_VISIBLE_LEVELS.includes(room.getHistoryVisibility()); + // This implements point 1. of the algorithm described above. In the order below, all + // of the following must be true for the banner to display: + // - The room history sharing feature must be enabled. + // - The room must be encrypted. + // - The user must be able to send messages. + // - The history must be visible. + // - The view should not be part of a thread timeline. + // - The usuer must not have acknowledged the banner. return { visible: featureEnabled && - canSendMessages && - !threadId && room.hasEncryptionStateEvent() && + canSendMessages && isHistoryVisible && + !threadId && !acknowledged, }; }; From 31a61248fb71c1c85eb88a26cb3068d1de831be7 Mon Sep 17 00:00:00 2001 From: Skye Elliot Date: Fri, 19 Dec 2025 14:14:51 +0000 Subject: [PATCH 4/5] docs: Fix spelling. Co-authored-by: Florian Duros --- src/viewmodels/composer/HistoryVisibleBannerViewModel.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/viewmodels/composer/HistoryVisibleBannerViewModel.tsx b/src/viewmodels/composer/HistoryVisibleBannerViewModel.tsx index f48076a6a6c..7d1ce9ec81f 100644 --- a/src/viewmodels/composer/HistoryVisibleBannerViewModel.tsx +++ b/src/viewmodels/composer/HistoryVisibleBannerViewModel.tsx @@ -95,7 +95,7 @@ export class HistoryVisibleBannerViewModel // - The user must be able to send messages. // - The history must be visible. // - The view should not be part of a thread timeline. - // - The usuer must not have acknowledged the banner. + // - The user must not have acknowledged the banner. return { visible: featureEnabled && From 1df66d690f4e2d77362b6706f2cb9a6f12129081 Mon Sep 17 00:00:00 2001 From: Skye Elliot Date: Fri, 19 Dec 2025 14:15:14 +0000 Subject: [PATCH 5/5] chore: Remove `jest-sonar.xml`. --- packages/shared-components/jest-sonar.xml | 147 ---------------------- 1 file changed, 147 deletions(-) delete mode 100644 packages/shared-components/jest-sonar.xml diff --git a/packages/shared-components/jest-sonar.xml b/packages/shared-components/jest-sonar.xml deleted file mode 100644 index c1dd77b1692..00000000000 --- a/packages/shared-components/jest-sonar.xml +++ /dev/null @@ -1,147 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file