Consume the team + record bootstrap endpoints
The Settings → firm → team page used to fan out three server prefetches (members, seats & billing, firm roles) on every render; the matter record page fanned out three to four (matter, participants, documents, saved views). The backend (pramaan-functions Story-1779) now exposes one aggregate per page, so this change collapses each page to a single round-trip while keeping the exact same client hooks and props — the data just arrives composed.
What changed
team/page.tsx calls
membersGateway.teamBootstrap() once, then seeds the
three existing per-resource caches with
setQueryData under the generated
appListMembersQueryKey /
appGetSeatsAndBillingQueryKey /
appListFirmRolesQueryKey. The client hooks
(useAppListMembers etc.) hydrate warm — no
refetch flash.
invitations + invitationReconciliation
stay browser→Clerk exactly as before — the producer
deliberately excluded them from the aggregate, so they remain
separate prefetches running in parallel with the one bootstrap
call.
record/page.tsx calls
mattersGateway.recordBootstrap(id) once.
matter / participants.items /
documents.documents pass straight to
LiveMatterDocumentsPage as props; the matter is the
anchor, so a 404 here still renders notFound() via
orNotFound.
savedViews seeds
appListDocumentSavedViewsQueryKey only when
savedViewsAvailable is true. When the backend's
best-effort saved-views sub-query degraded, the page skips seeding
so the client hook refetches live rather than hydrating a
misleading empty list.
Trust boundary (HR-21 / HR-22)
Both new gateway methods fetch as unknown and pass through
assertContract against the generated
teamBootstrapResponseSchema /
recordBootstrapResponseSchema before any caller trusts the
payload. Types and Zod schemas come only from src/gen,
synced from pramaan-functions main (5776381) as the first
commit of this PR. A drifted aggregate logs
api_contract_violation and throws.
Where it lives
src/lib/members/gateway.ts—teamBootstrap(): one read of/firm/team/bootstrap,assertContractonTeamBootstrapResponse.src/lib/matters/gateway.ts—recordBootstrap(matterId): one read of/matters/{id}/record/bootstrap,assertContractonRecordBootstrapResponse.src/app/[locale]/(workspace)/settings/firm/team/page.tsx— one bootstrap call + threesetQueryDataseeds; invitations prefetch unchanged.src/app/[locale]/(workspace)/matters/[matterId]/record/page.tsx— one bootstrap call; props from the composed payload; saved-views seed gated on the flag.openapi/app-api.json+src/gen/**— mirror sync picking up the two new aggregate DTOs (first commit).tests/unit/team-bootstrap-contract.test.ts,tests/unit/record-bootstrap-contract.test.ts— full-shape parse + gateway no-store + drift-rejection coverage.
Verify
pnpm lint pnpm test pnpm build
- Team one bootstrap round-trip for the 3 server resources (was 3); members/seats/roles render with no refetch flash.
- Invitations still client-side Clerk — behavior unchanged.
- Record one bootstrap round-trip (was 3–4); matter / participants / documents render from the composed payload.
- Empty first-run new firm (no extra members) and a matter with zero documents render their real zero states — not a fixture.
- Degrade
savedViewsAvailable=falseskips cache seeding; the register hook fetches live. - Contract a drifted aggregate logs
api_contract_violationand throws at the gateway.
Files changed
29 file(s), +2970 / -60 lines
| Path | Role | + | - |
|---|---|---|---|
| openapi/app-api.json | Config | +250 | -0 |
| src/app/[locale]/(workspace)/matters/[matterId]/record/page.tsx | App route | +21 | -22 |
| src/app/[locale]/(workspace)/settings/firm/team/page.tsx | App route | +20 | -18 |
| src/gen/client/appGetRecordBootstrap.ts | Generated contract | +63 | -0 |
| src/gen/client/appGetTeamBootstrap.ts | Generated contract | +56 | -0 |
| src/gen/client/index.ts | Generated contract | +2 | -0 |
| src/gen/hooks/index.ts | Generated contract | +16 | -0 |
| src/gen/hooks/useAppGetRecordBootstrap.ts | Generated contract | +130 | -0 |
| src/gen/hooks/useAppGetRecordBootstrapSuspense.ts | Generated contract | +129 | -0 |
| src/gen/hooks/useAppGetTeamBootstrap.ts | Generated contract | +118 | -0 |
| src/gen/hooks/useAppGetTeamBootstrapSuspense.ts | Generated contract | +117 | -0 |
| src/gen/index.ts | Generated contract | +66 | -0 |
| src/gen/schemas/recordBootstrapResponse.json | Generated contract | +884 | -0 |
| src/gen/schemas/teamBootstrapResponse.json | Generated contract | +338 | -0 |
| src/gen/types/AppGetRecordBootstrap.ts | Generated contract | +69 | -0 |
| src/gen/types/AppGetTeamBootstrap.ts | Generated contract | +61 | -0 |
| src/gen/types/RecordBootstrapResponse.ts | Generated contract | +42 | -0 |
| src/gen/types/TeamBootstrapResponse.ts | Generated contract | +31 | -0 |
| src/gen/types/index.ts | Generated contract | +25 | -0 |
| src/gen/zod/appGetRecordBootstrapSchema.ts | Generated contract | +88 | -0 |
| src/gen/zod/appGetTeamBootstrapSchema.ts | Generated contract | +83 | -0 |
| src/gen/zod/index.ts | Generated contract | +23 | -0 |
| src/gen/zod/recordBootstrapResponseSchema.ts | Generated contract | +47 | -0 |
| src/gen/zod/teamBootstrapResponseSchema.ts | Generated contract | +36 | -0 |
| src/lib/matters/gateway.ts | Library | +22 | -0 |
| src/lib/members/gateway.ts | Library | +24 | -0 |
| tests/unit/matter-documents-route.test.tsx | Test | +15 | -20 |
| tests/unit/record-bootstrap-contract.test.ts | Test | +128 | -0 |
| tests/unit/team-bootstrap-contract.test.ts | Test | +66 | -0 |