Commit Graph
9 Commits
Author SHA1 Message Date
richjohnrambo 440e29975a chore: remove unused web code and dependencies 2026-10-14 14:10:41 +08:00
Your NameandClaude Sonnet 5 4cd2b33c53 Migrate message forwarding to backend REST endpoint
Both real mobile clients (iOS, Android) forward messages via POST
/v1/im/messages/forward, never a client-side {pub} packet -- confirmed
by reading their source directly. Switches web's single-message and
bulk forward paths to the same endpoint, which copies message content
verbatim regardless of shape, eliminating the client-side flat-content
detection this used to require. Comment-sending stays on the existing
sendText() path, unchanged, to preserve current ordering.

Removes send.ts's now-unused forwardMessage/flatForwardPayload.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-25 12:27:37 +08:00
Your NameandClaude Sonnet 5 105ff76959 排查转发bug同类问题:修复查找聊天记录崩溃 + 复制功能复制出"undefined"
审查了所有"读取消息content做数据处理"的地方,找到两处和转发bug同一根因
(手机端扁平非Drafty内容格式)的真实问题:

1. 查找聊天记录(HistorySearchModal)的搜索结果渲染,对手机端发送的图片/文件/
   语音/视频/名片消息命中关键词时会让整个应用崩溃——Drafty.toPlainText在这
   类扁平内容上返回undefined,highlightMatches对undefined调用.toLowerCase()
   直接抛异常,而这个应用完全没有错误边界(ErrorBoundary)兜底,等于白屏。
   已用真实搜索接口返回确认snapshot里其实带着head字段(此前SearchHit类型
   没建模),据此在渲染前识别这几种扁平内容并显示[图片]/[文件]/[语音]/[名片]
   这样的占位文案,不再无脑喂给renderDrafty;顺手给highlightMatches补了防御
   性判空,避免类似疏漏再次崩溃整个应用。

2. 消息右键菜单"复制"功能,对手机端发送的扁平文本消息(如群公告自动发布的
   那条)和图片/文件/语音/视频消息,复制到剪贴板的是字面字符串"undefined"。
   文本类修成正确提取真实文本;附件类直接从"复制"这个操作里去掉(参考名片/
   通话记录消息已有的排除逻辑),复制一段JSON式的附件数据本来就没有意义。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-25 00:06:10 +08:00
