Skip to content

pit: page the endings list instead of stopping at 200 - #174

Merged
ralyodio merged 1 commit into
mainfrom
fix/tlds-pagination
Aug 1, 2026
Merged

pit: page the endings list instead of stopping at 200#174
ralyodio merged 1 commit into
mainfrom
fix/tlds-pagination

Conversation

@ralyodio

@ralyodio ralyodio commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

The bug

GET /api/moshpit/tlds returned 200 rows and nothing in the response said it had stopped there. ?limit=, ?offset=, ?page= and ?cursor= were all accepted and silently ignored — parsed on the ?q= search branch, dropped on every other one — so asking for page two returned page one.

That is indistinguishable from a registry that holds exactly 200 endings.

This is not a cosmetic gap. A client reads "absent from the list" as "nobody has claimed it" and acts on it. Meanwhile ?mine=1 returned the complete list, so the two views disagreed about what exists and nothing in either said which to believe. On an account holding 3,684 endings, the plain list showed 5% of them and looked complete.

The fix

Every answer now carries total, limit and offset:

{ "total": 3684, "limit": 200, "offset": 0, "tlds": [ ... ] }
  • Default page stays 200, so nothing reading this today sees different rows — only the news that there are more.
  • The reported limit is the one applied, not the one asked for.
  • ?mine=1 stays unpaged by default. It was the one call telling the whole truth; giving it a default page size would have moved the bug rather than fixed it. It pages when explicitly asked.
  • ?limit= is capped at 1000. Without a ceiling it is a way to ask for every row in the table, which is what a pager exists to avoid.
  • Junk (?limit=abc, 0, -5, ?offset=-1) falls back to the default page rather than erroring or returning nothing.

The ordering, which had to be fixed first

listTlds ordered by created_at DESC alone. A bulk claim writes one timestamp across every ending in it, so created_at is not a total order — a page boundary landing inside a batch shows one ending twice and skips another entirely.

listTldsForUser already documents this and carries the , tld tiebreak. listTlds now does too. Paging a list ordered by a non-unique key is how you get a pager that silently loses rows, so this was a prerequisite rather than a tidy-up.

Tests

New test/moshpit-tlds-pagination.test.mjs — 6 tests, and all 6 fail against main and pass with this change (verified by checking out the old sources and re-running). Coverage: the total is reported, limit/offset are obeyed, paging the whole list start to finish loses nothing and repeats nothing (the tiebreak case, using 260 endings that share one timestamp), the ceiling caps, junk falls back, and ?mine=1 stays whole.

Full apps/pwa suite: 337 pass, 0 fail, 0 skipped.

🤖 Generated with Claude Code

`GET /api/moshpit/tlds` answered with 200 rows and nothing saying it had
stopped. `?limit=` and `?offset=` were parsed on the `?q=` branch and
ignored on every other one, so asking for page two returned page one —
which is indistinguishable from a registry that holds exactly 200 things.

The failure that produces is not a missing feature. A client reads
"absent from the list" as "nobody holds it", and acts on that. The same
list read through `?mine=1` was already complete, so the two disagreed
about what exists and neither said which to believe.

Every answer now carries `total`, `limit` and `offset`. The default page
stays 200 so nothing that reads this today sees different rows, and the
reported limit is the one applied rather than the one asked for.
`?mine=1` stays unpaged by default — it was the call telling the whole
truth, and giving it a default page size would have moved the bug rather
than fixed it — but it pages when asked.

listTlds gains the `created_at DESC, tld` tiebreak listTldsForUser
already documents. A bulk claim writes one timestamp across every ending
in it, so created_at is not a total order, and a page boundary landing
inside a batch repeats one ending and skips another. Paging the list at
all required fixing that first.

`?limit=` is capped at 1000. Without a ceiling it is a way to ask for
every row in the table, which is what the pager exists to avoid.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown

vu1nz Security Review

0 finding(s) in PR #?

No security issues found.

@ralyodio
ralyodio marked this pull request as ready for review August 1, 2026 04:24
@ralyodio
ralyodio merged commit 0a60de7 into main Aug 1, 2026
3 checks passed
@ralyodio
ralyodio deleted the fix/tlds-pagination branch August 1, 2026 04:32
@ralyodio ralyodio mentioned this pull request Aug 1, 2026
ralyodio added a commit that referenced this pull request Aug 1, 2026
install.sh resolves releases/latest, so the ten commits merged since
v0.14.0 have been sitting on main unreachable — including the reason
`curl <name>` did not work.

The headline is parking. A parked name always resolved somewhere, but the
address it pointed at was a host that routes by Host header and answers
"Application not found" for a name it has never heard of, so
`curl scrambled.eggs` resolved and then died one layer up (#180). It could
not be fixed there: the platform rejects a Moshpit ending as a custom
domain and no public CA will certify a TLD outside the DNS root. The
bridge is already running locally for the name to resolve at all, so it
now serves the answer too — parked names point at loopback and a catch-all
responder 302s them to the Pit.

Underneath that was a quieter one. fetchTlds read the first page of the
ending list and stopped; the registry answers 200 rows and reports the
real total, but 200 rows look exactly like a complete list of 200. `.eggs`
sat past that line, so `dns install` wrote a config that did not route it
and the name failed to resolve — a DNS-shaped symptom three layers from
the cause. It was hiding 94% of the namespace: 3707 endings, 200 visible
(#181).

Also here:
  dns resolve now reports a parked name's page in the Pit instead of an IP
  that answers for nobody, with --open to go there (#179)
  the pit's /n/ pages are crawlable — robots.txt, a generated sitemap, and
  canonical tags that name the pit host rather than the app host it shares
  a service with (#175, #176)
  the claim box takes a whole name, claiming the ending first when it is
  free and minting the name under it (#173)
  the endings list pages instead of stopping at 200 (#174), the paste field
  reads names as well as endings (#172), related endings keep the name you
  are reading (#177), and integrations ship JSON support matrices (#178)

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant