Join a room already caught up on what was said before you
A room being joined for the first time has no marker, and a new buffer started at zero — so the backlog the server replays on JOIN sat entirely after the marker and was announced as unread. Being in a handful of busy channels was enough to read as a hundred and one unread posts in one and a hundred and ten in another, none of which anyone had missed: the reader was not away for them, they were not there yet. So a buffer nobody has seen before starts read rather than at the beginning. To a minute ago, not to this instant: a live line is timestamped by the server and this by our own clock, and where the two disagree a message stamped a few seconds behind us would land under the marker and never be counted at all. A minute is more skew than there will be and far less than the age of any backlog. What it costs is that a line said in the minute before you arrived counts as unread, which is the direction that shows too much rather than too little. Rooms that have been read are untouched — they come back from rooms.edn with a marker of their own and never meet this default — and a DM that arrives while nobody is looking still counts, because it is live and the grace only reaches backwards. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
6b820ee parent: a5354cb modified
src/frq/state.jolt +18 -1 | @@ -290,6 +290,22 @@ | ||
| 290 | 290 | :last-read-id (:id newest) |
| 291 | 291 | :last-read-at (max (:last-read-at buffer 0) (:at newest 0))))) |
| 292 | 292 | |
| 293 | +;; How far back a room nobody has seen before counts as already read. | |
| 294 | +;; | |
| 295 | +;; A room being joined for the first time replays its whole history, and none | |
| 296 | +;; of that is news — the reader was not away for it, they were not here. So a | |
| 297 | +;; new buffer starts caught up rather than at the beginning, or joining a busy | |
| 298 | +;; channel announces a hundred unread posts from before you arrived. | |
| 299 | +;; | |
| 300 | +;; Caught up to a minute ago rather than to this instant, because a live line | |
| 301 | +;; is timestamped by the server and this by our clock: the two disagree by | |
| 302 | +;; whatever the skew is, and a live message stamped a few seconds behind us | |
| 303 | +;; would land under the marker and never be counted. A minute is more skew than | |
| 304 | +;; there will be and far less than the age of any backlog, and what it costs is | |
| 305 | +;; that a message sent in the minute before you joined counts as unread — which | |
| 306 | +;; is the harmless direction. | |
| 307 | +(def ^:private fresh-room-grace-ms 60000) | |
| 308 | + | |
| 293 | 309 | (defn- ensure-channel [m name] |
| 294 | 310 | (if (contains? m name) |
| 295 | 311 | m |
| @@ -298,7 +314,8 @@ | ||
| 298 | 314 | ;; What has been seen, and what the count is derived from. |
| 299 | 315 | ;; `:unread` and `:mention?` are answers, not records — see |
| 300 | 316 | ;; `recount`. |
| 301 | - :last-read-id nil :last-read-at 0 :mention? false | |
| 317 | + :last-read-id nil :mention? false | |
| 318 | + :last-read-at (max 0 (- (clock/now-ms) fresh-room-grace-ms)) | |
| 302 | 319 | :kind (if (dm? name) :dm :channel) |
| 303 | 320 | :peer-did nil :last-activity 0 |
| 304 | 321 | ;; nick -> mode prefix, for the people panel |
| @@ -290,6 +290,22 @@ | |||
| 290 | :last-read-id (:id newest) | 290 | :last-read-id (:id newest) |
| 291 | :last-read-at (max (:last-read-at buffer 0) (:at newest 0))))) | 291 | :last-read-at (max (:last-read-at buffer 0) (:at newest 0))))) |
| 292 | 292 | ||
| 293 | +;; How far back a room nobody has seen before counts as already read. | ||
| 294 | +;; | ||
| 295 | +;; A room being joined for the first time replays its whole history, and none | ||
| 296 | +;; of that is news — the reader was not away for it, they were not here. So a | ||
| 297 | +;; new buffer starts caught up rather than at the beginning, or joining a busy | ||
| 298 | +;; channel announces a hundred unread posts from before you arrived. | ||
| 299 | +;; | ||
| 300 | +;; Caught up to a minute ago rather than to this instant, because a live line | ||
| 301 | +;; is timestamped by the server and this by our clock: the two disagree by | ||
| 302 | +;; whatever the skew is, and a live message stamped a few seconds behind us | ||
| 303 | +;; would land under the marker and never be counted. A minute is more skew than | ||
| 304 | +;; there will be and far less than the age of any backlog, and what it costs is | ||
| 305 | +;; that a message sent in the minute before you joined counts as unread — which | ||
| 306 | +;; is the harmless direction. | ||
| 307 | +(def ^:private fresh-room-grace-ms 60000) | ||
| 308 | + | ||
| 293 | (defn- ensure-channel [m name] | 309 | (defn- ensure-channel [m name] |
| 294 | (if (contains? m name) | 310 | (if (contains? m name) |
| 295 | m | 311 | m |
| @@ -298,7 +314,8 @@ | |||
| 298 | ;; What has been seen, and what the count is derived from. | 314 | ;; What has been seen, and what the count is derived from. |
| 299 | ;; `:unread` and `:mention?` are answers, not records — see | 315 | ;; `:unread` and `:mention?` are answers, not records — see |
| 300 | ;; `recount`. | 316 | ;; `recount`. |
| 301 | - :last-read-id nil :last-read-at 0 :mention? false | 317 | + :last-read-id nil :mention? false |
| 318 | + :last-read-at (max 0 (- (clock/now-ms) fresh-room-grace-ms)) | ||
| 302 | :kind (if (dm? name) :dm :channel) | 319 | :kind (if (dm? name) :dm :channel) |
| 303 | :peer-did nil :last-activity 0 | 320 | :peer-did nil :last-activity 0 |
| 304 | ;; nick -> mode prefix, for the people panel | 321 | ;; nick -> mode prefix, for the people panel |