Fix gap-loading for Mastodon timelines#482
Conversation
The four timeline endpoints (/api/v1/timelines/{public,home,
list/:list_id,tag/:hashtag}) treated min_id the same as since_id by
running a DESC query with a "newer than" filter, so a request with
min_id pointing into a large gap returned the newest posts above the
cursor instead of the posts immediately above it. Gap-loading clients
such as SubwayTooter could not converge.
Switch min_id to Mastodon's ASC-and-reverse semantics so the response
is the limit posts immediately newer than the cursor, still ordered
newest-first. Also honour since_id (previously declared in the
validator but never applied) and make min_id win when both are
supplied. Timeline responses now include a rel="prev" Link entry
alongside the existing rel="next" so clients no longer have to guess
the cursor parameter name.
The INBOXES sub-queries on /home and /list/:list_id apply the same
ASC switch so they pick the right post IDs before the outer query
runs.
Fixes fedify-dev#479
Assisted-by: Claude Code:claude-opus-4-7
|
@codex review |
|
Codex Review: Didn't find any major issues. Can't wait for the next one! ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request implements Mastodon-compatible pagination semantics for the public, home, list, and hashtag timeline endpoints. Key changes include the introduction of a buildTimelineLinkHeader helper for bidirectional Link headers and updated query logic to correctly handle min_id and since_id parameters. Feedback suggests optimizing the Link header helper by reusing the base URL object and refactoring the repeated pagination logic across endpoints into a shared utility to improve maintainability.
The helper now strips max_id, min_id, and since_id once on a base URL and clones it for each rel link, instead of building two URLs and running the same three deletes twice. fedify-dev#482 (comment) Assisted-by: Claude Code:claude-opus-4-7
Each of the four timeline endpoints derived the same useMinId / lowerBound pair from the query: a literal copy of the Mastodon precedence rule (min_id wins over since_id). Move the derivation into a single helper so a future change to the precedence stays consistent across endpoints. fedify-dev#482 (comment) Assisted-by: Claude Code:claude-opus-4-7
|
/gemini review |
|
@codex review |
|
Codex Review: Didn't find any major issues. More of your lovely PRs please. ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
There was a problem hiding this comment.
Code Review
This pull request updates the min_id handling for the public, home, list, and tag timeline endpoints to strictly follow Mastodon's pagination semantics. Key changes include the introduction of resolveTimelineCursor for consistent parameter priority and buildTimelineLinkHeader for generating bidirectional Link headers. The query logic was modified to use ascending sorts when min_id is present to support gap-loading, and comprehensive tests were added to ensure correct behavior across various cursor combinations. I have no feedback to provide.
#479 reports that clients like SubwayTooter cannot fill missing posts between cached ranges when talking to Hollo's timeline endpoints.
The four timeline endpoints (
/api/v1/timelines/public,/api/v1/timelines/home,/api/v1/timelines/list/:list_id, and/api/v1/timelines/tag/:hashtag) treatedmin_idthe same assince_id: a descending query withposts.id > cursorandLIMIT n. A request withmin_idinside a gap larger thanlimitreturned the newest posts above the cursor, not the posts immediately above it, so the middle of the gap was skipped and gap-loading never converged.Hollo now handles
min_idthe same way Mastodon does. Whenmin_idis set, the query switches toORDER BY id ASCwith the sameLIMIT, then reverses the fetched rows before returning them. The response contains thelimitposts immediately newer than the cursor, still ordered newest-first, so a follow-up request with the newrel="prev"cursor walks up through the gap.The endpoints now also apply
since_id, which the validator already accepted but the timeline queries ignored. When bothmin_idandsince_idare set,min_idwins. Timeline responses now include arel="prev"entry in theLinkheader alongside the existingrel="next", following the pattern already used by src/api/v1/notifications.ts. TheLinkheader now tells clients which cursor parameter to send next.The
TIMELINE_INBOXESsubqueries on/homeand/list/:list_idapply the same ascending-order path internally, so they select the right post IDs before the outer query runs over them.src/api/v1/timelines.test.ts adds five tests against
/publiccovering bidirectionalLinkheaders on a default fetch, large-gap convergence withmin_id,since_idwith descending timeline order,min_idprecedence oversince_id, andLinkheader cursor sanitisation.pnpm testpasses.Closes #479.