Rounds out the full rewrite with the remaining feature domains: group
management (admin panel, mute, files, announcements, QR-based invite/join),
contacts (friend profile, add-friend, blocklist, new-friends), favorites
(real media rendering across text/image/video/voice/file/link), settings
(profile/security/privacy/general/about), calls (Agora-backed audio/video
signaling + call history), and system messages/file assistant.
Also folds in a large batch of live-verified fixes surfaced by repeated
fresh code-review passes and hands-on E2E testing against the real
backend, including: WS reconnect not resubscribing any topic after a
disconnect, missing x-im-client-mid on every send path (protocol
compliance for retry dedup), group mute/member-settings/self-profile
endpoint corrections, relationship-state type fixes across search/QR/
friend-profile, tenant-switch logout gap, password-policy now read from
the real per-tenant config instead of guessed, NavRail highlight/
aria-current desync, several call-state-machine races, and responsive
layout fixes across all four breakpoints (mobile/tablet/laptop/desktop)
including a full mobile rebuild of favorites, call UI, and the settings
home/sub-list split.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- FileAttachment never surfaced a failed authenticated download -- the
card looked identically clickable whether it worked or not, and
clicking while broken silently no-op'd. Added an error state/retry
(useAuthedBlobUrl gained a retry() escape hatch for this).
ImageAttachment/AudioAttachment already handled this; only the file
card didn't.
- --shadow-btn was one token serving two different specs: round 2's fix
(0 6px 16px, from the 50px auth CTAs) actually regressed the 36px
messaging send button (ConversationView/VoiceRecorder), which needs
0 4px 12px per 02-messages.dc.html. Split into --shadow-btn /
--shadow-btn-sm.
- loadOlder's scroll-restore guard compared topicId by value, which
false-positives on an A->B->A round trip within one request's
lifetime (same string, but the DOM was torn down and rebuilt by the
intervening open/close). Switched to a generation counter, same
pattern as topics.ts's leave-race fix.
- errorToast wasn't included in ConversationView/DetailColumn's
topicId-reset effects -- a failed action's toast could keep showing
over a conversation/contact switched to afterward. Also switched
those effects (and MessagesLayout's) from useEffect to
useLayoutEffect -- since none of these components remount on
topicId change, a post-paint effect could let one frame render with
the previous topicId's stale UI still showing.
- VoiceRecorder never listened for MediaRecorder's own onerror -- a
mid-recording failure left the "录音中" timer ticking forever with no
indication the recording had already died.
- VerificationCodeButton's phone-change countdown reset (round 2) only
cleared the button's own state; RegisterPage/ResetPasswordPage's
challengeId was still tied to whatever phone the code was actually
issued for, with nothing cross-checking it against a since-edited
phone number.
- Agreement's checkbox used bare accent-color on a native input, which
only tints the checked fill and leaves size/radius/border to the
browser/OS; replaced with the spec's exact 16x16 border-radius:4px
box + check glyph.
- Added a catch-all route (App.tsx), TenantFeedbackPage image-picker
validation feedback + corrected remove-button style, and a handful of
smaller token/spacing corrections against screens/*.dc.html (NavRail
font-weight, LoginPage/TenantEntryPage logo size+shadow+color,
ChatListColumn row padding/icon size, --shadow-modal alpha).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- DetailColumn: remark-edit draft and the history-search modal's open
state weren't reset on contact switch (no key={topicId}, same long-
lived instance) -- could silently save a typed remark against the
WRONG contact. History-search state lifted to MessagesLayout so
ConversationView's header search icon (previously wired to nothing)
can open the same modal
- ConversationView: loadOlder's in-flight "restore scroll position"
callback and its loading flag weren't guarded against a conversation
switch happening mid-request, and the scroll-to-bottom effect keyed
only on messages.length so two conversations that happened to load
the same page size never got scrolled down on open
- ChatListColumn: pinned 系统消息/文件助手 rows used solid-color-square
avatars like regular contacts; the actual design-code source
(ChatList.dc.html) and reference screenshots use a light tinted
background with a colored icon instead
- MessageBubble: own ("我") messages hash-colored their avatar like any
contact instead of the design's fixed accent blue
- Every send/recall/favorite/forward action swallowed its own failure
silently (empty .catch or, for forward, no catch at all) -- moved
Toast from features/auth into app/ (shared) and wired real error
feedback through it
- MessageContextMenu: added the recall countdown label, Escape-to-close,
and viewport-edge clamping (a right-click near the window edge could
render partially off-screen)
- EmojiPanel now defaults to 最近 per the reference (was defaulting to
表情)
- HistorySearchModal: pagination (searchTopicMessages/getTopicMedia both
supported cursor/next_cursor already, nothing used it -- silently
truncated at one page), Escape-to-close, and a brief "没有找到" flash
on the first keystroke before the debounce caught up
- A handful of design-token corrections (--shadow-btn, NavRail's border/
spacing, LoginPage's logo radius/brand-panel padding, a placeholder
icon color) verified against screens/*.dc.html's exact pixel values
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>