fix: comprehensive P0 quality pass — audit findings resolved

- ReportRoomModal: use mx.reportRoom() SDK method, fix undefined CSS vars
  (--mx-surface/border → folds color tokens), add role/aria-modal/aria-labelledby,
  accessible select/input labels, per-error-code messages, auto-close on success
- About.tsx: clickable matrix_id + email_address links (Text as="a"), AbortController
  cleanup, runtime JSON type guard, loading state, role display for all role values,
  remove classList theming hack, use mx.getHomeserverUrl()
- RoomViewHeader: useLocalRoomName for header title, useReportRoomSupported gate,
  hide Invite/Settings/Report for server notice rooms, isCreator guard on Report,
  FocusTrap returnFocusOnDeactivate on topic overlay, Server Notice chip tooltip
- RoomInput: replace raw <div> with folds <Box> for server notice read-only message
- EditHistoryModal: isRawEditEvent type guard, handle next_batch truncation,
  getVersionBody handles formatted_body (strips HTML for text display),
  role/aria-modal/aria-labelledby accessibility, guard for undefined eventId,
  use config.space tokens (remove var(--mx-spacing-*) strings)
- RoomNavItem: remove duplicate getExistingContent (use exported getLocalRoomNamesContent),
  maxLength={255} on rename input, fix FocusTrap nesting (renameDialog state moved to
  RoomNavItem_, RenameRoomDialog rendered outside menu, menu closes before dialog opens),
  pencil icon opacity via config.opacity.P300
- useRoomMeta: export getLocalRoomNamesContent for reuse
- RoomIntro: useLocalRoomName, formatted topic viewer with Overlay/FocusTrap/RoomTopicViewer
- CallRoomName: useLocalRoomName for consistent rename display in call overlay
- General.tsx: fix #980000/#FF6B00 hardcoded hex → color tokens/CSS vars, URL Preview
  capitalization, improved encrypted preview warning text + Warning chip, add
  description to plain urlPreview setting
