Add shared workspaces for small teams - #175
Conversation
Engineering calculations are usually reviewed by a second person. Let a signed-in user create a workspace, invite colleagues with a link, and share calculation history and report defaults with them. Database (20260915000000_create_workspaces.sql): - workspaces, workspace_members, workspace_invites and workspace_calculations, all behind RLS keyed on membership through security definer helpers - create, invite, join, leave, remove and delete go through RPCs that take the caller from auth.uid(); nothing structural is writable directly - invite codes are 244 random bits, returned once; only the SHA-256 is stored, and links expire after 7 days or on revocation - shared entries are immutable, and the author name is stamped by a trigger so it cannot be forged and survives the member leaving - the last owner leaving or deleting their account promotes the longest-standing member; the last member leaving deletes the workspace - limits: 10 owned workspaces, 25 members, 20 active invites, 200 shared entries per workspace Site: - Workspaces section on the profile page: members, invite link, report defaults (company name, preferred standards), shared history with author and date - /workspaces/join/ accepts an invite on an explicit click; the invite travels as ?invite= because supabase-js consumes ?code= as an OAuth code - utility shells offer "Save to" (personal history or a workspace) and send the selected workspace's defaults to apps as cas:report-defaults - the protocol doc describes the new message; six locales translated supabase/tests/workspaces.test.mjs runs the migration on PGlite and checks 43 behaviours (isolation, forged authors, invites, hand-over, cascades, limits); CI runs it as pnpm test:db. Closes #122 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Updates to Preview Branch (feat/shared-workspaces) ↗︎
Tasks are run on every commit but only new migration files are pushed.
❌ Branch Error • Wed, 16 Sep 2026 18:20:25 UTC View logs for this Workflow Run ↗︎. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 04021052c2
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
Resolve the sitemap conflict with the embed routes from #174: keep both exclusions, /embed/** and /workspaces/** with their locale variants. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Summary
Closes #122. Signed-in users can create a workspace, invite colleagues by link, and share calculation history and report defaults with them.
supabase/migrations/20260915000000_create_workspaces.sql, profile → Workspaces,/workspaces/join/cas:report-defaultscreated_byplus a server-stampedcreated_by_nameon every shared entryDatabase design
is_workspace_member()/is_workspace_owner(). These are security definer, so the policies do not recurse intoworkspace_members' own RLS.create_workspace,create_workspace_invite,accept_workspace_invite,revoke_workspace_invites,leave_workspace,remove_workspace_member,delete_workspace. The caller always comes fromauth.uid(). Members and invites are not writable directly; owners can update onlyname,company_nameandpreferred_standards(column grants).created_by = auth.uid()and membership.full_name, falling back to the e-mail local part. Full e-mail addresses are never shown to other members. The migration does not depend onpublic.profiles, which is not in the repo (seedev-plans/supabase-pending-work-2026-08.md).Site changes
src/shared/workspaces— client module; RLS is the boundary, not this code.src/components/Workspaces— profile section: members, invite link, report defaults (read-only for members), shared history with author and date.src/pages/workspaces/join.tsx— noindex andno-referrer, so the invite code is not leaked viaReferer; excluded from the sitemap. The invite travels as?invite=: supabase-js consumes?code=as an OAuth PKCE code, which broke session detection during testing.cas:report-defaultsto the embedded app.dev-plans/utility-share-protocol.mddocuments the new message. Apps must treat it as optional and never overwrite user input.Verification
pnpm test:db(new; also added to CI) applies the migration to Postgres running in-process via PGlite, with a minimal Supabase scaffold, and checks 43 behaviours:pnpm typecheckpasses;pnpm linthas 0 errors;DOCUSAURUS_ONBROKENLINKS=throw docusaurus build --locale ensucceeds.Deployment notes
dev-plans/supabase-pending-work-2026-08.md, the repo and production migration histories have drifted, and migrations are not syncing automatically. Until it is applied, the Workspaces section shows its load error and the "Save to" selector stays hidden; personal history is unaffected.cas:report-defaultsyet. Each utility adopts it in its own repository, as with the share protocol itself.🤖 Generated with Claude Code