Skip to content

Sharing & Invites

This area covers 30 features. Watch the walkthrough, then use the reference below — each feature links to the exact moment it appears (▶).

Who this is for: Project lead / owner. Each feature below lists the role/permission it requires.

Features

Generate single-project invite link ▶ 00:00

As a project owner, I want to generate a shareable invite link so that I can onboard collaborators who may not have Aquilla accounts.

How it works. User opens Share dialog → Invite link tab → role/email/expiry picker → clicks 'Create invite link' → receives a unique URL on success. User can copy the URL to clipboard via copy button. On error (permission/network), user sees error message.

WhoProject lead / owner PermissionsManage members/invites: project_lead(500); role changes: maintainer/owner(≥600); link role capped at contributor(400)
Key files

src/components/SharePanel.tsx:170-367
src/lib/sync/invites.ts:79-115
e2e/specs/projects/share-invite-link.smoke.spec.ts

Configure invite link role ▶ 00:06

As a project owner, I want to specify the role (viewer/commenter/reviewer/contributor) that a recipient will receive so that I can grant appropriate access levels.

How it works. User opens Invite link tab → Role dropdown (aria-label='Role') shows current selection → user clicks dropdown → selects role option → trigger updates to show new role. Default is CONTRIBUTOR (400).

WhoProject lead / owner PermissionsManage members/invites: project_lead(500); role changes: maintainer/owner(≥600); link role capped at contributor(400)
Key files

src/components/SharePanel.tsx:257-279
src/lib/frontier/roles.ts (LINK_ROLE_OPTIONS)

Configure invite link expiry ▶ 00:00

As a project owner, I want to set when an invite link expires so that I can control the time window for joining.

How it works. User opens Invite link tab → Expiry dropdown (aria-label='Link expires') with options: 1 day, 7 days, 30 days (default), No expiry → user selects option → trigger shows new expiry. Null value means no expiry.

WhoProject lead / owner PermissionsManage members/invites: project_lead(500); role changes: maintainer/owner(≥600); link role capped at contributor(400)
Key files

src/components/SharePanel.tsx:313-339
src/components/SharePanel.tsx:162-167 (EXPIRY_OPTIONS)

Target invite to specific email ▶ 00:00

As a project owner, I want to send an invite to a specific email address so that the recipient can prefill their signup form and the invite is bound to their account.

How it works. User opens Invite link tab → enters email in 'Recipient email' input (optional) → regex validation triggers on blur or create attempt. If valid email entered, the join page prefills the signup form. If invalid email provided and user tries to create, error message shows: 'Enter a valid email address, or leave blank for an open link.' Valid: empty or RFC5322-pattern email.

WhoProject lead / owner PermissionsManage members/invites: project_lead(500); role changes: maintainer/owner(≥600); link role capped at contributor(400)
Key files

src/components/SharePanel.tsx:286-312
src/components/SharePanel.tsx:187-190 (email regex)

Copy invite URL to clipboard ▶ 00:00

As a project owner, I want to copy the invite link to my clipboard so that I can paste it into email or messaging.

How it works. After generating invite link, user sees readonly input with URL. Copy button (title='Copy URL', Copy icon) appears. User clicks button → URL copied to clipboard → 'Copied!' confirmation text appears for ~1.5 seconds.

WhoProject lead / owner PermissionsManage members/invites: project_lead(500); role changes: maintainer/owner(≥600); link role capped at contributor(400)
Key files

src/components/SharePanel.tsx:219-223
src/components/SharePanel.tsx:235-252

List active (unredeemed) invite links ▶ 00:13

As a project owner, I want to see all active invite links I've created so that I can manage and revoke them if needed.

How it works. Below the create-form, 'Active links' section renders if any invites exist. Shows token suffix (...last-8-chars), role, email/open-link badge, and expiry date. Empty state: section doesn't render. Loading state renders placeholder text.

WhoProject lead / owner PermissionsManage members/invites: project_lead(500); role changes: maintainer/owner(≥600); link role capped at contributor(400)
Key files

src/components/SharePanel.tsx:369-486
src/lib/sync/invites.ts:195-217

Revoke active invite link ▶ 00:00

