diff --git a/src/app/features/room/message/ForwardMessageDialog.tsx b/src/app/features/room/message/ForwardMessageDialog.tsx
index 01f8eddc0..171d89e68 100644
--- a/src/app/features/room/message/ForwardMessageDialog.tsx
+++ b/src/app/features/room/message/ForwardMessageDialog.tsx
@@ -144,6 +144,8 @@ function ForwardPreview({
shrink="No"
gap="200"
alignItems="Center"
+ role="group"
+ aria-label="Message to forward"
style={{
margin: `${config.space.S200} ${config.space.S400} 0`,
padding: config.space.S200,
@@ -224,7 +226,7 @@ function RecentChip({
src={avatarUrl}
alt={room.name}
renderFallback={() => (
-
+
)}
/>
@@ -254,6 +256,11 @@ export function ForwardMessageDialog({ mEvent, onClose }: Props) {
const [sentTo, setSentTo] = useState(null);
const [error, setError] = useState(null);
const [recents, setRecents] = useAtom(recentForwardTargetsAtom);
+ // Rooms whose comment message already delivered this session — so a retry after
+ // a forward failure doesn't re-post the comment (only the missing forward). Kept
+ // for the dialog's lifetime: a room that already got the comment won't get it
+ // again even if the text is later edited, which is the safe (no-duplicate) choice.
+ const commentSentRef = useRef>(new Set());
// Selection persists across query changes: a room selected then filtered out
// of the rendered slice stays selected.
const [selectedRoomIds, setSelectedRoomIds] = useState>(new Set());
@@ -315,12 +322,18 @@ export function ForwardMessageDialog({ mEvent, onClose }: Props) {
const sendForward = () => mx.sendEvent(id, null, mEvent.getType() as any, fwdContent);
// Send the optional comment first so it reads as a note above the
// forwarded content. The room counts as failed if either send rejects.
- return commentBody
+ // Track rooms whose comment already landed so a retry (after the FORWARD
+ // failed) doesn't post the comment twice — only the missing forward.
+ const needsComment = commentBody && !commentSentRef.current.has(id);
+ const step = needsComment
? mx
// eslint-disable-next-line @typescript-eslint/no-explicit-any
.sendMessage(id, null, { msgtype: MsgType.Text, body: commentBody } as any)
- .then(sendForward)
- : sendForward();
+ .then(() => {
+ commentSentRef.current.add(id);
+ })
+ : Promise.resolve();
+ return step.then(sendForward);
}),
);
@@ -408,6 +421,7 @@ export function ForwardMessageDialog({ mEvent, onClose }: Props) {
size="400"
radii="400"
outlined
+ aria-label="Search rooms"
placeholder="Search rooms…"
value={query}
onChange={(e: ChangeEvent) => setQuery(e.target.value)}
@@ -417,6 +431,7 @@ export function ForwardMessageDialog({ mEvent, onClose }: Props) {
size="400"
radii="400"
outlined
+ aria-label="Add a comment"
placeholder="Add a comment (optional)…"
value={comment}
disabled={sending}