Repository navigation
Conversation
Mobile showed member-management actions to owners only, offered the wrong invite roles, and let an invitation through when the organization had no free seat. Web already models all three. - Member and invitation rows now open for owners and admins; a row whose target holds the owner role stays owner-only, mirroring the server's owner-authority check and the web role dropdown. - The role menu and the invite sheet offer exactly the web's role lists for the caller. - The invite procedure refuses a seat-consuming invitation when the plan enforces seats and none is free, under an advisory lock so concurrent invites cannot both take the last seat. A billing manager takes no seat. - The invite sheet shows the web's seat warning with used/total and disables Send; a race is explained through the new server message. - The seat rule lives once, in app-shared, and the web dialog reads it from there.
| // Refuse before writing anything when the organization has no free | ||
| // seat: the advisory lock it takes makes two concurrent invites | ||
| // serialize, so neither can take the last seat twice. | ||
| await assertInviteSeatCapacity(tx, organizationId, role); |
There was a problem hiding this comment.
SUGGESTION: Seat check runs before the duplicate-invite checks, so a duplicate on a full organization reports the wrong reason
assertInviteSeatCapacity runs before inviteUserToOrganization, and the seat count includes pending invitations. On an organization at its seat limit, an address that is already a member or already has a pending invitation therefore fails the seat check first and surfaces as PRECONDITION_FAILED / ORGANIZATION_INVITE_SEAT_LIMIT_REACHED instead of the specific CONFLICT (This user is already a member of this organization / This email already has a pending invitation). Mobile maps the seat message to organization.inviteMember.noSeatsAvailable, so the user is told to ask for seats when the real answer is that the person already has one. Running the existing pending-invitation/member validation before the seat gate (or letting those errors win) keeps the message accurate.
| * only a plain member, because the invite mutation needs a billing role for a | ||
| * plain member and organization-management authority for anything above it. | ||
| */ | ||
| export function invitableRoles(role: OrganizationRole | undefined): OrganizationRole[] { |
There was a problem hiding this comment.
SUGGESTION: The role lists do not fully mirror web for Kilo staff
The doc comment says these lists match web's getAvailableInviteRoles / getAvailableRoles, but both web helpers take isKiloAdmin and return every role when isKiloAdmin || currentUserRole === 'owner' (web InviteMemberDialog.tsx:79, MemberRoleDropdown.tsx:53). Here an admin membership always stops at ['admin', 'member', 'billing_manager'], so a Kilo staff member on mobile is not offered owner for an invite or a role change, and the canManageOrganizationOwners(role) gate in members-screen.tsx hides actions on owner rows/owner invitations for them, while web shows them. Either thread the staff flag into these helpers (and the screen gates) or narrow the parity claim.
There was a problem hiding this comment.
bot: Agreed on the wording. Kilo staff overrides (isKiloAdmin) stay web-only on purpose: staff administer organizations from the web admin tools, not the mobile app. The next push narrows the comment to say the lists mirror web for organization roles only.
Code Review SummaryStatus: 2 Issues Found | Recommendation: Address before merge Executive SummaryThe incremental commit only updates the mobile sheet test's organization mock (no new defects), and the two earlier seat/role-parity suggestions remain present at current HEAD. Assumptions: Overview
Issue Details (click to expand)SUGGESTION
Files Reviewed (3 files)
Fix these issues in Kilo Cloud Previous Review Summary (commit e4f180a)Current summary above is authoritative. Previous snapshots are kept for context only. Previous review (commit e4f180a)Status: 2 Issues Found | Recommendation: Address before merge Executive SummaryThe seat-capacity enforcement and role delegation are sound, but the new seat gate runs before duplicate-invite validation and the mobile role lists drop web's Kilo-staff elevation, so two low-risk parity/UX gaps remain. Overview
Issue Details (click to expand)SUGGESTION
Files Reviewed (107 files)
Reviewed by deepseek-flash-latest · Input: 58.8K · Output: 8.1K · Cached: 1.2M Review guidance: REVIEW.md from base branch |
The sheet suite stubs `useOrgBoundary` with an organization that carries only an id. The invite sheet now reads the organization's plan, `require_seats` and seat count to decide whether Send is available, so the stub pointed the seat calculation at `undefined`. Give that suite's organization the fields the sheet reads, with seat enforcement off so the surfaces keep testing their own states.




Summary
Three organization member-management gaps on mobile:
canManageOrganization; rows whose target is an owner stay owner-only, and the role menu offers the same roles as web'sgetAvailableRoles.invitableRolesnow matches web'sgetAvailableInviteRoles(owners: owner, admin, member, billing manager; admins: admin, member, billing manager; billing managers: member).assertInviteSeatCapacitynow runs inside the invite transaction under an organization-scoped advisory lock, so two concurrent invites cannot take the last seat. It returnsPRECONDITION_FAILEDwith a stable message. Billing managers take no seat; organizations withoutrequire_seatsor on enterprise are unlimited. The rule moved to@kilocode/app-shared/organizationsso the web dialog and mobile use one implementation.Behaviour change: an organization that requires seats and has none free can no longer invite seat-consuming members from any client. The web dialog already blocked this, so web users see no change.
Verification
check:unused,check:i18n,check:plurals, format check; web typecheck and lint.organization-members-router.test.tsagainst Postgres, including seats full (rejected, no invitation row), billing manager allowed when full, and unlimited plans. WebInviteMemberDialogand mobile member/invite suites pass.PRECONDITION_FAILEDwith the new message.