Overview
hasDefaultOrganizationAdminAccess() always returns false, for every user, unconditionally. It reads the role from a field that is hardcoded to null when the session user is constructed. As a result, organization owners and admins never see the Admin section of the account dropdown — the "Members" entry only appears for users who happen to also be enterprise admins, via an unrelated code path. The correct role data is already fetched and available; the accessor is reading the wrong field.
Current State
src/lib/useSession.tsx:97 hardcodes organizationMembership: null when building the user object.
src/lib/organizationAccess.ts:10 — getDefaultOrganizationRole() returns user?.organizationMembership?.role ?? null, i.e. always null.
hasDefaultOrganizationRole() and therefore hasDefaultOrganizationAdminAccess() always evaluate to false.
src/ui/UserDropdown.tsx:67 gates the Admin section on hasDefaultOrganizationAdminAccess(user). Because it is always false, the Admin section and its "Members" entry render only through the enterprises.length > 0 branch — so an org owner/admin who is not an enterprise admin has no route to organization member management from the account menu.
- The correct data is already loaded:
orgApi.listOrganizations() returns each org with a resolved role ('owner' | 'admin' | 'member' | null), and it is populated in both src/lib/useSession.tsx (as user.organizations) and src/lib/TeamContext.tsx (as organizations).
Proposed Changes
1. Resolve the role from the selected organization
- Replace the
organizationMembership-based accessor with one that derives the role from the already-populated org list and the currently selected org:
getOrganizationRole(organizations, selectedOrgId) → organizations.find(o => o.id === selectedOrgId)?.role ?? null
hasOrganizationAdminAccess(organizations, selectedOrgId) → role is owner or admin
- This mirrors the existing, working
getEnterpriseRole / hasEnterpriseAdminAccess pair in the same file, which already take (list, selectedId).
2. Update the call site
src/ui/UserDropdown.tsx gates the Admin section on the new accessor, reading organizations and selectedOrgId from useTeam().
- Admin entries then correctly follow the organization the user is currently in, rather than a single global notion of "default organization".
3. Remove the dead field and accessors
- Drop
organizationMembership from the user type if nothing else consumes it, along with getDefaultOrganizationRole / hasDefaultOrganizationRole / hasDefaultOrganizationAdminAccess once the call site is migrated, so the broken accessor cannot be reintroduced.
4. Consider the duplicate organization fetch
useSession.fetchSession and TeamContext.refetchOrganizations both call orgApi.listOrganizations() on login, producing two orgs_list round-trips. If user.organizations has no remaining consumers after this change, the session copy can be dropped.
Acceptance Criteria
Out of Scope (for Now)
- Moving the organization/team switcher into the nav header — tracked in its own issue.
- Any change to how roles are resolved server-side in
meteor-backend/server/organizations.js; this is a frontend-only wiring fix.
- Enterprise role handling, which already works correctly.
Overview
hasDefaultOrganizationAdminAccess()always returnsfalse, for every user, unconditionally. It reads the role from a field that is hardcoded tonullwhen the session user is constructed. As a result, organization owners and admins never see the Admin section of the account dropdown — the "Members" entry only appears for users who happen to also be enterprise admins, via an unrelated code path. The correct role data is already fetched and available; the accessor is reading the wrong field.Current State
src/lib/useSession.tsx:97hardcodesorganizationMembership: nullwhen building the user object.src/lib/organizationAccess.ts:10—getDefaultOrganizationRole()returnsuser?.organizationMembership?.role ?? null, i.e. alwaysnull.hasDefaultOrganizationRole()and thereforehasDefaultOrganizationAdminAccess()always evaluate tofalse.src/ui/UserDropdown.tsx:67gates the Admin section onhasDefaultOrganizationAdminAccess(user). Because it is always false, the Admin section and its "Members" entry render only through theenterprises.length > 0branch — so an org owner/admin who is not an enterprise admin has no route to organization member management from the account menu.orgApi.listOrganizations()returns each org with a resolvedrole('owner' | 'admin' | 'member' | null), and it is populated in bothsrc/lib/useSession.tsx(asuser.organizations) andsrc/lib/TeamContext.tsx(asorganizations).Proposed Changes
1. Resolve the role from the selected organization
organizationMembership-based accessor with one that derives the role from the already-populated org list and the currently selected org:getOrganizationRole(organizations, selectedOrgId)→organizations.find(o => o.id === selectedOrgId)?.role ?? nullhasOrganizationAdminAccess(organizations, selectedOrgId)→ role isowneroradmingetEnterpriseRole/hasEnterpriseAdminAccesspair in the same file, which already take(list, selectedId).2. Update the call site
src/ui/UserDropdown.tsxgates the Admin section on the new accessor, readingorganizationsandselectedOrgIdfromuseTeam().3. Remove the dead field and accessors
organizationMembershipfrom the user type if nothing else consumes it, along withgetDefaultOrganizationRole/hasDefaultOrganizationRole/hasDefaultOrganizationAdminAccessonce the call site is migrated, so the broken accessor cannot be reintroduced.4. Consider the duplicate organization fetch
useSession.fetchSessionandTeamContext.refetchOrganizationsboth callorgApi.listOrganizations()on login, producing twoorgs_listround-trips. Ifuser.organizationshas no remaining consumers after this change, the session copy can be dropped.Acceptance Criteria
organizationMembership.npm run lint,npm run typecheck,npm run format, andnpm run test:allall pass.Out of Scope (for Now)
meteor-backend/server/organizations.js; this is a frontend-only wiring fix.