Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.
| Workspace is the workspace's settings, in one place. Its sidebar is grouped, General, Access, Money, Compute, Code, Agents, Chat, Artifacts, Security and Integrations, every page one click away with no settings inside settings, the groups folding and the owner-only pages hidden from members; what is not here yet is marked Soon with a hint. Members is a list with search and filters for role, two-factor and team, and Invite opens a dialog: who, by username, name or email, their role and a note that goes into the invitation; nothing is filled in inline any more. Invitations is its own page with All, Pending, Accepted, Declined, Expired and Revoked filters that say how many, and each row's menu can copy the link, send it again or revoke it; identity learned to send an invitation again and to carry the note. Permissions gathers every rule about who may do what, by part: Code's base permission and member privileges, who creates teams, who creates channels, and, marked Soon with what applies today, forking private repositories, adding agents to conversations, messaging agents directly, creating spaces and default sharing, creating workspace agents and raising budgets, deploying to production and publishing packages. General lost what moved. The permissions guide is new, and the workspaces, people and teams, access, chat, teams, authentication and billing guides say where things are now. | 1 | /** |
| 2 | * What the Invite dialog and the Invitations page post, answered the same | |
| 3 | * way from whichever page they are on (Members, Invitations): inviting | |
| 4 | * someone, sending an invitation again, and revoking one. Identity checks | |
| 5 | * the owner, the plan and the invite allowance again. Answering an | |
| 6 | * invitation of your own is lib/invitations.server.ts. | |
| 7 | */ | |
| 8 | import type { User } from "@g1t/contracts"; | |
| 9 | ||
| 10 | import type { InvitationResult, InviteResult } from "./invites"; | |
| 11 | import { inviteTarget } from "./people-search"; | |
| 12 | import { identity } from "./services.server"; | |
| 13 | ||
| 14 | /** | |
| 15 | * Invites whoever the form names (`member`: a username or an address) with | |
| 16 | * `role` and an optional `message`. Nobody joins without saying yes: a | |
| 17 | * username or an address gets an invitation to accept or decline. | |
| 18 | */ | |
| 19 | export async function inviteFromForm(user: User, slug: string, form: FormData): Promise<InviteResult> { | |
| 20 | const member = String(form.get("member") ?? "").trim(); | |
| 21 | const who = inviteTarget(member); | |
| 22 | if (!who) return { error: "Enter a g1t username or an email address.", outOfInvites: false }; | |
| 23 | const role = form.get("role") === "owner" ? "owner" : "member"; | |
| 24 | const message = String(form.get("message") ?? "").trim() || null; | |
| 25 | const result = await identity.inviteMember(user, slug, { ...who, role, message }); | |
| 26 | if (!result.ok) return { error: result.error.message, outOfInvites: result.error.code === "limit" }; | |
| 27 | return { invited: "email" in who ? who.email : `@${who.username}`, outOfInvites: false }; | |
| 28 | } | |
| 29 | ||
| 30 | /** `action=revoke-invite` or `action=resend-invite`, with the invitation's `id`; null for anything else. */ | |
| 31 | export async function invitationFromForm(user: User, slug: string, form: FormData): Promise<InvitationResult | null> { | |
| 32 | const action = form.get("action"); | |
| 33 | const id = String(form.get("id") ?? "").trim(); | |
| 34 | if (action !== "revoke-invite" && action !== "resend-invite") return null; | |
| 35 | if (!id) return { error: "Which invitation?", id }; | |
| 36 | const result = | |
| 37 | action === "revoke-invite" ? await identity.revokeWorkspaceInvite(user, slug, id) : await identity.resendWorkspaceInvite(user, slug, id); | |
| 38 | if (!result.ok) return { error: result.error.message, id }; | |
| 39 | return { done: action === "revoke-invite" ? "revoked" : "resent", id }; | |
| 40 | } |