Team

MembersFilterBar, MembersTable, PendingInvitesTable and InviteMemberDialog from packages/ui, against a 30-person roster with real search, filtering, sorting and paging, permission-shaped rows, and switchable failure.

Roster

An Admin actor: two Owners above their reach, one disabled member, mixed 2FA, their own row unmanageable. 30 rows at 25 per page, so page 2 is a real 5-row remainder.

30 members, Admin actor

MemberActions
Ada Hopper (you)ada.hopper@acme.coAdmin
Ada Hopperada.hopper@acme.coMember
Ada Hopperada.hopper@acme.coAdmin
Alan Liskovalan.liskov@acme.coOwner
Alan Liskovalan.liskov@acme.coAdmin
Alan Liskovalan.liskov@acme.coMember
Barbara Turingbarbara.turing@acme.coMember2FA off
Barbara Turingbarbara.turing@acme.coMember2FA off
Barbara Turingbarbara.turing@acme.coMember2FA off
Claude Perlmanclaude.perlman@acme.coMember
Claude Perlmanclaude.perlman@acme.coAdmin
Claude Perlmanclaude.perlman@acme.coMember
Donald Hamiltondonald.hamilton@acme.coAdmin
Donald Hamiltondonald.hamilton@acme.coMember
Donald Hamiltondonald.hamilton@acme.coAdmin
Edsger Johnsonedsger.johnson@acme.coAdmin
Edsger Johnsonedsger.johnson@acme.coMember
Edsger Johnsonedsger.johnson@acme.coAdmin
Grace Dijkstragrace.dijkstra@acme.coOwner2FA off
Grace Dijkstragrace.dijkstra@acme.coMember2FA off
Grace Dijkstragrace.dijkstra@acme.coMember2FA off
Katherine Lovelacekatherine.lovelace@acme.coMember2FA off
Katherine Lovelacekatherine.lovelace@acme.coMember2FA off
Katherine Lovelacekatherine.lovelace@acme.coMember2FA off
Margaret Shannonmargaret.shannon@acme.coMember · Disabled2FA off
1–25 of 30

Search matches name and email; "2FA off" is the filter an access review actually asks for. Filtering resets to page 1 and the count reads "of N matching"; sorting reorders the same set and leaves the count alone.

Empty states

Three different facts, told apart rather than collapsed into one line. Only the no-matches one has a way out of itself.

No matches

MemberActions
No members match these filters.

A filter that matched nothing — distinct from an empty workspace, and offering the way back.

No members yet

MemberActions
No members yet.

Nothing is filtered, so there is nothing to clear — the table says so and stops.

Page past the end

MemberActions
No members on this page.

A bookmarked ?page=4 against a roster that has since shrunk. The pager stays, so Previous is reachable.

Permission shapes

The actions column is driven entirely by each row's own assignableRoles / canRemove — a viewer without team/role:w or team/remove:w never receives a row that allows either.

Read-only viewer

MemberActions
Ada Hopper (you)ada.hopper@acme.coAdmin
Grace Dijkstragrace.dijkstra@acme.coOwner2FA off
Alan Liskovalan.liskov@acme.coOwner
Katherine Lovelacekatherine.lovelace@acme.coMember2FA off
Edsger Johnsonedsger.johnson@acme.coAdmin
Margaret Shannonmargaret.shannon@acme.coMember · Disabled2FA off
Claude Perlmanclaude.perlman@acme.coMember
Barbara Turingbarbara.turing@acme.coMember2FA off
1–8 of 8

team:r only — no row has an assignable role or canRemove, so no row renders a menu.

Single member (just you)

MemberActions
Ada Hopper (you)ada.hopper@acme.coAdmin
1–1 of 1

You can't reassign or remove yourself, so the actions column is empty even though you're an Admin.

Pending invites

No pending invites

PendingInvitesTable renders null — no heading, no empty-state row.

Three pending invites

EmailActions
sam.rivera@acme.coMember · Expired
priya.desai@acme.coAdmin ·
morgan.lee@acme.coOwner ·

The invite to Owner is above the Admin actor's reach, so it has no Revoke action.

Invite a member

InviteMemberDialog

Try "taken@acme.co" — the fixture rejects it with "That user is already a member."

Failure state

Role change always fails

MemberActions
Ada Hopper (you)ada.hopper@acme.coAdmin
Claude Perlmanclaude.perlman@acme.coMember
1–2 of 2

Row menu → Change role → Change role — every attempt fails with "Something went wrong — try again."

320px width

MembersTable in a 320px column

MemberActions
Ada Hopper (you)ada.hopper@acme.coAdmin
Grace Dijkstragrace.dijkstra@acme.coOwner2FA off
Alan Liskovalan.liskov@acme.coOwner
Katherine Lovelacekatherine.lovelace@acme.coMember2FA off
Edsger Johnsonedsger.johnson@acme.coAdmin
1–5 of 5

Last active drops first, then 2FA, then role and status. Nothing is lost on the way down: role and status fold into the member's line, and a member without 2FA keeps an explicit "2FA off" there — only the safe default goes unsaid. The actions menu never scrolls off.

Filter bar and table in a 320px column

MemberActions
Ada Hopper (you)ada.hopper@acme.coAdmin
Ada Hopperada.hopper@acme.coMember
Ada Hopperada.hopper@acme.coAdmin
Alan Liskovalan.liskov@acme.coOwner
Alan Liskovalan.liskov@acme.coAdmin
1–5 of 30

The bar wraps rather than scrolling: the search field takes the first line on its own and the two selects and the direction toggle fall to the second. Filter it down to nothing to see the no-matches state at this width.

No matches in a 320px column

MemberActions
No members match these filters.

The message wraps and the Clear filters button stays inside the column.