ux: reply null state, location error feedback, retry send, reaction keyboard nav

- Reply: distinguish loading (placeholder) from not-found (null) — show
  "Original message not available" instead of a stuck loading bar
- RoomInput: geolocation errors now surface inline (denied / timed out /
  unsupported); location button shows Spinner during fetch and is disabled
- Message menu: Retry Send + Cancel Message items appear when a message
  is in NOT_SENT or CANCELLED state, calling mx.resendEvent / cancelPendingEvent
- ReactionViewer: sidebar gains role=listbox / role=option and ArrowUp/Down
  keyboard navigation between reactions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-24 00:02:19 -04:00
parent 97b335773b
commit d58c445d74
4 changed files with 115 additions and 20 deletions
+46
View File
@@ -1189,6 +1189,52 @@ export const Message = React.memo(
<MessagePinItem room={room} mEvent={mEvent} onClose={closeMenu} />
)}
</Box>
{(mEvent.status === EventStatus.NOT_SENT ||
mEvent.status === EventStatus.CANCELLED) && (
<>
<Line size="300" />
<Box direction="Column" gap="100" className={css.MessageMenuGroup}>
<MenuItem
size="300"
after={<Icon size="100" src={Icons.Send} />}
radii="300"
onClick={() => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(mx as any).resendEvent(mEvent, room);
closeMenu();
}}
>
<Text
className={css.MessageMenuItemText}
as="span"
size="T300"
truncate
>
Retry Send
</Text>
</MenuItem>
<MenuItem
size="300"
after={<Icon size="100" src={Icons.Cross} />}
radii="300"
onClick={() => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(mx as any).cancelPendingEvent(mEvent);
closeMenu();
}}
>
<Text
className={css.MessageMenuItemText}
as="span"
size="T300"
truncate
>
Cancel Message
</Text>
</MenuItem>
</Box>
</>
)}
{((!mEvent.isRedacted() && canDelete) ||
mEvent.getSender() !== mx.getUserId()) && (
<>