As a project owner, I want to revoke an invite link before it's redeemed so that I can prevent unauthorized access.

How it works. In Active links list, user clicks Trash icon on a link → trash button replaced with confirmation checkbox + 'Revoke' button (destructive). Checkbox label: 'Confirm'. User checks → 'Revoke' enables → clicks → link is deleted (removed from list). Cancel button closes the prompt.

WhoProject lead / owner PermissionsManage members/invites: project_lead(500); role changes: maintainer/owner(≥600); link role capped at contributor(400)
Key files

src/components/SharePanel.tsx:442-480
src/lib/sync/invites.ts:223-247

Preview invite before redeeming ▶ 00:00

As a joiner, I want to see details about the project and role before committing to join so that I know what access I'm accepting.

How it works. User visits /join/:token (signed-out or signed-in). Invite preview loads async. If valid: card shows project name, role (capitalized, underscores to spaces), recipient email (if bound), and expiry. If expired/invalid: shows 'This invite link is no longer valid' error and 'Back to projects' button. If network error: shows 'Couldn't load invitation' + 'Try again' button.

WhoProject lead / owner PermissionsManage members/invites: project_lead(500); role changes: maintainer/owner(≥600); link role capped at contributor(400)
Key files

src/components/JoinPage.tsx:56-86
src/components/JoinPage.tsx:146-202
src/lib/sync/invites.ts:127-146

Preview multi-project invite before redeeming ▶ 00:00

As a joiner receiving a bulk invite to multiple projects, I want to see all projects I'm being invited to so that I can understand the scope before accepting.

How it works. User visits /join/:token with multi-project token. JoinPage loads preview via previewMultiInvite. Card shows: 'You're invited to N project(s)' + bulleted list of project names (with archived badge if applicable) + role summary. After acceptance, user sees all invited projects.

WhoProject lead / owner PermissionsManage members/invites: project_lead(500); role changes: maintainer/owner(≥600); link role capped at contributor(400)
Key files

src/components/JoinPage.tsx:169-194
src/lib/sync/invites.ts:276-293

Sign in from invite link (inline auth) ▶ 00:00

As an unsigned-out user, I want to sign in or create an account directly from the invite link page so that I don't have to navigate elsewhere.

How it works. User arrives at /join/:token while signed out. Card shows invite preview + inline auth form. User can: login via FrontierLoginForm (username + password) or click 'Create an account' to swap to FrontierSignupForm. After successful auth, session updates and card swaps to confirmation. Forgot password link navigates to FrontierForgotPasswordForm.

WhoProject lead / owner PermissionsManage members/invites: project_lead(500); role changes: maintainer/owner(≥600); link role capped at contributor(400)
Key files

src/components/JoinPage.tsx:274-320
src/components/git-import/FrontierLoginForm.tsx
src/components/git-import/FrontierSignupForm.tsx

Accept single-project invite ▶ 00:00

As a signed-in joiner, I want to confirm and accept a project invite so that I become a member and can access the project.

How it works. Signed-in user visits /join/:token. Card shows preview + 'Accept invitation' and 'Not now' buttons. User clicks 'Accept invitation' → phase becomes 'redeeming' → spinner shows → acceptServerInvite called → on success: redirects to /project/:projectId. On failure: phase='error' with message 'This invite link is no longer valid. Ask the project owner for a fresh link.'

WhoProject lead / owner PermissionsManage members/invites: project_lead(500); role changes: maintainer/owner(≥600); link role capped at contributor(400)
Key files

src/components/JoinPage.tsx:100-130
src/components/JoinPage.tsx:256-273
src/lib/sync/invites.ts:155-178

Accept multi-project invite ▶ 00:00

As a signed-in joiner receiving a multi-project invite, I want to accept and join all projects at once so that I get access to the entire workspace set.

How it works. Signed-in user visits /join/:token with multi-project token. Card shows preview listing all projects. User clicks 'Accept invitation' → acceptMultiInvite called → on success: user is added to all projects, redirects to /project/:projectId (first project). Analytics event INVITE_REDEEMED fired with invite_kind='multi', project_count=N.

WhoProject lead / owner PermissionsManage members/invites: project_lead(500); role changes: maintainer/owner(≥600); link role capped at contributor(400)
Key files

