mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-21 00:05:49 +02:00
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - People manage collaborators from the Members page. > - Cloud manages invitations outside the tenant's local invitation system. > - The local Invites tab is hidden on Cloud, so this page has no way to invite a person. > - This pull request adds an Invite people action for the current Cloud stack's owner or admin. > - The action opens the existing Cloud People settings for that stack. ## Linked Issues or Issue Description **What happened?** A Cloud owner opens Organization Settings → Members and finds no invitation action. The tenant-local Invites tab is hidden, and the page does not link to Cloud's invitation flow. **Expected behavior** Cloud owners and admins can start an invitation from Members. **Steps to reproduce** 1. Sign in to a Cloud-managed instance as the current stack's owner or admin. 2. Open Organization Settings → Members with `company.invites` hidden. 3. Look for an invitation action beside the page heading. **Paperclip version or commit** `7b7c4d4172d6aac14919e2682b702ae87bc17653`. **Deployment mode** Cloud-managed, authenticated. Related search: #2388 proposes broader member-management UI. This change only connects the existing Members page to Cloud invitations. No duplicate Cloud invitation action PR was found. ## What Changed - Add **Invite people** beside the Members heading for the current Cloud stack's owner/admin. - Read the role from the authenticated Cloud portfolio. Ownership of another stack does not enable the action. - Navigate to the current stack's People settings on the configured Cloud origin. Keep the local Invites tab hidden when configured. - Cover allowed roles, denied roles, loading, failed refresh, missing configuration, current-stack selection, and self-hosted behavior. Document the navigation contract. ## Verification - Focused Members and Cloud link tests: 23 passed. - Full UI suite: 6,669 passed across 634 files. - UI typecheck and `pnpm check:token-gates`: passed. - `pnpm build`: passed. - `pnpm -r typecheck`: passed. - All PR CI checks passed, including the full general, serialized, browser, and runner test jobs. The duplicate local repository-wide `pnpm test:run` was stopped after CI completed; it is not reported as a local pass. - Manual acceptance after tenant rollout: an owner/admin opens Members, selects **Invite people**, and reaches the same stack's Cloud People settings. A member does not see the action. ## Risks - The action needs a tenant app update before it appears on an existing stack. - The portfolio request must identify the current stack and its role. The action stays hidden when that information is unavailable or the request fails. - Cloud rechecks invitation authorization at the destination. No schema, API, or invitation-acceptance behavior changes. ## Model Used - OpenAI Codex, GPT-6, with reasoning, code execution, and repository tools. The session does not expose a more specific model identifier or context-window size. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge Co-authored-by: Paperclip <noreply@paperclip.ing>
66 lines
2.6 KiB
TypeScript
66 lines
2.6 KiB
TypeScript
/**
|
|
* Links out of a tenant instance and into the Paperclip Cloud app.
|
|
*
|
|
* The base always comes from the health `cloud` block (derived from
|
|
* `PAPERCLIP_CLOUD_API_ORIGIN`) — the cloud domain is never hardcoded, because
|
|
* aliases move and self-hosted instances have no cloud block at all. Only the
|
|
* origin of that value is used; a control-plane path suffix is dropped, exactly
|
|
* as the server-side portfolio proxy resolves it.
|
|
*
|
|
* These are always full top-level navigations, never in-app routes: the cloud
|
|
* harness shadows GET `/stacks` and `/stacks/*` on tenant hosts, so an in-app
|
|
* route under that prefix would be unreachable.
|
|
*/
|
|
export function cloudAppUrl(cloudBaseUrl: string | null | undefined, path: string): string | null {
|
|
const base = cloudBaseUrl?.trim();
|
|
if (!base) return null;
|
|
try {
|
|
const url = new URL(path, base);
|
|
// The base is server-supplied, but a stray non-web scheme must never reach
|
|
// `window.location.assign`.
|
|
if (url.protocol !== "https:" && url.protocol !== "http:") return null;
|
|
return url.toString();
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Entry-code handoff for another stack: cloud authenticates the user for that
|
|
* stack and wakes it if it is asleep before redirecting to its tenant host.
|
|
*/
|
|
export function cloudStackEnterUrl(
|
|
cloudBaseUrl: string | null | undefined,
|
|
stackSlug: string | null | undefined,
|
|
): string | null {
|
|
const slug = stackSlug?.trim();
|
|
if (!slug) return null;
|
|
return cloudAppUrl(cloudBaseUrl, `/stacks/${encodeURIComponent(slug)}/enter`);
|
|
}
|
|
|
|
/** Cloud's own create-a-stack flow, which replaces the in-app company wizard. */
|
|
export function cloudStackCreateUrl(cloudBaseUrl: string | null | undefined): string | null {
|
|
return cloudAppUrl(cloudBaseUrl, "/stacks/new");
|
|
}
|
|
|
|
/** Cloud manages human invitations in the current stack's People settings. */
|
|
export function cloudStackInviteUrl(
|
|
cloudBaseUrl: string | null | undefined,
|
|
stackSlug: string | null | undefined,
|
|
): string | null {
|
|
const slug = stackSlug?.trim();
|
|
if (!slug) return null;
|
|
return cloudAppUrl(cloudBaseUrl, `/workspaces/${encodeURIComponent(slug)}/settings?section=people`);
|
|
}
|
|
|
|
/**
|
|
* Cloud's organization portfolio in its explicit manage view. `?manage=1`
|
|
* matters: the plain launchpad auto-forwards a solo user straight back into
|
|
* their one openable stack, and the caller here has just archived that
|
|
* stack's only company — bouncing back into it is exactly what the
|
|
* navigation is escaping.
|
|
*/
|
|
export function cloudPortfolioManageUrl(cloudBaseUrl: string | null | undefined): string | null {
|
|
return cloudAppUrl(cloudBaseUrl, "/orgs?manage=1");
|
|
}
|