diff --git a/client/js/irc/catchup.ts b/client/js/irc/catchup.ts index faa80afb6..6071d09a6 100644 --- a/client/js/irc/catchup.ts +++ b/client/js/irc/catchup.ts @@ -544,7 +544,8 @@ function sendTargets(client: IrcClient, state: TargetsState): void { // From the start: the server's index moves on messages only (not joins, // parts, topics, redactions), so the newest line we hold can be well past // a quiet channel's newest message, and a window starting there would - // leave it out. Newest first, so a long history costs nothing extra. + // leave it out. The limit counts from the first bound, so the newer one + // goes first: the newest `limit` targets, as in the spec's example. const since = new Date(0); const until = new Date(Date.now() + TARGETS_AHEAD_MS); const tags: Record = {}; @@ -559,8 +560,8 @@ function sendTargets(client: IrcClient, state: TargetsState): void { command: "CHATHISTORY", params: [ "TARGETS", - `timestamp=${since.toISOString()}`, `timestamp=${until.toISOString()}`, + `timestamp=${since.toISOString()}`, String(historyLimit(client, TARGETS_LIMIT)), ], }); diff --git a/docs/projects/connect-burst.md b/docs/projects/connect-burst.md index 8cd686266..d91bbc66e 100644 --- a/docs/projects/connect-burst.md +++ b/docs/projects/connect-burst.md @@ -200,7 +200,7 @@ The background catch-up used to go in JOIN order, so the channel that had been written to waited behind every quiet one, and a reconnect fetched every channel whether anything had happened in it or not. Now the first channel to need the background queue on a connection asks one -`CHATHISTORY TARGETS timestamp= timestamp= ` +`CHATHISTORY TARGETS timestamp= timestamp= ` (at the front of the queue) and the catch-up of every other channel is parked until it answers (`catchup.ts`): @@ -210,8 +210,10 @@ parked until it answers (`catchup.ts`): now because nefarious2 drops a target whose newest message is later than it (`filter_targets_window`), and a server clock ahead of ours would lose exactly the channel just written to. The limit is 100 (capped by ISUPPORT - `CHATHISTORY`). nefarious2 lists the channels we may read history of: - those we are a member of, and `+H` (public history) channels without + `CHATHISTORY`). The newer bound goes first: TARGETS takes BETWEEN's + semantics, the limit counting from the first argument, so oldest first + would get the oldest `limit` from a server that follows the spec (#216). + nefarious2 lists the channels we may read history of: those we are a member of, and `+H` (public history) channels without membership (`check_history_access`). So it is asked once the JOINs are in, newest first; a listed `+H` channel we are not in is simply not among the parked ones. diff --git a/test/irc/catchup.ts b/test/irc/catchup.ts index 1dc660edd..60cfd95ab 100644 --- a/test/irc/catchup.ts +++ b/test/irc/catchup.ts @@ -189,11 +189,12 @@ describe("Connect burst: batched JOIN, catch-up paced by fake lag, lazy MODE (ir "#four", ]); - // One TARGETS, first in the queue, from the start of time (nothing is - // held yet) to a day past now. + // One TARGETS, first in the queue, from a day past now back to the + // start of time (nothing is held yet): newer bound first, so a server + // following the spec counts the limit from the newest (issue #216). const [targets] = collect(h, 1).map((s) => s.line); expect(targets).to.match( - /^@label=tg\d+ CHATHISTORY TARGETS timestamp=1970-01-01T00:00:00\.000Z timestamp=2026-08-28T12:00:\d\d\.\d{3}Z 100$/ + /^@label=tg\d+ CHATHISTORY TARGETS timestamp=2026-08-28T12:00:\d\d\.\d{3}Z timestamp=1970-01-01T00:00:00\.000Z 100$/ ); answerTargets(h, targets, [ ["#three", "2026-08-27T11:59:00.000Z"], @@ -604,7 +605,9 @@ describe("Connect burst: batched JOIN, catch-up paced by fake lag, lazy MODE (ir const h = heldThenDropped(HELD); const [targets] = collect(h, 1, isTargets).map((s) => s.line); // From the start: events move what we hold, not the server's index. - expect(targets).to.include("TARGETS timestamp=1970-01-01T00:00:00.000Z "); + expect(targets).to.match( + / TARGETS timestamp=\S+ timestamp=1970-01-01T00:00:00\.000Z \d+$/ + ); answerTargets(h, targets, [ ["#three", "2026-08-27T12:05:00.000Z"], // written to since ["#seance", "2026-08-27T12:04:00.000Z"], // the active one: served already @@ -664,7 +667,9 @@ describe("Connect burst: batched JOIN, catch-up paced by fake lag, lazy MODE (ir h.sent(); joinAll(h); const [targets] = collect(h, 1, isTargets).map((s) => s.line); - expect(targets).to.include("TARGETS timestamp=1970-01-01T00:00:00.000Z "); + expect(targets).to.match( + / TARGETS timestamp=\S+ timestamp=1970-01-01T00:00:00\.000Z \d+$/ + ); answerTargets(h, targets, []); expect(fetched(collect(h, 20).map((s) => s.line))).to.deep.equal([ "#two",