src/components/JoinPage.tsx:100-130
src/lib/sync/invites.ts:295-318

Share panel Members tab ▶ 00:00

As a project owner, I want to view and manage project members directly so that I can see who has access and modify roles.

How it works. User opens Share dialog → Members tab (default active) → sees MembersPanel with: (1) existing members list showing username, role, source badge (via org/group/creator), (2) if member is locked (org/creator source), shows lock hint tooltip instead of delete button, (3) if member is unlocked, shows role-change select (capped at caller's max role) and trash delete button, (4) add-member input at top (UsernameTypeahead or direct invite).

WhoProject lead / owner PermissionsManage members/invites: project_lead(500); role changes: maintainer/owner(≥600); link role capped at contributor(400)
Key files

src/components/SharePanel.tsx:95-150
src/components/MembersPanel.tsx

Add member to project by username ▶ 00:00

As a project owner, I want to add an existing Aquilla user to my project by their username so that I can grant them access.

How it works. User in Members tab types username in UsernameTypeahead → dropdown shows matching users (scoped search) → user picks suggestion (sets value.resolved) → role defaults to CONTRIBUTOR → user clicks Add button (or types username, hits Enter). Call addProjectMember. On success: member appears in list. On failure: error message shows.

WhoProject lead / owner PermissionsManage members/invites: project_lead(500); role changes: maintainer/owner(≥600); link role capped at contributor(400)
Key files

src/components/MembersPanel.tsx:71-84
src/components/UsernameTypeahead.tsx:59-250
src/hooks/useUserSearch.ts

Change member's project role ▶ 00:00

As a project owner, I want to change a member's role so that I can adjust their permissions.

How it works. In Members tab, next to a member's name is a role dropdown (aria-label='Change role'). Dropdown shows roles the caller can grant (capped at callerMaxRole). User selects new role → onChangeRole fires → role updates. If member is locked (org/creator source), dropdown is hidden and lock hint shows instead.

WhoProject lead / owner PermissionsManage members/invites: project_lead(500); role changes: maintainer/owner(≥600); link role capped at contributor(400)
Key files

src/components/MembersPanel.tsx:109-134
src/hooks/useProjectMembers.ts

Remove member from project ▶ 00:00

As a project owner, I want to remove a member from my project so that I can revoke their access.

How it works. In Members tab, next to unlocked member is a Trash icon button (aria-label='Remove {username}'). User clicks → onRemove fires → member disappears from list. If member is locked (org/creator), trash button hidden and lock hint tooltip shows.

WhoProject lead / owner PermissionsCreate invites & share links: project_lead(500); tokenized link role capped at contributor(400) — never managerial
Key files

src/components/MembersPanel.tsx:135-150
src/hooks/useProjectMembers.ts

Add multiple users to multiple projects via dialog ▶ 00:00

As an org admin, I want to add an existing Aquilla user to multiple projects at once so that I can onboard them efficiently.

How it works. Org admin opens Members page → clicks 'Add to projects' button (disabled if no projects) → MultiProjectInviteDialog opens. User types username in UsernameTypeahead → picks suggestion (verified badge shows). User toggles project checkboxes (selectable roles shown per-project, default CONTRIBUTOR). User clicks 'Add to projects' → parallel addProjectMember calls → on success, per-project 'added' chips appear, cancel button becomes 'Close'. Dialog stays open.

WhoProject lead / owner PermissionsManage members/invites: project_lead(500); role changes: maintainer/owner(≥600); link role capped at contributor(400)
Key files

src/components/MultiProjectInviteDialog.tsx:55-350
src/pages/MembersPage.tsx:168-177
src/lib/frontier/members.ts

Switch invite mode between username and email ▶ 00:00

As an org admin inviting via the multi-project dialog, I want to toggle between inviting existing users (@user) and sending email invites so that I can choose the right path for each recipient.

How it works. MultiProjectInviteDialog shows mode-toggle buttons: '@user' (default, username typeahead) and 'email' (email input type). User clicks 'email' → recipient input becomes type='email', description changes to 'Email invites are per-project'. Clicking '@user' swaps back to typeahead. In email mode, submit button hidden; instead guide shows 'use each project's Share panel'.

WhoProject lead / owner PermissionsManage members/invites: project_lead(500); role changes: maintainer/owner(≥600); link role capped at contributor(400)
Key files

src/components/MultiProjectInviteDialog.tsx:62-77
src/components/MultiProjectInviteDialog.tsx:177-181
src/components/UsernameTypeahead.tsx:165-210

Verified Aquilla user badge ▶ 00:00

As a user filling an invite form, I want to see when I've picked a verified Aquilla user so that I know the username exists and is resolved.

How it works. In MultiProjectInviteDialog or SharePanel Members tab, user types username → types match a suggestion → user clicks suggestion (handlePick sets value.resolved). Verified badge appears next to input (data-tooltip='Verified Aquilla user', green background, 'Verified' text). Badge disappears if user edits the input away from the picked username.

WhoProject lead / owner PermissionsManage members/invites: project_lead(500); role changes: maintainer/owner(≥600); link role capped at contributor(400)
Key files

src/components/UsernameTypeahead.tsx:160-163
src/components/UsernameTypeahead.tsx:255-270

Username typeahead search ▶ 00:00

As a user adding members, I want to search for usernames as I type so that I can find the right person.

How it works. User types in username input → after 2+ chars, useUserSearch fetches /api/v2/users/search with scoped=true (org/project-overlap). Dropdown shows matching results sorted alphabetically, excluding already-added users. Sub-2-char input shows hint. No matches shows 'No Aquilla user matching that name'. Clicking result picks it (sets resolved).

WhoProject lead / owner PermissionsCreate invites & share links: project_lead(500); tokenized link role capped at contributor(400) — never managerial
Key files

src/components/UsernameTypeahead.tsx:59-250
src/hooks/useUserSearch.ts

Pending invitations inbox (received invites) ▶ 00:00

As a user with pending email-targeted invites, I want to see them on my dashboard so that I can accept them even if the email link never arrived.

How it works. User lands on dashboard (/ or /projects). If they have pending invites addressed to their account email (via listMyPendingInvites), a 'Pending invitations' card renders. Card shows: project name(s), inviter name, role, expiry. 'Review & accept' link navigates to /join/:token. If no invites, card doesn't render. On failure, returns empty list (dashboard card hides).

WhoProject lead / owner PermissionsManage members/invites: project_lead(500); role changes: maintainer/owner(≥600); link role capped at contributor(400)
Key files

src/components/org/OrgHome.tsx:172-173
src/components/org/OrgHome.tsx:320-345 (rendering)
src/lib/sync/invites.ts:337-354

Targeted vs open invite badge ▶ 00:00

As an org admin viewing pending invites, I want to see whether each invite is targeted to a specific email or an open link so that I can understand its scope.

How it works. In Members page Pending invitations section, each pending invite row shows: project name · role · [email badge OR 'open link' badge] · expiry. Targeted invite shows email in blue chip (tooltip: 'Targeted invite...'). Open invite shows 'open link' text.

WhoProject lead / owner PermissionsManage members/invites: project_lead(500); role changes: maintainer/owner(≥600); link role capped at contributor(400)
Key files

src/pages/MembersPage.tsx:290-365
src/components/org/OrgHome.tsx:330-340

Revoke pending invite (org admin) ▶ 00:00

As an org owner/maintainer, I want to revoke a pending invite so that I can cancel an accidental share or revoke an employee's access before they join.

How it works. In Pending invitations list (MembersPage), org owner hovers/taps invite row → X or trash button appears. User clicks → confirmation or immediate DELETE /api/v2/projects/:id/invites/:token → row disappears from list. Error message shows if revoke fails.

WhoProject lead / owner PermissionsManage members/invites: project_lead(500); role changes: maintainer/owner(≥600); link role capped at contributor(400)
Key files

src/pages/MembersPage.tsx:346-390
src/hooks/useOrgInvites.ts:66-87
src/lib/frontier/orgs.ts (listPendingOrgInvites, revokeProjectInvite)

External collaborators governance ▶ 00:00

As an org maintainer, I want to see everyone who accesses my projects without being org members so that I can audit and revoke their access.

How it works. Members page shows 'External collaborators' section (maintainer+ only, null hides it for non-maintainers). Lists: username + 'external' badge. For each project grant, shows: project name · role · revoke button (if direct grant) OR 'via group/creator' tooltip (if group/creator source). Clicking revoke removes that project grant only (user may stay if other projects grant them).

WhoProject lead / owner PermissionsCreate invites & share links: project_lead(500); tokenized link role capped at contributor(400) — never managerial
Key files

src/components/org/ExternalCollaboratorsSection.tsx
src/pages/MembersPage.tsx:207-212
src/lib/frontier/external-collaborators.ts

Cross-org project visibility on accept ▶ 00:00

As a joiner accepting an invite to a project in another organization, I want the project to appear on my dashboard so that I can navigate to it.

How it works. User accepts invite to a project in org A (user is in org B). Project surfaces on /projects under 'Shared with you' partition. Also visible on / (dashboard Overview). Clicking navigates to /project/:id and syncs normally.

WhoProject lead / owner PermissionsManage members/invites: project_lead(500); role changes: maintainer/owner(≥600); link role capped at contributor(400)
Key files

src/components/org/OrgHome.tsx:300-345
src/lib/frontier/shared-projects.ts (partitionSharedProjects)
src/pages/DashboardPage.tsx (Dashboard)

Invite URL copy confirmation toast ▶ 00:00

As a user copying an invite link, I want a visual confirmation so that I know the copy succeeded.

How it works. After clicking Copy URL button, 'Copied!' text appears below the input for ~1.5 seconds, then fades away.

WhoProject lead / owner PermissionsManage members/invites: project_lead(500); role changes: maintainer/owner(≥600); link role capped at contributor(400)
Key files

src/components/SharePanel.tsx:219-223
src/components/SharePanel.tsx:244

Onboarding invite step (simplified flow) ▶ 00:00

As a user setting up their first project, I want to invite teammates directly from the onboarding checklist so that I can quickly build a team.

How it works. Onboarding checklist shows 'Invite step'. Two paths: (1) By username: type collaborator username → click Add → 'Added {username} as contributor' success message, (2) Share link: click 'Create link' → URL appears with copy button. Both paths show onboarding-flavored UX (smaller form, simplified copy).

WhoProject lead / owner PermissionsManage members/invites: project_lead(500); role changes: maintainer/owner(≥600); link role capped at contributor(400)
Key files

src/components/onboarding/checklist/InviteStep.tsx

Navigate away from pending invite ▶ 00:00

As a joiner on the invite confirmation page, I want to decline the invite and return to the dashboard so that I can explore without committing.

How it works. On JoinPage, user clicks 'Not now' button → navigates to / (dashboard root). Project is not added to their membership.

WhoProject lead / owner PermissionsManage members/invites: project_lead(500); role changes: maintainer/owner(≥600); link role capped at contributor(400)
Key files

src/components/JoinPage.tsx:270-272

Invalid/expired invite error handling ▶ 00:00

As a joiner with an invalid or expired invite link, I want clear error messaging so that I know what went wrong.

How it works. User visits /join/:token with invalid token or expired invite. Preview fetch returns reason='expired' or 'invalid'. Card shows error state with AlertCircle icon. If expired/invalid: 'This invite link is no longer valid. Ask the project owner for a new invite link.' If network error: 'Couldn't load invitation' + 'Try again' button. Both show 'Back to projects' link.

WhoProject lead / owner PermissionsManage members/invites: project_lead(500); role changes: maintainer/owner(≥600); link role capped at contributor(400)
Key files

src/components/JoinPage.tsx:138-254
src/lib/sync/invites.ts:32-37, 127-146

Project link visibility gating ▶ 00:00

As a non-member, I want to ensure I can't access the project's sync data even with an invite link so that membership is the only gate to content.

How it works. Invite acceptance adds user to project_members. /sync-token endpoint checks membership; if user not a member, returns 401. Local-only project view still works but sync-worker writes return 401 (blocked).

WhoProject lead / owner PermissionsManage members/invites: project_lead(500); role changes: maintainer/owner(≥600); link role capped at contributor(400)
Key files

src/components/JoinPage.tsx:33-40 (comments)
src/lib/sync/invites.ts (comments at top)