fix: resilience, speaker animation, CSS variable fixes
CI / Build & Quality Checks (push) Successful in 10m39s
CI / Trigger Desktop Build (push) Successful in 6s

- Wrap RoomTimeline in ErrorBoundary — a single bad event no longer
  crashes the entire timeline; shows a graceful "Timeline unavailable"
  message instead
- Wrap RoomInput in ErrorBoundary — composer crashes show a fallback
  placeholder rather than a blank white section
- Animate SpeakerAvatarOutline with a 1.2s pulse keyframe so it's
  visually distinct from a static ring; respects prefers-reduced-motion
- Fix var(--border-surface-variant) undefined variable in UserRoomProfile
  device session rows; replaced with color.SurfaceVariant.ContainerLine

UNTESTED — verify at chat.lotusguild.org post-deploy.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-18 19:19:54 -04:00
parent abf15391f6
commit e2b957b6bd
3 changed files with 65 additions and 19 deletions
@@ -1,4 +1,4 @@
import { Box, Button, config, Icon, IconButton, Icons, Spinner, Text, toRem } from 'folds'; import { Box, Button, color, config, Icon, IconButton, Icons, Spinner, Text, toRem } from 'folds';
import React, { useCallback, useEffect, useRef, useState } from 'react'; import React, { useCallback, useEffect, useRef, useState } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { VerificationRequest } from 'matrix-js-sdk/lib/crypto-api'; import { VerificationRequest } from 'matrix-js-sdk/lib/crypto-api';
@@ -197,7 +197,7 @@ function UserDeviceSessions({ userId }: UserDeviceSessionsProps) {
style={{ style={{
paddingTop: config.space.S100, paddingTop: config.space.S100,
paddingBottom: config.space.S100, paddingBottom: config.space.S100,
borderTop: `${toRem(1)} solid var(--border-surface-variant)`, borderTop: `${toRem(1)} solid ${color.SurfaceVariant.ContainerLine}`,
}} }}
> >
<UserDeviceRow userId={userId} device={device} /> <UserDeviceRow userId={userId} device={device} />
+16 -3
View File
@@ -1,4 +1,4 @@
import { style } from '@vanilla-extract/css'; import { keyframes, style } from '@vanilla-extract/css';
import { color, config, toRem } from 'folds'; import { color, config, toRem } from 'folds';
export const LiveChipText = style({ export const LiveChipText = style({
@@ -16,6 +16,19 @@ export const ControlDivider = style({
height: toRem(16), height: toRem(16),
}); });
export const SpeakerAvatarOutline = style({ const speakerPulse = keyframes({
boxShadow: `0 0 0 ${config.borderWidth.B600} ${color.Success.Main}`, '0%': { boxShadow: `0 0 0 ${config.borderWidth.B600} ${color.Success.Main}` },
'50%': { boxShadow: `0 0 0 ${toRem(4)} ${color.Success.ContainerActive}` },
'100%': { boxShadow: `0 0 0 ${config.borderWidth.B600} ${color.Success.Main}` },
});
export const SpeakerAvatarOutline = style({
'@media': {
'(prefers-reduced-motion: no-preference)': {
animation: `${speakerPulse} 1200ms ease-in-out infinite`,
},
'(prefers-reduced-motion: reduce)': {
boxShadow: `0 0 0 ${config.borderWidth.B600} ${color.Success.Main}`,
},
},
}); });
+47 -14
View File
@@ -1,4 +1,5 @@
import React, { useCallback, useMemo, useRef } from 'react'; import React, { useCallback, useMemo, useRef } from 'react';
import { ErrorBoundary } from 'react-error-boundary';
import { Box, Text, config } from 'folds'; import { Box, Text, config } from 'folds';
import { EventType } from 'matrix-js-sdk'; import { EventType } from 'matrix-js-sdk';
import { ReactEditor } from 'slate-react'; import { ReactEditor } from 'slate-react';
@@ -109,13 +110,31 @@ export function RoomView({ eventId }: { eventId?: string }) {
return ( return (
<Page ref={roomViewRef} style={chatBgStyle}> <Page ref={roomViewRef} style={chatBgStyle}>
<Box grow="Yes" direction="Column"> <Box grow="Yes" direction="Column">
<RoomTimeline <ErrorBoundary
key={roomId} fallback={
room={room} <Box
eventId={eventId} grow="Yes"
roomInputRef={roomInputRef} direction="Column"
editor={editor} alignItems="Center"
/> justifyContent="Center"
gap="400"
style={{ padding: config.space.S400, opacity: 0.7 }}
>
<Text size="H4">Timeline unavailable</Text>
<Text size="T300" align="Center">
An error occurred while rendering messages. Try refreshing the page.
</Text>
</Box>
}
>
<RoomTimeline
key={roomId}
room={room}
eventId={eventId}
roomInputRef={roomInputRef}
editor={editor}
/>
</ErrorBoundary>
<RoomViewTyping room={room} /> <RoomViewTyping room={room} />
</Box> </Box>
<Box shrink="No" direction="Column"> <Box shrink="No" direction="Column">
@@ -129,13 +148,27 @@ export function RoomView({ eventId }: { eventId?: string }) {
) : ( ) : (
<> <>
{canMessage && ( {canMessage && (
<RoomInput <ErrorBoundary
room={room} fallback={
editor={editor} <RoomInputPlaceholder
roomId={roomId} style={{ padding: config.space.S200 }}
fileDropContainerRef={roomViewRef} alignItems="Center"
ref={roomInputRef} justifyContent="Center"
/> >
<Text align="Center">
Message composer encountered an error. Try refreshing.
</Text>
</RoomInputPlaceholder>
}
>
<RoomInput
room={room}
editor={editor}
roomId={roomId}
fileDropContainerRef={roomViewRef}
ref={roomInputRef}
/>
</ErrorBoundary>
)} )}
{!canMessage && ( {!canMessage && (
<RoomInputPlaceholder <RoomInputPlaceholder