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.co
Ada Hopperada.hopper@acme.co
Ada Hopperada.hopper@acme.co
Alan Liskovalan.liskov@acme.co
Alan Liskovalan.liskov@acme.co
Alan Liskovalan.liskov@acme.co
Barbara Turingbarbara.turing@acme.co2FA unknown
Barbara Turingbarbara.turing@acme.co2FA off
Barbara Turingbarbara.turing@acme.co2FA off
Claude Perlmanclaude.perlman@acme.co
Claude Perlmanclaude.perlman@acme.co
Claude Perlmanclaude.perlman@acme.co
Donald Hamiltondonald.hamilton@acme.co
Donald Hamiltondonald.hamilton@acme.co
Donald Hamiltondonald.hamilton@acme.co
Edsger Johnsonedsger.johnson@acme.co
Edsger Johnsonedsger.johnson@acme.co
Edsger Johnsonedsger.johnson@acme.co
Grace Dijkstragrace.dijkstra@acme.co2FA off
Grace Dijkstragrace.dijkstra@acme.co2FA off
Grace Dijkstragrace.dijkstra@acme.co2FA off
Katherine Lovelacekatherine.lovelace@acme.co2FA off
Katherine Lovelacekatherine.lovelace@acme.co2FA off
Katherine Lovelacekatherine.lovelace@acme.co2FA off
Margaret Shannonmargaret.shannon@acme.co· Disabled2FA off
1–25 of 30Page 1 of 2

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.
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.
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.
No members on this page.Page 4 of 2

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.co
Grace Dijkstragrace.dijkstra@acme.co2FA off
Alan Liskovalan.liskov@acme.co
Katherine Lovelacekatherine.lovelace@acme.co2FA off
Edsger Johnsonedsger.johnson@acme.co
Margaret Shannonmargaret.shannon@acme.co· Disabled2FA off
Claude Perlmanclaude.perlman@acme.co
Barbara Turingbarbara.turing@acme.co2FA unknown
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.co
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.coMemberExpired
priya.desai@acme.coAdminExpired
morgan.lee@acme.coOwnerExpired

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.co
Claude Perlmanclaude.perlman@acme.co
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.co
Grace Dijkstragrace.dijkstra@acme.co2FA off
Alan Liskovalan.liskov@acme.co
Katherine Lovelacekatherine.lovelace@acme.co2FA off
Edsger Johnsonedsger.johnson@acme.co
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.co
Ada Hopperada.hopper@acme.co
Ada Hopperada.hopper@acme.co
Alan Liskovalan.liskov@acme.co
Alan Liskovalan.liskov@acme.co
1–5 of 30Page 1 of 6

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.
No members match these filters.

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