Repository navigation
Description
AMENDED 2026-08-30 — scope split. The body below is left as written, for history. The simulation-fixture and Playwright section is no longer part of this issue: it moved to #627. See this comment for what ships here, what moved, and which acceptance criteria the shipped code deliberately does not meet.
Problem
Three SPA tables render one page from a server-paged endpoint with no paging affordance. Rows beyond that page silently disappear:
| Surface | Call sites | Effective cap | ORDER BY | Consequence |
|---|---|---|---|---|
| Customer book | web/src/routes/CustomersPage.tsx:42 |
default 100 | customer name, then id ascending | Alphabetically later customers vanish; a newly created late-sorting customer can disappear after refresh. |
| Flock bird-movement ledger | web/src/routes/FlocksPage.tsx:217, :233 |
explicit 50; endpoint max 500 | date, then id descending | The oldest movements vanish after the 50th row, including after recording a new movement. |
| Inventory item movement ledger | web/src/routes/InventoryPage.tsx:147 |
explicit 100; endpoint max 500 | date, created timestamp, then id descending | The oldest purchase, usage, and adjustment rows vanish after the 100th movement. |
All three datasets can grow without a product-defined bound. The movement ledgers are append-only, so they become more likely to cross the cap over time.
Existing reusable primitive
web/src/components/usePagedList.ts already owns paging, request sequencing, deduplication, load-more state, error recovery, and loaded-window refresh. History, Expenses, Sales, Feed, Water, and Audit already use it.
The customer endpoint already accepts offset; listCustomers currently exposes only limit. Both movement API helpers already expose limit and offset.
Proposed scope
- Migrate CustomersPage, the Flocks bird ledger, and the Inventory movement ledger to
usePagedList. - Expose
offsetfromlistCustomers. - Render the existing translated Load more affordance while another page may exist.
- Preserve the currently loaded window after create/record/correct mutations instead of collapsing back to page one.
- Keep each repository's existing stable ordering.
Acceptance criteria
- Every row remains reachable after the first page on all three surfaces.
- A full page exposes Load more; a short page withdraws it; an in-flight load cannot be started twice.
- A failed extension keeps already loaded rows and permits retry; a failed replacement does not leave stale rows under a new context.
- Recording a bird movement or inventory mutation refreshes all pages the user had loaded.
- Creating a customer refreshes the loaded customer window without silently hiding already reached rows.
- Focused frontend tests cover crossing each old cap and loading the next page.
- No hardcoded English is introduced.
Not in scope
The paged/searchable flock and customer selectors are tracked separately because a native <select> needs a different reusable UI primitive.
Implementation order
This is the first implementation group.
- Complete this issue by reusing the existing
usePagedListhook for Customers, bird movements, and inventory movements. - Follow with Add a reusable searchable paged entity picker for flock and customer selectors #512 for the larger cross-layer searchable picker work.
- Do not implement Five screens silently truncate the flock list at 100, ordered by name #509 separately; Add a reusable searchable paged entity picker for flock and customer selectors #512 is its implementation vehicle and should close both Add a reusable searchable paged entity picker for flock and customer selectors #512 and Five screens silently truncate the flock list at 100, ordered by name #509.
#511 and #512 are technically independent, but this issue goes first because it uses an established primitive and has the smaller design surface.
Related issues
- Five screens silently truncate the flock list at 100, ordered by name #509 — originating flock-selector truncation defect discovered by E2E.
- Add a reusable searchable paged entity picker for flock and customer selectors #512 — second implementation group for reusable flock/customer pickers; it also closes Five screens silently truncate the flock list at 100, ordered by name #509.
Simulation and E2E fixture
Expand SimulationDataSeeder with deterministic over-cap bands owned by this issue:
- at least 101 customers, including a named sentinel that sorts after the first 100;
- at least 51 bird movements for one known flock, including an oldest-page sentinel;
- at least 101 inventory movements for one known inventory item, including an oldest-page sentinel.
Use the normal handlers/domain paths and deterministic existence probes so a clean rerun converges to AlreadySeeded. Update ComputeExpectedCounts, SimulationManifestCounts/lifecycle metadata as applicable, ValidateCounts, the manifest fingerprint, and SimulationSeedCommandTests; the fixture must continue to certify exact counts rather than a >= floor.
Add focused Playwright coverage that reaches each sentinel through Load more. Keep the fixture names deterministic and verify the existing quick suite still targets intended rows after the customer catalog crosses 100; do not mask a broken write with tolerated statuses.
If new Simulation:* options are introduced instead of structural constants, update SimulationOptions, tools/simulation/bootstrap.sh, docker-compose.sim.yml, and verify-harness.sh in the same change.
Activity
mforce commentedon Aug 31, 2026
Amendment — scope split, 2026-08-30
What is being implemented now: the SPA half only — CustomersPage, the FlocksPage bird-movement ledger and the InventoryPage movement ledger migrate onto the existing
usePagedListhook, andlistCustomersgainsoffset. That is branchfix/511-page-truncated-tables, and it meets this issue's AC list for "Proposed scope" and acceptance criteria 1–7 plus theoffsetand stable-ordering bullets, verified by Vitest.What this issue's body still asks for and the SPA PR deliberately does NOT deliver: everything under "## Simulation and E2E fixture", and the Playwright coverage that reaches each sentinel through Load more. Those are now #627.
Why they were split. The fixture section is much larger than its wording implies. Read at
fae8d821:SimulationDataSeederseeds 3 customers (CustomersWanted,SimulationDataSeeder.cs:1025), not the ≥101 the section wants.BirdMovementwrite exists anywhere in the seeder, so the ≥51-movement band is new seeding machinery rather than a raised count.SimulationManifestCountscarries no customer field and no bird-movement field, so the bands need new fields on that record; the fingerprint is a SHA256 over a canonical serialization that includescounts, so every pinned fingerprint moves andSimulationSeedCommandTests,SimulationSeederTestsandSimulationCrossDayRerunTestsall re-pin.InventoryMovementsTotaland theAdjustment/Discardsplit, whichComputeExpectedCountsderives arithmetically andValidateCountsasserts exactly.It also collides with PR #626 (#625), which is editing
SimulationDataSeeder.csandSimulationSeederTests.csright now.Nothing is dropped. #627 carries the fixture section verbatim plus the findings above, and names the acceptance criteria the SPA PR does not meet so they are not left to rot. This issue closes when the SPA PR merges; #627 carries the rest.
test(web): resolve paged-list rows by cell text, not accessible name (#…
test(web): raise the paging suites' per-test budget, not the run's (#750