Skip to content

[Performance] Isolate dialog state to scoped row components to eliminate table re-renders#2807

Open
RajuGangitla wants to merge 8 commits intoinkeep:mainfrom
RajuGangitla:main
Open

[Performance] Isolate dialog state to scoped row components to eliminate table re-renders#2807
RajuGangitla wants to merge 8 commits intoinkeep:mainfrom
RajuGangitla:main

Conversation

@RajuGangitla
Copy link
Copy Markdown

@RajuGangitla RajuGangitla commented Mar 24, 2026

Fixes: #2806

Summary:
Refactored MembersTable to eliminate cascading re-renders caused by dialog state living at the table level. Previously, opening any dialog (invite, change password, delete confirm, role change, revoke) triggered a full re-render of all 50+ rows.

Benchmark results (50 members/invitations) (After Optimisation) :

  1. Opening a Invite Dialog 300ms (approx) ----- > less than 120 ms (proof : Please check the attached image or video for more reference)

Changes:

Extracted InviteButton — owns inviteDialogOpen state + InviteMemberDialog
Extracted MemberRow — owns all per-member dialog state: ChangePasswordDialog, ProjectAccessDialog, MemberConfirmationModals (delete + role change)
Extracted InvitationRow — owns revokeModal + MemberConfirmationModals
MembersTable now has zero useState calls — pure layout/sorting component

please check this video:
opti-members.webm

image

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Mar 24, 2026

⚠️ No Changeset found

Latest commit: a76e700

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@vercel
Copy link
Copy Markdown

vercel bot commented Mar 24, 2026

@RajuDev0 is attempting to deploy a commit to the Inkeep Team on Vercel.

A member of the Team first needs to authorize it.

@itoqa
Copy link
Copy Markdown

itoqa bot commented Mar 24, 2026

Ito Test Report ❌

16 test cases ran. 1 failed, 15 passed.

Overall, the unified members-page QA run passed 15 of 16 test cases, confirming stable behavior for core admin workflows and edge cases including page/table rendering, invitation creation and pending-row actions, dialog reset and rapid-click isolation, row-scoped role/delete/revoke flows, idempotent destructive actions, empty-state rendering, clipboard-denial resilience, invitation-link tamper rejection and URL-encoding safety, and non-admin access restrictions. The only failure was a medium-severity concurrent multi-tab stale-confirmation bug where revoking an invite in one tab and then confirming a stale revoke modal in another can incorrectly show a success toast instead of an already-revoked conflict state, indicating missing conflict handling in the revoke/confirmation logic.

❌ Failed (1)
Category Summary Screenshot
Edge 🟠 Stale revoke confirmation does not handle invite-already-revoked conflicts and can show duplicate success instead of a conflict error state. EDGE-4
🟠 Stale revoke confirmation reports success after invitation already changed
  • What failed: The stale confirmation path can still close as success and show an "Invitation revoked" success toast instead of surfacing a conflict/already-revoked error and recovery state.
  • Impact: Admins can receive false success feedback in concurrent-tab workflows and miss that the action conflicted with already-mutated server state. This weakens trust in destructive-action feedback and complicates incident debugging.
  • Introduced by this PR: Yes – this PR modified the relevant code
  • Steps to reproduce:
    1. Open /default/members in two tabs as an admin user.
    2. In tab A, open Revoke invite for a pending invitation and leave the confirmation modal open.
    3. In tab B, revoke the same invitation and complete the action.
    4. Return to tab A and confirm revoke in the stale modal.
    5. Observe that the stale flow can resolve as success instead of showing a conflict/already-revoked error state.
  • Code analysis: I reviewed the invitation-row revoke flow and shared confirmation hook. InvitationRow treats any cancelInvitation response without error as success and always toasts success, while the generic confirmation hook always closes the modal after resolved onConfirm with no stale-conflict branch or retry/reload guidance.
  • Why this is likely a bug: The production code has no branch to detect and present stale-conflict outcomes, so stale confirmations can be reported as successful even when the invitation was already revoked elsewhere.

Relevant code:

agents-manage-ui/src/components/members/invitation-row.tsx (lines 25-43)

const handleRevokeInvitation = async (inv: Invitation) => {
  setRevokingInvitation(inv.id);
  try {
    const { error } = await authClient.organization.cancelInvitation({
      invitationId: inv.id,
    });

    if (error) {
      toast.error('Failed to revoke invitation', {
        description: error.message || 'An error occurred while revoking the invitation.',
      });
      return;
    }

    toast.success('Invitation revoked', {
      description: `Invitation to ${inv.email} has been revoked.`,
    });
    onInvitationRevoked?.();

agents-manage-ui/src/components/members/hooks/use-confirmation-modal.ts (lines 23-34)

const handleConfirm = async () => {
  if (!data) return;

  setIsLoading(true);
  try {
    await options.onConfirm(data);
    closeModal();
  } catch (error) {
    setIsLoading(false);
    throw error;
  }
};
✅ Passed (15)
Category Summary Screenshot
Adversarial Rapid confirm spam on revoke modal remained idempotent, removing each target invite once without UI instability. ADV-1
Adversarial Rapid Add interactions still rendered only one Add Team Members dialog with no stacked overlays. ADV-2
Adversarial Non-admin user could not access admin-only controls on the members page. ADV-3
Adversarial Revoked and fabricated invitation links both resolved to invalid invitation states, and API verification transitioned from valid (pending) to not_found after revoke. ADV-4
Adversarial Not a real bug. Previous failure was caused by clipboard-read tooling timeout. Re-verification confirms invite links URL-encode email payload before copy/render, so malicious characters remain encoded and are not executed. ADV-5
Edge Empty-state behavior is correctly implemented; no product defect confirmed. EDGE-1
Edge Closing and reopening Add Team Members reset validation state, input content, and role selection as expected. EDGE-2
Edge Clipboard write denial did not break invite actions or page interactivity. EDGE-5
Logic Invite flow opened from Add, sent a new invitation, and showed the pending row in-table after Done. LOGIC-1
Logic Cross-row delete/role interactions stayed scoped to the intended member row, with correct confirmation and follow-up dialogs. LOGIC-2
Logic Demoting a secondary admin to Member opened Assign to Projects for the same user and completed assignment successfully. LOGIC-3
Logic Delete-member confirmation remained row-scoped; rapid confirm input produced one effective deletion and refreshed table state. LOGIC-4
Logic Revoke modal targeted the correct invitation email, revoke succeeded, and the invitation row was removed from the members table. LOGIC-5
Happy-path Members page rendered with admin controls and expected table structure on /default/members. ROUTE-1
Happy-path Pending invitation rendered as its own row with Copy invite link and Revoke invite actions available. ROUTE-2

Commit: 6c1c974

View Full Run


Tell us how we did: Give Ito Feedback

@vercel vercel bot temporarily deployed to Preview – agents-docs March 24, 2026 16:40 Inactive
@vercel
Copy link
Copy Markdown

vercel bot commented Mar 24, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
agents-docs Skipped Skipped Mar 31, 2026 1:43am

Request Review

@itoqa
Copy link
Copy Markdown

itoqa bot commented Mar 24, 2026

Ito Test Report ✅

19 test cases ran. 19 passed.

The unified members-page QA run passed 19/19 test cases with zero failures, confirming baseline admin access and rendering on /default/members (including a successful /api/auth/dev-session 200 {"ok":true} checkpoint) and overall stability across member/invitation management flows. Most importantly, authorization and safety controls held under adversarial behavior: non-admin users remained strictly read-only with no admin controls exposed via deep links/history churn, row-scoped modals and loading prevented cross-row mis-targeting during rapid or concurrent actions, invite workflows correctly handled reset/validation/mixed outcomes/double-submit and malicious input without side effects, revoke/copy-link behaviors were correct (single effective revoke, same-origin URL-encoded links), and edge usability conditions (empty-tenant state, refresh/back-forward interruptions, and mobile viewport interactions) all behaved as intended.

✅ Passed (19)
Category Summary Screenshot
Adversarial Rapid double-submit produced a single effective pending invitation for the target email. ADV-1
Adversarial Rapid repeated revoke confirms produced one effective revoke without cross-row side effects. ADV-2
Adversarial Rapid cross-row interactions kept action context and modal targeting tied to the initiating row. ADV-3
Adversarial Script/SQL/javascript-like payloads were treated as invalid text with no execution or navigation side effects. ADV-4
Adversarial Deep-link/session abuse attempts (back/forward/refresh and route transitions) did not expose admin-only controls for non-admin users. ADV-5
Adversarial Copied invite links remained on localhost and preserved URL-encoded email values correctly. ADV-6
Edge Empty-tenant members view correctly shows a single No members yet. row with no pending badges or row action controls. EDGE-1
Edge Mixed success/error results were shown once, and reopening after Done reset to a clean default form. EDGE-2
Edge Malformed comma-separated emails were rejected with Invalid email format feedback before submission. EDGE-3
Edge Refresh/back-forward interruption checks left no frozen overlays, and modal flows remained interactive. EDGE-4
Edge Mobile viewport (390x844) kept row actions and Add dialog controls reachable with scroll restored after close. EDGE-5
Logic Add dialog opened, canceled, and reopened with clean state and no overlay leakage. LOGIC-1
Logic Role-change confirmation stayed bound to the selected row and only updated that member. LOGIC-2
Logic Revoke confirmation stayed bound to the selected invitation row, including under rapid interactions. LOGIC-3
Logic Admin-to-member demotion opened project assignment and saved the updated member role state. LOGIC-4
Logic Delete guardrails blocked protected rows and successful delete removed only the intended member. LOGIC-5
Resilience Concurrent row updates remained row-scoped and did not freeze unrelated row actions. RESILIENCE-1
Happy-path Admin members page loaded locally without login redirect; members header/count, Add button, and composed admin name/email row were visible; dev-session API checkpoint returned 200 with {"ok":true}. ROUTE-1
Happy-path Non-admin direct navigation to members preserved read-only behavior: no Add/destructive controls, static role badges, and read-only Project Access interactions. ROUTE-2

Commit: ffa6a1e

View Full Run


Tell us how we did: Give Ito Feedback

@vercel vercel bot temporarily deployed to Preview – agents-docs March 25, 2026 04:00 Inactive
@itoqa
Copy link
Copy Markdown

itoqa bot commented Mar 25, 2026

Ito Test Report ❌

20 test cases ran. 1 failed, 19 passed.

The unified members-management run completed 20 test cases with 19 passing and 1 failing, confirming expected behavior across page rendering and self-guardrails, invitation create/revoke/link format flows, role and project-access updates, password actions, non-admin permission boundaries, mobile/refresh-navigation resilience, and adversarial safety/race scenarios (including malformed input rejection and tampered invitation-link handling). The only significant finding was a medium-severity pre-existing defect in Copy invite link where clipboard write failures are not awaited/caught, causing a false “Invite link copied” success toast even when copying is denied, which can mislead users and disrupt invitation sharing.

❌ Failed (1)
Category Summary Screenshot
Adversarial 🟠 Clipboard-denied copy can still show a misleading success toast because clipboard write rejection is not handled. ADV-6
🟠 Clipboard-denied invite copy still shows success toast
  • What failed: The UI reports success (Invite link copied) even when the clipboard write fails, so the user gets a false-positive result instead of an error.
  • Impact: Users can believe an invite link was copied when it was not, which causes sharing failures and support friction. This can repeatedly break invitation workflows in restricted browser environments.
  • Introduced by this PR: No – pre-existing bug (code not changed in this PR)
  • Steps to reproduce:
    1. Sign in as an admin and open /default/members.
    2. Open the actions menu for a pending invitation.
    3. Run in an environment where clipboard write is denied (or override navigator.clipboard.writeText to reject).
    4. Click Copy invite link and observe that a success toast still appears even though copy failed.
  • Code analysis: I reviewed the invitation action handler path in the members UI and found the copy action does not await or catch clipboard failures; the success toast is emitted unconditionally. Runtime code changes for this run only touched local dev server config and do not affect invitation copy behavior.
  • Why this is likely a bug: The production handler always shows a success message without verifying clipboard success, so denied writes deterministically produce incorrect UX feedback.

Relevant code:

agents-manage-ui/src/components/members/components/invitation-actions-menu.tsx (lines 26-30)

const handleCopyInviteLink = () => {
  const link = `${window.location.origin}/accept-invitation/${invitation.id}?email=${encodeURIComponent(invitation.email)}`;
  navigator.clipboard.writeText(link);
  toast.success('Invite link copied');
};

agents-manage-ui/src/components/members/invitation-row.tsx (lines 75-79)

<InvitationActionsMenu
  invitation={invitation}
  isOrgAdmin={isOrgAdmin}
  onRevokeInvitation={(inv) => revokeModal.openModal(inv)}
/>
✅ Passed (19)
Category Summary Screenshot
Adversarial Submitting mixed malicious/malformed invite payload produced validation rejection (invalid email format) without unsafe rendering or script execution. ADV-1
Adversarial Non-admin session on /default/members remained restricted: no Add entry point, no editable role/project controls, and no privileged flows reachable via shortcut attempts. ADV-2
Adversarial Tampered accept-invitation URL payloads were handled safely with a controlled invalid invitation outcome and no script execution. ADV-3
Adversarial Duplicate-tab revoke race completed gracefully; after refresh, the invitation was absent in both tabs and UI remained interactive. ADV-4
Adversarial Rapid role-toggle resolved to one confirmed target role, and persisted state matched after reload. ADV-5
Edge Empty-state behavior is implemented correctly; no product defect was identified in the members table rendering path. EDGE-1
Edge Canceling one row dialog then opening another row dialog preserved row identity with no stale cross-row state. EDGE-3
Edge Invite dialog double-submit protection is enforced by submit-loading state and disabled controls. EDGE-4
Edge Revoke confirmation repeated clicks are guarded by loading/disable behavior, preventing duplicate effects. EDGE-5
Edge Refresh with an open dialog and back/forward navigation recovered to a usable members page without ghost modal state. EDGE-6
Edge Mobile viewport kept members actions reachable, and Add/Revoke dialogs remained operable and dismissible. EDGE-7
Happy-path Members page rendered with expected admin controls, member count alignment, and self-guardrail protections. ROUTE-1
Happy-path Add invitation flow validates input, creates invite, and refreshes pending invitations after close. ROUTE-2
Happy-path Revoke invitation path confirms, calls cancel API once, and refreshes list state. ROUTE-3
Happy-path Member was promoted from Member to Admin via row role dropdown, and the role persisted after reload. ROUTE-4
Happy-path Demotion from Admin to Member opened the Assign to Projects follow-up dialog for the same member. ROUTE-5
Happy-path Project access assignment and role update saved successfully and persisted after reopening the dialog. ROUTE-6
Happy-path Change Password mismatch validation and other-user Reset password action both behaved correctly without UI lockup. ROUTE-7
Happy-path Copied invite link uses the expected accept-invitation route and URL-encoded email query. ROUTE-8

Commit: a0ac351

View Full Run


Tell us how we did: Give Ito Feedback

@vercel vercel bot temporarily deployed to Preview – agents-docs March 25, 2026 08:58 Inactive
@itoqa
Copy link
Copy Markdown

itoqa bot commented Mar 25, 2026

Ito Test Report ❌

19 test cases ran. 1 failed, 18 passed.

Overall, 19 end-to-end checks on /default/members and related invitation flows ran with 18 passes and 1 failure, confirming stable behavior across route rendering/sorting, admin and non-admin permission boundaries, invite validation and mixed outcomes, revoke/delete actions, role changes, project access management, navigation/refresh idempotency, mobile viewport usability, and invitation-link tampering safety. The single confirmed product issue is a medium-severity bug in Add Team Members results where clicking Copy link shows a success toast even when clipboard-write permission is denied, causing false-positive feedback and potential invite-sharing delays.

❌ Failed (1)
Category Summary Screenshot
Adversarial 🟠 Invite Copy link reports success even when clipboard write fails with permission denial. ADV-3
🟠 Clipboard denial is misreported as successful invite link copy
  • What failed: The UI shows a success toast (Invite link copied) even when clipboard write is denied. Expected behavior is an error toast or other failure state consistent with the actual write failure.
  • Impact: Users may believe an invite link was copied when it was not, causing failed sharing attempts and onboarding delays. This creates false-success feedback in a core invite workflow.
  • Introduced by this PR: No – pre-existing bug (code not changed in this PR)
  • Steps to reproduce:
    1. Open /default/members as an admin and submit an invitation from Add Team Members.
    2. Configure clipboard-write to be denied for the browser session.
    3. In the Team Members Added result state, click Copy link for a successful invitation.
    4. Observe that a success toast appears even though clipboard write is denied.
  • Code analysis: I reviewed invite and member action code paths in the manage UI. The invite copy-link handler calls navigator.clipboard.writeText without awaiting or handling rejection, then immediately emits a success toast; in contrast, reset-password copy awaits the clipboard write inside try/catch and correctly reports failures.
  • Why this is likely a bug: The invite flow emits success without checking clipboard-write outcome, which directly contradicts the observed permission-denied runtime behavior and the safer error-handled pattern used elsewhere in the same feature area.

Relevant code:

agents-manage-ui/src/components/members/invite-member-dialog.tsx (lines 270-281)

{result.status === 'success' && result.link && (
  <Button
    type="button"
    size="sm"
    variant="ghost"
    className="h-7 px-2 text-xs gap-1 shrink-0"
    onClick={() => {
      if (result.link) {
        navigator.clipboard.writeText(result.link);
        toast.success('Invite link copied');
      }
    }}
  >

agents-manage-ui/src/components/members/member-row.tsx (lines 121-133)

const result = await createPasswordResetLink({
  tenantId: organizationId,
  email: member.user.email,
});
await navigator.clipboard.writeText(result.url);
toast.success('Reset link copied to clipboard', {
  description: 'Share the reset password link with the user.',
  duration: 6000,
});
} catch (err) {
  toast.error('Failed to create reset link', {
    description: err instanceof Error ? err.message : 'An unexpected error occurred.',
  });
✅ Passed (18)
Category Summary Screenshot
Adversarial Non-admin users could not access admin controls, including forced selector attempts. ADV-1
Adversarial Tampered invitation URL did not execute script input and rendered a controlled invalid invitation state. ADV-2
Adversarial Navigating during an in-flight revoke did not leave stale disabled controls or corrupt invitation row state on return. ADV-4
Edge Rapid invitation-row switching preserved row-scoped revoke modal state without leakage. EDGE-1
Edge Rapid repeated revoke confirmation produced one effective destructive outcome without duplicate or remnant invitation state. EDGE-2
Edge Refreshing while a dialog was open recovered to a clean, interactive members page and dialogs remained usable afterward. EDGE-3
Edge After repeated back/forward transitions, members page controls stayed interactive and action menus still opened correctly. EDGE-4
Edge Malformed comma-separated emails showed inline invalid-email validation and stayed in input state without transitioning to final results. EDGE-5
Edge Duplicate plus new email produced deterministic mixed results, and after Done the successful invite appeared as a pending row. EDGE-6
Edge Mobile members view remained usable at 375x812, including row action menus and Project Access dialog open/close without interaction lockups. EDGE-7
Happy-path /default/members rendered with Members heading, count badge, and correctly ordered visible row data. ROUTE-1
Happy-path Admin could open the Add Team Members dialog and close it via Cancel while the page remained stable. ROUTE-2
Happy-path Invite dialog handled malformed input, showed mixed invite results, and after Done the successful invite appeared as a pending members row. ROUTE-3
Happy-path Revoke modal stayed scoped to the selected invitation, rapid double-confirm remained idempotent, and member-page interactions stayed stable after navigation churn. ROUTE-4
Happy-path Demotion path correctly opens Assign to Projects dialog in row-scoped member flow. ROUTE-5
Happy-path Promotion from Member to Admin completes without showing Assign to Projects. ROUTE-6
Happy-path Project Access manage flow completed successfully (runtime used non-production fallback project data). ROUTE-7
Happy-path Delete member action removed the target row and kept members table stable. ROUTE-8

Commit: f51c603

View Full Run


Tell us how we did: Give Ito Feedback

@vercel vercel bot temporarily deployed to Preview – agents-docs March 26, 2026 05:15 Inactive
@itoqa
Copy link
Copy Markdown

itoqa bot commented Mar 26, 2026

Ito Test Report ❌

19 test cases ran. 3 failed, 16 passed.

Overall, 19 test cases were run with 16 passing and 3 failing, and the passing coverage showed stable behavior across core members/invitations flows including admin route rendering, row-scoped modal/state isolation, invite create/revoke/copy/accept paths, validation and mixed-result handling, rapid revoke stability, tamper rejection on accept links, graceful clipboard-error handling, role-demotion and concurrent row actions, self-row restrictions, navigation interruption recovery, callback-driven refresh, and mobile portrait/landscape usability. The three confirmed defects were: a high-severity authorization gap where an admin can delete an owner via direct remove-member API despite UI protections, a medium-severity Add Members race allowing rapid repeated submits to trigger duplicate invite attempts, and a medium-severity empty-state logic issue where tenants with only the current user do not show the expected “No members yet” fallback.

❌ Failed (3)
Category Summary Screenshot
Adversarial 🟠 Rapid repeated submits can run duplicate invite loops because submission re-entry is not synchronously guarded. N/A
Edge 🟠 Members empty-state is incorrectly gated by total member count, so a tenant with only the current user does not show the expected No members yet. fallback. EDGE-1
Edge ⚠️ Admin can remove an owner through direct API call despite owner immutability in member UI. EDGE-4
🟠 Rapid repeated Add Members submission can create duplicate invites
  • What failed: The handler can execute more than once before disabled UI state takes effect, so duplicate invite attempts can be sent for the same input instead of enforcing a strict single-flight submit.
  • Impact: Users can accidentally trigger duplicate invitation attempts, causing confusing mixed outcomes and avoidable invite management churn. This weakens trust in invitation reliability during fast interactions.
  • Introduced by this PR: No – pre-existing bug (code not changed in this PR)
  • Steps to reproduce:
    1. Navigate to /default/members as an org admin.
    2. Open Add Team Members and enter a valid invitee email.
    3. Trigger submit repeatedly in rapid succession (for example click plus Enter).
    4. Observe multiple invite attempts can execute before the UI disabled state fully applies.
  • Code analysis: I reviewed the invite dialog submission flow and found no immediate isSubmitting guard at the top of handleSubmit, while the only protection is state-driven button disabling that occurs after React processes state updates.
  • Why this is likely a bug: The code relies on asynchronous UI disabling without a synchronous re-entry check, so near-simultaneous submit events can enter the invite loop more than once.

Relevant code:

agents-manage-ui/src/components/members/invite-member-dialog.tsx (lines 56-77)

const handleSubmit = async (e: React.FormEvent) => {
  e.preventDefault();

  if (!emails.trim()) {
    setError('At least one email is required');
    return;
  }

  if (!organizationId) {
    setError('No organization selected');
    return;
  }

  if (!isOrgAdmin) {
    setError('You are not authorized to invite members');
    return;
  }

  setIsSubmitting(true);
  setError(null);
  setInvitationResults([]);

agents-manage-ui/src/components/members/invite-member-dialog.tsx (lines 251-253)

<Button type="submit" disabled={isSubmitting || !emails.trim()}>
  {isSubmitting ? 'Adding...' : 'Add Members'}
</Button>
🟠 Empty-state rendering when no members and no invitations
  • What failed: The table renders the current user's member row instead of the expected No members yet. fallback state for this scenario.
  • Impact: Admins in newly created or nearly empty tenants see misleading membership state and lose the intended empty-state guidance. This can confuse onboarding and mask whether additional members/invitations exist.
  • Introduced by this PR: Unknown – unable to determine
  • Steps to reproduce:
    1. Open a tenant that has only the current user and no pending invitations.
    2. Navigate to the members route (/{tenantId}/members).
    3. Observe the table body state for the members list.
  • Code analysis: The page passes the full organization members list directly into MembersTable, and the table only renders the empty fallback when members.length === 0; this excludes the "only current user" case required by the test scenario.
  • Why this is likely a bug: The production code path enforces an empty-state condition that cannot match the documented "no members except current user" expectation, so behavior is deterministically wrong for that valid tenant state.

Relevant code:

agents-manage-ui/src/components/members/members-table.tsx (lines 70-78)

{members.length === 0 && pendingInvitations.length === 0 ? (
  <TableRow noHover>
    <TableCell colSpan={4} className="text-center text-muted-foreground">
      No members yet.
    </TableCell>
  </TableRow>
) : (
  <>

agents-manage-ui/src/app/[tenantId]/members/page.tsx (lines 93-99)

<MembersTable
  members={organization?.members || []}
  pendingInvitations={pendingInvitations}
  currentMember={currentMember}
  organizationId={tenantId}
  onMemberUpdated={fetchData}
  isOrgAdmin={isOrgAdmin}
⚠️ Admin can remove owner member via remove-member API despite UI protections
  • What failed: Expected owner deletion to be rejected server-side, but the backend accepted the request (200) and removed the owner.
  • Impact: Admin users can bypass UI restrictions and delete the organization owner account through a direct request. This can remove the highest-privilege account and create an account-recovery and governance risk.
  • Introduced by this PR: No – pre-existing bug (code not changed in this PR)
  • Steps to reproduce:
    1. Sign in as an admin user who is not the owner and open /default/members.
    2. Confirm the owner row has no delete action in the UI.
    3. Send POST /api/auth/organization/remove-member with the owner member id and organizationId=default while authenticated as admin.
    4. Observe the API returns 200 and the owner membership is removed.
  • Code analysis: The members UI intentionally blocks owner-targeted destructive actions, but the backend removal hook in auth only performs cleanup work and never checks whether the target member is an owner before allowing removal.
  • Why this is likely a bug: Production code enforces owner immutability only in UI paths, while the backend member-removal flow lacks an owner-role guard and therefore permits a forbidden state via direct API use.

Relevant code:

agents-manage-ui/src/components/members/member-row.tsx (lines 64-68)

const canEditMember = (): boolean => {
  if (!isOrgAdmin || !currentMember) return false;
  if (member.id === currentMember.id) return false;
  if (currentMember.role === OrgRoles.ADMIN && member.role === OrgRoles.OWNER) return false;
  return true;
};

agents-manage-ui/src/components/members/components/member-actions-menu.tsx (lines 39-43)

const canDeleteMember = (): boolean => {
  if (!isOrgAdmin || !currentMember) return false;
  if (member.id === currentMember.id) return false;
  if (member.role === OrgRoles.OWNER) return false;
  return true;
};

packages/agents-core/src/auth/auth.ts (lines 387-405)

beforeRemoveMember: async ({ member, organization: org }) => {
  try {
    if (config.manageDbPool) {
      const { cleanupUserTriggers } = await import(
        '../data-access/manage/triggerCleanup'
      );
      await cleanupUserTriggers({
        tenantId: org.id,
        userId: member.userId,
        runDb: config.dbClient,
        manageDbPool: config.manageDbPool,
      });
    }

    const { revokeAllUserRelationships } = await import('./authz/sync');
    await revokeAllUserRelationships({
      tenantId: org.id,
      userId: member.userId,
    });
✅ Passed (16)
Category Summary Screenshot
Adversarial Rapid repeated revoke confirmation produced a single effective revoke outcome and left the members UI stable. ADV-1
Adversarial Malformed invite entries were rejected, and a corrected valid email invite succeeded. ADV-3
Adversarial A tampered email query was blocked while the valid invitation link loaded the join flow. ADV-4
Adversarial Clipboard write failure handling is non-blocking and does not create a crash or freeze path. ADV-6
Edge Self-row exposed only Change password, with no self-delete or self role edit path. EDGE-3
Edge Invalid invite tokens were rejected, then corrected mixed input produced per-email success/failure results and clean dialog completion. EDGE-5
Edge Refresh/back-forward interruptions recovered without ghost overlays or blocked controls. EDGE-6
Logic Row-scoped modal/action state stayed isolated across invitation and admin rows. LOGIC-1
Logic Concurrent row actions stayed row-scoped without table-wide locking. LOGIC-2
Logic On /default/members, invitation-side mutations refreshed row state correctly: newly invited pending row appeared immediately after Done, and the same row disappeared immediately after revoke confirmation, with no manual page reload required. LOGIC-3
Mobile In portrait (390x844) and landscape (844x390), header/badge/Add remained reachable; member and invitation row menus opened; invite copy action and dialogs worked without stuck overlays. MOBILE-1
Happy-path Members route loaded for admin with Add control and table columns visible. ROUTE-1
Happy-path Admin invited a unique member email via Add dialog, closed with Done, and saw a pending invitation row without stale dialog state. ROUTE-2
Happy-path Role demotion opened Assign to Projects and Skip returned cleanly to the members table. ROUTE-3
Happy-path Pending invitation was revoked from the row action flow and removed successfully with confirmation feedback. ROUTE-4
Happy-path Copied invitation link opened the accept-invitation route with valid invitation UI on localhost. ROUTE-5

Commit: acce7f0

View Full Run


Tell us how we did: Give Ito Feedback

@vercel vercel bot temporarily deployed to Preview – agents-docs March 27, 2026 16:18 Inactive
@itoqa
Copy link
Copy Markdown

itoqa bot commented Mar 27, 2026

Ito Test Report ✅

20 test cases ran. 20 passed.

The unified members/invitations verification run passed completely (20/20 test cases), showing stable rendering and control behavior on /default/members across baseline, edge, advanced, mobile, navigation, and empty-state scenarios with no plausible production defects identified in reviewed code paths. Key findings were that admin actions remained correctly row-scoped and resilient under rapid interaction (single invite dialog, accurate role/reset/delete targeting, idempotent destructive confirms), invitation handling was secure and correct (pending-list refresh, malformed email rejection, safe URL-encoded link copy, tampered accept-link rejection, revoke recovery after refresh), and authorization/session safety held (non-admin read-only with bypass attempts blocked, and mid-confirmation session invalidation failing safely without unintended data mutation).

✅ Passed (20)
Category Summary Screenshot
Adversarial Malformed and injection-like email inputs were blocked with invalid-email validation feedback. ADV-1
Adversarial Copied invite link used correct URL encoding and opened a valid accept-invitation page. ADV-2
Adversarial Direct UI/API manipulation attempts from non-admin context did not mutate invitation/member state. ADV-3
Adversarial Revocation confirmation after session invalidation returned unauthorized behavior and preserved data. ADV-4
Adversarial Repeated destructive confirms resulted in a single effective deletion outcome. ADV-5
Adversarial Tampered/missing email deep-link handling aligns with code path that validates via verify endpoint, not email-status lookup. ADV-6
Edge Empty-state text is correctly hidden when the organization still has the admin member row. EDGE-1
Edge Rapid repeated Add interactions kept only one Add Team Members dialog open. EDGE-2
Edge Rapid row switching preserved correct modal identity and mutation target. EDGE-3
Edge Refreshing with revoke modal flow remained recoverable and actions stayed interactive after reopening. EDGE-4
Edge Back/forward navigation with open dialogs recovered cleanly without orphan backdrops or blocked interactions. EDGE-5
Edge Mobile viewport interactions remained accessible for Add dialog, row actions, and delete confirmation controls. EDGE-6
Logic Project Access dialog behavior remained scoped to the selected row across row switches and modal reopen flows. LOGIC-1
Logic Admin demotion correctly opened assign-mode and persisted Member role after submit. LOGIC-2
Logic Reset-password behavior was row-scoped; missing action on some rows matched credential-provider gating. LOGIC-3
Logic Non-admin user view stayed read-only with no Add or destructive controls. LOGIC-4
Happy-path Members page loaded with admin Add control and stable members table rendering. ROUTE-1
Happy-path Invite dialog accepted a unique valid email and showed a new pending invitation after Done. ROUTE-2
Happy-path Fast A->B role switching kept confirmation and update scoped to Row B. ROUTE-3
Happy-path Pending invitation revoke completed after refresh and removed the row without stale disabled controls. ROUTE-4

Commit: 5128b84

View Full Run


Tell us how we did: Give Ito Feedback

@vercel vercel bot temporarily deployed to Preview – agents-docs March 30, 2026 02:57 Inactive
@itoqa
Copy link
Copy Markdown

itoqa bot commented Mar 30, 2026

Ito Test Report ✅

19 test cases ran. 19 passed.

The unified run passed all 19 of 19 test cases with zero failures, confirming stable Members page behavior across baseline rendering, alphabetical sorting, invite dialog lifecycle, role-change workflows, delete/revoke confirmations, invite-link handling, and navigation/mobile recovery. Most importantly, state and dialogs stayed correctly row-scoped under rapid/concurrent interactions (including double-submit/double-confirm races), malformed invite input was safely rejected without script execution or partial submission, revoked invite links were invalidated as expected, and non-admin privilege-escalation attempts were blocked by both UI and backend authorization with no unauthorized data changes.

✅ Passed (19)
Category Summary Screenshot
Adversarial Non-admin privilege-escalation attempts were blocked: admin controls remained hidden and direct forced mutation from page context was rejected (403), with state unchanged. ADV-1
Adversarial Submitting a mixed valid/invalid/script-like email payload produced client validation error for invalid entry and did not execute script or silently partially submit. ADV-2
Adversarial Rapid double-confirm kept final role state deterministic without contradictory success states. ADV-3
Adversarial Code review and rerun evidence indicate confirmation text is row-scoped; prior failure was setup/connectivity-related, not product logic. ADV-4
Edge Rapid row switching kept each confirmation dialog bound to the current row, and revoke affected only the chosen invitation. EDGE-1
Edge While Row A role changes ran, Row B actions remained usable with row-scoped loading behavior. EDGE-2
Edge Under rapid repeated Add activation, only a single invite dialog instance was present and UI remained stable. EDGE-3
Edge Rapid cancel/reopen/double-confirm on revoke modal resolved to one successful revoke without UI corruption. EDGE-4
Edge Members page recovered cleanly after refresh/back/forward, then stayed interactive when dialogs and row actions were opened/closed in mobile view. EDGE-5
Edge Mobile members interactions remained reachable and stable after reload/navigation interruption cycles, with overlays opening and dismissing cleanly. EDGE-6
Logic Demoting a member opened the Assign to Projects flow for that same row and closed cleanly. LOGIC-1
Logic Copy invite link produced the expected /accept-invitation/{id}?email={encodedEmail} localhost format. LOGIC-2
Logic Visible non-pending members on the page remained in case-insensitive alphabetical order. LOGIC-3
Logic Non-admin Members view hid admin controls, DOM-force attempts found no usable admin action path, and unauthorized mutation replay was denied (401) with no membership/invitation changes. LOGIC-4
Happy-path Members page loaded with expected table structure, admin controls, and at least one visible member row; no pending invitations were present in this seeded state. ROUTE-1
Happy-path Invite dialog opened with focused email input, toggled open/close repeatedly, and table UI remained unchanged/interactable with no stuck overlay. ROUTE-2
Happy-path Role update affected only the selected member row, and concurrent interaction on another row stayed functional. ROUTE-3
Happy-path Delete modal stayed bound to the selected member, and confirming delete removed only the targeted row. ROUTE-4
Happy-path Pending invitation revoke flow, revoked-link invalidation, and row refresh behavior worked as implemented. ROUTE-5

Commit: 248d54d

View Full Run


Tell us how we did: Give Ito Feedback

@vercel vercel bot temporarily deployed to Preview – agents-docs March 31, 2026 01:43 Inactive
@itoqa
Copy link
Copy Markdown

itoqa bot commented Mar 31, 2026

Ito Test Report ✅

14 test cases ran. 14 passed.

The unified members-management test run passed completely (14/14), confirming core admin workflows on /default/members including page load, scoped menus, invite creation, role updates, demotion-triggered assign-project handling, invitation revocation, and member deletion with correct table refresh behavior. The most important findings were strong safety and isolation guarantees: non-admin users could not access privileged controls or cross-tenant routes, malformed invite payloads were rejected, rapid keyboard/click abuse did not trigger hidden or duplicate destructive actions, dialogs remained correctly row-scoped across rapid switching, refresh/back-forward and mobile (390x844) interactions stayed stable, and no UI lockups were observed.

✅ Passed (14)
Category Summary Screenshot
Adversarial Malformed invite entries were rejected with explicit invalid-email validation and were not accepted as successful invitations. ADV-1
Adversarial Tenant path tampering did not grant cross-tenant member management; non-admin access remained denied outside the default tenant. ADV-2
Adversarial Keyboard Enter/Space spam and rapid multi-click abuse did not open destructive flows; cross-tenant path stayed denied. ADV-3
Edge Delete, role-change, and invitation revoke dialogs stayed bound to their currently targeted rows; refresh cleared transient dialog state and back-forward returned to an interactive members page. EDGE-1
Edge Rapid confirmation interaction did not duplicate revoke behavior; only one invitation was removed and UI stayed consistent. EDGE-2
Edge Transient member/invitation dialogs remained correctly scoped, page refresh cleared open dialog state, and browser back-forward returned to a fully interactive members screen. EDGE-3
Edge Mobile layout interactions remained accessible and closing overlays restored scroll state. EDGE-4
Logic Invite flow created a pending invitation and refreshed the table after Done; malformed payload was rejected; invitation revoke completed once without cross-row modal interference. LOGIC-1
Logic Role change stayed scoped to QA Member A, demotion branch opened assign-project only for QA Admin Demote, skip closed cleanly, and delete executed once with refreshed list. LOGIC-2
Logic Demotion to Member triggered Assign to Projects only for QA Admin Demote, then closed via Skip; other rows remained stable and final delete action executed once. LOGIC-3
Logic Delete confirmation executed a single time for QA Member Delete and list refreshed cleanly with member count decrement and functional remaining rows. LOGIC-4
Logic Revoke was initiated from the pending invitation row and stayed row-scoped; the member row context was not affected and page interaction remained stable. LOGIC-5
Happy-path Admin reached /default/members; heading, count badge, and Add button rendered; member and invitation menus opened/closed correctly. ROUTE-1
Happy-path Non-admin members view stayed constrained: no Add/admin destructive controls, and direct navigation to another tenant was denied. ROUTE-2

Commit: a76e700

View Full Run


Tell us how we did: Give Ito Feedback

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Performance] Opening invite dialog causes full table re-render (Members Table Fully Refactored)

2 participants