- sanitize.ts: fix hex color regex to support 3/4/6/8 digit hex (CSS4 #RGBA, #RRGGBBAA)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-01 21:30:27 -04:00
parent 3db87db03f
commit 16dddcb9f0
11 changed files with 363 additions and 213 deletions
+21 -36
View File
@@ -61,7 +61,7 @@ import { getRoomPermissionsAPI, useRoomPermissions } from '../../hooks/useRoomPe
import { InviteUserPrompt } from '../../components/invite-user-prompt';
import {
LOCAL_ROOM_NAMES_KEY,
LocalRoomNamesContent,
getLocalRoomNamesContent,
useHasLocalRoomName,
useLocalRoomName,
} from '../../hooks/useRoomMeta';
@@ -82,29 +82,15 @@ function RenameRoomDialog({ room, onClose }: RenameRoomDialogProps) {
const mx = useMatrixClient();
const inputRef = useRef<HTMLInputElement>(null);
const getExistingContent = useCallback((): LocalRoomNamesContent => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const raw: unknown = (mx as any).getAccountData(LOCAL_ROOM_NAMES_KEY)?.getContent();
if (
raw &&
typeof raw === 'object' &&
'rooms' in raw &&
// eslint-disable-next-line @typescript-eslint/no-explicit-any
typeof (raw as any).rooms === 'object'
) {
return raw as LocalRoomNamesContent;
}
return { rooms: {} };
}, [mx]);
const getCurrentLocalName = useCallback((): string => {
const content = getExistingContent();
const content = getLocalRoomNamesContent(mx);
return content.rooms[room.roomId] ?? '';
}, [getExistingContent, room.roomId]);
}, [mx, room.roomId]);
const handleSave = useCallback(() => {
const newName = inputRef.current?.value.trim() ?? '';
const existing = getExistingContent();
if (newName.length > 255) return;
const existing = getLocalRoomNamesContent(mx);
if (newName === '') {
const { [room.roomId]: _removed, ...rest } = existing.rooms;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -116,15 +102,15 @@ function RenameRoomDialog({ room, onClose }: RenameRoomDialogProps) {
});
}
onClose();
}, [mx, room.roomId, getExistingContent, onClose]);
}, [mx, room.roomId, onClose]);
const handleClear = useCallback(() => {
const existing = getExistingContent();
const existing = getLocalRoomNamesContent(mx);
const { [room.roomId]: _removed, ...rest } = existing.rooms;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(mx as any).setAccountData(LOCAL_ROOM_NAMES_KEY, { rooms: rest });
onClose();
}, [mx, room.roomId, getExistingContent, onClose]);
}, [mx, room.roomId, onClose]);
const handleKeyDown = (evt: React.KeyboardEvent<HTMLInputElement>) => {
if (evt.key === 'Enter') handleSave();
@@ -169,6 +155,7 @@ function RenameRoomDialog({ room, onClose }: RenameRoomDialogProps) {
placeholder={room.name}
variant="Secondary"
radii="300"
maxLength={255}
onKeyDown={handleKeyDown}
autoFocus
/>
@@ -210,10 +197,11 @@ function RenameRoomDialog({ room, onClose }: RenameRoomDialogProps) {
type RoomNavItemMenuProps = {
room: Room;
requestClose: () => void;
onRenameClick: () => void;
notificationMode?: RoomNotificationMode;
};
const RoomNavItemMenu = forwardRef<HTMLDivElement, RoomNavItemMenuProps>(
({ room, requestClose, notificationMode }, ref) => {
({ room, requestClose, onRenameClick, notificationMode }, ref) => {
const mx = useMatrixClient();
const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
const unread = useRoomUnread(room.roomId, roomToUnreadAtom);
@@ -226,7 +214,6 @@ const RoomNavItemMenu = forwardRef<HTMLDivElement, RoomNavItemMenuProps>(
const space = useSpaceOptionally();
const [invitePrompt, setInvitePrompt] = useState(false);
const [renameDialog, setRenameDialog] = useState(false);
const handleMarkAsRead = () => {
markAsRead(mx, room.roomId, hideActivity);
@@ -260,15 +247,6 @@ const RoomNavItemMenu = forwardRef<HTMLDivElement, RoomNavItemMenuProps>(
}}
/>
)}
{renameDialog && (
<RenameRoomDialog
room={room}
onClose={() => {
setRenameDialog(false);
requestClose();
}}
/>
)}
<Box direction="Column" gap="100" style={{ padding: config.space.S100 }}>
<MenuItem
onClick={handleMarkAsRead}
@@ -330,11 +308,13 @@ const RoomNavItemMenu = forwardRef<HTMLDivElement, RoomNavItemMenuProps>(
</Text>
</MenuItem>
<MenuItem
onClick={() => setRenameDialog(true)}
onClick={() => {
requestClose();
onRenameClick();
}}
size="300"
after={<Icon size="100" src={Icons.Pencil} />}
radii="300"
aria-pressed={renameDialog}
>
<Text style={{ flexGrow: 1 }} as="span" size="T300" truncate>
Rename for me
@@ -425,6 +405,7 @@ function RoomNavItem_({
const { hoverProps } = useHover({ onHoverChange: setHover });
const { focusWithinProps } = useFocusWithin({ onFocusWithinChange: setHover });
const [menuAnchor, setMenuAnchor] = useState<RectCords>();
const [renameDialog, setRenameDialog] = useState(false);
const unread = useRoomUnread(room.roomId, roomToUnreadAtom);
const typingMember = useRoomTypingMember(room.roomId).filter(
(receipt) => receipt.userId !== mx.getUserId(),
@@ -533,7 +514,7 @@ function RoomNavItem_({
size="50"
src={Icons.Pencil}
aria-label="Custom local name"
style={{ opacity: 0.6, flexShrink: 0 }}
style={{ opacity: config.opacity.P300, flexShrink: 0 }}
/>
)}
</Box>
@@ -592,6 +573,7 @@ function RoomNavItem_({
<RoomNavItemMenu
room={room}
requestClose={() => setMenuAnchor(undefined)}
onRenameClick={() => setRenameDialog(true)}
notificationMode={notificationMode}
/>
</FocusTrap>
@@ -612,6 +594,9 @@ function RoomNavItem_({
</PopOut>
</NavItemOptions>
)}
{renameDialog && (
<RenameRoomDialog room={room} onClose={() => setRenameDialog(false)} />
)}
</NavItem>
);
}