Ask for the backlog freeq restores channels without
A signed-in user's channel came up empty but for its own "Joined #test". The server re-joins an authenticated user's channels at registration and sends JOIN, topic and NAMES for each — but not the history a fresh join is replayed. It leaves that to the client, which had never asked. So the end of the names list is now where a channel notices it was restored rather than joined: an ordinary join has already been replayed its backlog by the time 366 arrives, so a buffer still empty at that point asks for one with CHATHISTORY LATEST. The check ignores system lines, since the join notice is itself the first thing in an otherwise empty buffer. Guests are unaffected — their buffer is full when 366 lands, and no request goes out. Verified against irc.freeq.at both ways: a normal join still comes back with its 102 messages and no duplicates, and an emptied buffer asking for its own history gets 101. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
94e59a2 parent: 75e557e modified
README.md +1 -0 | @@ -101,6 +101,7 @@ surface — that surface does not work on Android either, while the syscalls do. | ||
| 101 | 101 | * Guest connect (`NICK`/`USER`), `001` welcome, `PING`/`PONG` keepalive |
| 102 | 102 | * Auto-joins `#test` on `irc.freeq.at` |
| 103 | 103 | * Join channels, channel buffers with unread counts, send and receive `PRIVMSG` |
| 104 | +* Backlog on join, and `CHATHISTORY` for the channels freeq restores instead | |
| 104 | 105 | * Join/part notices, DMs bucketed under the sender's nick |
| 105 | 106 | * Discover list, search over buffers, disconnect |
| 106 | 107 | * Conversations listed most recently opened first |
| @@ -101,6 +101,7 @@ surface — that surface does not work on Android either, while the syscalls do. | |||
| 101 | * Guest connect (`NICK`/`USER`), `001` welcome, `PING`/`PONG` keepalive | 101 | * Guest connect (`NICK`/`USER`), `001` welcome, `PING`/`PONG` keepalive |
| 102 | * Auto-joins `#test` on `irc.freeq.at` | 102 | * Auto-joins `#test` on `irc.freeq.at` |
| 103 | * Join channels, channel buffers with unread counts, send and receive `PRIVMSG` | 103 | * Join channels, channel buffers with unread counts, send and receive `PRIVMSG` |
| 104 | +* Backlog on join, and `CHATHISTORY` for the channels freeq restores instead | ||
| 104 | * Join/part notices, DMs bucketed under the sender's nick | 105 | * Join/part notices, DMs bucketed under the sender's nick |
| 105 | * Discover list, search over buffers, disconnect | 106 | * Discover list, search over buffers, disconnect |
| 106 | * Conversations listed most recently opened first | 107 | * Conversations listed most recently opened first |
modified
src/frq/state.jolt +13 -0 | @@ -51,6 +51,9 @@ | ||
| 51 | 51 | ;; joined as soon as the server sends 001 |
| 52 | 52 | (def auto-join "#test") |
| 53 | 53 | |
| 54 | +;; How much backlog to ask for when the server did not volunteer any. | |
| 55 | +(def history-limit 100) | |
| 56 | + | |
| 54 | 57 | ;; name -> {:name :messages [{:from :text}] :unread n :joined? bool} |
| 55 | 58 | (defonce channels (atom {})) |
| 56 | 59 | (defonce current (atom nil)) |
| @@ -138,6 +141,16 @@ | ||
| 138 | 141 | ;; already there is just a second line of noise. |
| 139 | 142 | (when fresh? (push-message! ch "*" (str "Joined " ch)))) |
| 140 | 143 | (push-message! ch "*" (str from " joined")))) |
| 144 | + ;; End of NAMES. A plain JOIN is replayed history before this arrives, so | |
| 145 | + ;; a channel that reaches here with nothing in it was restored rather | |
| 146 | + ;; than joined — freeq re-joins an authenticated user's channels at | |
| 147 | + ;; registration and leaves the backlog for the client to ask for. | |
| 148 | + "366" (let [ch (second params) | |
| 149 | + said (remove :system? (get-in @channels [ch :messages]))] | |
| 150 | + (when (and ch @conn (empty? said)) | |
| 151 | + (irc/send-line! @conn | |
| 152 | + (str "CHATHISTORY LATEST " ch " * " history-limit)))) | |
| 153 | + | |
| 141 | 154 | "PART" (let [ch (first params)] |
| 142 | 155 | (if (= from @form-nick) |
| 143 | 156 | (swap! channels #(-> % (assoc-in [ch :joined?] false) |
| @@ -51,6 +51,9 @@ | |||
| 51 | ;; joined as soon as the server sends 001 | 51 | ;; joined as soon as the server sends 001 |
| 52 | (def auto-join "#test") | 52 | (def auto-join "#test") |
| 53 | 53 | ||
| 54 | +;; How much backlog to ask for when the server did not volunteer any. | ||
| 55 | +(def history-limit 100) | ||
| 56 | + | ||
| 54 | ;; name -> {:name :messages [{:from :text}] :unread n :joined? bool} | 57 | ;; name -> {:name :messages [{:from :text}] :unread n :joined? bool} |
| 55 | (defonce channels (atom {})) | 58 | (defonce channels (atom {})) |
| 56 | (defonce current (atom nil)) | 59 | (defonce current (atom nil)) |
| @@ -138,6 +141,16 @@ | |||
| 138 | ;; already there is just a second line of noise. | 141 | ;; already there is just a second line of noise. |
| 139 | (when fresh? (push-message! ch "*" (str "Joined " ch)))) | 142 | (when fresh? (push-message! ch "*" (str "Joined " ch)))) |
| 140 | (push-message! ch "*" (str from " joined")))) | 143 | (push-message! ch "*" (str from " joined")))) |
| 144 | + ;; End of NAMES. A plain JOIN is replayed history before this arrives, so | ||
| 145 | + ;; a channel that reaches here with nothing in it was restored rather | ||
| 146 | + ;; than joined — freeq re-joins an authenticated user's channels at | ||
| 147 | + ;; registration and leaves the backlog for the client to ask for. | ||
| 148 | + "366" (let [ch (second params) | ||
| 149 | + said (remove :system? (get-in @channels [ch :messages]))] | ||
| 150 | + (when (and ch @conn (empty? said)) | ||
| 151 | + (irc/send-line! @conn | ||
| 152 | + (str "CHATHISTORY LATEST " ch " * " history-limit)))) | ||
| 153 | + | ||
| 141 | "PART" (let [ch (first params)] | 154 | "PART" (let [ch (first params)] |
| 142 | (if (= from @form-nick) | 155 | (if (= from @form-nick) |
| 143 | (swap! channels #(-> % (assoc-in [ch :joined?] false) | 156 | (swap! channels #(-> % (assoc-in [ch :joined?] false) |