ux: reply null state, location error feedback, retry send, reaction keyboard nav
CI / Build & Quality Checks (push) Successful in 10m17s

- 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 f3b5e550f9
commit f8cc11e125
4 changed files with 115 additions and 20 deletions
+33 -13
View File
@@ -24,6 +24,7 @@ import {
OverlayCenter,
PopOut,
Scroll,
Spinner,
Text,
config,
toRem,
@@ -183,8 +184,13 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
const [toolbar, setToolbar] = useSetting(settingsAtom, 'editorToolbar');
const [locating, setLocating] = React.useState(false);
const [locationError, setLocationError] = React.useState<string | null>(null);
const handleShareLocation = () => {
if (!navigator.geolocation) return;
if (!navigator.geolocation) {
setLocationError('Geolocation not supported.');
setTimeout(() => setLocationError(null), 4000);
return;
}
setLocating(true);
navigator.geolocation.getCurrentPosition(
(pos) => {
@@ -197,7 +203,17 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
geo_uri: geoUri,
} as any);
},
() => setLocating(false),
(err) => {
setLocating(false);
const msg =
err.code === 1
? 'Location access denied.'
: err.code === 3
? 'Location timed out.'
: 'Failed to get location.';
setLocationError(msg);
setTimeout(() => setLocationError(null), 4000);
},
{ timeout: 10000 },
);
};
@@ -858,8 +874,22 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
{gifError}
</Text>
)}
{locationError && (
<Text
size="T200"
style={{
color: 'var(--tc-danger-normal)',
padding: '2px 6px',
alignSelf: 'center',
whiteSpace: 'nowrap',
}}
>
{locationError}
</Text>
)}
<IconButton
onClick={handleShareLocation}
disabled={locating}
aria-label="Share location"
variant="SurfaceVariant"
size="300"
@@ -867,17 +897,7 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
title="Share location"
>
{locating ? (
<Text
size="T200"
style={{
fontWeight: 800,
fontSize: '10px',
letterSpacing: '0.04em',
lineHeight: 1,
}}
>
...
</Text>
<Spinner variant="Secondary" size="100" />
) : (
<Icon src={Icons.SpaceGlobe} size="100" />
)}