feat(api): cursor pagination on remaining flat lists (#191) #203
No reviewers
Labels
No labels
area:auth
area:ci
area:db
area:infra
area:native
area:pwa
area:service
epic
feature
foundation
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
james/carol!203
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "191-pagination-remaining-lists"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #191.
Applies the cursor-pagination convention codified in #179 to the three remaining flat-list endpoints. /api/notes already shipped it as the reference; this PR brings the rest in line.
Endpoints changed
GET /api/educations(start_date DESC, id DESC){ ts: start_date, id }GET /api/profile/contacts(display_order ASC, id ASC){ order, id }GET /api/account/tokens(created_at DESC, id DESC){ ts, id }Each endpoint adds
?cursor=<opaque>&limit=<n>(default 50, max 200, clamped) and returns the standard envelope:Response-shape diff
Before: bare JSON array.
After:
{ data: [...], next_cursor: string | null, has_more: boolean }.Notes
idreplaces the secondarycreated_attiebreaker inProfileContactsRepository.listByUserId. A reorder mutation rewritesdisplay_orderbut does not touchcreated_at, soidis the only stable tiebreaker available for the cursor.GET /api/profilekeeps its embedded contacts inline by design (it's a single bundle, not a list endpoint).docs/api-conventions.mdcalls this out.apps/api/app/(app)/experience/education-section.tsx,apps/api/app/(app)/account/tokens/tokens-client.tsx) are updated to unwrapdataand request?limit=200. Both surfaces show the entire list today — education history is small per user and active PATs are soft-capped at 25. Folding these into proper cursor pagination via@carol/api-clientis left for #184 when the universal client adopts the new shape.@carol/api-client's generated typed surface is regenerated; its drift gate stays green.parseSort/parseFilterhelpers (#192).Test plan
pnpm install --frozen-lockfilepnpm -F @carol/api typecheckpnpm -F @carol/api lintpnpm -F @carol/api testagainst SQLite + Postgres matrix (633 passing)pnpm -F @carol/api openapi:checkpnpm -F @carol/api openapi:coverage(54 routes)pnpm -F @carol/api-client checkRefs:
docs/api-conventions.md§Pagination.apps/api/app/api/notes/route.ts.Switches the education list endpoint from a flat array to the cursor-pagination envelope ({ data, next_cursor, has_more }) per the convention from #179. Ordering stays (start_date DESC) with id DESC as the deterministic tiebreaker; the cursor encodes both. The PWA education screen is updated to unwrap data and request limit=200 — a follow-up under #184 will fold this into @carol/api-client. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>Switches the profile-contacts list endpoint from a flat array to the cursor-pagination envelope ({ data, next_cursor, has_more }) per the convention from #179. Ordering stays (display_order ASC) with id ASC as the deterministic tiebreaker (replacing created_at, which a reorder mutation does not touch — id is stable under reorders). The bundled /api/profile GET still returns its embedded contacts inline by design — it's a single bundle, not a list endpoint. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>Switches the personal-access-tokens list endpoint from a flat array to the cursor-pagination envelope ({ data, next_cursor, has_more }) per the convention from #179. Ordering stays (created_at DESC) with id DESC as the deterministic tiebreaker; the cursor encodes both. The (user_id, revoked_at) index from migration 009 still backs the scan. The PWA tokens screen unwraps data and requests limit=200 — well above TOKEN_MAX_ACTIVE_PER_USER's soft cap of 25. Also regenerates the @carol/api-client typed surface from the updated spec so its drift gate stays green. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>📊 Test coverage
Patch coverage: no testable lines changed.
Overall (
app/,lib/,db/, excluding UI per ADR-0019):Soft thresholds per ADR-0019. Coverage is informational and does not block merge.
Trivy (container image)
Threshold:
high· Total findings: 121 · At/above threshold: 16.27.0, 7.28.0, 8.5.08034950932d868427474