fix/polish: wave 1+2 improvements across six features

B1 - GIF upload progress: spinner on GIF button + disabled state while
     fetch+upload is in flight; clears on success or error
B2 - PiP position persistence: drag end saves left/top to localStorage;
     entering PiP restores saved position (clamped to current viewport)
B3 - PiP snap-to-corner: double-click the PiP overlay snaps to nearest
     corner with a 180ms CSS transition; saves new position
B4 - Device sessions loading state: useOtherUserDevices now returns
     {status:'loading'|'error'|'success', devices} instead of bare
     array; UserDeviceSessions shows spinner while loading
B5 - Device sessions error state: catch in hook sets status:'error';
     panel shows warning icon + 'Could not load sessions' message
B6 - Screenshare fullscreen Safari guard: hide button when
     document.fullscreenEnabled is false (iOS Safari, some mobile)
B7 - Status save error: show critical-coloured error text below Save
     button when saveState.status === AsyncStatus.Error
B9 - Encrypted search coverage counter: 'X / Y cached' badge next to
     'Encrypted Rooms' heading using existing localResult fields
D2 - PiP screenshare spotlight: track auto-spotlight in a ref; release
     spotlight when screenshare ends while in PiP mode

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-30 17:13:54 -04:00
parent 2255795cae
commit bc63714a07
7 changed files with 132 additions and 38 deletions
@@ -7,6 +7,7 @@ import { VerificationStatus } from '../../hooks/useDeviceVerificationStatus';
import { DeviceVerificationStatus } from '../DeviceVerificationStatus';
import { DeviceVerification } from '../DeviceVerification';
import { useOtherUserDevices, UserDevice } from '../../hooks/useOtherUserDevices';
import { ContainerColor } from '../../styles/ContainerColor.css';
import { UserHero, UserHeroName } from './UserHero';
import { getMxIdServer, mxcUrlToHttp } from '../../utils/matrix';
import { getMemberAvatarMxc, getMemberDisplayName } from '../../utils/room';
@@ -122,10 +123,34 @@ type UserDeviceSessionsProps = {
userId: string;
};
function UserDeviceSessions({ userId }: UserDeviceSessionsProps) {
const devices = useOtherUserDevices(userId);
const devicesState = useOtherUserDevices(userId);
const [expanded, setExpanded] = useState(false);
if (!devices || devices.length === 0) return null;
if (devicesState.status === 'loading') {
return (
<Box alignItems="Center" gap="200" style={{ padding: config.space.S200, opacity: 0.6 }}>
<Spinner size="100" variant="Secondary" />
<Text size="T200">Loading sessions</Text>
</Box>
);
}
if (devicesState.status === 'error') {
return (
<Box
className={ContainerColor({ variant: 'Critical' })}
alignItems="Center"
gap="200"
style={{ padding: config.space.S200, borderRadius: config.radii.R300 }}
>
<Icon size="100" src={Icons.Warning} />
<Text size="T200">Could not load sessions.</Text>
</Box>
);
}
const devices = devicesState.devices;
if (devices.length === 0) return null;
return (
<Box