feat(frontend): organization management UI + role-aware permission gating (stacked on #267) - #268
Open
amal66 wants to merge 3 commits into
Open
feat(frontend): organization management UI + role-aware permission gating (stacked on #267)#268amal66 wants to merge 3 commits into
amal66 wants to merge 3 commits into
Conversation
Introduce an organizations tenant layer on top of the existing per-user model: every account gets an auto-provisioned personal org, orgs carry owner/admin/member RBAC via org_members, and teams group members inside an org. projects/documents/workflows/tabular_reviews gain a nullable org_id (ON DELETE SET NULL) so org membership becomes a third access branch alongside row ownership and shared_with emails — in the access helpers, the overview RPCs, and the org-aware /orgs REST module. Mechanical port of the organizations/RBAC feature from amal66/mike@main (b3166dd) onto the upstream layout. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CEguyEgXa9JjCciXCcVemC
…nches Give the three access branches a Drive-style role ladder instead of raw ok/isOwner flags: row owner → owner, shared_with email → editor, org owner/admin → manager, plain org member → viewer. A single capability matrix (lib/permissions.ts) maps roles to what routes may do — view, content.edit, docs.organize, structure.manage, members.manage, container.delete — and every project/document/review write route now declares the capability it needs instead of hand-rolling an owner check. This makes the ADR's 'org membership grants visibility, not ownership' promise real: plain org members are read-only (previously the org branch returned ok:true and most write routes gated on nothing beyond ok), and org owner/admins can curate content (manage folders, sharing, review structure) without being able to delete containers they don't own. Notable tightenings, all fail-closed: - folder rename/move/delete, doc-set/column edits on reviews, and clear-cells are manager+ (generalising the owner-only folder-delete gate that landed upstream in Open-Legal-Products#193) - version pushes, edit resolution, chat, and review generation are editor+ (org viewers excluded) - project PATCH (metadata + sharing) is manager+, so org admins can manage without owning; project/review DELETE stays owner-only - GET /projects/:id and /people now go through checkProjectAccess (the roster previously 404'd for org members who could read the project) Detail responses expose access_role alongside is_owner so the client can render per-role affordances. can() is exhaustively unit-tested (role × capability), and route suites cover the new gates. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ting The client half of the org/RBAC feature: orgs become visible and usable, and the UI stops deriving permissions from a single is_owner boolean. - Settings → Organizations: create orgs, expand into a member roster (profile names/emails, role select with owner/admin/member, remove or leave with confirm; 409 last-owner protection surfaced inline) and teams (create/delete, add/remove members). Personal orgs stay hidden — they are plumbing, not a firm. - New project modal gains an Organization select (personal workspace default) so org-scoped projects can actually be created; sidebar user menu links to Organizations. - GET /orgs/:id/members and /teams responses enriched server-side with mirrored profile email/display_name (same source as /people) so the client never renders a bare user id. - lib/permissions.ts mirrors the server capability matrix; detail pages derive the caller's role from access_role (falling back to the is_owner list-row contract) and gate affordances through one canDo() seam: project details/sharing manager+, folder rename/move/delete manager+, doc rename/move and folder create editor+, review columns/document-set/clear-cells/details manager+, container delete owner-only. This also closes the previously unguarded folder-tree actions and un-read-onlys org admins. - OwnerOnlyPopup generalised with a requiredRole tier so denials name the role that can act, and now shows the owner's email when known. - Review details save only sends project_id when it changes (moving is owner-only server-side; managers editing a title must not 403). - Tests: client matrix + roleFrom contract, OrganizationsPage flows (create, roster, role change, error surfacing, member read-only), OwnerOnlyPopup tiers. Frontend suite 74 passed; backend 307 passed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Collaborator
Author
|
Went through the full diff — the core feature here is solid, but the PR bundles several independent changes worth calling out. Breakdown: 1. Backend pagination/search/sort (the stated scope)
2. Frontend lazy-load integration
3. Perf/schema work inside the migration that goes beyond pagination
4. State-management removal in
5. Shared
6. Drive-by logic fix in delete-selected (both pages)
Requested changes:
Happy to re-review quickly once that's done — #1 + #2 + the tests look good and I don't expect issues with the core approach. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #267
This PR is the frontend half of the organizations/RBAC feature: it sits on top of #267's branch and carries its two commits — review the top commit only (
feat(frontend): organization management UI + role-aware permission gating). If #267 merges first, this rebases to a single clean commit.#267 shipped the tenant layer and the project role ladder server-side, but nothing in the product surfaced it: no way to create an org, no way to see members, and a UI that still derives every permission from a single
is_ownerboolean (including the org-admin case where the server allows an action but the client renders read-only). This PR closes those UI/UX flows.What it adds
Settings → Organizations (new page, plus a sidebar entry)
409 An organization must keep at least one owner.last-owner protection.Org-scoped project creation
The New Project modal gains an Organization select (default: Personal workspace) populated from
GET /orgs. This is the write path that makes the whole feature real: until now nothing in the UI could create a project with anorg_id.Role-aware permission gating (replaces
is_owner-only logic)frontend/src/app/lib/permissions.tsmirrors the server's capability matrix, and detail pages derive the caller's role from theaccess_rolefield #267 added (falling back to the historicis_ownercontract on list rows). OnecanDo(capability)seam threads through the project workspace:members.manage(manager+)structure.manage(manager+)docs.organize(editor+)content.edit(editor+)structure.manage(manager+)Notable fixes this delivers:
OwnerOnlyPopupgrows arequiredRoletier ("Only the owner or a manager can rename folders.") and now shows the owner's email when known, so a denial tells the user who to ask.project_idwhen it actually changed — moving a review is owner-only server-side, and a manager editing just the title must not 403.Backend (small, additive)
GET /orgs/:orgId/membersandGET /orgs/:orgId/teamsare enriched with mirrored profileemail/display_name(same source as the projects/peopleendpoint) so the client never has to render a UUID.UX walkthrough (screenshots)
Captured against a live local stack (Supabase CLI + MinIO + backend + prod
next start), three users in one org: an owner, an admin (→ manager role on org projects), and a plain member (→ viewer).As the org owner
1 — Organization management (Settings → Organizations): roster with role selects and descriptions, teams with member chips.
2 — Creating a project inside the firm: the new Organization select in the New Project modal.
3 — Owner's project view: full toolset (Folder, Add documents, Actions).
4 — People with access modal.
As a plain org member (viewer)
5 — Same project, read-only: no Folder button, Add documents disabled.
6 — "View details": inputs disabled.
7 — Delete attempt: owner-only popup instead of a silent 404.
As an org admin (manager)
8 — Manager toolset: Folder and uploads available — the previous server-allows/client-forbids inconsistency is gone.
9 — "Edit details": editable, with Update action.
10 — Container deletion still owner-only, even for admins.
Testing
cd frontend && npm test→ 74 passed (10 files), including new suites: the client capability matrix asserted cell-by-cell against the server's, theroleFromfallback contract, OrganizationsPage flows (create org, roster rendering, role change, inline 409 surfacing, member read-only mode), and OwnerOnlyPopup tier copy.cd frontend && npx tsc --noEmit→ clean;npm run lint→ 0 errors;next build→ clean production build.cd backend && npx vitest run→ 307 passed (member/team enrichment covered by the orgs service suite).next start): org create → member add → role change → viewer/editor/manager gating in the project and review UIs.Provenance
All new code written for this PR (no fork port — the fork has no org UI). Follows the existing conventions: account settings tab rail,
AccountSectionglass cards,PillButton/ModalSelect/AddUserInputprimitives,apiRequestclient layer, theOwnerOnlyPopupguard idiom, and testing-library patterns from the frontend harness.🤖 Generated with Claude Code