Skip to content

admin: polished tenancy UX (Org/User/TenantMembership/Role) — replace generic CRUD with invite + role-picker + membership composer #71

@rrrodzilla

Description

@rrrodzilla

Problem

The dynamic admin shell at /admin/* is wonderful for the long tail of schemas, but the four schemas that govern tenancy and identityOrganization, User, TenantMembership, Role — are the universal control surface for every SchemaForge product, and they're the most painful to operate through the generic CRUD form.

Today, to put a new user into an org as an admin, an operator has to:

  1. Open /admin/User → click "Create" → fill in email, display_name, role_rank, roles[] (manual array), password_hash (?!), organization (paste a ULID).
  2. Open /admin/TenantMembership → click "Create" → paste the User ULID into user, type \"Organization\" into tenant_type, paste the Organization ULID into tenant_id, paste a Role ULID into role.

Two ULID copy-pastes per user just to onboard them. There is no concept of "invite by email" — the operator has to mint a password_hash manually or coordinate out-of-band.

For a SchemaForge product that's resold (the Engage case: federal contracting CRM sold to multiple firms), this is the first thing a customer admin tries to do and the first thing that makes the platform feel hand-built.

Requested

Promote the four tenancy schemas from "dynamic CRUD" to first-class admin flows in /admin/*. None of this needs new schemas — it's UX on top of what's already there.

1. /admin/users — invite + manage

  • Invite by email: form takes email, display_name, an org picker (filtered to the orgs the operator can write to), a role picker. Backend mints a one-time invitation token, emails it (or returns it for copy-paste in dev), creates the User + TenantMembership row in one transaction.
  • Reset password: per-row action that forces a password change on next login.
  • Role assignment: real role picker, not a comma-separated text input. Show role badges with their role_rank.
  • Hide password_hash from the create/edit form entirely (it's already @hidden in the schema — the admin shell should respect that even on the generic form).

2. /admin/orgs (or /admin/organizations) — tenant root management

  • Create Org: today already works but should auto-bootstrap a TenantMembership for the operator (or a configurable initial admin user) so the org isn't immediately stranded.
  • Members tab: per-org view showing TenantMemberships + Role with inline add/remove. No raw ULIDs visible.
  • Empty-state: when there are zero non-platform-admin users in an org, show "Invite your first user" instead of an empty table.

3. /admin/memberships — TenantMembership as a relationship, not a row

  • Picker-driven create: user combobox (search by email), org combobox (search by name), role combobox. Tenant_type defaults to Organization and is hidden unless multiple tenant roots exist in the schema.
  • Bulk add: "add users X, Y, Z to org Q with role R."
  • Per-user view: when drilling into a User, show their TenantMemberships as a list with org name + role, not a relation field of ULIDs.

4. /admin/roles — role catalogue with rank visualization

  • Show roles ordered by role_rank, with a visual hierarchy.
  • Block deletion of a role that's referenced by any TenantMembership.
  • Surface which schemas' @access annotations reference each role.

5. Cross-cutting: tenancy chrome

Why This Belongs in SchemaForge, Not Each Product

Every SchemaForge product needs this same UX. Today each product either:

  • ships the generic CRUD form (rough — see above), or
  • reimplements user/org provisioning in its own curated pages (Engage was about to do this — and would have duplicated logic that the platform should own).

Centralizing it means every customer of every SchemaForge product gets the same polished onboarding experience, and the schemas stay the single source of truth.

Relationship to #70

#70 (auth: /auth/me + /auth/switch-tenant) is the data plane for the active-tenant chrome in #5 above. This issue is the UI surface; #70 is the API contract that lights it up.

Use Case

Engage (Govcraft/engage) is being prepared for resale to federal contractors. The first thing a customer admin does after their org is provisioned is invite their BD team. Today that's 2 form pages, 3 ULID copy-pastes per user, and a manually-minted password_hash. That's a deal-breaker for a resold product.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions