From 0ace395cfb0c037c0d40b66009396f7036501485 Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 24 Sep 2026 18:02:25 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E7=BE=A4=E8=81=8A=E8=87=AA?= =?UTF-8?q?=E5=B7=B1=E5=8F=91=E6=B6=88=E6=81=AF=E5=90=8E=E8=81=8A=E5=A4=A9?= =?UTF-8?q?=E5=88=97=E8=A1=A8=E5=87=BA=E7=8E=B0=E8=99=9A=E5=81=87=E6=9C=AA?= =?UTF-8?q?=E8=AF=BB=E7=BA=A2=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit reconcileMeSubs() 的周期性 me.getMeta(withLaterSub()) 拉取,对已知会话原有的 回归保护(seq<=prev.seq && unread>prev.unread) 只防得住"seq没变但unread变大" 这一种情形。自己在已打开的会话里发消息时:本地 confirmedReadSeq 会同步立即 更新(早于服务端),但发给服务端的 note read 是异步的;如果这次拉取的请求 恰好在发送前就已经在途,返回时 seq 已经真实前进(不满足 seq<=prev.seq)、 read 却还没追上,旧保护不生效,虚假的 unread 就被原样套用——哪怕这条消息 就是自己刚发的。 补一个独立的、无条件的兜底:用已有的 confirmedReadSeq 记录做下限,read 永远 不会被这次拉取拉回到低于"本客户端已确认看过的位置",与原有保护并存、不冲突。 Co-Authored-By: Claude Sonnet 5 --- web/src/lib/tinode/topics.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/web/src/lib/tinode/topics.ts b/web/src/lib/tinode/topics.ts index 47568324..15984eb0 100644 --- a/web/src/lib/tinode/topics.ts +++ b/web/src/lib/tinode/topics.ts @@ -750,6 +750,24 @@ function reconcileMeSubs() { c.unread = prev.unread; notifyTopic(c.topic); } + // Separate, real bug this same guard doesn't cover (live-confirmed 2026-09-24, reported + // directly): sending your OWN message into a topic that's already open bumps `confirmedReadSeq` + // synchronously (ConversationView's noteRead effect), long before the async `{note read}` this + // relies on has actually reached the server. A `{get}` already in flight since before that + // moment can come back with a real, ADVANCED `seq` (the new message the server just accepted) + // paired with a `read` that hasn't caught up server-side yet -- `seq <= prev.seq` above is + // false (seq genuinely changed), so that guard correctly doesn't fire, and this spurious + // `unread:1` gets applied verbatim even though the user is the one who just sent it. Clamp + // `read` to what THIS client already knows it has confirmed seeing, regardless of what the + // fetch says -- a floor, never pushes `read` past the server's own value when the server is + // genuinely ahead of this record (e.g. read from a different, already-synced device). + const confirmed = confirmedReadSeq.get(c.topic) || 0; + if (confirmed > (c.read || 0)) { + c.read = confirmed; + if ((c.recv || 0) < confirmed) c.recv = confirmed; + c.unread = Math.max(0, (c.seq || 0) - confirmed); + notifyTopic(c.topic); + } } }) .catch(() => {});