Firm detail now shows the actual firm membership roster
Operators can inspect every accepted firm member on the firm permalink, not only the firm-admin subset or PRAMAAN support roster. The panel consumes the existing generated adminListMembers path backed by GET /admin/firms/{firmId}/members.
What changed
The old FirmAdminsPanel was replaced by FirmMembersRosterPanel in the firm detail composition. The page no longer renders a duplicate admins-only list beside the support roster.
FirmDetailPage prefetches adminListMembersQueryOptions, and the client panel uses useAdminListMembers with the same query. The browser refetch path is /api/internal/firms/[firmId]/members.
firmDetailGateway.listMembersWithResponseHeaders calls the gateway as unknown and parses with generated adminListMembersQueryResponseSchema before the UI trusts the payload.
Operator view
- Columns are
Member,Role,Status, andJoined. - Roles and statuses render through
StatusBadge, preserving the shared admin token semantics. - The panel header shows seat usage and total member count from the member-list envelope.
- Zero-member first-run firms keep the table header and show the direct empty state:
No firm members. - If a default internal-domain row appears, the panel flags it as
Internal-domain row presentfor diagnosis.
Existing contamination path
The previous Firm admins panel rendered firm.admins from the firm-detail aggregate, not the filtered members endpoint. That matches the staging symptom where internal PRAMAAN rows appeared as firm admins.
This Story keeps the scope to the accepted baseline: consume the existing GET /admin/firms/{firmId}/members endpoint. No functions endpoint or reconciliation endpoint was added.
Coverage
Storybook coverage lives at Firms/FirmMembersRosterPanel with populated, empty first-run, and identity-hat-violation stories. Visual checks were captured from Storybook for the populated and empty states.
Unit coverage includes the panel rendering/empty/diagnostic states and the BFF route's generated path/query validation plus x-pramaan-* header forwarding.
Verification proof
Local verification for this story: pnpm exec tsc --noEmit, pnpm lint, NEXT_PUBLIC_ENV=local GATEWAY_SERVICE_KEY=local-test-key pnpm build, pnpm test, pnpm run test:storybook, and git diff --check.
Lint passes with the existing React Compiler warnings around shared TanStack Table usage in src/hooks/use-data-table.ts, src/components/data-table/data-table.stories.tsx, and tests/unit/data-table.test.tsx.