Know a replayed line the server sent no tags for
The backlog is well tagged — of a hundred and one lines the server replayed on JOIN, a hundred carried both `time` and `msgid`, and every one of those timestamps parses. One did not. No tags at all, mid-replay, sitting between two that had them. A line with no msgid is one `seen?` cannot recognise, so it is appended again on every rejoin; a line with no `time` is stamped `now`, because there is nothing else to stamp it with, and is therefore always newer than the read marker. Together that is a buffer that grows a copy per reconnect and a room that cannot be finished reading — three rejoins of a three-line backlog left five messages, all five unread. The doubling is as old as dedupe-by-msgid; the marker is what turned the rest of it into a room that stays unread however often it is opened. An untagged line still has a sender and words, and for a line the server declined to name that is identity enough. Ours and the system's are left out: they have no msgid either, and a second "ok" from this client or a second "alice joined" is an event rather than a replay. What it costs is that one person saying one thing twice, untagged both times, shows once — which only arises when the server has already lost the tags. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
a5354cb parent: 234c54f modified
src/frq/state.jolt +24 -2 | @@ -340,8 +340,30 @@ | ||
| 340 | 340 | ;; what keeps a rejoin from doubling the buffer — and what |
| 341 | 341 | ;; keeps a replayed *pre-edit* row from landing under a line |
| 342 | 342 | ;; already showing the current text. |
| 343 | - seen? (and id (some #(= id (:id %)) | |
| 344 | - (get-in m [channel :messages])))] | |
| 343 | + ;; And sometimes it replays a line with no tags at all — | |
| 344 | + ;; no msgid to know it by and no time to place it. That | |
| 345 | + ;; line has no identity, so it arrives new on every rejoin: | |
| 346 | + ;; appended again, timestamped `now` because there is | |
| 347 | + ;; nothing else to timestamp it with, and therefore always | |
| 348 | + ;; newer than the read marker. Left alone it is a buffer | |
| 349 | + ;; that grows a copy per reconnect and a room that cannot | |
| 350 | + ;; be finished reading. | |
| 351 | + ;; | |
| 352 | + ;; What it does have is a sender and words, which for an | |
| 353 | + ;; untagged line is identity enough. The cost is that the | |
| 354 | + ;; same person saying the same thing twice — both times | |
| 355 | + ;; untagged — shows once. Ours and the system's are left | |
| 356 | + ;; out of it: those have no msgid either, and a second | |
| 357 | + ;; "ok" from this client, or a second "alice joined", is | |
| 358 | + ;; a real event rather than a replay. | |
| 359 | + seen? (if id | |
| 360 | + (some #(= id (:id %)) (get-in m [channel :messages])) | |
| 361 | + (and (not= "*" from) | |
| 362 | + (not= from @form-nick) | |
| 363 | + (some #(and (nil? (:id %)) | |
| 364 | + (= from (:from %)) | |
| 365 | + (= text (:text %))) | |
| 366 | + (get-in m [channel :messages]))))] | |
| 345 | 367 | (cond |
| 346 | 368 | ;; The copy we already hold is the pre-edit one, and this is |
| 347 | 369 | ;; the server's collapsed row saying so. Same message, later |
| @@ -340,8 +340,30 @@ | |||
| 340 | ;; what keeps a rejoin from doubling the buffer — and what | 340 | ;; what keeps a rejoin from doubling the buffer — and what |
| 341 | ;; keeps a replayed *pre-edit* row from landing under a line | 341 | ;; keeps a replayed *pre-edit* row from landing under a line |
| 342 | ;; already showing the current text. | 342 | ;; already showing the current text. |
| 343 | - seen? (and id (some #(= id (:id %)) | 343 | + ;; And sometimes it replays a line with no tags at all — |
| 344 | - (get-in m [channel :messages])))] | 344 | + ;; no msgid to know it by and no time to place it. That |
| 345 | + ;; line has no identity, so it arrives new on every rejoin: | ||
| 346 | + ;; appended again, timestamped `now` because there is | ||
| 347 | + ;; nothing else to timestamp it with, and therefore always | ||
| 348 | + ;; newer than the read marker. Left alone it is a buffer | ||
| 349 | + ;; that grows a copy per reconnect and a room that cannot | ||
| 350 | + ;; be finished reading. | ||
| 351 | + ;; | ||
| 352 | + ;; What it does have is a sender and words, which for an | ||
| 353 | + ;; untagged line is identity enough. The cost is that the | ||
| 354 | + ;; same person saying the same thing twice — both times | ||
| 355 | + ;; untagged — shows once. Ours and the system's are left | ||
| 356 | + ;; out of it: those have no msgid either, and a second | ||
| 357 | + ;; "ok" from this client, or a second "alice joined", is | ||
| 358 | + ;; a real event rather than a replay. | ||
| 359 | + seen? (if id | ||
| 360 | + (some #(= id (:id %)) (get-in m [channel :messages])) | ||
| 361 | + (and (not= "*" from) | ||
| 362 | + (not= from @form-nick) | ||
| 363 | + (some #(and (nil? (:id %)) | ||
| 364 | + (= from (:from %)) | ||
| 365 | + (= text (:text %))) | ||
| 366 | + (get-in m [channel :messages]))))] | ||
| 345 | (cond | 367 | (cond |
| 346 | ;; The copy we already hold is the pre-edit one, and this is | 368 | ;; The copy we already hold is the pre-edit one, and this is |
| 347 | ;; the server's collapsed row saying so. Same message, later | 369 | ;; the server's collapsed row saying so. Same message, later |