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>
Rebuilt tenant/login/register/reset-password to match the design handoff's
01-account screens precisely (previously only loosely approximated), and
ported the old app's real business rules instead of guessing:
- LoginPage: desktop split branding panel (>=900px), terms-agreement
checkbox gating submit, forgot-password link, password visibility toggle,
full ctrl-error translation table (lib/tinode/errors.ts, ported from the
old app's CTRL_ERROR_TEXT_CN/translateCtrlError -- registration/login go
over native WS, not REST, so client.ts's REASON_MESSAGES never applied).
- RegisterPage: rebuilt as the real unified username-OR-phone form (not
phone-only) -- username optional unless phone is blank, invite code
required+validated only on the username-only path, matching the server's
actual resolveRegistrationAccount rules. connection.ts's
registerAccountByPhone generalized into registerAccount() supporting both
paths, including the Tinode.credential(null) crash the old app had to
work around on the username-only path.
- New ResetPasswordPage wired to /auth/password-reset/* (a different
endpoint family than registration's /auth/verification-codes/*).
- Shared components: PasswordInput, VerificationCodeButton, Agreement,
Toast.
- Fixed tinode-sdk.d.ts: the real method is getCurrentUserID(), not the
invented getAuthenticatedUserID() -- caught live, login crashed past a
successful auth.
- Fixed a responsive bug present on all four auth pages: fixed w-[380px]
forms overflowed past the viewport edge below ~430px wide.
Verified end-to-end against the real backend (api.118im.com, tenant
"default"): fresh phone registration, password reset, re-login with the
new password, and the username-only path's invite-code rejection all
round-tripped correctly.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Add RegisterPage (phone + SMS code) and lib/api/auth.ts verification-code
endpoints, wired into App.tsx routing.
- Fix registerAccountByPhone(): the injected acc.phone extra field must be
bare 11-digit, not E.164 -- server checks digit length before normalizing.
- Fix LoginCtrl shape: SDK's login/loginBasic/createAccountBasic promises
resolve with the ctrl fields flat, not wrapped in {ctrl: {...}}.
- Fix tinode-sdk.d.ts: the real method is getCurrentUserID(), there is no
getAuthenticatedUserID().
All three bugs found and fixed via live testing against the real backend
(api.118im.com, tenant "default"), confirming registration and login both
work end-to-end.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>