avatarBgClass() built its class name via template-literal interpolation
(`bg-avatar-${i + 1}`). Tailwind's build finds utility classes by statically
scanning source text for complete class-name strings -- a dynamically
assembled name is invisible to that scan, so `.bg-avatar-1` through
`.bg-avatar-11` were never generated in the compiled CSS at all. Every
avatar in the app (chat list, conversation header, message bubbles, detail
panel) rendered with a fully transparent background instead of its color --
the element's className looked correct in the DOM, which is why this
shipped across several commits before being caught by checking computed
style rather than the class name itself.
Fixed by writing the 11 class names out as a literal array Tailwind's
scanner can actually see.
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>