listProposals ends in LIMIT ?1 with a default of 100 and a cap, and / never mentions it. A group that has decided a hundred and one things has a record whose oldest entry cannot be reached from any page in this app, and nothing on the screen admits it. A record with a silent horizon is not a record.
What done looks like
[ ] listProposals takes an optional before (a proposal id) and returns, with the rows, whether there are more behind them — from the one query it already runs (ask for limit + 1 rows and drop the extra, never a second COUNT(*) over the table).
[ ] before is validated at the boundary: a whole number, not a numeric string coerced (#41 is the precedent), and anything else is invalid with a sentence.
[ ] / shows an "Older proposals" link when there are more, carrying ?before=<id> alongside whatever ?state= is set (#37) — plain links, no JavaScript, and the link is a <a> a guest can bookmark and share.
[ ] A page of older proposals says where it is ("Proposals older than #41") and links back to the newest, so nobody is stranded on page four with only a back button.
[ ] GET /api/proposals takes before and returns it in its shape, so a script can walk the whole record; the README shows the loop in three lines.
[ ] MCP's list_proposals takes before with one sentence saying how to page, because an agent summarising a group's history is the caller that will hit the cap first.
[ ] The terminal's list pages with the same action rather than raising its own limit.
[ ] Tests: 150 seeded proposals are all reachable by following before to the end, with no row seen twice and none skipped; before on a nonexistent id answers an empty page rather than an error; before as "12" is refused; ?state=decided&before=… keeps both; the last page offers no "Older" link.
[ ] bun test is green and a page of 100 rows still answers in single-digit milliseconds.
Files I expect to change:src/actions.ts, src/actions.test.ts, src/pages/home.ts, src/routes.ts, src/mcp.ts, src/tty.ts, src/server.test.ts, README.md.
Risks: keyset paging on id DESC is correct and an OFFSET is not — with proposals being opened while somebody reads, an offset shows the same row twice and hides another. If #37's sort=active has landed, paging by activity needs its own keyset and this issue does not owe one: page the default order, and say in the pull that the active sort shows its first page only. Do not raise the limit as a substitute for paging; the cap is what makes the page fast.
Fit: Ledger — the record reachable to its last row. A person assigns; I do not.
listProposalsends inLIMIT ?1with a default of 100 and a cap, and/never mentions it. A group that has decided a hundred and one things has a record whose oldest entry cannot be reached from any page in this app, and nothing on the screen admits it. A record with a silent horizon is not a record.What done looks like
listProposalstakes an optionalbefore(a proposal id) and returns, with the rows, whether there are more behind them — from the one query it already runs (ask forlimit + 1rows and drop the extra, never a secondCOUNT(*)over the table).beforeis validated at the boundary: a whole number, not a numeric string coerced (#41 is the precedent), and anything else isinvalidwith a sentence./shows an "Older proposals" link when there are more, carrying?before=<id>alongside whatever?state=is set (#37) — plain links, no JavaScript, and the link is a<a>a guest can bookmark and share.GET /api/proposalstakesbeforeand returns it in its shape, so a script can walk the whole record; the README shows the loop in three lines.list_proposalstakesbeforewith one sentence saying how to page, because an agent summarising a group's history is the caller that will hit the cap first.beforeto the end, with no row seen twice and none skipped;beforeon a nonexistent id answers an empty page rather than an error;beforeas"12"is refused;?state=decided&before=…keeps both; the last page offers no "Older" link.bun testis green and a page of 100 rows still answers in single-digit milliseconds.Files I expect to change:
src/actions.ts,src/actions.test.ts,src/pages/home.ts,src/routes.ts,src/mcp.ts,src/tty.ts,src/server.test.ts,README.md.Risks: keyset paging on
id DESCis correct and anOFFSETis not — with proposals being opened while somebody reads, an offset shows the same row twice and hides another. If #37'ssort=activehas landed, paging by activity needs its own keyset and this issue does not owe one: page the default order, and say in the pull that the active sort shows its first page only. Do not raise the limit as a substitute for paging; the cap is what makes the page fast.Fit: Ledger — the record reachable to its last row. A person assigns; I do not.