Your NameandClaude Sonnet 5 3df61e9e59 接入服务端消息草稿持久化,修复切换会话/刷新页面丢失未发送草稿(禅道#19)
协议文档docs/IM-前后端对接文档.md:1003/1247把`PUT /v1/im/topics/{topic}/draft`
标注成"待实现"、把本地保存写成默认方案——但读im_be自己的源码(server/im_http.go
的serveIMTopicDraft,接的是store.Conversations.GetDraft/PutDraft)确认这个
接口其实真实可用,文档只是没跟上后端进度。之前草稿只有draftByTopic这一份纯
内存状态,没有任何持久化,刷新页面或者(取决于路由是否把ConversationView
整个卸载)切换会话都可能把草稿丢光。

实现:打开一个会话时(每个topic每个会话只拉一次)用GET拉取服务端草稿,如果
本地这个session里还没有内容才用它填充(本地正在打的字优先,不会被服务端的
旧快照打断);输入停止1.5秒后用PUT保存(带乐观锁version),不是按键就存;
切换到别的会话或整个离开消息页面时立即flush掉还没来得及等到防抖的最新内容,
不会因为切太快而丢最后几个字。加了"最后一次确认同步的内容"记录,避免"打开
会话→加载已有草稿→内容变化触发保存"这个误判成"用户编辑了"从而立刻把同一份
内容又存一遍的多余请求。

实机验证(systest04账号,因为systest01/systest02当时正被用户在真实浏览器里
测试):
- 刷新页面:输入框正确恢复之前的草稿内容
- 切到另一个具体会话再切回:正确恢复(走的是服务端往返,不是本地状态留存,
  切换瞬间有个最多一两秒的短暂加载间隙,之后正确填充)
- 输入后不到1.5秒就切走:flush机制确认立即保存,没有丢内容
- 加载已有草稿本身不会触发多余的重复保存(验证了修复前这个问题真实存在——
  版本号无缘无故多跳了一次)
- 发送消息后:本地和服务端的草稿都正确清空

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-16 21:55:55 +08:00
Your NameandClaude Sonnet 5 5c3f3b7653 feat(web): complete groups/contacts/favorites/settings/calls/system domains + extensive review-round fixes
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>
2026-09-09 20:16:33 +08:00
Your NameandClaude Sonnet 5 1bd8d7f804 fix(web): history search used response shapes the real server doesn't send
Confirmed live against the real backend: docs/IM-前后端对接文档.md's flat
`/media` items ({thumbnail_file_id,file_id,width,height}) and flat search
hits ({file:{...}}, {url,title,...}) don't match what the server actually
returns. Both endpoints really return the raw original message (`{txt,fmt,
ent}`, search hits wrapped one level deeper in `snapshot`) -- the same shape
lib/messages/drafty.tsx's renderDrafty already knows how to render, reused
here instead of hand-picking fields from a shape that doesn't exist. This is
also why image/video thumbnails were rendering as generic icons: the field
they were reading was never present in a real response.

Extracted useAuthedBlobUrl (authenticated file-fetch-to-blob-URL) out of
AttachmentPreview into lib/hooks.ts so the history modal's own thumbnail
grid can reuse it instead of duplicating the fetch/header logic.

Also adds the URL safety-check gate (docs/IM-前后端对接文档.md:1822,1898)
before opening a link found via the 链接 tab, which this view didn't have
at all before.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-08 03:58:22 +08:00
Your NameandClaude Sonnet 5 6fcd779530 feat(web): pagination, quote/reply, favorite, and single-message forward
- 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>
2026-09-08 02:39:23 +08:00
Your NameandClaude Sonnet 5 80bf0aa41f feat(web): 查找聊天记录 history search + media grid in the detail panel
Closes a real visual gap flagged directly: the detail panel jumped straight
from the quick-action buttons to 备注/置顶聊天/etc, skipping the "查找聊天
记录" row + 4-icon media grid (图片/视频/文件/链接) that's prominently
visible in the reference design (README §3.4) -- deliberately deferred in
an earlier pass, but a dead-end-free real implementation was worth building
now rather than leaving a visible hole.

- lib/api/messages.ts: searchTopicMessages/getTopicMedia, endpoint shapes
  from docs/IM-前后端对接文档.md:1251-1254 (verified live: both return real
  200s against the actual backend, not just matching the docs on paper).
- HistorySearchModal: centered modal (README §1.5's 弹层 pattern) with the
  five tabs, debounced search (lib/hooks.ts's useDebouncedValue, ported from
  the old app's use-cached-data.js), empty/loading states.
- DetailColumn: wires the row + grid to open the modal on the right tab.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-08 02:27:12 +08:00
Your NameandClaude Sonnet 5 2272b42c6f feat(web): message context menu, recall, and basic multi-select
Right-click a message for 复制/撤回/删除/多选 (README §3.5). 引用/收藏/翻译/
转发 deliberately left out of this pass -- quote and forward need their own
composer-state flows, and there's no confirmed single-message favorite
endpoint yet; a menu item that doesn't work is worse than a smaller correct
menu.

- lib/messages/actions.ts: gates recall to own messages, server-confirmed
  seq (not the SDK's local pending-seq placeholder), and bootstrap's
  message_policy.recall_window_seconds -- scoped to P2P for now, no
  group-admin recall yet.
- lib/api/bootstrap.ts + api/hooks.ts: first GET /bootstrap wiring
  (message_policy, forward limits, etc.), cached indefinitely per session.
- lib/api/messages.ts: recall goes through REST, not native WS -- and
  requires a body `{version, reason}` this backend tracks per-message
  independently of Tinode's own seq (confirmed in
  docs/IM-前后端对接文档.md:1248 and by hitting a live 400
  MALFORMED_REQUEST/version first, since no version field is actually
  delivered anywhere in the WS {data} packet this app reads messages from).
  Defaults to version:1 -- the correct value for any never-before-edited
  message, which covers the common case; a real edit/recall race surfaces
  as a version-conflict error rather than being silently papered over.
- Basic multi-select: enter via the context menu's "多选" (no long-press
  gesture yet), header becomes "已选择 N 条消息"+取消, bottom bar currently
  offers only 删除 (bulk 转发/收藏 wait on those features existing).

Verified end to end: recall correctly withheld past the 120s window and
correctly available within it, the real recall REST call round-trips and
the message disappears locally; multi-select mode enters/exits and
pre-selects the long-pressed/right-clicked message.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-08 02:01:53 +08:00