Four of the admin's lists stop at a number nobody is told and one has no cap at all, so the shop quietly hides its own rows as it grows.
What the code says today: /admin/products is products({ limit: 500 }) (src/admin.ts:92), /admin/giftcards is giftcards.all() with LIMIT 200 inside it (src/giftcards.ts:61), /admin/mail is outbox(50) rendering fifty whole receipts on one page (src/mail.ts:92), /admin/inventory's ledger is history(50) (src/inventory.ts:70), and discounts.all() (src/discounts.ts:35) has no LIMIT whatever — every code the shop has ever written, rendered. None of the five says how many rows exist, and none offers a second page, in an app whose storefront pages at 24 and says so.
Done looks like
[ ] The pager #46 brings to /admin/orders is used by all five lists — one helper in src/pages/admin.ts, not five.
[ ] Each list module gains the count beside its query: catalog.countProducts already exists; discounts, giftcards, mail and inventory get theirs. The page reads "Showing 25–48 of 314".
[ ] ?page= is validated at the boundary: not a number, zero, negative or past the end is the nearest real page, never a negative OFFSET, never a 500, and always bound — never interpolated.
[ ] discounts.all() takes a limit and an offset like its neighbours, so no query in src/ can return an unbounded list.
[ ] The outbox pages at a size chosen for what it renders — a page of receipts is kilobytes each — and one message keeps its anchor (#email-<id>) so the link from an order still lands on it.
[ ] The terminal reads the same queries and still fills its tabs; nothing there loses rows because a default argument moved.
[ ] Tests that fail before the change: a list with more rows than one page shows the next link and the second page's rows; ?page=0, ?page=-1, ?page=abc and ?page=99999 each answer 200 with a page of the list; and the counts are the counts.
Five lists in one run is the size of it: if it has to be cut, the discounts (uncapped) and the outbox (fifty receipts on one page) are the two that are actually wrong, and the rest is a count and a pager.
Default arguments are read by the terminal as well as by the web pages; changing one changes both, which is the point, but check src/desk.ts rather than assuming.
This waits for #46, which is where the admin's pager is born; building a second one here would be the failure.
Four of the admin's lists stop at a number nobody is told and one has no cap at all, so the shop quietly hides its own rows as it grows.
What the code says today:
/admin/productsisproducts({ limit: 500 })(src/admin.ts:92),/admin/giftcardsisgiftcards.all()withLIMIT 200inside it (src/giftcards.ts:61),/admin/mailisoutbox(50)rendering fifty whole receipts on one page (src/mail.ts:92),/admin/inventory's ledger ishistory(50)(src/inventory.ts:70), anddiscounts.all()(src/discounts.ts:35) has noLIMITwhatever — every code the shop has ever written, rendered. None of the five says how many rows exist, and none offers a second page, in an app whose storefront pages at 24 and says so.Done looks like
/admin/ordersis used by all five lists — one helper insrc/pages/admin.ts, not five.catalog.countProductsalready exists;discounts,giftcards,mailandinventoryget theirs. The page reads "Showing 25–48 of 314".?page=is validated at the boundary: not a number, zero, negative or past the end is the nearest real page, never a negativeOFFSET, never a 500, and always bound — never interpolated.discounts.all()takes a limit and an offset like its neighbours, so no query insrc/can return an unbounded list.#email-<id>) so the link from an order still lands on it.?page=0,?page=-1,?page=abcand?page=99999each answer 200 with a page of the list; and the counts are the counts.Files I expect to change
src/admin.ts,src/pages/admin.ts,src/catalog.ts,src/discounts.ts,src/giftcards.ts,src/mail.ts,src/inventory.ts,src/desk.ts,src/server.test.ts,src/units.test.ts,README.md.Risks
src/desk.tsrather than assuming.Suggested: mason.