From c62acc6634802871856d37a97282c5b1f322a50b Mon Sep 17 00:00:00 2001 From: Zack Date: Mon, 11 May 2026 13:36:07 +0200 Subject: [PATCH] Refactor MKeyVerificationRequest to shared view MVVM (#33461) * Refactor key verification request to shared view * Fix prettier * add tests to pass coverage --- .../messages/MKeyVerificationRequest.tsx | 87 ---------- apps/web/src/events/EventTileFactory.tsx | 19 ++- .../MKeyVerificationRequestViewModel.ts | 101 +++++++++++ .../messages/MKeyVerificationRequest-test.tsx | 116 ------------- .../events/EventTileFactory-test.ts | 107 +++++++++++- .../MKeyVerificationRequestViewModel-test.tsx | 161 ++++++++++++++++++ .../received-auto.png | Bin 0 -> 23217 bytes .../sent-by-me-auto.png | Bin 0 -> 23743 bytes .../with-timestamp-auto.png | Bin 0 -> 24026 bytes packages/shared-components/src/index.ts | 1 + .../MKeyVerificationRequestView.module.css | 12 ++ .../MKeyVerificationRequestView.stories.tsx | 60 +++++++ .../MKeyVerificationRequestView.test.tsx | 65 +++++++ .../MKeyVerificationRequestView.tsx | 69 ++++++++ .../MKeyVerificationRequestView.test.tsx.snap | 94 ++++++++++ .../MKeyVerificationRequestView/index.ts | 13 ++ 16 files changed, 699 insertions(+), 206 deletions(-) delete mode 100644 apps/web/src/components/views/messages/MKeyVerificationRequest.tsx create mode 100644 apps/web/src/viewmodels/room/timeline/event-tile/MKeyVerificationRequestViewModel.ts delete mode 100644 apps/web/test/unit-tests/components/views/messages/MKeyVerificationRequest-test.tsx create mode 100644 apps/web/test/viewmodels/event-tiles/MKeyVerificationRequestViewModel-test.tsx create mode 100644 packages/shared-components/__vis__/linux/__baselines__/room/timeline/event-tile/EventTileView/MKeyVerificationRequestView/MKeyVerificationRequestView.stories.tsx/received-auto.png create mode 100644 packages/shared-components/__vis__/linux/__baselines__/room/timeline/event-tile/EventTileView/MKeyVerificationRequestView/MKeyVerificationRequestView.stories.tsx/sent-by-me-auto.png create mode 100644 packages/shared-components/__vis__/linux/__baselines__/room/timeline/event-tile/EventTileView/MKeyVerificationRequestView/MKeyVerificationRequestView.stories.tsx/with-timestamp-auto.png create mode 100644 packages/shared-components/src/room/timeline/event-tile/EventTileView/MKeyVerificationRequestView/MKeyVerificationRequestView.module.css create mode 100644 packages/shared-components/src/room/timeline/event-tile/EventTileView/MKeyVerificationRequestView/MKeyVerificationRequestView.stories.tsx create mode 100644 packages/shared-components/src/room/timeline/event-tile/EventTileView/MKeyVerificationRequestView/MKeyVerificationRequestView.test.tsx create mode 100644 packages/shared-components/src/room/timeline/event-tile/EventTileView/MKeyVerificationRequestView/MKeyVerificationRequestView.tsx create mode 100644 packages/shared-components/src/room/timeline/event-tile/EventTileView/MKeyVerificationRequestView/__snapshots__/MKeyVerificationRequestView.test.tsx.snap create mode 100644 packages/shared-components/src/room/timeline/event-tile/EventTileView/MKeyVerificationRequestView/index.ts diff --git a/apps/web/src/components/views/messages/MKeyVerificationRequest.tsx b/apps/web/src/components/views/messages/MKeyVerificationRequest.tsx deleted file mode 100644 index 6765f330da..0000000000 --- a/apps/web/src/components/views/messages/MKeyVerificationRequest.tsx +++ /dev/null @@ -1,87 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2019, 2020 , 2023 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial -Please see LICENSE files in the repository root for full details. -*/ - -import React, { type JSX } from "react"; -import { type MatrixEvent } from "matrix-js-sdk/src/matrix"; -import { LockSolidIcon } from "@vector-im/compound-design-tokens/assets/web/icons"; -import { EventTileBubble } from "@element-hq/web-shared-components"; - -import { _t } from "../../../languageHandler"; -import { getNameForEventRoom, userLabelForEventRoom } from "../../../utils/KeyVerificationStateObserver"; -import { useMatrixClientContext } from "../../../contexts/MatrixClientContext"; - -interface Props { - mxEvent: MatrixEvent; - timestamp?: JSX.Element; -} - -interface MKeyVerificationRequestContent { - body?: string; - format?: string; - formatted_body?: string; - from_device: string; - methods: Array; - msgtype: "m.key.verification.request"; - to: string; -} - -/** - * Event tile created when we receive an m.key.verification.request event. - * - * Displays a simple message saying that a verification was requested, either by - * this user or someone else. - * - * EventTileFactory has logic meaning we only display this tile if the request - * was sent to/from this user. - */ -const MKeyVerificationRequest: React.FC = ({ mxEvent, timestamp }) => { - const client = useMatrixClientContext(); - - if (!client) { - throw new Error("Attempting to render verification request without a client context!"); - } - - const myUserId = client.getSafeUserId(); - const content: MKeyVerificationRequestContent = mxEvent.getContent(); - const sender = mxEvent.getSender(); - const receiver = content.to; - const roomId = mxEvent.getRoomId(); - - if (!sender) { - throw new Error("Verification request did not include a sender!"); - } - if (!roomId) { - throw new Error("Verification request did not include a room ID!"); - } - - let title: string; - let subtitle: string; - - const sentByMe = sender === myUserId; - if (sentByMe) { - title = _t("timeline|m.key.verification.request|you_started"); - subtitle = userLabelForEventRoom(client, receiver, roomId); - } else { - const name = getNameForEventRoom(client, sender, roomId); - title = _t("timeline|m.key.verification.request|user_wants_to_verify", { name }); - subtitle = userLabelForEventRoom(client, sender, roomId); - } - - return ( - } - className="mx_EventTileBubble mx_cryptoEvent" - title={title} - subtitle={subtitle} - > - {timestamp} - - ); -}; - -export default MKeyVerificationRequest; diff --git a/apps/web/src/events/EventTileFactory.tsx b/apps/web/src/events/EventTileFactory.tsx index 19dba1da0d..152a69c1ec 100644 --- a/apps/web/src/events/EventTileFactory.tsx +++ b/apps/web/src/events/EventTileFactory.tsx @@ -20,6 +20,7 @@ import { import { EncryptionEventView, HiddenBodyView, + MKeyVerificationRequestView, TextualEventView, useCreateAutoDisposedViewModel, } from "@element-hq/web-shared-components"; @@ -37,7 +38,6 @@ import { WIDGET_LAYOUT_EVENT_TYPE } from "../stores/widgets/WidgetLayoutStore"; import { ALL_RULE_TYPES } from "../mjolnir/BanList"; import { MatrixClientPeg } from "../MatrixClientPeg"; import { useMatrixClientContext } from "../contexts/MatrixClientContext"; -import MKeyVerificationRequest from "../components/views/messages/MKeyVerificationRequest"; import { WidgetType } from "../widgets/WidgetType"; import MJitsiWidgetEvent from "../components/views/messages/MJitsiWidgetEvent"; import { hasText } from "../TextForEvent"; @@ -47,6 +47,7 @@ import { shouldDisplayAsBeaconTile } from "../utils/beacon/timeline"; import { type IBodyProps } from "../components/views/messages/IBodyProps"; import { ModuleApi } from "../modules/Api"; import { EncryptionEventViewModel } from "../viewmodels/room/timeline/event-tile/EncryptionEventViewModel"; +import { MKeyVerificationRequestViewModel } from "../viewmodels/room/timeline/event-tile/MKeyVerificationRequestViewModel"; import { TextualEventViewModel } from "../viewmodels/room/timeline/event-tile/TextualEventViewModel"; import { HiddenBodyViewModel } from "../viewmodels/room/timeline/event-tile/body/HiddenBodyViewModel"; import { ElementCallEventType } from "../call-types"; @@ -95,7 +96,21 @@ function EncryptionEventWrappedView({ mxEvent, ref }: IBodyProps): JSX.Element { const EncryptionEventFactory: Factory = (ref, props) => { return ; }; -const VerificationReqFactory: Factory = (_ref, props) => ; +function MKeyVerificationRequestWrappedView({ mxEvent, ref }: IBodyProps): JSX.Element { + const cli = useMatrixClientContext(); + if (!cli) { + throw new Error("Attempting to render verification request without a client context!"); + } + + const vm = useCreateAutoDisposedViewModel(() => new MKeyVerificationRequestViewModel({ mxEvent, cli })); + + useEffect(() => { + vm.setEvent(mxEvent); + }, [mxEvent, vm]); + + return ; +} +const VerificationReqFactory: Factory = (ref, props) => ; function HiddenBodyWrappedView({ mxEvent, ref }: IBodyProps): JSX.Element { const vm = useCreateAutoDisposedViewModel(() => new HiddenBodyViewModel({ mxEvent })); diff --git a/apps/web/src/viewmodels/room/timeline/event-tile/MKeyVerificationRequestViewModel.ts b/apps/web/src/viewmodels/room/timeline/event-tile/MKeyVerificationRequestViewModel.ts new file mode 100644 index 0000000000..5d4de14f0f --- /dev/null +++ b/apps/web/src/viewmodels/room/timeline/event-tile/MKeyVerificationRequestViewModel.ts @@ -0,0 +1,101 @@ +/* + * Copyright 2026 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +import { type JSX } from "react"; +import { type MatrixClient, type MatrixEvent } from "matrix-js-sdk/src/matrix"; +import { + BaseViewModel, + type MKeyVerificationRequestViewModel as MKeyVerificationRequestViewModelInterface, + type MKeyVerificationRequestViewSnapshot, +} from "@element-hq/web-shared-components"; + +import { _t } from "../../../../languageHandler"; +import { getNameForEventRoom, userLabelForEventRoom } from "../../../../utils/KeyVerificationStateObserver"; + +interface MKeyVerificationRequestContent { + body?: string; + format?: string; + formatted_body?: string; + from_device: string; + methods: string[]; + msgtype: "m.key.verification.request"; + to: string; +} + +export interface MKeyVerificationRequestViewModelProps { + /** + * Caller-provided client. + */ + cli: MatrixClient; + /** + * Verification request message event. + */ + mxEvent: MatrixEvent; + /** + * Optional timestamp element rendered in the tile footer slot. + */ + timestamp?: JSX.Element; +} + +/** + * ViewModel for key verification request message events. + */ +export class MKeyVerificationRequestViewModel + extends BaseViewModel + implements MKeyVerificationRequestViewModelInterface +{ + public constructor(props: MKeyVerificationRequestViewModelProps) { + super(props, MKeyVerificationRequestViewModel.computeSnapshot(props)); + } + + public setEvent(mxEvent: MatrixEvent): void { + this.props = { ...this.props, mxEvent }; + this.updateSnapshotFromProps(); + } + + public setTimestamp(timestamp: JSX.Element | undefined): void { + this.props = { ...this.props, timestamp }; + this.snapshot.merge({ timestamp }); + } + + private updateSnapshotFromProps(): void { + this.snapshot.merge(MKeyVerificationRequestViewModel.computeSnapshot(this.props)); + } + + private static computeSnapshot({ + cli, + mxEvent, + timestamp, + }: MKeyVerificationRequestViewModelProps): MKeyVerificationRequestViewSnapshot { + const myUserId = cli.getSafeUserId(); + const content = mxEvent.getContent(); + const sender = mxEvent.getSender(); + const roomId = mxEvent.getRoomId(); + + if (!sender) { + throw new Error("Verification request did not include a sender!"); + } + if (!roomId) { + throw new Error("Verification request did not include a room ID!"); + } + + if (sender === myUserId) { + return { + title: _t("timeline|m.key.verification.request|you_started"), + subtitle: userLabelForEventRoom(cli, content.to, roomId), + timestamp, + }; + } + + const name = getNameForEventRoom(cli, sender, roomId); + return { + title: _t("timeline|m.key.verification.request|user_wants_to_verify", { name }), + subtitle: userLabelForEventRoom(cli, sender, roomId), + timestamp, + }; + } +} diff --git a/apps/web/test/unit-tests/components/views/messages/MKeyVerificationRequest-test.tsx b/apps/web/test/unit-tests/components/views/messages/MKeyVerificationRequest-test.tsx deleted file mode 100644 index 0265bd44d1..0000000000 --- a/apps/web/test/unit-tests/components/views/messages/MKeyVerificationRequest-test.tsx +++ /dev/null @@ -1,116 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial -Please see LICENSE files in the repository root for full details. -*/ - -import React from "react"; -import { type RenderResult, render } from "jest-matrix-react"; -import { type MatrixClient, MatrixEvent } from "matrix-js-sdk/src/matrix"; - -import MKeyVerificationRequest from "../../../../../src/components/views/messages/MKeyVerificationRequest"; -import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext"; -import { filterConsole } from "../../../../test-utils"; - -describe("MKeyVerificationRequest", () => { - filterConsole( - "The above error occurred in the component", - "Error: Attempting to render verification request without a client context!", - "Error: Verification request did not include a sender!", - "Error: Verification request did not include a room ID!", - ); - - it("shows an error if not wrapped in a client context", () => { - const event = new MatrixEvent({ type: "m.key.verification.request" }); - const { container } = renderEventNoClient(event); - expect(container).toHaveTextContent("Can't load this message"); - }); - - it("shows an error if the event has no sender", () => { - const { client } = setup(); - const event = new MatrixEvent({ type: "m.key.verification.request" }); - const { container } = renderEvent(client, event); - expect(container).toHaveTextContent("Can't load this message"); - }); - - it("shows an error if the event has no room", () => { - const { client } = setup(); - const event = new MatrixEvent({ type: "m.key.verification.request", sender: "@a:b.co" }); - const { container } = renderEvent(client, event); - expect(container).toHaveTextContent("Can't load this message"); - }); - - it("displays a request from me", () => { - const { client, myUserId } = setup(); - const event = new MatrixEvent({ type: "m.key.verification.request", sender: myUserId, room_id: "!x:y.co" }); - const { container } = renderEvent(client, event); - expect(container).toHaveTextContent("You sent a verification request"); - }); - - it("displays a request from someone else to me", () => { - const otherUserId = "@other:s.uk"; - const { client } = setup(); - const event = new MatrixEvent({ type: "m.key.verification.request", sender: otherUserId, room_id: "!x:y.co" }); - const { container } = renderEvent(client, event); - expect(container).toHaveTextContent("other:s.uk wants to verify"); - }); -}); - -interface TestTileErrorBoundaryProps { - children: React.ReactNode; -} - -interface TestTileErrorBoundaryState { - error?: Error; -} - -class TestTileErrorBoundary extends React.Component { - public constructor(props: TestTileErrorBoundaryProps) { - super(props); - this.state = {}; - } - - public static getDerivedStateFromError(error: Error): Partial { - return { error }; - } - - public render(): React.ReactNode { - if (this.state.error) { - return "Can't load this message"; - } - - return this.props.children; - } -} - -function renderEventNoClient(event: MatrixEvent): RenderResult { - return render( - - - , - ); -} - -function renderEvent(client: MatrixClient, event: MatrixEvent): RenderResult { - return render( - - - - - , - , - ); -} - -function setup(): { client: MatrixClient; myUserId: string } { - const myUserId = "@me:s.co"; - - const client = { - getSafeUserId: jest.fn().mockReturnValue(myUserId), - getRoom: jest.fn(), - } as unknown as MatrixClient; - - return { client, myUserId }; -} diff --git a/apps/web/test/unit-tests/events/EventTileFactory-test.ts b/apps/web/test/unit-tests/events/EventTileFactory-test.ts index b1ff4c57bf..f37e30667f 100644 --- a/apps/web/test/unit-tests/events/EventTileFactory-test.ts +++ b/apps/web/test/unit-tests/events/EventTileFactory-test.ts @@ -6,8 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com Please see LICENSE files in the repository root for full details. */ +import React from "react"; +import { render, screen } from "jest-matrix-react"; import { mocked } from "jest-mock"; -import { EventType, type MatrixClient, MatrixEvent, MsgType, Room } from "matrix-js-sdk/src/matrix"; +import { EventType, type MatrixClient, MatrixEvent, MsgType, Room, type RoomMember } from "matrix-js-sdk/src/matrix"; import { JSONEventFactory, @@ -20,9 +22,25 @@ import SettingsStore from "../../../src/settings/SettingsStore"; import { createTestClient, mkEvent } from "../../test-utils"; import { TimelineRenderingType } from "../../../src/contexts/RoomContext"; import { ModuleApi } from "../../../src/modules/Api"; +import MatrixClientContext from "../../../src/contexts/MatrixClientContext"; const roomId = "!room:example.com"; +function makeVerificationRequestEvent({ sender, to }: { sender: string; to: string }): MatrixEvent { + return mkEvent({ + event: true, + type: EventType.RoomMessage, + user: sender, + room: roomId, + content: { + msgtype: MsgType.KeyVerificationRequest, + from_device: "DEVICE", + methods: ["m.sas.v1"], + to, + }, + }); +} + describe("pickFactory", () => { let client: MatrixClient; let room: Room; @@ -206,14 +224,30 @@ describe("pickFactory", () => { it("should return a MessageEventFactory for a UTD event", () => { expect(pickFactory(utdEvent, client, false)).toBe(MessageEventFactory); }); + + it("should not render key verification requests which do not involve the current user", () => { + const event = makeVerificationRequestEvent({ + sender: "@alice:example.com", + to: "@bob:example.com", + }); + + expect(pickFactory(event, client, false)).toBeUndefined(); + }); }); }); describe("renderTile", () => { let client: MatrixClient; + let originalRenderMessage: typeof ModuleApi.instance.customComponents.renderMessage; beforeEach(() => { client = createTestClient(); + originalRenderMessage = ModuleApi.instance.customComponents.renderMessage; + }); + + afterEach(() => { + ModuleApi.instance.customComponents.renderMessage = originalRenderMessage; + jest.restoreAllMocks(); }); it("rendering a tile defers to the module API", () => { @@ -258,4 +292,75 @@ describe("renderTile", () => { mxEvent: messageEvent, }); }); + + it("renders an incoming key verification request with the wrapped shared-components view", () => { + const sender = "@alice:example.com"; + const room = new Room(roomId, client, client.getSafeUserId()); + jest.spyOn(room, "getMember").mockImplementation((userId: string) => { + if (userId === sender) return { name: "Alice" } as RoomMember; + return null; + }); + mocked(client.getRoom).mockReturnValue(room); + + const verificationRequestEvent = makeVerificationRequestEvent({ + sender, + to: client.getUserId()!, + }); + + const tile = renderTile( + TimelineRenderingType.Room, + { mxEvent: verificationRequestEvent, showHiddenEvents: false }, + client, + ); + if (!tile) throw new Error("Expected a key verification request tile"); + + render(React.createElement(MatrixClientContext.Provider, { value: client }, tile)); + + expect(screen.getByText("Alice wants to verify")).toBeInTheDocument(); + expect(screen.getByText("Alice (@alice:example.com)")).toBeInTheDocument(); + }); + + it("renders an outgoing key verification request with the wrapped shared-components view", () => { + const recipient = "@alice:example.com"; + const room = new Room(roomId, client, client.getSafeUserId()); + jest.spyOn(room, "getMember").mockImplementation((userId: string) => { + if (userId === recipient) return { name: "Alice" } as RoomMember; + return null; + }); + mocked(client.getRoom).mockReturnValue(room); + + const verificationRequestEvent = makeVerificationRequestEvent({ + sender: client.getUserId()!, + to: recipient, + }); + + const tile = renderTile( + TimelineRenderingType.Room, + { mxEvent: verificationRequestEvent, showHiddenEvents: false }, + client, + ); + if (!tile) throw new Error("Expected a key verification request tile"); + + render(React.createElement(MatrixClientContext.Provider, { value: client }, tile)); + + expect(screen.getByText("You sent a verification request")).toBeInTheDocument(); + expect(screen.getByText("Alice (@alice:example.com)")).toBeInTheDocument(); + }); + + it("throws when a key verification request tile is rendered without a client context", () => { + jest.spyOn(console, "error").mockImplementation(() => {}); + const verificationRequestEvent = makeVerificationRequestEvent({ + sender: client.getUserId()!, + to: "@alice:example.com", + }); + + const tile = renderTile( + TimelineRenderingType.Room, + { mxEvent: verificationRequestEvent, showHiddenEvents: false }, + client, + ); + if (!tile) throw new Error("Expected a key verification request tile"); + + expect(() => render(tile)).toThrow("Attempting to render verification request without a client context!"); + }); }); diff --git a/apps/web/test/viewmodels/event-tiles/MKeyVerificationRequestViewModel-test.tsx b/apps/web/test/viewmodels/event-tiles/MKeyVerificationRequestViewModel-test.tsx new file mode 100644 index 0000000000..2afd7e4227 --- /dev/null +++ b/apps/web/test/viewmodels/event-tiles/MKeyVerificationRequestViewModel-test.tsx @@ -0,0 +1,161 @@ +/* + * Copyright 2026 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +import React from "react"; +import { type MatrixClient, MatrixEvent, type Room, type RoomMember } from "matrix-js-sdk/src/matrix"; + +import { MKeyVerificationRequestViewModel } from "../../../src/viewmodels/room/timeline/event-tile/MKeyVerificationRequestViewModel"; + +describe("MKeyVerificationRequestViewModel", () => { + const roomId = "!room:example.org"; + const myUserId = "@me:example.org"; + let cli: MatrixClient; + let room: Room; + + beforeEach(() => { + room = { + getMember: jest.fn(), + } as unknown as Room; + cli = { + getSafeUserId: jest.fn().mockReturnValue(myUserId), + getRoom: jest.fn().mockReturnValue(room), + } as unknown as MatrixClient; + }); + + const createEvent = (sender?: string, to = myUserId, eventRoomId: string | undefined = roomId): MatrixEvent => + new MatrixEvent({ + type: "m.room.message", + room_id: eventRoomId, + sender, + content: { + msgtype: "m.key.verification.request", + from_device: "DEVICE", + methods: ["m.sas.v1"], + to, + }, + }); + + it("throws if the event has no sender", () => { + expect(() => new MKeyVerificationRequestViewModel({ cli, mxEvent: createEvent() })).toThrow( + "Verification request did not include a sender!", + ); + }); + + it("throws if the event has no room", () => { + expect( + () => + new MKeyVerificationRequestViewModel({ + cli, + mxEvent: createEvent("@alice:example.org", myUserId, ""), + }), + ).toThrow("Verification request did not include a room ID!"); + }); + + it("renders a request sent by me", () => { + jest.mocked(room.getMember).mockReturnValue({ + name: "Alice", + } as RoomMember); + + const vm = new MKeyVerificationRequestViewModel({ + cli, + mxEvent: createEvent(myUserId, "@alice:example.org"), + }); + + expect(vm.getSnapshot()).toMatchObject({ + title: "You sent a verification request", + subtitle: "Alice (@alice:example.org)", + }); + }); + + it("renders a request sent by someone else", () => { + jest.mocked(room.getMember).mockReturnValue({ + name: "Alice", + } as RoomMember); + + const vm = new MKeyVerificationRequestViewModel({ + cli, + mxEvent: createEvent("@alice:example.org"), + }); + + expect(vm.getSnapshot()).toMatchObject({ + title: "Alice wants to verify", + subtitle: "Alice (@alice:example.org)", + }); + }); + + it("falls back to user ID when no room member is available", () => { + const vm = new MKeyVerificationRequestViewModel({ + cli, + mxEvent: createEvent("@alice:example.org"), + }); + + expect(vm.getSnapshot()).toMatchObject({ + title: "@alice:example.org wants to verify", + subtitle: "@alice:example.org", + }); + }); + + it("updates the snapshot when the event changes", () => { + const vm = new MKeyVerificationRequestViewModel({ + cli, + mxEvent: createEvent("@alice:example.org"), + }); + const listener = jest.fn(); + vm.subscribe(listener); + + vm.setEvent(createEvent(myUserId, "@bob:example.org")); + + expect(vm.getSnapshot()).toMatchObject({ + title: "You sent a verification request", + subtitle: "@bob:example.org", + }); + expect(listener).toHaveBeenCalledTimes(1); + }); + + it("updates only timestamp when the timestamp changes", () => { + const timestamp = 14:56; + const vm = new MKeyVerificationRequestViewModel({ + cli, + mxEvent: createEvent("@alice:example.org"), + }); + const listener = jest.fn(); + vm.subscribe(listener); + + vm.setTimestamp(timestamp); + + expect(vm.getSnapshot().timestamp).toBe(timestamp); + expect(listener).toHaveBeenCalledTimes(1); + }); + + it("does not emit when the event-derived snapshot is unchanged", () => { + const vm = new MKeyVerificationRequestViewModel({ + cli, + mxEvent: createEvent("@alice:example.org"), + }); + const listener = jest.fn(); + vm.subscribe(listener); + + vm.setEvent(createEvent("@alice:example.org")); + + expect(listener).not.toHaveBeenCalled(); + }); + + it("does not emit when the timestamp is unchanged", () => { + const timestamp = 14:56; + const vm = new MKeyVerificationRequestViewModel({ + cli, + mxEvent: createEvent("@alice:example.org"), + timestamp, + }); + const listener = jest.fn(); + vm.subscribe(listener); + + vm.setTimestamp(timestamp); + + expect(listener).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/shared-components/__vis__/linux/__baselines__/room/timeline/event-tile/EventTileView/MKeyVerificationRequestView/MKeyVerificationRequestView.stories.tsx/received-auto.png b/packages/shared-components/__vis__/linux/__baselines__/room/timeline/event-tile/EventTileView/MKeyVerificationRequestView/MKeyVerificationRequestView.stories.tsx/received-auto.png new file mode 100644 index 0000000000000000000000000000000000000000..fe9fbd0acdc66648cc4369dbf749eec8eb96431c GIT binary patch literal 23217 zcmZWxc|eTo_n#S)HWY103!&0VT4)=)Pz{w(sw-{Ul~&aZ*IMZ!q;*_NiK5b?bx3tB zEh^filveHg-tWBcJ2Oq+-(SX=_kEsoK4*E(InSAHkPQwj=UK~x!{L_e9o%~ihg(9& z;RqF6OMznPz2*%L$B)z7yUW-I-_yuR*=X`sX~0#)<^fISB)_sYmG|KBjn93!&lepb z>)lydL^9qOoJu1K9sMWd&>b1xoI5`__VR}>JCsF{+0n!C_olp^={#w>`lC92%vXO=o*C7d&mH>o;#I*w+yjfafgilDbX9)&@-5Zjytraw-N24! z53B2OOS&A-P1Q%{7w7j$5Qtp-k|bI@p-^gC`h}#0kNX*|@%O=72ir_r(rYZrZ>jyV z^xsIL;P8Y-p@Wu2RW;v7RL6AdYU*nOZuBZ<)p5h?6p}8%7iVDkk7d~8dm6Mija+^_Nn?(77ii)q5;f+FzxdUIp8W5V{;){6@|=JHr1c zIO;RrWpNsV_nwK?>QXoJ)T-bK+JXb=nb#)nZ$`e}&$}#WG`(i+a;sq?*Y>0ISniSI z^qQYPr_5r2PhzCpKXW{u9^S4Uu&4dM6zy?y$=Sh=u_BLfsJIm*>P(oGs#{aW5k#)# zLa&_}FuWxY+&uiz_arN{@S4bOctWT!KSi`LFiINzpZ==XrpV*=*%aK5k#gKLMkYqC zw|aoc$WM4}NWj(uR_l3dC4r?oq)mhYxmUTX__PK{i8YUL)N2xRrnwU>!5lBoaT|+n9(v7eSN24=X@PKBScLZevaQ>^ez)8}pJrwEnT2-|v*&W$Rp% zU4QOHNkZWY*XeA7)5_C5X(c+-J$Jw5JLt5vnVPTRn;-r)@%7iWBE_7ZI}*ct5xg-= zA?+iE?+B$2IQ&eXu3UJP1v% zF_+sPmVfsC1KqogS1#)0n9WokR%){r_x*i4tNA@|>w^L<#WJ1gXrIw?ko7t%LG}iOMMJHSTmK)MV zlP6>5r)mnOE$$Qq4t^C zrK4BZSa>*_-{xo>F4xr6y`LKwIMsD<`e2ynKypt1EigE{kToi5w>0}qyDGvDoX;Ql zH~3lZ_$3zN@WF;ZBLC_xj%K@5x9v?AhxH#iQaOaLOUL=0`!s#r;)sve?hpxG#p5S! z+D_PfGijNd?hlJ{eQWdV!O!!5k_OY_!~~S`C*piYQcBLWhN=e_@=d#Ija2M$@8shP z?e(sewFsIWmF+kdp})TGgZvM%^Z)VLMyQkyK9&%>9cK7z@^xXLpzeR|db>vJ?BYt> z*9jpYY?z(szopr`u0V7AhtHOVCcd<-Ie`H+7xO==_NJAOx4f4Rk?`wGm>7DX7S<6L zq|=wvAJp0vA9qu*e*S!G=*fwIO&Rs)|I`M}&!+jthHQOsE7Ub$*z9C&OhmcrWEYs# zXTtQ0+q)gJr(?!OzQuUoX!LH9>U-M}tI={@s)g6GcH?Hw-j6k6{= zCdtube=JK*+QkU*+UD(;>U`WPTIa{7GeQ30_c_k2HE)AE^iS%(O?jempWKCi_qPWM9_ot&2`mwtLU(xlG~>F%Q5LBlDbO zhM%aji@l+*)+k?jn!aR(hVpz~h0XnwioAxL`?6nT*FCcgIexjq=7FWSQr`Bh$1SZx zCnidC?+=@Ao2s0#4l*p5Ep<&*jlIz{)Ba{+Zk+Fy)Ge(I!oDT%xBm71?}Hr{0loJ$ z2A>F=d6hR75|BUMC{K(P7oIdn?RL@GHTI?>*8juv_}EpPJq4EH z2}W*>e_iW6GudW(ebfCDS_Tu}2Dgs?@F;T5Y!a6+vx_-+A?Exiz1zFbPPw-_-ISdd z51dGdP%#^Bm6-Qg=UG8@AN^MIVR_Kx;dSASHuLZJR@_J{(~-0Il+W!}?@$nXUf*x4 zQ|;@C>iwc^O=KU?V=8_2yw`#8o3(C*%UeOMVMYc!RC)6niUOy5MRj$@B2G9B7Pb7{ zbL)Jznzpco&#%=gUPpTJ#$&sB!w=M#Y>sk?F3=tNPol_9XSnMGSn$x=lDWT}0%zXy z21!Wm{~{hYR~9`7Y;T@1IGD?>oDm7!7R=D(H}yEcs|Ci_4#%&(n4J*mv$3J?A~={VedE@7}CCfhsg{d_*9@#Zk9OZIz#QHt2d=@EiCeksvcYNw?s^*ifl3;1h zHg17AC0<{?wwf=h9qYE%M0*t7(sYW`^=ce-bU)^$?sKK>L3#Ojz$wRG)#04CaOjrS zo$mZ*U8T~pYxeCGSn6FC|C8~sa4t<6P%}V{Pcxy z2LoH@G!%1|7}XsT}Y0j9k{vX#bhb^OeYw|TJC(Unp5rU z`eWA7`sc&llafb*I^pz*4JllqDX%sK2Hf;kU-wLOp!8_|bYIxh2NpZ$SGHJ(#cb~% z8GiYY<9$wlwr29%sqPa}Ifapgfl+mH30eMl4~Lo7x|qt_QU{9XXUeY*j%AjL7R=8U zb-5i5dz(s|`;t~(JX&KJH~+I=T5TdOtNW9CN|$X^yKZFkX5U|vB9+%1#63z?O`jBG zU-b8i4@gR{x%p=9d3?e-fLwT3i3RB7D=t^rX$9C6!^y5DM$v>)K2|1iQ|pcgRXl=bmuQ zyI|emoOxpppO}zi(&*QjSE3T`AHrIDUE|a@$7#Z0AvN3acVNKOtKpox4t+XCzm-fa z9G*t__ho*Vsf+0t&mYfAu^JoHt}9WuTR&6yBI$mZUV*7*jk!nhmKPV5)qeW4o^YQO z8fs0_3OG|}_&bPWO3t!QjhAj%GN$PyQnJqD$ee!_r^L+c3Gc@Fvi?c~-5NXH>4)+! zEyN)tIKKTl&Zi?YHxjQ~l=?YPa;(vNen!c~a7AX7C4}!k#X%MmhGV4^J6PLExy^HG z_SZ<<%TocN>1WbP^H&d-t6N_D9h&pQXleT7na81OpZezlyQ@TtUjJ2CpIh%n*vES! zjFl{q_8JlfWaDGkJ+O4ITlTIiURQgkOV+RQ$^OUB$o^$@c5T}AXNF5##~NHq_qgmF z=yeT}D-NBvj=s>oW~*(_)0DEEcjvBu|LxIQ5*A!#sH>_dK0AFdC`(o>Zf+~YoTBua zTVY!tSZr%EKGk0wHFsye&vj=+psCMDXYjD@`Fnx7lbTJ9{)fb>YDV7i1uE-iwnsWX zIh;m$>-EE@b@^nV2mJOQ<+7n+SA90o9 z==!WwAb-rc##}{bUh353jfYxKn+j(enp{5hooNyr&h2$J+AsgGaZ-EwrOI&L<_Fec zv%caJRTRhMH6s2&cCSXxe|p@X(lx%rmpoNyQFwKEuwg*A(OtgDgA~$9j_Q3(5B-Pt zjS$7ozshNH->mj>*T0Sg)nxi9&VGGnbN}?_$}oMyApNNqALNp=&dBEuyswHoH$D5q zqg)KK?qhS0|8us~YUBu3%s%*p7Tu>|=$+V<*%fC0RQHqW`QQHqmMr(&HXG}9Uhh{B zEhTMdTwhuL>%v=gjXl<_!*TQG@~Qu6+NxH&LuMVmo3xUVT~I#~oH?2We~$g|7>BIi zJGM(IX1Y1?g+XjX$y%P=k(S_;%0qHR;{H9toa}Y6Shc`tb_@K0eP!+ zSe=H-MY~Bjw^`Yd8oRXFzduhM$#$!Z$2+aVijWLmckHvJxQO@sZ2#n+M>+wMpUplP z8EWq+-7c_s#`1^zwAkcYNMKvGci4`$w*)KQIw0=5(IRlNO=Ez5`QLAUY*j7S+%o)W zbTr*5Z&dB}lzFw^_w@^bWHny2*OV5IpU5oGmV!#-<;3SubpzR7R2MnH{gL4SCLv)|AW)mrD$nX_)`JhbK~tm$irIR2P@t@?LBL&X4~)6`R>}# zpJ|=b-i;R^U3)SwSOGPVIOjSyE1kJ_oD$L7oW4A@ZfVHr6_1bgIu%%>5#s}eQOixa zwJG7HR?Mi;aTy{yiXf>VFjV4*)s2z4+4faImLY>D`S2Hj7?g+EO@tc5Gs>ff^RAZp9|>wjgAM+NF6tK zzcN!gF=%=qJ1uvHQ|F&+X$qM?ws_V4RdP_myWZhi*38i74IG6KLblA!bo?21%dM_F zTz|u$rOb{6@7Q0q zQdi5)p7SIE|^!xDSx3 zip9qU`n}BQ3+nI?SL+@?s>L$}0pp<_WjdO3lNGuuk3~{x{ub?NXT7BTb80yyYdfQO>Ijb!UxddLpb-Y5C4hDQ&{;V>|3R(x18WDr8wfdra=6 zetCSRI3oIg>%&B=4=it_2sl>0PI{}hF^$?K}Gxc^AX31!~XT^~Z#f3AL! zlyS;Du&ynoVmEIwS_iFjBkdGQD^)Ygb-dwSR3JWQ>h$KTv~`JCM%Z+Jfs*FjGoJzK zbMFho?*d}WbmwQ}JDo)i&(~BAA}1Xfv}K3xhS?mCvQTJ)DE4JfC%^nzWbK*JQDfn+ zpp##=bFe`r@NT)=I}VZJ)Xy`fKfId)wz{``@X?+Q%KYi&`0`EVp_KnLg>N`#Hmo1} zukTK|YK{41bcxt_aD?@(9Ybpmgsfh}m+PMCd!^V8N}_levXtufP;P?>ai1X&nZsBz72 zYaO&3$Y==Bq6pgD^sca@vx8wlA+MbB^ySmOoOl0z(ypYapup$;^u@6{JIKX?rk8b8 z4VJ|EY;egPd^WSyQh4INnb*TL#ZRrG`nsP9bRCFl8oK|swF@fh5zXALS_r!jbbg1K zS_UaxEmt#x!1-`8OvTc>F>bh3Hlkh0c4*{?MN@;V#@)V4Kkqd5#+Y031b3C$HBr0j zAqeTd(hZ#+xTd>q*lzluweV@DuUbAz5gelGtII#@RThYgI+$=S6tu`$8il_Rxa^ z_k)JRx?~&Q1`S8q8AAN~@3z!%*xk5ejfGcj()D#Ju7o|qnzWcvlPsyR#OAM$l|mIV zYPDPnX1~w4L+1KnmT$)O38f}8>*K2jpZor`81MMAp;24Er0l=<4pXzWHffsw$o|)x zJ8W=UTrToSTRagVtV|b2?IQXKP?W^Q;9Pwde(dUUQDM=S1wZBc2|<#)Ctd{Dr`LL>DHW^L ziB+Un&+|i;W-mWa<@>-RCod5<`=!IWp*JHePQu@5S)6mRfP5Ymuyzi&B zj-`IyFq<`WNG^G`W>1!pmgcWIr&ytugvYmTDg|~0Z`I1VH+j!FY!g&{qs81hrifLR~(4FW$IpyC!8v`L&`^-c6gUN3yZ@SM67Tgp{ z3lp&PlWqE=1|`V;T;Fc7?-x_?u|HG(dq0@eIClN4#?FSSGR zfXnNSR>g^n$sT;F-r!o`96j*DvgjSB*y=8CN>%^6XRTe4MuwU@ye7NLpv2D{5$pFk z0F}2juo^m&lhIY;Vl;5tDu1%I);j3+{K|NNzzY|h(!U)~8TTH$X?aKDd)Co8u((}5|%U5ZC=+5nxjh%uM zRf8YJyDm%}?f1?fj&bknFS`MS)}8sELp=|Tc6vjmX4^DyLN{*&Ze~OY?X18O@tg!T z-w`>7U(IGsQ}<7d!~_?Q%;&Z#xPB~b`7Nn7J2~JayIL1c~BuQ4OP z8=fZ`Pn9b@)!mul;3cgQ<6pDt@Q04noWibjPB=k!Dp=_-QepZ_;ufgH(r+BU6`zyL zE3Z5s{wT^R)jir{>||Zcm)7ADr9fY&xtiPY%M$8>z4IJz)Q*bg2xq(ytPb)nt4k^L zYAAw6f?M?Y?vGn1qi|gY6EE{m9z8)7-T++~ZH^walHua-JV4uu&yu*jKRVLC@9psX z-eX^*PM_;~dgk6e)3W&q)31(ZXgM9Z*{T35U4roVtRM+H^hyoF&o_SZCgkv2CL@XC zJ9w5AkVthLNaKfk9Ezgj;UNnJ2-yOEZ>40#zi=_NA^b14{3iS;NuPL1gr8!Tx$O>X z9r)0;imY!&D7?le@%=l;QUREkxAgM6bLKR^F%-ga`G z%&!j8)Tzk)X9ja?HQ$Hn#LwR?yo`b{a>UA~>7Jdaj#m4BeZ=w|CD$Vy%WiMYMwDs?LUgb1J+xK6~enDwdQ~EWulb`cqu=fO}74`dJp_ z3fx?Dg{^DQ>ns}~vIzD#iDm*CHj!;*;6pTK+;_cMFNw>f+Q?v!kvHSh(Gr_F1{xmA z4PlnI;d@9z$QBhDpkcL#ysSX!r(hKTEG*kk!ik4OI}T zL`aWSoFgALK_!2<;_qD#no;Gm9=AV9!nr*BL{H^TxP~f$u~IJ=7robh&0(4rVatL= zlW6*Yn7(o$H^0nf%G!l(-zWiMuI)U)3f%p6vlzO(il4$+)V={>vGX3~0#mfhLO_@6 z*om@E&ZP)pUr^V>wtMIvvJB*(P}0#o=oNtcqsh0C3>L{D6cgkNtN)x2i7QrokUnyJf7wAs~6Hee1pFa9`0VCj-Mu0UqBDX)? ztF(x2RRMC)y39|!p^UPBp)82U`^fkuZz4xi?y-Hb8e)ppUeW^rN%G$m)rBvXodyfa zezXQC6{qoOi~s`8u$P4E<9du9Go>j|2T>d>wPgn3c-Bh^jozfv)kKo2g#n)h*44S$ggP`qji8EIQXR3;I2v z>j8b$ONRwI|LPEmBbTQk&|{Mxve6|gVkuuwgSq5@zoFXs(xQ^|W#hPjKtwu4g~%lckwk>(yoL=&+s)0ZsV#s_%gdxiChx$JD`!l?DTX(k zSp=E#Nm^$Z!R>3y_l2H4!b&VxxIAwB1O4AcPgmf8lOy`aQ8Qt&02# zwHdl^g&B}D=Z-VDi`-xphd%8A_PCeQ<^m+hvMj*b=t59~`UW`J7vTuRg*U>x3cm04##eWFjcJTh0n?=OZqdionwK_fAxh zTZA!&KZhR#SdOZM??yTA-ejeYGD9;5Sl&)OT8qI#QzVR`z&#ZJSZY>rFy%>9vf_jn zWFAv+=EcbgH(1K}L^vl+qCJ6C^h6)aFM#_^kgQ4_;^CsV^@e_885Lxlkrcnf3X-@7 z+1fXmPK6rQ1>z@*=Q#7R70(wgw2)nPZm4L({kXkWALE?0)Kz&%h#tO<asF!V7I6|K}P3p_P*4 z3;0pi85#~*o1%@GAlx8PyLl3rc1}JHx_B9L6wN7(kCI9mioO9FU8R1&0fYJ!ArU)s z+rYIxH%feCfNQdnERFCV4ym7FK(HX={NPd3gG#EudSMd}cjandS$);TdF zCFbHNlFJU!3h_Q<)vW6az#!a0TwY=KZ~nsD+E1Lsw+2eNg-eONpx@cDgA9GPGW<{sx^@wQXX ztQhQ(`ZLy)n5jPmGEw}CRSC(kc;nCKOk+S+ z_?P3QsRO93ve!t99od@#2~&p%Zr?N$0@)ye!9_i*>-emvs>>GHGK^S4eR!>sP_uv& z65VY;^Pk=%Uc8dHNIxbJ%3f3}q~F&gDJ&QTm+=wM{E5Qw!?bmTL`HB2y^*znKR<1w zt$0G7@M|3=m!wT#vxhSDzrqTgF_e2u3``*VdQrdQ0^$lJCE`oQ$!vTA7IzyCH$YwR zrqJuP7~oJ1nfJ~;XB_M@hv+*Ij>t%cG^Fr`wb?kwO9ZZ+pv?=N|7s(F09BEv5&Z{| zs~SRivYwPP+wtQ~{Bj7wGr=vu&q10^NY7LBi|HW0<2QpSHut8GI*8qBcX8&Kx9a(r zyP`XMF{YK`w5oQjW3^JSy`KW&4GyfIpaK2c{R`)d?XhQV5NZwSQ% zx6?ZvB#{4AEXR8=c2-5CtUh9fNf{%gEQ-F6@Bk2h6D5;e zPeu0IBci+nUg(DYMNP^J=IOvM2~`GI8QBN~eHNOouwb2+1>A(FQ{WM!y-6|5;{ahP znKGCTfy}YdB5F|}|3kPSA&aZYD_l6M(4F7Xad-`hR@TvhNpB26ukY2BGPacgL1%>P zH>ty-TUk*L(ONgScg^#tu}VF>2ipr|72w)-dAb;ZS&N{;Qm3HeAN})OY^9{KF}{Ws z#H%5QC)=1gBR!$ZB5r6u2VS5hBY^LksiAq5dY<_dn`e0^w!{sP`A zB5vU%50Jloq~3t(eXv4=1$}xy2o!oQvy|NktrAI97Tk3GKUuTvWZEM_6-V>cEwzi2 zK!m-%hBH`li$IFl1uX+4cQ$L7D>h%qnh>`HgT2s~oR=abKyJn;KrjVw2lIB>8|94jxYC5(t}-^QfT}i2HYURmVIkrn#Tr|P zbQvUHs@iZIm7M;D@DtTD)fOZ#?apjuhWPWq0h0AB`cOuVUtSx2KJ~zs^W+gqeT4-V2-7uzQ~Z&e=5n3l|~~X zZ3dC_yEF2}ga(+M4upIa$jcqGdRIkkY<|d89ECKD7|Q66TZ@=D2+-bk&UApWdwHvV zCJN8MhxL1N0{|=9LxLBhJlzff+{JC!8Jp5)wLR+q!nA4Ear9-{4uTh&sD^^(MdxL4 zm}U{9)mIO_Xq-)*`AuuJ8`KW8W*5k@aEi53=57kF$C-GUg#NMz?(!-1PkovjY zcrJdqXVF}>KKOOik6GK^Mi(~p?6bfg1`Kfxt~V(GY}`oVrq_XNSna{E6-h`ERfzNO z6s?Rf#_99F)ek=TA4BxAPfB+K_0B>Luw@UFnVpBa**GDV=6l#j1q$e9=PEm zHg}P7u~Y^E%PzV&VmMa&p&hhe-~ehwh}ek=G^EY)1?u!z+2G3yNF{Eexxo0&CP`fR zp<-mv2wfL>Lz;GmFbikyY_kU2xdn+2cS8FAG`!KbXln^ej>|4;cw>O?qW#2Ytq@D) zWLq8bhUwow;?@r)>k&G$cO#GpM-i>pK`oh6_7WqKlNA=@q-r~h9G#G1<0e5pQkFCKcuCT98w1uOpyuSG1Iqn2T z28>}j3lU*~aTD2FgvqX2*?tE)@^K1tG+ zB3sy7g7-FkL7VI-BY8rvcLEDb!LIpkmhM{s#&46=X`q8U@f|O_ut4V$RI=Jf*aNs< zqjVf&vW>tmC~KAU2&`bwum|JzfFY`&l=5#AV)J@|NLE-wKO{<$dvWF*f2p!X28e&M zfWWagf4S|;VhWF~!?@l_H76u4N=nT@R5SCX0?a~$v5CDYh-&)92UHA(zKyn%X_tY{ zxmABrsDXC!i!`9yX_vABC%RG0%FYRSf?J86{=r4q+XyBc$0Z3Z!_BFO2Bo2{~uqlV^;| znOepjf#t!TaQyZuCv-7)jNw;Cu12{$kvuK;`5LB5iB~r_pF`;TfX?$mk45KNcAO5j z?s*vK_h0%h(7|!&Ld0wt=ozo9sbL~$KLsTyg#+@iJ8-l;LQ4Lg+$l9bog#D=NQWYV$J-t*7@1uU8V^lk z0Z>suNrNY3pcGa7yKV!v3}St@o0>K7gnbY__UW0iU7JzYaI9NTra>f@%y`NssAPpH z{m&Wjv@>~0i!J^xktax)!YiFAxkwu$yJ_b5ED^$Yw$Kd6&3Buc`Me5hNBW7kP-e-fGU7GM~r5b zbu#-w`r^7R$kNIl_ybHUy8_Z*IckP=A&w|DGteYI0qLJ)q+=|??-(txB>oaef9ah9 z!zK6+63}@+xcS4G=e}j!{^1GwBCmsh)e%6vk2_}{b1(P_sZ|G%p^w#fFZTkA%=ob> za#^)L|LFpm-~K+OaqKvdlM24EktHhxsj>Y=xISNAL6(-$5Bs=A)Nmpxd)RJFkaLm> zE?vg&H~NZgPsHmrAj{Tpl+%}M7Gu{N50n|aZ5gX!l$(844#F_b{7u#<7yosug>7k^ zpf|n>1wG`aB0a2Y5SluWU4JAUg596-)T%Sqr`Z9yyi);fvy}?Gwr1Gw10XAnCSe(U zGFFDfLr{u8NLX`-?OdZlUT35TsF=?(`fdn(z^L+n-KbbJbj4Lvs088X? z8T6W{TiWUXwgc%Qz_RVa9<0$ugGyyLD(a!l0L#nupBS)oueTCGULfm$L>IM>u4HNw zo1rJkf=3+7*tbwce-k1@C29jU^tpZwWRXl$N%)r%$sG=dg`&GhI9vE2r4x(T`E4ZnBOPdXh?N_iv2&Wq!7FlvVE%H5kr2u9T2J**R zR41*PW>YnV^c+cf()Vl~UHMvoZKY3${7F*X>&cvWe-`(W|ya$WIz2GW*KmP&+Dmcti6Wu_{(ynS34=|GoT} zIJPX<9@rxFm1&P{(`1r_9@7>6-NdT(bN1hH5J zD^zIW~Rx4yV;roA9vlfy}E zFbgBsv|Dt+?!bNEw)m~@QD|Wyz33r+=m=JIo8^PkXO~xQR z)q?xP1Lopbci9Ni2`EXBS_KuC* z7;NRfbGj^(ZR$JTtUEYrHVAs5_a*eeF!-PtLB?IGA*d(2dpL(OtO3Dfj2M!f3B2^r z)h`O_r-Tw_HSk$u3K_eYi=z-9B+PO)Z-2jVPlB$6fYKL#!(d@U*qMYh+NMTQt2DPt zu&d|XCJ-huMy+b`tP5EkgmDo|mmn}J6C_ySsB1wORi{J+NZYsLO<5LRjaayyWiQ%V z7jk1V&uB0^qD|h4e2N?cNoIo?<&f${48cr;)nLYeIwhctmet)#%mynIk0tW;y69$e;Ul#LaC%y9iBxEX4I-45~`Lq+peu$ z+%wpYz+H7#i9INhAc=yzD*uMQGG;-7icU&`z;-ILru~$NGTZPcH5&rkh5pyGEA98u z+2Yl5h}5DLXU$;hA_&i#MTI#5Nu2uQawM&zp5)drVA!F32>4mV@dk8s@rz6|(y)PD zD>i~E^%^tKzm$ShBaRqZ8OVPv&xF36 z`$C_Vq{;y&H(egKJz1%NCOM2fz7eYr({w@ex?&wn?vSs@ke;oOgmxtE_jcy;Xv8tz zW!1cvqnR2S!hjlki@KJpdMB=irn?APJV6YMTa$S5y+b(jsKH)i+?jmWqg$|VtDMBR zGhtRA3_X1TRyCBwYioHcbTKk`>dG)puwM zzI2Yp%7JgI$PACGcm?jG6n2*ZiV2aCN-qWOIai%$Iy^ZLFNAmni5E%i_Zr|5pf zs}Okj_LVQLc?{@>_CV@;jjMCGgJ*#g=y_lsBV-l?rpGC+&w7A7}2WfFR ze31O)#?3?p#gZ|SZ~u^3&6b2C=@K@QPhNpX%6I0$Pys_I*hqe7I%RZ00Jf2QtomTQ zu?%Vx=14yL`7>7EZP7?R41_X=K^KqYla3M?zdPRpMH|~lJ_#Fg0XK$IT$5st%*Uf# zQeAi=qCl`1Fa*t3dtoLW#z0moLzyK8C6wlV2XtyT!n*_ueAKz;z<{nG67bu9kGWy3`QBkP)iH~!6UTy;AsedL4av(ScG#x zrw}`@Qg$$v6@x%v=dc;&rQ#$$jgiZW0mPc09JXOR!o@>77L8&M2o%n-epXb$@dX9q zH0+y0Id_OD6I_L4As0}MAZ||UIkx#UWZVT$I1T&e>~}oFd>&-3hcRW+X=Nop9FAuT z^0x!k!QEZDBM}X<PGqy+(nytGZ*O#BdcDDeSKo{!i3i_sjqXf1^ z;F#MB^}VZ<$$q9Oqe6%wdcW?F2v;ZABt8>V1dKlpYKx~|zAWp z5ilN|GXS=+Nqh$VGz97#qT?w<9JDlHK97K5=V*(HiA>^S;*v}OfaIY_#^~}5K_OJg)NZ_=`d3_ zz^)t8lbHQE#4(T#vyeQjzRYC@$|MRGym*jMqzgmsrAch32B=G$U&aL2_ihf_uI$QN z4#x1g%KjchY=)E~u%BW}WVRT7(HR4GVfd_^d|Tgj?GN~j><4majtP@YjND??1Y`ibZ%y767MW|iA^jHj z2t>ucr}^m%WPbZQl)N!0Tl?IX$Q!ajNVVk}34B0s>-T5By#&r>;ICQYvnaDC+459G z*S|rUvWmOX!E8+|$PB1Jrq^$*_o2>&yuODyU<1bhKFfRTI5HYUea2nO$i6F(X}I_q zVa$?CSH3m^o*{pC1agfSb7r!Fh)4e#Cv_EaY}sF0#SChr!mp9PrUJhZ!zmt+z}jNS z`(#u6^ac`bJ4oqp>JdM>CTg+hAdC%o;pgh*>1opSjiDsQB#hMMBkboV@|Qf}O5l3a%cs4I<^%EoaQ{E9 zqtCb?Szlli8A|p4N#Nj5D4>WfXbyf1hBLpam;gPPoxHW!R3cb4Ne@ovJKJ}!0nroU zDW{p;h)0Mn?jbzs9P8) z9s~KGgdRl>>qJ=^a&wFyVzq4HC^xe~Cd2l7>%qW?AK zUoKmQi`fASJ*5ij&hUB z7^=PGW0~wJG+e4wA47hOb!Iu-BP|#l@zZ*8-WFnW_vn!#*5b$qWiINirOmru_9LSa z><)7sGQS57YQ2`$#cttAxW1JUWd}n;~pj$WPg( z@oj|33s%lHjV}{VNn~=vK8=soE0`x59|GZ7)A*mHV2lCT8iteDr}4>4@v5lWI6!SY zYWg1;>`{O6oIrY<-@t8O)Ty%uo#1kmo=xO3hD*<6r$2(Ms7Ipt;N?~&q=J98^*v{G zMlm~29=-lzQhfEl?IcE8Zo1lmL!ZJ~TF^10wP6^S#-`o4L5sWc>rghzfI4P9# z4AKuqse(DgP3S-hIY+00x1(eDm@_ckDpR3er}?Y=vP5w{OQ*Z$Cy9IJ`@UIK2I#z}9$UjwtRe z8}Q#YaID1d-~a9A{u@T$WW>eW*JZ8cFg^UsuTb*%9pkvYWp|$SM)eM!&=PH_KJU^O zF*KKdvwU8!Y^6lo;An-g!~5w8Uo)TaKi%eY6-}1WzTa+KZe>@Bfj{YT)7(Cy=NZ+)a$G`G4jW{|sR|BNASaH27edpvQ5V z*JnD!*fS^gGbi#OhI||+VeZ>mb}ab#-S=B6S7LLw!gGAMUR!$)fs~9rG;)M$jwAUK zjlw$0$-X!pfn;kOo?BozJ(J+2&5w|M0bfu5FbQ0qd=rNcld-74`>lho&-lKkeeJ|i zWlpvq9Q+djY{To_A33VTqIbOKYzyH+iZ_ zBaC)&)$gP!$0m)0Y3`Y5Qb+&wkd(I;VWOG?-yZ9`yk3j8EKU~%L*^xUuchsStk;-P zmxDF#VqQJ@M$ECu|V{T2_Y|KWz;`eyCndEHdBa(6lJ?lN`#Yw5LnAAGEPd1Zx9 z`j?Hig=NZZYl}qOIts~g(VD9ESwFv=ajC61sc!UTz3khuZz{hkKb<($*l6pkIlH{j zK85-Z&#!UQ8zYiCi#9nGzKV<5^g1cuLoi5MD|*e<(cUP}!LnuJE(P;;KEJE96-3kq zvn=Q8;P48Fr+|MUkmx^?Q zohmdG26HF#=gxTKbo`a=Xm%>sQ5Zd%BXM&erRea?^|m=eblh0yhxMk87haC`2H8aG zk5K2=912s5%%c7vb(06LxN%=EZUp5XF43XW{W_^8UszpXV zj#lWoYU=!*>I^Q52|X`ZB<6b{!gqmVq$}JZ_S3Y)*_h66@SpLhlCFZD4*${7I#U;& zX7|pa&d=-3ixWL1qHbDMwak(|YNIb}_D;p68J)`pVBEnBarHCkFY`SyMN^}8Y$yPjDGbq1?x z^6ZNH{ozLFdDmhEjlTF77f0nX0!$ViO$3!H?k9PT+m7V6osW*roe8t;c+VsM)jTdv zV8Um_|3zn@<^_2Xz0T_373OY#TtUogFL*)^Ydf zf17zR+IzzPMX=`HD6hU(D*Xda9k%98GN)z26{WmSlE=SF1V;$<>D#SXocizBg}w7; ze_WgF#qNq-KP0Fa9rQXee|tuQYm>VqPnMpwQ>?d-^<9zFN;ePn3ExFOtu}ABmzGKI z|K51n7K9w=Jd!%!qd39UEX0!4i-tGCb`VAwM=3+s9%^@MiF#78D~{x(W?vATg5Z$- zBo_R5`XVSjKsG67yL|SqcT-=K^ZERDygMe^tgH5C?2PZN@y`0rp!=icS_)6jUHzsm zlc_W^wSSG~l*K}y=L#={)#}5fYk~e31NRtJ1V_4e98W1OmbJdn6Zc@y!r}eM5pbH| z@`{+ax;A&Yav=@Xa;>4}sgCu^UwxI-TI4hak`G>da@Oj;bDV0~voE^t0h{*i+S@V~ zzH?@*`r_jJMBuH}Q$B)67WwsCFU&P}|0wRxnhAS7s_pye_P_?~h|NDt+*-0AR&@q1 zj%uA%nLj0%;}-l#Pj$1X$MExGZZV@D8mjvm&i~a`$WzH5i&3x|l9~^{YI5QBx(*GV zJn7uQ!l}x&Vqwm^JY6QvT7Pz`2nZ)BXgq_MWqc<6ZPJc z1L3QzKWokoME2y!cIZI~VSTkqOr*U@jj=-d0d@F;h~t?>k{lKYRwZhm)j zST^4C_>o@VWWnP6^%fUn>$a>cr6(pn4VtNAi+`uP&A$%z1(zQf>PlH$nEkx7NK}MJ z?maQWtN7LQ;Wcx=$!7-U{eLf)j&eLO_qTNS#Sa|o11>e0w~g}9>H$WI<05C^hUX`> zcqdz(914VUXQMnj$g@Q)I|tl|(fp7kua_>CyLGv3&aA&UI#((YJSSo1J)VB%=3MXr zJ>?eXk(6NX9fKuZI&QL~`{mT%rcN}DBwib=)y(yHlUkm0-PQHzM0%ZBMUq#~&$}HC z!E+ka5*tHVv$HkZHfMY#75DcIP|N7Cm>OUry! z=%U)**$Khv`mxS$)BYoR+m5|W`OD!=nO^@m_ zN)mI6oT~%PA`ds>aKdI^FNL|5#T zr%qDaNKnJY!h3wvoo|&}i;G^y8Jl}cdOz)ZF!=AnjB-Ko$L8-N4Hb!DE!cb|9xtuZ zq8PRCA@ge~^;b*e{uY7bt?ONLG)Ai@WR1{A{h7#Wxs2xRf@j?y)GO!Oh7F2NDwpM$ zPaRO72|hZig?}>0n^e&1F;a6gx{cUqI zb8YzD(Hj?4eqc7ZBU{k-je7LU=(vi+i3azXUh57hz5K1;QQdo<-~E!)|5j|9fY!4X z4xRqQ)bhribrDWQ|7v{=i4Aj^Q{Gx77IvawSwxVI`^q1$xjOIsy-pE*8`iS3VC0VQ z=weOvkdK>p#LX@RY5ARPUZY(}3i-btzA4HCzZeWH(9`pm&#_r}@w4vyWbRaZhky6H z*3^FoI~9DVwN*vb28LTy+DT%C8*Se_Xd$=l(3&_fvG1u_X`^0Sb+(`S@|9$0K;C4dbkN*%h2^dB zx{6@E>9h~a`>ik7mrs>i8wKilbv$~oK&X-m_mU9HfHZAYq{%02mv;S4cGtP-V4+E6 zJ)iwh1FC(fJMVV>TIT$OchHeH$sK+v1Mf<{ScTh(ip}>FS(4ofJl~Caw*?68+)@>6 z6PNp?-|Ir;F|Dr1@&Z|&UM-a#YSw&kW)ZjS8=>6649_?3inpf}uGM~7{mJ7^wv}LC ze%{$lUc;M5Ug>#DRhb{q?6~*IGBLZW+;`Fa$BrJKUlL;eNs#tayae;5cVzVZ&)>>> z%H36ZzW$h2n8weke}k8eU;X&>x!0!nqrH<4v?9D$4`jA2q|~)~7A2ewmVUFdLVdJQ zK6hY~TkB2ZR{z_rsTQSbL+bpxMZrE}pXRjM#=nJVtrND-I#n7TxHuN46&bxa(G~t# zW3rjQM4{l~wd4<>9o|Ep=5Lf22EBZ}D-^~Iuvkf?BSH_wJsEY3nD5;6>pwZV^?vw& z)=GBw8Teb`E27-yW%*LeNU12osmoHZ6j!#dJ4$4-)zi)N=I7tjV*V$Rti!Huh(+%BWME=+N8qRAn&pkQI9EvA}ZtXfYC@xvY=jhV5mg_#}V)|=I8L;mS5 z-0y8Rko?K*@6FR^`khoX3nscdGta(VAK`0e>hFBUh(sTYivt9=eEDA-)CHW`uC*C#tFwn}e(tQOv7`gg%M+k(HvvhRr5-Gw)g z1_<%RMcg>Ouuyq$P5IC}@2!6rSO8_>ttG`w=WNAZ{WG!Y)t2XwSPyg^TI&pop!t$~ za^9*}sjRTbD%@LkVXCOFG6AZax)Iq-1SxvhJ%(DH7%H zb&`xUb$xcZoc#IV>b zF`>IOU;{lO@&6-*ag8Yuk70a3i;MEO+6-mcys^qcX{1~02Be$oY+E(= zv;EeZ!2YCy3-9(Py`QC6pd3iNM5D=kpouUtcd;m9WE;eH;cxWhSow4cW*6DJ$ zFPra*3Wh?8Al+V=uJ8@Y9n7(%?(F>q1*t&Y{6x-FO`Oqjht9W|t$v-sT01kEFP5nE zze(*ZmQ4zPMoD&}(JASeWi>@Q+I?4IR)^o%yWd42#%3am3zG#VqUJSJqZU=ewr;J( zX_d){QPn+>H`yu^@yd5pi)B1q)K~Fi(Ql^fi!=polk!}>tY`nE=2A1`K23M#^e6ha zxHd_YYfQd(pC7Lqwe0PMXb~K}IJZ8fT2~D=GkvyNXMF0D*aEMt_gv@Bt2Oa+&44%v z?=DyI4iNSB7${iuX?UM&MV`t!z`IBcNIyg&(9A97v|3nuJ;^Db!c{~$eaG@+!7ONws&NvDD^)t$ogM2 z8?sg`{0;Rw*=U?|E26d8w{3n}8B^8oJKt{(ZNm4IaigX~IsTD9bc-H+Ml$5UyMFuY z-#&+4YE#dWZYmPJKK8w+5F%~FokKaJ9-ji0bq)1=Zp>99#YzQ)o!mpaM19@gt+`Tm z<&SciFJL;|6N_GB(Fl?>wJ^Enp2n`wCi}cSsqVK*=6+P0^+%P+o>8l!{ zg_M+2(AfB$cWaRJdDc?q4Kn}j^o|zw==pgD4)W;13XMtAX0Pfp1e57{?)qYta*%^} ze^W}MwQ=z8)ZB{Ea%G2Rw;}K8dbPov-GXNBKUI1t%Q{AUwM0YDPrRO+YW4ghqVRtB z;>`eZ&I(Tekx;rY^`<>(a zAKjYko9t6{pcOv2r8Z*lVbqPjE7Kx9(VMOxdQ&i%({PPXI@;^R!pI6Ob(x11|NiF- z4eKwr@X2P&;4DBxQXAz|PA;E2V4c>dao77r$vhh*!VrJ2C2!A$V1^Y^J%Xnk~^ zmHf-Ax!}C;ykI_d8k}UX-sZ(B%fXvnLCf-eE#BnM^rb*@`!bZ8J(%YZVHtL5`gf@R ztXP&--WW4i!Uw_?JohSUubW5p&@auX7D=58@~>XzzL*`#b=Z+MeBf+Pv%IK#`-dGT zH6Q7IPKcg9`BE)_C*S(R8SBXO)M2mlKJSOB0rTTS@`g{3@8V>|$b(>?tp&n2t91&3 zcb9JxL9$P&ddZIIY4B9lg}?2$ru)|@jQl)v^S9oYO-`jy-6wHphfU?WBAgP>xMpAOd?g)SoTQ`IpOiWHYrNS> z51jDfh=lK=XDL+m(@~*kF5W-pmN5FLGrH<2c_P50MMo|SLhnQg+px%d^0)nLV=3XQ9R^e|YT2j;p>n%G0c`H6{ z<}vd5sdrGrXkG7>!JFOXgU&&-FAR5=Dq5;O*0Kt_5_?Q*ZDc|3sM3vJU*{%$s-FXu z%}@3mN#aPrS1n!ljXN(k&+_fO z*zwfap5jquW_I&;y4RqAcFw2wVENb*DNRIuY9IP;hnfZS#zotAxa>wM#k$_Z37*ga z_FjIz#-EOoqxfAC;Q}_xfv-3CxDKaruf5GoWR>&6s~zeK{VBemxA^aP*!tydW=q)J zA7fJUXNFH}%Y~TfPMooMK3UkdxR5zl?=@Nx-LlzxSAkEddC@Poht=kk{Go-}&U9mQ z|AN426R+WeVx>h3L(xM)-Cu&r%ohPq+EO;(dbmy4@?Ex+=;FK%QhNWgtg4pFoZIaE z)pyNgx!RDQv+*7g@Zv>nv5T)_Lj43{^(*y)e}|gLCrQrB50Y1i2&^}A_X!ZyP^~h5 zq+8JM)fuS#`AS^mLto4GT4VFFZkz5`1s+wKhCGKnEepT$r8HQD{~k@s?%Ft#KbfZ- zSzM=AkgHzr16Ymz#>wGNzGaIEt}_yrp=L9$2YO!mE-rkPNNLCMoK$T$vp^P!{sMtQiECjBnuXopm z4W1rK=@z}xH#gFwB^s!cpMB+8+V{nh#95^^ zh~f&Z_^;(>tMyRcjoh?QjmbteGv7byb7Rm2uj@^+T$9n}Fe=IiwrwC;x( z&0K;2cCz97^~G3m-|pzaufFP@&3tg{!<#I{Oljm-9Nb*gX!F_8>n#pRfdH%REZ6LQ zll9lH#PXwWh7FmcHBzj}GPtE9BDKPAOfBx!08$YKsrFf4uYZ2G@Tb=Failot9X1HQ z9D07Q=xq1mVeYtX@pdWGU1zDyyET)iR&@N!9~3;~?Xb8b*6#VGsC$W0*c!cZBtCY?lwb)Nkr_^FDYrqA1?qXopwuf#a3_>8~Swv2T7@y&cqTHB@YHN{b< zD>W9TIy;~;2m}XHwFGBo`zJ6l2_v&5W+^X)Iwg);%obFAabT#|)8<_r#G!k)R;wxklKpIM>YVM{4Z?QsPYt}UPUIg-j8&sR zJ8JEdKcqj?(kZF=9*~&neh5RFi=8IZ;oG7#(+clO%|`y-rrYg+bP30NWo&EWPfgE~ zqpHq#LX5YhY+e>6{3dG5v$9wIX2Pq2`^y`L{qoa9VcM@IvWCTZ4S-Wd7v%o0>75JGWM=i|01YexGW1aP*hkyXuZ!;nmB$Z*4(u zJjjV$PoJWdzYu})1nW+k|1nXsV!gTg@|@RAfq`+kBfUi_)xHm336F+9qCJ=ZQOI?Qkz1y89h64 za86w{#pFmrzgKw*`-q7arfmfi6kg&*Oz_x{a30SYN>bBfU;*gGv=NxYC?){yK!5WS z`A~aR-hm6~tjadAv2h3laFhucdYuY3oh z?g}Aa<|nRUb5?vKABg&W*Av1`ya6gaYOZh)HQeG3zc_I@b`MK18*>82zO8f}(3UZx z_A$3O2)+B=!mceeUpqm~p?^Vs4#(+Wh?59y%bq;aI<#9SU*P1Vexf>UquW6nYfG~y z`6m~wk2>w4vq(|K86lyNe1QknrIfG?)(M#$rFyWg3v?ohN-G%a81f+3(`Oj+YM>+X zkJ2jUIu_T$t0+w_hxN!*r6v8Es;osu5b!DTTWJH^^LS3@6O`&vcwTUi*pl_frzsb) zSdZDEE9ghy`RWt~k`;o3Z=gCHNsn}PR(u}&9~INls(FOGjAI{5A+M{eahbDw2I z@&(}K6W)0r7MI7Hyqvi+`6r?SWu;}Xk|S&3N{?I^auCoFxuC>_Jn5`Web3OnE+Xqg zCDhzK?@1fzx?hdRTBx)RrCfO&>xAmQ9M&Vpm4xX#Qg-94Xu1!AeMO}AY^JZ1sE!<_ zi09Bc6%7fVWAGjUw`^zjTmGr5JTmsVKnDy2Uv>_ ziME|B-_xz_^utcuQ$rB^WTpK+9R4AREpWENG0#)Z+yawb>tQ;KPKkT~w0)pn4(v|u z;;x6FMEimw1Im`U*-snT;^!E6hWf4y=vtn)tqOhSEY4M%<}Q9P{^dWOsfxZ_DS)L- z9FaeQ-+wfrVCZ`h<~aub13H%xr>f*&Wu2O{)K(`|exsfBhH$v1$*-+gzl(!D;1vz}(^$GTvk^V$LolqNo(B!ak50C2t`_kYZ2 zNZ!s99|`ykxU8gIz&n-$8t4;h@x`*hOQ!O6w4Oqxxr*zPO9w&wCS-z`7J?78d@`Of zIL8LHmzoTpqOsa@j}W@d@#s}FZ_${htqWy9F?H=DR?3lfDZt$MgaqsvyKY>zFo?MZ zABa*Q|H&4jb|ik_Hfvw#Lmjk$t2Srr&R#!hFPTOS?Qq(5H_Su!+R?dt1?q;qU>NCx7j>EGqs zf(eXPincWfi*uaze0c!4lBY0TfP4aqhY*`bTYv)53>(7m@&xd5;~4!uT8pxaJDy$_ z#Up|Cxz)B18pDsZVGqQnlyg8leJ17{Jt*zKp~s^bj2~kA>>mbupLyVvVR43#e_z+{ zVOSB*DX`7L4u{{q9rjGSUd%em!~l{Zmw_=T+WNN$8j`)UuEa*4al@(KJYK;CO>p2?=tbs$Aa$WR4eH$3xOW z1W|qbdoa(mU?X$0S)36fkO&B*>ik_4u@&uwoNX5NIQ&&Ga^1@Z8T%RjN1&+TtR#uZ zg`Q?!8bAH?A=FgmEb`BRHPf%gl`)9WEboMK2|tcWG&%Z*K50$gA$D7(6(*`{)9NLv z1!o3<{-k|9!$uqUegk|r{20%7lx2UMQVhBZ$&H&Z<38ihkO>ZiAEFco%3rRrBh>9I zOC%_OQVQ<6g1~53f*vX&4~QdZm&OHKI_ptO=`WCT!1G;tUwR|r5R~zy^o&q*49t0I zw;dY9oQI-Zgk<+q&^OP23&pX4J6Cs;HwcVx2X z;Gf5eXRrgg5hPEyvzrxm;y~4fZrcaZye>O;;ztlEg^fy$DJbOTExTZXE$#>-ID?@I zKp~5wP0$P8;!wt+(QRqdjYe<{jgi|EoUhXcLpYQ|gI znInJWc#qZ*Wf14WYaMdw3X$TRI)-Q_5IkMD?uIZ!oI{Gp!QlsomU3;0VkostFxeA_ z-w2^xIc+3EhoRC+1MI9+r75tNotNpva?vvWCqZoEYxY0qRtFiO^BiqhE zH<0C3h;%<3n#bRkOAE(9FgyWyK4P~!BksUyFviYqAz1taW!wIbM_DSy_eonE#m_*r^HzM1kYl(V zR7C@qZ$Nmsv1SPM2>Le^QJnst1DGhkLdK)$JMk|FsW?XwH5&w0W(E!M%7-B0T78Y> z4NE*a3_Mynd$z1Gw}maJS2}_Z{EWtAw9&f{`W6Jt#6D0Qz4bup6Qsm3)|`OElL(O< z158W-Iz)_m`3IVnYM{?!@PfGFeE~DnCvv#z>8(X2)DMHNf6|Q&$xRJyMf5=<`XqP8 zL5{yg(m-CN*5Gi%HBv!L0S!zD2;%oN7IxJUT){9mp3}gP0O@)Rg5acdrwW^@YWayf z$ykUsSjPyKeL#v3e2psjgL-ms>JkkSSCRL>79g#V+{LmEqVhg*;tP}Tl%cKp#nCSz1yN*0*NkdYUPdhC(*JU-2)V%FIA7Z2feC+ zIdwIQ{KyGnEN4`99zfdNdvlF20X!Mmt;J=)Kb zzj!-E9)~{%$IT>U7h7s1|K*CmM-Ya?DSYM*o68X>CvjJ==mB6_QG%3ROw01h+$A&_ za#HOc!C7M0aDogm31j+sbv(VDrt{t;2!$W}u3k&Ac9a4wt}|L`YN; z=$)jlyEfjBq{>hhY%c@_qv*-603j7CP@o=XFeC>KDJQ<+0K~3cJn>9-r|f|fvq$;` z@)#uom%#9-G&o46(2{x)T+CkR6vZ;n|A!MftG z=?OMXlA&(1b%*ugIa6jPpaq~jKB0;2xZz|j9kzG`+!*r4yJ8amN#4k&E|6LPY`U#c zF>Q+}TU7#&zlF^4)?~w3d2B7qsPMcTXiQH27T4p$Q2H{|3ztMtWo61Dw>k7i1pSW6 zi-=0eMj)QU+-JfOtspO1*Z}L5&FG$=InSU?4`k3w;MiZC<S+b z>6-VDwveO|QalOkPf7ChVjXZp>PBP(MK4&Nx?{!k_?|=(@@vCNK44!WK#FySE9J1G zYsD*(7E}LW7i*nB8O2#`t=tdmx1Po_90O4W0V=rKv#_2TWyOXLfNPa=I(rFXvpQc& zvF8GCIc@`4Oz0`14x7Rtsc->}wRRhgU(!H1CaXd@5IdaJj3dIIY^G}rU@ zgpgcVn&%Ztgmu&Xb^PL$-dI_dvEYYpm}cxDeORG{hETTiIsZPT7-@ttgkbA#9ZeUN+9*aI>^;5cNVO`blA##k}Rh)TrCZ5Nqoibua!{3 zwLo;r?YCuK4$zpi(V_Ie07#bKgzb>PH^t(_L5%70%`BviCbk^7<=V_sSJ?F{Ubznp z;`GF-?+vMeO~1%h95(Akgp>q$$tX*|;+067T>ZZ;34+BGj^%3Nl^n3n6CVpsfVL)d zudft=b+tq*#EHqzxw07GPbCtCY}2Gr3;p31qSso^`k;W=#B5XnaNkP5YdsIpOzz5S z9_Udb5oB>+XhKawlpho}p@kmWA+x|*BRG2AFD4a-& z1Tg(E^cIibyRckZ=@d1JO8{=z*WYuv!X{*xq($auNdBK^Oc-}2w9}!(C*u{3bNnes zq;E(+Gh+uR^_XUVk#j8QnDzkFCysZBH3g7f*bWBczNG3Gu9b0o5qT+$`w}R1SuGy8 z3N*{;eaSrw799!(G>CE5hVXd}o688nyfQ}sBK*>l0JgMAAbB&&dv53gcwS^GBKlL( zqXyBvdJE`p%lKO~hlOpmNTRs_#R5pTFF!+XFKq6R&LiQ0FaqL?Xux$39#4!5vOAyJmcLw zTbv_MK$I+^kH7L!sxHeZcx6D}@;S?XNHf|LR9+$up2q9I|8V1i$*k$}8zBDgo?F{N z{A~FsSq2t>b}gvcA0tN5krkkwf^q(23AzGLf-0WnJQt+>lPHb#M5BZx7)$#H?{B(T z>kvaRWVG7N>1`?%vHFywdOz?c67bYv zj}uT`g=XzL!InG$i7s`;SiTc6|jp=QK|| z0ns}&N5k)R5a|TF$LE6aCsw}-hAV>_8YY4Yiv!Q%2zz-5GPxW@m9a1RDtLTt&!g=? zfoOzvVZ^ZQU}fW2LvC7zMOqeG4CIZ6F4{E4{uN!%42+p_5r}`@7;8#bEFEJe_krkOTY=FRRj-!ut(ep>k6@W1t*@3Pa z=k+z%b>bmOoOS_)XI3t!IS$G-oU3)@pB(K*Mv8YBEA+!mfG+0<|B{9PxrGADSHFH| zm>|l~6S9c`r1?aHTmci>--bX>1L9Ul(Q#EoUxC@J(6%OUfX{O8)%gYxx~zo@gAn;D zShIE@9BE7;i7^vc2-IS-O{%vMozQv}ltBZy?oPZhS%V7ivCTq`Mu>PF_Uu??K)(mB z6vn=$>17=D?3=7)_Q@@Ridw+>)y;t0{V#AYGkjNSV0S|jT@O^`Bh%I}n4{0VYy`9I zxf=VH8HyptpT(xuY6CCET7Q=4Fmi2ti#pEn&$+8eD-O)l6|5*np1=*KSvdwQ)wls@ z-2TP!C9N;=3nSorBC#&SgkCD6ghJqa%eH8Kp46qDV54qu9vJahQL9H;kNd&el8Bt1 ztf{Znj=6(N7jxOtyC2(j-Xng=z=dt=3G%gkIf@o z7h2csfk1BigV;nb{!}F_XrVqH3FHM)jr1Wgf<+cC5h+{bK%#RQwXsZ{+>viT0bmLE zcwKtx1gk}~kijKxCRzgPU&_+=uq;8Fc4-A(@@8$_*o5b`F{Dg%;@cZD>yf`{!c;gx zoNMJKQZ*-=F_E_rrMQ!%R9PD$78(Z+@2@V=9V+xOjrUcqpG4!xhw&d z(Ey^DO>+Bxpfb&NR6O~Mx4;rm8QB2PQ>iu7GNoi&Hj_b}MuMyv_sxuzG>~V#!5L;I zGFl=xFf$!6Lp}$EaJqd`S-QN%R<xEf~N!w19c?&4>H*|&!xhMk~TuALLzpmHnN=6FknF^%g=V=Bsir6Md} zSe1Dm6^1e1ViN^K-bXbpxzgRZ4fuHXXD?c4g2=S#7QoK2S+6299T@5$TXEO#Auk6J zSJ~&kWrliQn=sP+Uby?VcU}C$hIx~_IF)>0xTRBz1H0WJxmIgkxLSK148#R3W!XN zZQ2EZJ>!oG(X0(n_(V%0j2+SX588hCjj})@V|*_toh-#&Plx0y0eZ>$){^OlY-aO> zgd)r2JBTV4@yXsa9vE{iSR5|7f?<3nvb+n6RXI4!O!o|Axf9Oe_0`O447iG-A4 zs!U}(#`M!d!;*i$F5t{*Vgp?Edh!G0m!g>AD_7cRUHU z)%)_3KG6wzZWawT`jC9}{TK}@FMXheL{$RCUZJQkV;=D`HXU5nq7lHgwDX%7DFec( z4%QIhp(Jn;g+P9mGjST|5b~f~<|VDDy1u0DuH_^4iUS#Of-dV2AVFaretFULl_jA8(+7;Ie)lAs+2{Qx~hAThah$fXM=@`_3lgm13y(X>Ypc{GtBHDr- zL|Ekg+Oq@Q zmS6|(+US78lzn4%K~Rzl5aAj@4uq1^-@vMBVFDdra}b6ju~{N==zt{s5FmfRD-9x5 zCYKfatp~KS`ziAq@$D03rfh0~m(~fy8)6bE1rOOA6bgo;z+o`OGdVz#890y+7SOS~ zV>ZPlY^4~nn{Op_WNQXGcR*bo&o*mK-VgfPe}5$qWd61p(Gf&=OtarZB+APqk&2mH zoP!G47|$rRA%=6B^D?r6#8Tf7WW6-{f{3r z>ByK7IFMZx@xVN%jXV>_k~vLngfE1`z8Wd&Awk}?&hLv3faGR(UrG$N*0JYjD^Me@C{O22(Vj>?XI9$kT>bhY=m?cD* zqsNoSaD60yT;dM#jy7u%d3+Vvdex4DC0i+It4)7CO&V<7eOZZogEO8z5N3elGrpOE zuYJe;U?MBffq@ESt|o#C2-k5&%$7swf1rXxDxtI*g^9Wq?K-dIw$XdKp_u!ub>5yd(lnGS7IU>WrWRY?o9b7KL^C}sNK3p=okawrr;vIYCF1Iu)Yl(29f zmIBK(RZx(D<)y$f`UWzv{QrPubPt=ulv&HAz%q@i6XL`^j44h`lIi@&2Z9ddq`!Ml zUtXek#x+XcOVUz%GpTHpViGmFvdLBoj6^31TJBkNp3^y<&q5PWQV(l@%#8*$KoHq(M2>vGq8=tr79 z^g<%60ii_2O_(PX?q89+S8x|>mU21m=Nojr+hLD#@%B7}__+Hj4=W;x!-eo9-UA~0 zOL&A_4&T**hEnD6IHE?h~-j0FcWk6l&F@&aeWB}){ z(mD1CVc_pD@aLZuYk!owWGKuTxwcPCYT!zfvOvZ`8At6rlqF zxS5Oz0xi$;sL+cQATI&rk5)dRx%(9^=3lSqi3IW0Y-MTUvu0oL9@|0u$4wW}{it}X z7)}2$aR}u7nLRY`2bzCz!-zBwA(PQ!P#SQqdT^{R0xuVv-!gG5+Vl&7f^-6k9z4tn zfESto5q=qnG4iL|kSxK_meO&m84sA5$DZ_?5!#TiD_4>`+_quXw=G-8e3qu!HOO!5 zHR_|6W>OMCUL4cC^0xX}#(e^64G$=4CwZGrdvm;JMrY($IDm|;9wvcQ6>J3?H)DYL z4erb4VCRHDK}L-@U2FkYzl8Nw>APi^gJ9ynDmUNyWsvi_7{;8R}5IZu5U@+tZ zu;GDeIjHgBi!cd`INGih8z9}YnlUqJ^AAabR?%>QR@W-+=c2Pl_TZ^U2EvK~feC%F zhBbN_|C$7IWyFm@Md>Y(-kl-QZicI#kGu;(P5bVgV`&>1d&_$a89@H*!N*L;L0<6S zeC)6u#%4YKw{%dq=O2RJGRO6O%94mcEK}GVhhpLWD~Vet51>Jj{uKul3mhHFwpSQ4 zRkU~DrXA;)vS8%k2Y*nNVR|SMvj8b@=Mf-K7I3btb46JRPjASfEC-}xhlK-kTS>qh zw5vET!&a0ioGSy2kO>AVQD*V9mZML~LJNI?@CuZ8I{hHp{K6ctE6QmxRP~{6CHm-f zOP{#K8sr7y0Z`(Nx=iT4*&zRo@q>F@_>$S-oD`s`ur}d1$p3Kfk+Z-V`y|Y7kiVo!m^MWX1t@zQIGgZm5M=^ZL8AA=6bJ z(|%-Wa0JSTldlDs!G-{Td35_0B9d)Rw49b@@C--n@>-(yXCUsk=VUg!a$yW+JOPN; z?Pj3==(jK!p}LWDH#vW4od|#R7>Jbb3}^eX9PC2BF{$0IlfHyF{l+A5JFbsy;(#&+ z?o7Ke`MsXVxHf8mP<|R(54`LjaZEGKZB}VFGe8xi4u8eOMV0k0GZV z=So>jreeWn<_ang48ROmHX$e{<4O2HAI1-PZLstME4?V^>;9L5(=fX0AH zh&7B)d`V*cpowi5pRf~W#Awr(4CAv#Ux>(*0dp81a%%{B+G^pPu!iwze|i}SjC;V9 zZ7UC%FbeYg1AxfAwlr32!OmCqi zMD{hZX5V+ebMKuQ)cvEEe)xTt#2Dj>Z+3b-M%P#!GMt_)0T1Gs!mrOGu zd|pXYtas4p`B*M9+1u|uKW*|^e!MMDz}#O;v2L3}jdSuPLf@I&vkGIim-{_vF9W7L z<8r1#TAR4%c6;|FcoGsn4jqk4%3nA5D6%I~XQ)T69t3_osT&>iQ-XT+!{J z`z#cro*>wBEtkfb4~JZCac`RTDpIt&NgnLVxjeBjzRJVEIjj@28B_D@hMZeCNZC+NF5Av2f$A8~G{zlNf zd9PMv)mz>R&m8lsJ;xi8YpaTHKXd3hKUkV8;u|UAY+;itUt z*J|TEPL92)Rr$#!YIXf{DT4uZ{a*u*6pYqS<*2j|7DV3B(^i(Z%b9%a>??%oQ8I$; z8?{b0i35_3DtzDJjjHKq>=1O!?Js=5?LB^Rsg)>iBKv$aG=Y4D5Ec>VBDN7w7BOA(*B z=gmO;ClBV{KXR#eF4uaHraIL5(y{hZbit9D$xD4H{SS*eD9eQJNW9M#d~G>5-87#c zo0zkXS3=wOWq^u`*LarLre2+bv78OwGaQ43(y|4%>5VF{BbwI+KzUk2Wd zP0Uo0`EjGq=wd{$y=J41`mFP-tFKII-8#%%^E9|;cjpJ@N(Ss18oyK?WO7BVquwcc z&p6^c4Sa4}r;U=DywvZ4V{RI2ycp@Fscg|xJ<;c?c$e?Ww5n)bMxC8{z)f$*!|NUY z9M`ie8R0DIy7)~ock;(fNQQgg2g9Ds?+Fj)ii)*IMX-aQ%R|XI@86g8{6R;_x!+dB z${o+Hbh+Ve;q9$I6q}yCm-VUPYYF)FU&Gw=yz|qY z<$~(-lbGD=pOVP0;)Ifx*wcUZFk>hC@l$`^tiSt4_PCp0Ls~%q@SKlU`Ao^m*m({07&i z4a-D496bJ-Tt1*v+NW$Epxk~(u6U0e{F@P><@le-e5hHl(w6q(>rS1|Z|A4gcyR9- zu&94%_PW}+{6|Zpd;gC>t&W}tvw60WJ+gOQ8%G1T)+H40b2KivX`kIQ6BTAxd~EK~ ze4EU@kc7BbH_K(k9%^3EENOtmP-;tA@?JeQT1eHb=i~Q)*~ddwmlM7vR*!j}JU(S~ zzwvRoQ(11Z`Bd+GdE}R$x$4%1y`KG^_Bp#}*DDXuZngyJ>D zGy6Wy3`q~Z(eYNv8Sp9TP!aIY+A*(E-4HoH5^WQ3u?0TZ=r#0cwxwBPWx%z@`5d>t z4;`C&-DmGrwS-y*286pdZgrU-w0m&+>6iuojCi2(VnWdY$dyWUzs+ewE0agUu_R zC#UV6t=QR?dvoPFq2zN;Ucb4_??6P9NccGPHlxqp+s|{nX1CW=`K;LNfxm4%AvlR= z0|i?&3HTt@QEie$SnAF9wso%j|v~t!H;>!{zww3v)B~HN1q)r)v!@D^41I zyZo`U#c!y$M zxc`&uJxeb6;pO7F@j78}q+CbDTdhlH zQu@V|@Lx2?@`&oLr~Ld8bfsW}YrbvvX|=lXdK0^B`M!^>0XbpQo@!n*6CTs4-q{M; zYLma*`s~d}G2C<0pQ_Ikgtrbyns4=*u4`)7conj>gZq8fhbB2g7ZJV2$eE-3-NP%g z`wj*QZxFKoc>I-gxyby4MC+m5HX^RG!y?%Mo%6rJtsnKDnk$bi%#F&m+MZFmZ?GgQ zT6O%d`LyvImk$T8%)Psh#1CZtHy3a3DJSgFO!08%j0-zw=vBTfKy`4U)p1$SZ}??Xl{M?{Ta`cuI>w* z@2i>|KtUY27yHKl)FozickeBUl+W7t^z`X^-k?bLzWMj}$1S{yWZfu*X)bh0{uh=u;e)}?cxAX>=y1XYB!-aR-%)dOm@miROjnKT{CSu9M z_$@2uM;_Vdew=v|9qC(hA$+@SME4kFQjG^pRUkI0_3`Xk7q;tnX9U=iFBnFaQ=}6H zp44xy@!#N1>dBcNQ2#k8Z9aR{PVIM@X0o|Ty^#5jh-hVBFFipbhx*J<7su9nRRzNC zJ;H~Cuj-FYiD+u|XNdy0Jb;xd1n;H1lI<{d% z&Pep*-{P#6x6+CJIu{L3>EUw5GB1myn0Iq|y~_+g(KK(S+Bln_-12d_SSL&< z^T$)R(UCJ-yB=xig_|4qdbbXJ7tgQ#8yB9tOL@gm3zuw^tX{)~@T--FBQrfrTwZ(n ztTFtWNLugxAID^Huj7*+H@Wu+O2?;^>359mF|xcTTkuL+BJayxUNKeeI`xT9-1+;s zWchf-P5HUJdMtMi7bSneTXOqn^<_%;3Rl|JoPT`R*{3b!_WE^PMY*JH^KaU2%LhDC zk+S%QkSb7pvENfH+xGPX!?>cI1+#p&}j zxuol3A*m6wU9+FNyp2ZR=p^2s^|5ORy%Tv*{PF81zc%k%uhV^Am7F`QmaR^d_7prbH8P!& z-Jx(jvuo&`P>s6CZ0DiYQA6|Z!jpmYbpMECNEi?aP?r%mz5Z?2r>?48wOt8WeTkFP z$Ms@Vdw=O&`94}@B5Rk``*_cvs1@o{Ke;ViUP-qEJ<{%{U*{hRjBLc5S8sG0SxPD{|%FQ*oGi@8o zl@MA^YF{m@^a{p_Q{*R97E=HSdJx_TYJF(jSl1mr0*0lfIt@5zf?VZnDEKaJJ;eWNX&^ zHCLPLipt44)v{Yh9wqo|N*ue>77{tbRR%5|>p1yEdef|Id)eII#N3VGZgCSWQs&2W zkg^U5;x~{W9v$YDxiMsCCdKmB`d5@C7uLy%^BXxoI4m31=k7M8-!c*sj?@jFuJW@U zX*C!8&ObI%@#Zisml#(*(|E1pE?-djnhv$BrJhgK_Dg$88eUf(qb^&SB$utD^x@Tj z`j_WR`m_x@6q`K!daTEO5B{+C5^I0bF}pY<`qsy%uXj~D7W7*7XOCM~yvyGRCCzuXGHEMq z_1<52C_X-2OR6_9be`UA1vT4UO_>i$+Ul(-5JET}s2SysJ6=xAHZb-0HZjpEqH_nd zeaG3Of0pf9+;V!G9|EJ$^3CQa4$Zl}a&4L{o*Mk6r=u-j9a;l;;LD6}XuW^+>+xi0emZ zdZ+$I|C*ez3`DBFQ1$tN+v@B4y{fW?3&cKTL4LJN_|WblNe z;pwC_lH8=rU136&=mL5x`{!7f|r46Pv>Q2`Ar=@3Cdk4q85alxmfa4<>X)M z*!Xo4kF`O&?#Jc6F_Hbl)>xpA4u& z-hg55)K#0znhTC?!G=)&Hi#Jss*aRuSdNSIuX1g?YFPX0ufpW-^C4!!k@GVhLlX8m zlNWk2%C!gYLA{qX<*^@No&bp4>H)mk6B;f6^w`w>kuEDypZ9p?)b}9?#5iBjvzl9N zz{1O`KkIVB2XTnc!S>5@__Ve{kmf0fblB%rp=9gca#y0I zOjA~qd$=yX=7W1r(#7>xzRE_MzHW4Fnr-RJ>MtITyxMN$SZQ>zBd~m(_n_ZRr`zhY zT_uxg(|uX8WoaW^KE1BhJMBIW=4V*cvkiV0nUmC3pY`8bFaufWuJbWtz09GxslSOG z)7P8iTa-TtXQb@U2skNy3QncC7MhWd_e3$C*>Ow-T1?GGb5fcR- z(;bFW9*~(|Pg=6zQS3aOL8K~z11I^r=g^}E|CLricKbLo^u0W8GIIW-#A&zRKHG@% z<~GweZ7rYs@cTBnG{{3cT%9pLH!WuNt-*6#q~L9AJbzQ2b?o82(zgA(OV#HlOHNt8 z*)sg&k+|8~uEL?!ezFm_lUFEI?@)m7bl4owrs2>XzaJj{H1PhrOknQZXziZ$vMB>M z0JJ#N)-j%=xmQj#ce35&?EI|vy32{F265?)>6Rhku0KB8aYBvnYTlFG5wUalm|Q{0 zO2M#|fnjzz$^j}@hTIezob}eb*E=Tlrq%t~ID5O!Rzx;;=BUTy_j#e)!c(zs?N6dx zCwO)IgAM1-s63*GL+ui$7bI)|W z(_pJef5ft~-FCTR>g^@oYRc`A-gDLVxt=W#4vE}#Dppp!K3{(4;`N$|Kc}~?2gkY4 z^T=n8udrOn(0n$;?RTEQS*YK9qa(efyqv1f80j``u57ZHRT@pv8#XR?><@SEa|7({ z{nOKj8Hocs9;K_0lQCQ(B4B=Ml51zHzg&K_`skaCKP{R|O$VUHv+RQM?WBQ`GIwIZ z`r+wQ78JI#Ew7I395;13zjva>t@F8e!6Egj2N?tZ`9-bmNIE3q_aV77*aedDXj1)^ z?|J`fihpr$+U>gDdqxCta_y|M(AxTO%M5&Sn|OZP@}7LodJ~%*BY=LgYsR`-Dbr0k zZ^K0D|MtZt7Z=`Wj7l1qH&Id>ysPsia&D$zW=mOgq|3o=Y1L;P0o1xMS}fA~UOsm) zUue(=dUN&2k!X*90b-QkH~TZ62j&z)5Y&%PX5GKXr+@m7&$i*eRizoV&K&(qunw&6D=O@C1zp~+3rczXZ`YT3EA;n48P{0_NDXZ zOUv2O%I}wRTYQfU)~qq~XHy?b3UzI~cDCZA!H>YM5AOZ$O+ta{Y9A6JY~$yw_h9)5 z3;!ehokwxnA_uSLtug%Ks#vSUZ*(q6sVzvab!ND@->gbMFcQ*}+jv%!YDL<}W?Szb z2p3a-?Z4V@9oU}Mx#?1semb8``N@Wqo!Kdq8~Q`HM|ibAIo$tStFHa&<>HYox!H5= zLOY@6Yx(m?yV$(gMgH$M${;+k`9xLMVe|5y3&Az{y53N>!!N@84<>5VQXb`MXNg^%UM z%y<U==pY|A?d%WCFNBI_muCYta(MctmXje2sSFn6r}b>^Xah1P#-nlHz)`5$5=O30xilmue4W2=n+ zXq7oyhRy&+s#xGTJ}-TO_E*n*&N-<^s$1SWTfD5IJmvSRZO@{{3uK;}QI74ki;Yk7 zcQ wnfZrt#-jk4&aN+-b;U&FI0Rj{i*9_T3J$vnb)_~$AUx5zm2O8R<{n$ zjpxeBWH@;Es}7JMqKUQxgzM2na^31|?l~&2=MuQV-NH*=u_F7d%bHykV-5Xwe1I^S zd*$u+85}uM*7ffJ0i(^iSHc61q)&E4%H|q-HhGTcWen%%ITi@>0zBe6_we_0-v`C^ zr_-vAwCc#b>ra|q2C6^4Wi{ba{v-WMveAH!#Iekw!c)#a50ro2n*M2zNMV9+^b%B+ zjxDcF*$n2DCA_ioyO4V)W9n_}``C{<=L|a|{l3d&*}R?QuJ`tKVVld=vmO4I^q1(r zpQ%m@V@e;fdAJ}Ca;w7Yr$~Wo9h>k^^U3DCWO8b~M2KH%m64)CaRT|Y^Uu7U{U3+( zAtVb!<+a~q_HvWImO{feFZYSx;9ZwOQogL#{8C|P9$u2(!YTdbK#1w;uC_u=*;IX- zoV>?_Jx(PTTDPr*nqAhi!9G#wWwPdzrUti{hx=und8Jp-_c#Pk;0 zhNM0~O76XvZV540+ZpK{V&-x$+@*0R|F;`|AUv!&v|)7ml+^T8;dj}N%MBCI!G*4E z%=zyrx5Af(>5gmWGY-=m#MTCUuS}_U9Hw(MpA^xhk!WCazGBAPEGS^|kA&Kj@w2m| zivAM9-kU1TT*KewAJKU@S(Q6DT7CBWcFP~BRRP?dof?;%yfj6oMxO_IC{3MSQ|y{4 zMzZ)rIEZz!P~UYLS~%yL)P3BUeMrG@QZ~1Otxg9t@Q%=0rukYX$G%4Hp32+b*u}=Zzw&ClHY%hc zs(Cs>xx*K^%$VD#eQ16zrrP223d;~P*8u&`Z;g0#B%;R?g`JzLt1p+Rwxs8(ZYy*5 zDIYVR?GDki&TR4hULFO-SH99jv-uaD@h0^E<*Cl&%^Q3U{g9=ymPu z#-vZTg#Of(Py3aqO&85Ls*KZmjt+miJ+sNB=6r`zCpGEXjy-V2F*IHlbu+DL?$G3# zK0ZP2smZ;Q#(}&0LT9q`BPWZZos#t}RTLVa=R7j?unB6$l8K&+hD9RL+>uk~4LSmh zJTIvto!jJCd&iATDCuPT{ywJwz2hraCFFZ@8%^UcjUCy zx96L??VZbMTPqix((f-<{MGAslJ)h@gHVD$<`TS5ucvq1M_OJ|-tyF01e`0Hrd)g3 zuio!guzToTHSd?HKAlL9XQxlkeBphRNQ-(G)+jp@4#7S#Dk}D)$pyo6<yhV{moFOLD9_=R zc}MaP%TjqJoQ1$5Bu%#EAPti3mv7R2i`(;60N-eSZ)7rPWuFz_JpBFe1e!2@mh`!4tADjDa9v)HW zfj&qzAnKKd2eG38(BskVplgZ`UL{&eCFANvgFa&o zFj~h*z92tk-v#%$igs;>Su-b}CP`k4gPo@3s2oaXf^qZRRBE%~<)M4lV|)e3ii(=|i!pn=#9H*=kzPE7n zF|dHRfY|qN$<59q=>yNcwtBP)ef9=C8|sxA>ZM{xk*=<*-Tv(c&~=w0x8<`v+cn~? zH=~Q5mFtQg?7)$<-twx$mmSyD0-s~(dz|m7e2!;HS{T_bt`S!Pt@pS5-(Vl z_tZ*E%S7MJRDRT8rO&{s&q7{em+*K(9*-`1oxW>3SXHZ?$gnEKjw>`0n0{WxL%#j8 zeLaH?cbL383+Y*PZ{%ejfToN@K8xIeetMuP9A~ zg}sOUyt0kb!XEQ4rXUo{5r_E%@lWfu$)r`pUnpnnpj^Y_Gp@;9M7%DGZ~>dtfKLP; z0~)YRK>|JJ>BJ)RFd_5=pqnapps*;Vm@5>U``{6z0G+TWdNt zFVfEeQANxGwTWO7(9iB$!ffqROlT9p-^vF}#d*MzN$0VOBtPKbv3daXO@}`(&_zpE z;w@KOg#%qhQ;ZJNVlt%?4qko(^b^`QS?Od_EYVms09S8)ymc{sBim7aGS{UT$wexb z1er{4h-+>AxnvPtFb(!@qaxvAOv9oL-C%-ufxcR`V|C{<@)MQ_BKREW`$*BeqNN;o zrv(WFKLGuTh8R$&*#!EO459Z7SW%fd#hleOC4!NIZ!IO1nIiB0{->; zj%m^=2^;b?Tq1%ec?ypaqMCiAHzVf6clg=ZU zLzV;GKLWb=sm}{^(H-mY9f)Oc^=c+!4BH|iQ7;$0;r%61a4e zYQ)T#mmq)WvQus>Yu+cg^~Qp;BC$wR6esZEuJr;+1wKo84B>YB%U!yQi*g*r0HS>S zuU-6=Ab$cJm%b*;rPSe(6ACQFLP${&mV+5juv?TNcuqMI5-SeI{-7&0= z(T*b@l)AuS>-{3mlwR;C%?p-t-bcdq{rg^dK`+aOgWAI%Dq!&~)PROXa}xC?EW0d8 z>j1X%5&i)z|2i9h8spMy7;1dPR*%kZ6@Zlj9)&R~E-d(EZiF>88lKVD!V|9YzjJ|W z(ak{SXiQ;}iQQW*Du|g9KrU%4S|GFk(xP3@k^*vY(;pTxPfQ>w-B1eWnzf{z8BI~A z3t)Fv#<`xmC%#a0pj+99a~%$&)wA*tE!_zkwEQLBE!|AN1IKo)vK1CQQD;hQ?0=aK0EF4=c#*cgz@c>d4kQ35L zc)B>0Zmv^Ez2J+S=jUA3OxKTjPflGWs>=g%ir&*@Ih)p;r`AC7VyESG`5a}K61c@Y zC6DqQCE;ZBck^km_$;NHozHd)!QrRM3*ki`ZskY1y~M$!(n5C;S#-xM2)mn)b8+uL zVREK>I%|RZtn-`|Pdm%{1!p}VN9BC@yV)Y1#YWCXK)$l%m=0B#V6c#~sng(_P`VHZ z#J4gQp3E=!AZh&P(gUy>HIraKFPnKJeIdmJX~*0|wD?xiLJRu>i2|k|$ZKpx|4TI{ z7+@0nZUHUEioB(J(A!NG8;~b-l|YLQ+mBDM<9Z?xhl|Qi2XqJA*lC2m0?% zF_|NOP`40lSJJk>`6kfv zHMXBz{f>UZUb;n|84mf3NVag5vLBcqc^SmwDWQb7n{JkfzJ2oMqeZ5Gs0}+b zSS{p-olv^EIHf^_Y4UB-$MWbqN0*j-4~F>D7ZZPsU`;QdQF}In@jnT@+CymL(M2a$ z(c=Qb!udqI70FEUf(smlpc?W*%NIHhk|Ucw)64yU`D5)jXqVwDNfpO@fVv-;-+lU8 zA5-i$dpc}{V<+d7+**gyoym%uzf;ms3EW)U|H4;2E5q)yidz$_i6FB>}V|c8TJ|a5VHA)LpQVOU5TZ?mM}kHEjI@Ql9UA zZ0yJAQZTWOf{Ta5ohjU+yBvQK)dyxDsI^RUBHo1J7)=VsJP4R?t&6gP-8t*f82O3v z9;7_q`z#aL2~~*Dj zh~f<2hVx^Qjjq8;4t?9r_zdd}sf%NIry=zluD)MTVUg$KDF1=Rf24d}bWTKdjp0zG z#Q*j9eSt1F%ANEC&ci>hI=#&11QTEjbZ?rCq&D78f3gv_4@Bib=5S)YxuJ%pxNr$F z9e}zTl(MDPY8B`oYfUgNC!z0(Ej#iT`5(oFT@dwk$XfS7DbFt$bD~y+iu@@U2X#oLG*%E3Vhr!;WaY(ME`xvFHo<8mPfB!A7C~on3jW~1DCEb@PlMQ8(@3Jh^8@bLCY1z z#%RyE{18nUTY=;Q7Uxu~DnqqQav}s^v7-X6v|}60W9jm&TjKy70+C(+H0;I0Z#$z< z+>t^SLnK-T60I~#JIIItluyJSF%m>Ef`isN+b0XsObNUTCV7Hor$%azvIK!n>E>(k zKsHXTJIg{A9Y`ZJ2y@V`{>@kv(Wy}cPhh=<@x}orI#Ehi6gO^{RIsofvN1PXCBHAZVYn)tM1LZa41(>Nt z-;9Qw_WQtC`R48g?VMcm4r;aQU_4xcinm9_9$F!w)rypqNMjARnuZnJ=zjIM%5 zu;*_p`w#^K@M=KsDtSVoDb;yCfJv|qka3y@?o#1Rup z6NUO&RLO-^Q&cF`Hkgud!u18G7t96>C2!O2pX6Kwd!tgk71iy+^q$T1JVGIT+h*MA61G_xtHz~h~3P=K#6xc zkjzQrWGI)&K7uIb=2>3gjkO8$vg| z8ePf@iYZTG3uPvroiIJVtpEjy@(3D%diL1)F^e=^x4f9Ni! zM8yL7D(*yBq{yJ-RAYRG{{{#Otafq+z4x&@T@>HRwTQkPiZpH;OPvoyeFpM}h(Os3 zTikoCXEUP%q1$9f5GX9@{w$H5&z!{K?dzaQ6Czz<2j0HhsPq^OZ1Ukm=RaGIx_KU- zF2m;3Ga%_1sb#37louQ^^k{E&5F|bLonB#)9Eti^mVOGEIE?6|MBh46D6k$VtK?wKgRKSf}4*r@lA^x%0t)0N^dS9g`}PRP=wad3v?LR&d_I&;V@9=}`z zd~fMfJ>(HByc5GU*_Z18Jt8a_VM^UixWMSMmP7OwRtV=sH%W?SE21mI9~?yZZUnuZ zpcHe2Vu+u=F3>kp%Nat2^Xx!ICE5LvVDRBNQZQm_sFk*G20C`YU+!hn*;6!W89+Y@ zrVW1OxIpKz$|kSkNCEzK8>61H&_zp+;w@7FU$cvk#wu8bnyK=H?IH(ocFJ$?jQyZy zq23|10YJc~j~x63PC@&isy+}VF3?49Ht1Wu1;q__Q}GjKPeP|31UxSAPRTc-$`%XO zmf)Zi(Dh3vK?-x)@`VMAMMZJxxSu>g!MYMUA7h9@>G?k;kta)q@h=$%+8ElcbpJ9E z%7I!z$fu_+V@S&BC(7xi>cZH9F&xfx!Cwya6)6Vnf+%)l4Es-S{B?&;hZ{d$2L+J4 z7P3gXPX(1pC*G27h5?+~EkM_-1S>F%#lB0nc{#xrj#b^A%Sb=B@PSx!^%zw5y`%Cf ziH<#AnRN1Ug<5GHv?kENDHaod@zZp(;k6j7P zO0|gMo2nOF;3ii!EKUgk1aZR|1uQuuPC%g$Z2*u)&T{mtA1qxbErpQA(@BM8_uA+! zO0enVf^fi!cK@|yhEG%pq1#Fa-SHW%f5ch)OX$qm`J@3p2|!Zuc8osQ3`SYqrG) zqW(v^4dHy{%`p1wc%t`-^$49h4BR_@{9tr=FV7p9qXU}kgLzM{?$9gh&I{ziP zE^&}Buv)o@48zmxw6*)v=j3FWnnM0?hP2)HEcHng{{Txr3>Phkl?!eEA*d2ywjK+f zAZIDkB$v0ei;+Zc;F*jlGN5uZk|Mxiqx|o#GZv=w0lC3ZQ5qgZ+Y?lQI7$L+#q6ab zTJry5yeN45KZmoVT~LRfyRM( zUrm+mg};p4hXy7?aI%DNbK;q8?7h!;g<~$}q=_=drTK*&Xo7 zU+`1ZE4D+uXF^sg~LFX+aFRX)4VfSeCuW@bxExz60`zC-Kj66qo1KnM) zbE^%|1aPk`dc4MpY&Wm62(6r6hXD z3MHuY3l-{DQ0{}Qmsm8_701(^{suTboBjPE`iZ&19)rewUkL32xrd|}&c$a2;0kQO z2blXRY{l{d5{)tlQh>*71Lj65ueBNe53bLr%S%}e=WmPk;}eX;fpBH)@5s&5_gmONCT*GviLU8erWc#;JsK5;=a}Y)nfML_x%B{~(KajPcql45UVE!a4+6DE( z(j#~$EYm&&7vEw}9iqqeV)jtf5{2O6A-T7a+d5QGt90@pJfe%VovUqA7Gn7rhu{+& zjp$AVVz27X&DW%imtzTLy2#u^ z&jx*UiZ**FS}tQJf(@LObJx$+P^%}Be=;@xb|Zk`F9VUl3zHXu=zTQe))=y!!h@(5 zZB)>>#YC2|{~2%!VkIUMS*C0D9{D)3DhslVec=J85(4hCAj{Ynu-TVI$TIeY-|R}a z7bDB`6<`kSe~@KN%$I~uGy4;4Cv0$#ITb1(io;x1@`A#_ z#SN1a5$KEZn^TFg0v%gU-GM56Vd{l}En__82mx3OWn#;i9P4-;(EIfTY#BSsfL8!k z!NZCzql{=Y!WbBt6M* zHDDwuj5{*$;x1pKv5+;8%_V^T8X6U{{#iWlQU+FdU3b}$*_B`(@fHsE$^LhvtE~3Z zb}mF9Yp`S$_1ojaWLh&}j9FLy(ty)?C^8<&UrRWu8DTQ!9;_K=V1f=G9h0`8gF?D= zfY|^GC?+&Z^p6FA8C}ofJ=qp! z9-m_W<@I_Iz>IE%x{|Pp;GjBX6TF}a*jP3xeu+bG7WX1&mk0N~{HsSzbCiLbqP~-g zkSpx`JWXg~EOzOJUcD;5wNg`*BV!@D#5@PReU38uXYq`SD!BUfu?E1+V-m^mdL!nP z*3!TtZImCBPEp}JMF-^Q*m61=v|~cZ6<96Kz?QK&P4LXWSS5mC%b53%vc;hw_yV?! z(SzCIkU1O{Y#9?=AE12p#n>|4hd54gFUFQJZwAy-x(o6f3$~1j@>X<5@4Cg}RVegx6587E#w{?1x8y!Xi)7BSg4FGcoT%$Jp`!@fKmw)zS^-k zAJiTI(T|1Qg?b#tnW4>@rJH zLZSQT@Nd=hmD>{~k^EbLivcLl)eW+WtV(#kR>c6!5oqsrx__C zPe2!)`rZX0O_BTuO~I%gHgUxuTet^B_B~}~U&Qk07D+qV^be3ppCMp=)?dk};DIyM ziqO5W)E~^`|Jahj9{G;$dVKIMNJNSp?GB6eeuFeD57+!*$}HxCb&g8vzQLL9^G1K5 zZDHZt;2A6Tx>~H$W2g$<5aR|V_y~!`J0KAvpeb^LvS`4C<;4NY3$7SOMg*OTfkfDg z1FRD+i(VW6=+m0vn2TN<&}EFc;0cRg9Dp$}6-;f(z&`nG0OJZ4`hH}_MRW%=w{C(~ z+Dwn>j3R#k?d^ccrx`*n6@F7t5B=>xH@jlS(r_9!;FDL2?tlR%w`yl?RwI-X+QoLP z20HVd1iINeh(){h0sUXHHe(N{8liIY_gIN{`*UmIH6qjjR&Ab|PF zEr^KRhkhXU{GAcxCrm%O1rEf`TZ}f?r7Q4GjHJtemr)MXWWohH%*%wAG3`eai?9xF z0ba(Wr%%2RS>RUb3)rxkOiiGw7OMvu#0P$ovZ+?~n^Nyi|Z;Oy(z^YXg0j-W8d=)ZN z-;Rtfh7Qp$iE=p4iQ`-w>;0KLkUvVT7cge*adZv_ z$BHqd{7GhPabb%wW-MS$ah%$@7-Pn~{p#v>m5VWE%uMjR8hwc;7GccT7dG&8TI2d? zQ}COkSeT-pnkz6&aj~;7{0QqgVvgolF!~fM7{c37U}R)8{}{i6&H^LS|1+BZ%ZP?l zbr=(t(fpWb@=3cs>z9mf4_IgK zhm4UDIVAz#eYf=}BO(NukVauy$td7bloP`&CaH0RZW!o&2YWeV@)AWOux+&Q>h7d$ z0*Mav1=ONTVJ<59h-}OSEsVg&y@jl!NF-&VulZ}7>5TB3MUzXA)=MjK^*5%L|BsU! zLn=-GH>qi_4NLI^$|XREW3xV(dNhgk-5hp4P0*r&y1w2T{9kN`m=+yi=f90#h!WZa zKnRqEoPh(-l=xMzk%!kHF5safr=Jx*ec)nc8FT;^Vi}B~!^+3uN(XuYEx0Pfl*@pX zv8AAS!O)lk3|JXsd!7dd*pM%hfH?{)(^Zv5?gl=(EU+@h_Bh)wsNY#(Wo&!C64XtE z^U%XEVP%9ZaDa;Le_&-S5}joaO5|JwD`V$>xd!z$)U?C9Sx`%al`~8|*lkE=g|pyz z(LjZiFujDb#WOKy!T5Q7f!&3hs4TcYkrTN&$T`5dS=R%9(`FIX0s_k;mHLAQJ677G~TIts}tB<(6E zBMC#ExrUcp4ec$?w7u^Q>s%O2!^Hjp=HG;3Foc5cKh6iKGs(cb|M?mQZ_K1i2x6SH z75@Fq?u~9acBuC!Y!?a&)H8w(gQFZz_C5L58vQorE~3yn;xkNVPuLzO{$e>Sq%Jt( zWvRzL5i@0}EG~W?T>N{ryga=6$T|rVgt+*n)*vk;P_c(H(0sT&!)JWV) { + const vm = useMockedViewModel(snapshot, {}); + + return ; +}; + +const MKeyVerificationRequestViewWrapper = withViewDocs( + MKeyVerificationRequestViewWrapperImpl, + MKeyVerificationRequestView, +); + +const meta = { + title: "Timeline/Timeline Event/MKeyVerificationRequestView", + component: MKeyVerificationRequestViewWrapper, + tags: ["autodocs"], + args: { + title: "Alice wants to verify", + subtitle: "Alice (@alice:example.org)", + className: "", + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Received: Story = {}; + +export const SentByMe: Story = { + args: { + title: "You sent a verification request", + subtitle: "Bob (@bob:example.org)", + }, +}; + +export const WithTimestamp: Story = { + args: { + timestamp: 14:56, + }, +}; diff --git a/packages/shared-components/src/room/timeline/event-tile/EventTileView/MKeyVerificationRequestView/MKeyVerificationRequestView.test.tsx b/packages/shared-components/src/room/timeline/event-tile/EventTileView/MKeyVerificationRequestView/MKeyVerificationRequestView.test.tsx new file mode 100644 index 0000000000..c61a00b138 --- /dev/null +++ b/packages/shared-components/src/room/timeline/event-tile/EventTileView/MKeyVerificationRequestView/MKeyVerificationRequestView.test.tsx @@ -0,0 +1,65 @@ +/* + * Copyright 2026 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +import { composeStories } from "@storybook/react-vite"; +import { render, screen } from "@test-utils"; +import React from "react"; +import { describe, expect, it } from "vitest"; + +import { MockViewModel } from "../../../../../core/viewmodel"; +import { MKeyVerificationRequestView } from "./MKeyVerificationRequestView"; +import * as stories from "./MKeyVerificationRequestView.stories"; + +const { Received, SentByMe, WithTimestamp } = composeStories(stories); + +describe("MKeyVerificationRequestView", () => { + it("renders the Received story", () => { + const { container } = render(); + + expect(container).toMatchSnapshot(); + expect(screen.getByText("Alice wants to verify")).toBeInTheDocument(); + expect(screen.getByText("Alice (@alice:example.org)")).toBeInTheDocument(); + }); + + it("renders the SentByMe story", () => { + const { container } = render(); + + expect(container).toMatchSnapshot(); + expect(screen.getByText("You sent a verification request")).toBeInTheDocument(); + expect(screen.getByText("Bob (@bob:example.org)")).toBeInTheDocument(); + }); + + it("renders a timestamp", () => { + const { container } = render(); + + expect(container).toMatchSnapshot(); + expect(screen.getByText("14:56")).toBeInTheDocument(); + }); + + it("applies a custom className to the root element", () => { + const vm = new MockViewModel({ + title: "Alice wants to verify", + subtitle: "Alice (@alice:example.org)", + }); + const { container } = render(); + + expect(container.firstChild).toHaveClass("custom-verification"); + }); + + it("forwards the provided ref to the root element", () => { + const ref = React.createRef() as React.RefObject; + const vm = new MockViewModel({ + title: "Alice wants to verify", + subtitle: "Alice (@alice:example.org)", + }); + + render(); + + expect(ref.current).toBeInstanceOf(HTMLDivElement); + expect(ref.current).toHaveTextContent("Alice wants to verify"); + }); +}); diff --git a/packages/shared-components/src/room/timeline/event-tile/EventTileView/MKeyVerificationRequestView/MKeyVerificationRequestView.tsx b/packages/shared-components/src/room/timeline/event-tile/EventTileView/MKeyVerificationRequestView/MKeyVerificationRequestView.tsx new file mode 100644 index 0000000000..6814883e91 --- /dev/null +++ b/packages/shared-components/src/room/timeline/event-tile/EventTileView/MKeyVerificationRequestView/MKeyVerificationRequestView.tsx @@ -0,0 +1,69 @@ +/* + * Copyright 2026 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +import classNames from "classnames"; +import React, { type JSX } from "react"; +import { LockSolidIcon } from "@vector-im/compound-design-tokens/assets/web/icons"; + +import { type ViewModel, useViewModel } from "../../../../../core/viewmodel"; +import { EventTileBubble } from "../EventTileBubble"; +import styles from "./MKeyVerificationRequestView.module.css"; + +export interface MKeyVerificationRequestViewSnapshot { + /** + * Main title text for the verification request. + */ + title: string; + /** + * Label for the other user involved in the request. + */ + subtitle: string; + /** + * Optional timestamp element rendered in the EventTileBubble footer slot. + */ + timestamp?: JSX.Element; +} + +export type MKeyVerificationRequestViewModel = ViewModel; + +export interface MKeyVerificationRequestViewProps { + /** + * ViewModel providing the current verification request snapshot. + */ + vm: MKeyVerificationRequestViewModel; + /** + * Optional CSS classes passed through to EventTileBubble. + */ + className?: string; + /** + * Optional Ref forwarded to the root DOM element. + */ + ref?: React.RefObject; +} + +/** + * Renders a timeline bubble describing a key verification request message. + */ +export function MKeyVerificationRequestView({ + vm, + className, + ref, +}: Readonly): JSX.Element { + const { title, subtitle, timestamp } = useViewModel(vm); + + return ( + } + className={classNames(styles.content, className)} + title={title} + subtitle={subtitle} + ref={ref} + > + {timestamp} + + ); +} diff --git a/packages/shared-components/src/room/timeline/event-tile/EventTileView/MKeyVerificationRequestView/__snapshots__/MKeyVerificationRequestView.test.tsx.snap b/packages/shared-components/src/room/timeline/event-tile/EventTileView/MKeyVerificationRequestView/__snapshots__/MKeyVerificationRequestView.test.tsx.snap new file mode 100644 index 0000000000..2ca016919d --- /dev/null +++ b/packages/shared-components/src/room/timeline/event-tile/EventTileView/MKeyVerificationRequestView/__snapshots__/MKeyVerificationRequestView.test.tsx.snap @@ -0,0 +1,94 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`MKeyVerificationRequestView > renders a timestamp 1`] = ` +
+
+ + + +
+ Alice wants to verify +
+
+ Alice (@alice:example.org) +
+ + 14:56 + +
+
+`; + +exports[`MKeyVerificationRequestView > renders the Received story 1`] = ` +
+
+ + + +
+ Alice wants to verify +
+
+ Alice (@alice:example.org) +
+
+
+`; + +exports[`MKeyVerificationRequestView > renders the SentByMe story 1`] = ` +
+
+ + + +
+ You sent a verification request +
+
+ Bob (@bob:example.org) +
+
+
+`; diff --git a/packages/shared-components/src/room/timeline/event-tile/EventTileView/MKeyVerificationRequestView/index.ts b/packages/shared-components/src/room/timeline/event-tile/EventTileView/MKeyVerificationRequestView/index.ts new file mode 100644 index 0000000000..641da39c23 --- /dev/null +++ b/packages/shared-components/src/room/timeline/event-tile/EventTileView/MKeyVerificationRequestView/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright 2026 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +export { + MKeyVerificationRequestView, + type MKeyVerificationRequestViewProps, + type MKeyVerificationRequestViewSnapshot, + type MKeyVerificationRequestViewModel, +} from "./MKeyVerificationRequestView";