Repository navigation
Let organizations share projects with their clients - #463
Merged
Merged
Conversation
A supplier admin invites an administrator in the client's organization by email from a new Sharing tab on the project. The client picks which of their organizations the project goes into, and its admins then get a read-only "Shared projects" menu: hours, consultants, tasks, notes and cost at the supplier's rates and in the supplier's currency, with a CSV export that leaves out email addresses. Either side can end the share. Sharing never widens the app's existing scopes. Shared projects are only reachable through the Shared:: policies, so time registration, reports, the API and the MCP tools behave exactly as before. Also: - Signing in, or finishing onboarding, returns to the page the user was headed to, so an invitation link survives the sign-in detour. - The RubyUI tabs controller shows the active tab again after a morphing Turbo refresh; redirecting back to the same URL left every tab hidden. - The report period navigation takes the path to link to, so the shared pages reuse it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The invitation no longer needs a SendGrid template: it is rendered from app/views/user_mailer, translated with the other share strings, and goes out over the existing SendGrid SMTP relay. That drops the PROJECT_SHARE_INVITATION_*_TEMPLATE_ID variables, works with letter_opener in development, and lets the tests check the content. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Someone who only belongs to the owning organization has nowhere to put the project, so the invitation is rejected when it is sent. An invitee who administers only the owning organization now gets a message saying so, instead of being told they need to be an administrator. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The mailer layout now reproduces the SendGrid template: logo card on a grey background, centred heading with the accent rule, a primary button, the Stemplin Support sign-off and the address footer. The logo is a PNG rendered from the app's SVG, served from our own asset host. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
4 tasks done
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.
Why
When our consultants work on a project for a client that also uses Stemplin, the client wants to see the hours and cost we register on it. This lets the supplier share a project with the client's organization, read-only.
How it works
/workspace/projects/:idinvites an administrator in the client's organization by email. Pending invitations show a "copy link" button; live shares and invitations can be revoked./share_invitations/:token) shows what will be shared and lets the invited admin pick which of their organizations the project goes into. Only the address the invitation was sent to can open it. Signing in from the link returns to it./shared/projects), shown once something is shared. It lists shared projects per supplier with hours and cost for a period. Each project has a detail page with consultant/task filters, notes, and a CSV export without email addresses. Amounts use the supplier's rates and the supplier's currency. The client can also remove a share.Only the client's admins see shared projects for now. Spectators/members can be given access later through
ProjectAccesswithout schema changes.Authorization
Sharing does not widen any existing scope. Shared projects are only reachable through new
Shared::policies (Shared::ProjectPolicy,Shared::TimeRegPolicy,Shared::ProjectSharePolicy), so time registration, reports, the API and the MCP tools behave exactly as before. The owner's side usesWorkspace::ProjectSharePolicy.test/policies/shared_project_scopes_test.rbchecks that a share leaks into none of the regular scopes and that the client can't change the project or its time.Data model
New
project_sharestable:project,organization(the recipient, set on acceptance),invited_by,invited_email,invitation_token,status(pending/accepted/rejected/revoked),expires_at(7 days) and timestamps per transition. Shares are never deleted, only revoked. Partial unique indexes allow one open invitation per email and one live share per organization per project.Other changes
after_sign_in_path_fornow returns to the stored location, and the onboarding wizard's exits do the same, so an invitation link survives the sign-in or sign-up detour. This applies to every deep link, not just invitations.Invitation email
The invitation is a plain Rails email (
app/views/user_mailer/project_share_invitation_email.{html,text}.erb), translated with the other share strings and sent in the invited user's locale. It goes out over the existing SendGrid SMTP relay, so no SendGrid template or environment variable is needed. In development it opens in letter_opener, and there is a mailer preview at/rails/mailers/user_mailer/project_share_invitation_email. The "copy link" button on the Sharing tab also works when email isn't set up.Not included
Per-share toggles for hiding amounts or notes (notes are always shared), notifications when a share is revoked, and PaperTrail on
ProjectShare.Test plan
bin/rails test: 396 runs, 0 failuresbin/rails test:system: 8 runs, 0 failuresbin/rubocop: no offenses🤖 Generated with Claude Code