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>
- ConversationView: infinite-scroll "加载更多" -- scroll-to-40px triggers
topic.msgHasMoreMessages()+getMessagesPage() to fill exactly the missing
seq ranges (not naive since/before paging, since server-side deletions can
punch holes in an otherwise-contiguous range), with scroll position
preserved across the prepend.
- lib/tinode/send.ts: sendReply() ports the old app's exact quote
construction (Drafty.replyContent truncates, Drafty.quote wraps with the
sender's name, Drafty.append inlines the whole quote block ahead of the
new text -- the receiving side never needs to look up the original
message). forwardMessage() sends a single message directly to one picked
target topic (ForwardPickerModal) -- simplified from the old app's
stage-then-compose flow, trading that nicety for a much smaller dialog.
- lib/messages/drafty.tsx: added the 'QQ' Drafty style-tag case ported
against the SDK's actual Drafty.quote() output shape (no `key`/`data` of
its own -- verified by reading tinode.dev.js, not guessed).
- lib/api/messages.ts: favoriteMessages() -- a single message is just a
one-item batch against POST /v1/im/favorites/batch (confirmed in
docs/IM-前后端对接文档.md:1935).
- Context menu now shows 引用/复制/收藏/转发/多选/[撤回]/删除 in spec order
(README §3.5); 翻译 still deliberately absent (no real backend for it,
confirmed by research).
Verified end to end: sent a real quote reply and confirmed the exact wire
packet (head.reply, QQ fmt span, MN entity) and that it renders correctly
via the new QQ formatter case; favorite POSTed and returned created_count:1;
forward POSTed the expected {head:{forwarded:"topic:seq"}} packet.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Phase 2 core loop: chat list and single-chat conversation now read/write real
Tinode topic data end to end, replacing the earlier hardcoded placeholders.
- lib/tinode/topics.ts: per-topic pub-sub layered on connection.ts's pattern
(Topic.onData/onInfo/etc are single-slot callbacks, claimed once and fanned
out). Implements the old app's subscribe lifecycle: `me` subscribed once
per session (metadata only), each contact gets a one-shot preview
subscription (withLaterData(1), never left), opening a conversation
leaves-then-resubscribes with a full history page (withData, not
withLaterData -- a preview prefetch already nudging _maxSeq would make
withLaterData return empty).
- lib/tinode/hooks.ts: useChatList()/useTopicMessages() via
useSyncExternalStore, snapshots cached and only invalidated on real
topic-change notifications (not recomputed every render).
- lib/tinode/send.ts: sendText/retryMessage via createMessage+publishDraft
(optimistic local echo), not the simpler publish() -- matches the old
app's approach. retryMessage flushes the failed local entry and rebuilds
before republishing, per the old app's context-menu retry handler.
- lib/messages/{time,avatar,attachments,status,preview,dispatch,drafty}:
formatting utilities. Avatar color hash ports the old app's Java-style
string hash but with modulus 11 (this design's palette) not the old app's
16. @mention renders flat accent blue per the new design, not the old
app's per-user hash color -- a deliberate simplification. File-type icons
(PDF/Word/Excel/PPT) are new-design-only; the old app buckets all
documents identically. Date/time formatting is written fresh against the
design spec (today+time combined, weekday tier) since the old app never
combines a relative-date word with a time in one string.
- ChatListColumn, MessageBubble, ConversationView: real components replacing
the placeholder chat list and "占位" conversation view.
- tinode-sdk.d.ts: expanded with the real Topic/MetaGetBuilder/Drafty
surface needed above, verified against the installed SDK source
(createMessage/publishDraft/messages/msgStatus/noteRead/flushMessage/
Drafty.format's exact treeBottomUp contract, MESSAGE_STATUS_* statics).
Verified end to end against the real backend: used the still-functional old
app (a second account, ixp355087) to create a real friendship and exchange
messages with the new app's test account, confirming the new chat list and
conversation view correctly render pre-existing real data, sending a new
message shows the right optimistic-then-delivered status transition, and
the old app receives it live in return.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>