fix(room-preview): make the preview card actually populate + richer
CI / Build & Quality Checks (push) Successful in 10m53s
CI / Trigger Desktop Build (push) Successful in 14s

Two review agents found the preview card degrades to 'id + logo + Join' for real
reasons. Fixes:

- **via was dropped at the summary fetch** (root cause). RoomSummaryLoader now
  accepts + forwards `via` to getRoomSummary (and keys the query on it);
  JoinBeforeNavigate passes viaServers; the lobby Preview chip carries data-via and
  Lobby.handleOpenRoom appends it to the navigated URL. Without this, previews of
  rooms the HS isn't already in came back sparse/404.
- **No loading/error state** -> RoomSummaryLoader now surfaces {loading,error};
  JoinBeforeNavigate shows a Spinner while loading instead of the degraded card.
- **Room id leaked as name AND topic AND header** -> stop using the raw `!id` as
  the topic fallback (show 'No description'); name falls back to canonical_alias
  then alias-localpart; the page header shows the summary name.
- **Richer, membership-aware card**: forward canonical_alias (shown under the name),
  world_readable ('Readable' badge), and membership -> the button now shows Accept
  invite / Requested / Banned correctly instead of a Join that lies; a 'hero' layout
  for the single-preview page (primary Join button, unclamped topic). Removed dead
  `|| undefined ||`.

IRoomSummary re-adds canonical_alias (the SDK type Omit<>s it though MSC3266
returns it). 738 tests pass, build clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-07 14:46:21 -04:00
co-authored by Claude Opus 4.8
parent 7fd3164b1f
commit 0c6003fb87
5 changed files with 157 additions and 78 deletions
+7 -2
View File
@@ -36,7 +36,7 @@ import { useCategoryHandler } from '../../hooks/useCategoryHandler';
import { useMatrixClient } from '../../hooks/useMatrixClient';
import { allRoomsAtom } from '../../state/room-list/roomList';
import { getCanonicalAliasOrRoomId, rateLimitedActions } from '../../utils/matrix';
import { getSpaceRoomPath } from '../../pages/pathUtils';
import { getSpaceRoomPath, withSearchParam } from '../../pages/pathUtils';
import { StateEvent } from '../../../types/matrix/room';
import { CanDropCallback, useDnDMonitor } from './DnD';
import { ASCIILexicalTable, orderKeys } from '../../utils/ASCIILexicalTable';
@@ -411,8 +411,13 @@ export function Lobby() {
const handleOpenRoom: MouseEventHandler<HTMLButtonElement> = (evt) => {
const rId = evt.currentTarget.getAttribute('data-room-id');
if (!rId) return;
// Preview chips carry the room's resident servers so getRoomSummary can
// resolve a room the homeserver isn't already in (else the preview is sparse).
const via = evt.currentTarget.getAttribute('data-via');
const pSpaceIdOrAlias = getCanonicalAliasOrRoomId(mx, space.roomId);
navigate(getSpaceRoomPath(pSpaceIdOrAlias, getCanonicalAliasOrRoomId(mx, rId)));
let path = getSpaceRoomPath(pSpaceIdOrAlias, getCanonicalAliasOrRoomId(mx, rId));
if (via) path = withSearchParam(path, { viaServers: via });
navigate(path);
};
const togglePinToSidebar = useCallback(
+5 -2
View File
@@ -102,14 +102,17 @@ function RoomJoinButton({ roomId, via }: RoomJoinButtonProps) {
// room isn't joined — renders the preview card instead of the timeline.
function RoomPreviewChip({
roomId,
via,
onOpen,
}: {
roomId: string;
via?: string[];
onOpen: MouseEventHandler<HTMLButtonElement>;
}) {
return (
<Chip
data-room-id={roomId}
data-via={via && via.length > 0 ? via.join(',') : undefined}
onClick={onOpen}
variant="Secondary"
fill="None"
@@ -391,7 +394,7 @@ export const RoomItemCard = as<'div', RoomItemCardProps>(
</Box>
) : (
<Box shrink="No" gap="100" alignItems="Center">
<RoomPreviewChip roomId={roomId} onOpen={onOpen} />
<RoomPreviewChip roomId={roomId} via={content.via} onOpen={onOpen} />
<RoomJoinButton roomId={roomId} via={content.via} />
</Box>
)
@@ -439,7 +442,7 @@ export const RoomItemCard = as<'div', RoomItemCardProps>(
joinRule={summary.join_rule}
options={
<Box shrink="No" gap="100" alignItems="Center">
<RoomPreviewChip roomId={roomId} onOpen={onOpen} />
<RoomPreviewChip roomId={roomId} via={content.via} onOpen={onOpen} />
<RoomJoinButton roomId={roomId} via={content.via} />
</Box>
}