cpatpa replaces shared_with JSONB arrays with proper join tables
Two migrations swap the upstream `shared_with text[]` pattern for `project_members` and `review_members` join tables, both with FK to `users(id)`, RLS forced, and a live backfill of existing rows. The legacy JSONB columns stay in the schema for one cycle as a rollback net.
Migration 0015 (6e9bf945) creates project_members(id, project_id FK, user_id FK, role CHECK IN ('member','editor'), invited_by FK, joined_at, UNIQUE(project_id, user_id)) with indexes on both FKs. A DO block walks every project's shared_with JSONB, matches each email to an active user by lower(email::text), and inserts a project_members row. Unmatched emails are counted and reported via NOTICE then dropped - they had no user account behind them and couldn't have accessed the project anyway. Owner-matches are skipped. The legacy shared_with column stays on projects but application code stops reading and writing it.
lib/access.ts is rewritten against project_members: checkProjectAccess now joins the table rather than scanning emails, and a new listAccessibleProjectIds helper replaces the JSONB @> containment queries in the projects list route. The API response shape is kept identical - shared_with is still a string[] of emails in the response, but it's now derived from project_members JOIN users. Frontend code needs no changes.
Migration 0016 (510d7e86) mirrors the same pattern for standalone tabular reviews. review_members has the same column shape; ensureReviewAccess in lib/access.ts now checks review_members for direct shares and falls through to project_members via checkProjectAccess for project-scoped access. The actual drop of both legacy JSONB columns and their GIN indexes is deferred to a follow-on migration once retention UI lands.
The role CHECK IN ('member', 'editor') enum is minimal. editor is reserved for a follow-up that permits document upload/edit while keeping share and role management on the owner. If you want richer roles, that's an extension point.
Spotted something wrong? Or know the PR text has fresher detail than the writeup above?