Skip to content
318 linesCodeBlameRaw

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.

Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1import { CheckCircle2, Ticket } from "lucide-react";
2import { Form, Link, data, redirect } from "react-router";
3
4import type { InvitePreview } from "@g1t/contracts";
API and MCP server, Rust identity service, registration, site redesign5
6import type { Route } from "./+types/register";
Social cards for every page: og.g1t.sh7import { page } from "../lib/meta";
API and MCP server, Rust identity service, registration, site redesign8import { AuthCard } from "../components/auth-card";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look9import { ContinueWithGithub, OrDivider } from "../components/github";
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas10import { Honeypot } from "../components/honeypot";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look11import { githubSignInEnabled } from "../lib/github.server";
Merge branch 'worktree-agent-a985c01f43538af01'12import { Avatar, Button, ErrorText, Field, Input, SubmitButton } from "../components/ui";
API and MCP server, Rust identity service, registration, site redesign13import { identity } from "../lib/services.server";
Merge invite emails that confirm the address: the emailed link carries a proof only that email has, so signing up from it needs no code; shared links and typed codes still confirm14import { cleanCode, cleanProof, invitePath, looksAutomated, sharedDomainsHint, sharedInviteLine } from "../lib/invites";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look15import { clientKey, registrationMode } from "../lib/registration.server";
API and MCP server, Rust identity service, registration, site redesign16import {
17 assertSameOrigin,
18 getViewer,
Device sign-in replaces registering and minting tokens over the API19 nextPath,
API and MCP server, Rust identity service, registration, site redesign20 startSession,
21} from "../lib/session.server";
22
Social cards for every page: og.g1t.sh23export function meta(args: Route.MetaArgs) {
24 return page(args, { title: "Create an account · g1t" });
API and MCP server, Rust identity service, registration, site redesign25}
26
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look27export async function loader({ request, context }: Route.LoaderArgs) {
Device sign-in replaces registering and minting tokens over the API28 if (getViewer(context)) throw redirect(nextPath(request));
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look29 const inviteOnly = (await registrationMode()) !== "open";
30 const code = cleanCode(new URL(request.url).searchParams.get("invite"));
31 // The invite is checked before anything else on the page: without a good
32 // one, there is no form to fill in.
33 const checked = code ? await identity.checkInvite(code, clientKey(request)) : null;
34 const invite: InvitePreview | null = checked?.ok ? checked.value : null;
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas35 // A good invite is used on its own page, which knows whom it is from and
Merge shared invite links: label, uses, expiry, domains; joined through recorded (identity 0038)36 // where it leads; it signs up, joins and lands in one go. A shared link
37 // for a group signs up here: it joins nothing, and the form says which
38 // group it is for.
Merge invite emails that confirm the address: the emailed link carries a proof only that email has, so signing up from it needs no code; shared links and typed codes still confirm39 // The invite email's proof goes with it, so the address it proves stays
40 // confirmed there.
41 if (invite && code && !invite.sharedLabel) {
42 throw redirect(invitePath(code, cleanProof(new URL(request.url).searchParams.get("proof"))));
43 }
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look44 // Signing up with GitHub carries the invite code and `next` through it.
45 const params = new URLSearchParams();
46 if (code) params.set("invite", code);
47 if (nextPath(request) !== "/") params.set("next", nextPath(request));
48 const query = params.toString();
49 return {
50 inviteOnly,
51 code,
52 invite,
53 inviteError: checked && !checked.ok ? checked.error.message : null,
54 github: await githubSignInEnabled(),
55 githubUrl: query ? `/auth/github?${query}` : "/auth/github",
56 started: Date.now(),
57 };
API and MCP server, Rust identity service, registration, site redesign58}
59
60export async function action({ request }: Route.ActionArgs) {
61 assertSameOrigin(request);
62 const form = await request.formData();
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look63 const client = clientKey(request);
64
65 if (form.get("intent") === "request-access") {
66 // A bot gets the same thanks as a person, and nothing is kept.
67 if (looksAutomated(form)) return { requested: true };
68 const result = await identity.requestAccess(
69 String(form.get("email") ?? ""),
70 String(form.get("about") ?? ""),
71 client,
72 );
73 if (!result.ok) return data({ requestError: result.error.message }, { status: 422 });
74 return { requested: true };
75 }
76
77 const code = cleanCode(String(form.get("invite") ?? ""));
78 if (looksAutomated(form)) return data({ error: "Something went wrong. Try again." }, { status: 422 });
79 // The invite first, so its problem is the one shown.
80 let joins: string | null = null;
81 if (code) {
82 const checked = await identity.checkInvite(code, client);
83 if (!checked.ok) return data({ error: checked.error.message }, { status: 422 });
84 joins = checked.value.workspace?.slug ?? null;
85 }
API and MCP server, Rust identity service, registration, site redesign86 const result = await identity.register(
87 String(form.get("username") ?? ""),
88 String(form.get("email") ?? ""),
89 String(form.get("password") ?? ""),
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look90 code || null,
91 client,
API and MCP server, Rust identity service, registration, site redesign92 );
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look93 if (!result.ok) return data({ error: result.error.message }, { status: 422 });
94 // An invite to a workspace lands in it; anything else goes where it was going.
95 const next = nextPath(request);
96 throw redirect(joins && next === "/" ? `/${joins}` : next, {
API and MCP server, Rust identity service, registration, site redesign97 headers: { "set-cookie": startSession(result.value.sessionToken) },
98 });
99}
100
Merge shared invite links: label, uses, expiry, domains; joined through recorded (identity 0038)101/** Who sent the invite and what it joins, above the form; for a shared link, the group it is for. */
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look102function InvitedBy({ invite }: { invite: InvitePreview }) {
103 const from = invite.invitedBy;
Merge shared invite links: label, uses, expiry, domains; joined through recorded (identity 0038)104 const group = sharedInviteLine(invite.sharedLabel);
105 if (group) {
106 return (
107 <div className="mb-6 flex items-center gap-3 rounded-lg border border-accent/30 bg-accent/5 p-3" role="status">
108 <span className="inline-flex size-9 shrink-0 items-center justify-center rounded-full bg-accent/15 text-accent">
109 <Ticket size={18} />
110 </span>
111 <p className="min-w-0 text-sm leading-5 font-medium text-fg">{group}</p>
112 </div>
113 );
114 }
API and MCP server, Rust identity service, registration, site redesign115 return (
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look116 <div className="mb-6 flex items-center gap-3 rounded-lg border border-accent/30 bg-accent/5 p-3" role="status">
117 {invite.workspace ? (
118 <Avatar name={invite.workspace.slug} image={invite.workspace.avatar} size={36} square />
119 ) : from ? (
120 <Avatar name={from.username} image={from.avatar} size={36} />
121 ) : (
122 <span className="inline-flex size-9 shrink-0 items-center justify-center rounded-full bg-accent/15 text-accent">
123 <Ticket size={18} />
124 </span>
125 )}
126 <p className="min-w-0 text-sm leading-5">
127 {from ? (
128 <>
129 <span className="font-medium text-fg">{from.name ?? from.username}</span>
130 {from.name && <span className="font-mono text-muted"> @{from.username}</span>}
131 </>
132 ) : (
133 <span className="font-medium text-fg">The g1t team</span>
134 )}{" "}
135 <span className="text-muted">
136 invited you
137 {invite.workspace ? (
138 <>
139 {" "}to <span className="font-medium text-fg">{invite.workspace.name}</span>
140 </>
141 ) : (
142 " to g1t"
143 )}
144 .
145 </span>
146 </p>
147 </div>
148 );
149}
150
151function SignUpForm({
152 loaderData,
153 error,
154}: {
155 loaderData: Route.ComponentProps["loaderData"];
156 error: string | undefined;
157}) {
158 const { invite, code, inviteOnly } = loaderData;
159 return (
160 <>
161 {invite && <InvitedBy invite={invite} />}
162 {loaderData.github && (
API and MCP server, Rust identity service, registration, site redesign163 <>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look164 <ContinueWithGithub href={loaderData.githubUrl} />
165 <OrDivider />
API and MCP server, Rust identity service, registration, site redesign166 </>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look167 )}
168 <Form method="post" className="relative space-y-4">
169 <Honeypot started={loaderData.started} />
170 {inviteOnly || code ? (
171 <Field label="Invite code" hint={invite ? undefined : "From your invite link or email."}>
172 <span className="block font-mono">
173 <Input name="invite" defaultValue={code} required={inviteOnly} spellCheck={false} autoCapitalize="none" />
174 </span>
175 </Field>
176 ) : null}
API and MCP server, Rust identity service, registration, site redesign177 <Field
178 label="Username"
Agents as a team: lifecycle, merge queue, billing and a new shell179 hint="Lowercase letters, digits and hyphens. It is how you sign in and how others see you."
API and MCP server, Rust identity service, registration, site redesign180 >
181 <Input
182 name="username"
183 autoComplete="username"
184 required
185 autoFocus
186 maxLength={39}
187 pattern="[a-z0-9](?:[a-z0-9]|-(?=[a-z0-9]))*"
188 />
189 </Field>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look190 <Field
191 label="Email"
Merge shared invite links: label, uses, expiry, domains; joined through recorded (identity 0038)192 hint={invite?.email ? `This invite is for ${invite.email}. Use that address.` : sharedDomainsHint(invite?.sharedDomains)}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look193 >
API and MCP server, Rust identity service, registration, site redesign194 <Input name="email" type="email" autoComplete="email" required />
195 </Field>
196 <Field label="Password" hint="At least 10 characters.">
197 <Input
198 name="password"
199 type="password"
200 autoComplete="new-password"
201 required
202 minLength={10}
203 />
204 </Field>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look205 <ErrorText>{error}</ErrorText>
API and MCP server, Rust identity service, registration, site redesign206 <div className="pt-2 *:w-full">
Merge branch 'worktree-agent-a985c01f43538af01'207 <SubmitButton pending="Creating account…">
208 {invite?.workspace ? `Create account and join ${invite.workspace.slug}` : "Create account"}
209 </SubmitButton>
API and MCP server, Rust identity service, registration, site redesign210 </div>
211 </Form>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look212 </>
213 );
214}
215
216/** Invite-only and no good code: enter one, or ask for access. */
217function InviteOnly({
218 loaderData,
219 actionData,
220}: {
221 loaderData: Route.ComponentProps["loaderData"];
222 actionData: Route.ComponentProps["actionData"];
223}) {
224 const requested = actionData && "requested" in actionData;
225 return (
226 <div className="space-y-8">
227 <section id="invite" className="scroll-mt-24">
228 <h2 className="text-sm font-medium">Have an invite?</h2>
229 <p className="mt-1 text-sm text-muted">Paste the code or link from your invite.</p>
230 <Form method="get" className="mt-3 flex gap-2">
231 <div className="min-w-0 grow font-mono">
232 <Input
233 name="invite"
234 aria-label="Invite code"
235 placeholder="g1t-xxxx-xxxx-…"
236 defaultValue={loaderData.code}
237 required
238 spellCheck={false}
239 autoCapitalize="none"
240 />
241 </div>
242 <Button type="submit" variant="quiet">
243 Continue
244 </Button>
245 </Form>
246 {loaderData.inviteError && (
247 <div className="mt-2">
248 <ErrorText>{loaderData.inviteError}</ErrorText>
249 </div>
250 )}
251 </section>
252
253 <section id="request" className="scroll-mt-24 border-t border-line pt-8">
254 <h2 className="text-sm font-medium">Request access</h2>
255 {requested ? (
Merge branch 'worktree-agent-ad7c6d88d93adc817'256 <p className="mt-3 flex items-start gap-2 rounded-md border border-success/40 bg-surface p-3 text-sm" role="status">
257 <CheckCircle2 size={16} className="mt-0.5 shrink-0 text-success" />
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas258 <span>
259 You are on the list, and your invite comes to that address when a place opens. The first time an address
260 asks, we email it to confirm.
261 </span>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look262 </p>
263 ) : (
264 <>
265 <p className="mt-1 text-sm text-muted">
266 Leave your email and we will send an invite as we open up. Someone already on g1t can also invite you.
267 </p>
268 <Form method="post" className="relative mt-3 space-y-3">
269 <input type="hidden" name="intent" value="request-access" />
270 <Honeypot started={loaderData.started} />
271 <Field label="Email">
272 <Input name="email" type="email" autoComplete="email" required maxLength={254} />
273 </Field>
274 <Field label="What will you build? (optional)">
275 <textarea
276 name="about"
277 rows={3}
278 maxLength={1000}
279 className="w-full rounded-md border border-line bg-bg px-3 py-2 text-sm outline-none transition-colors placeholder:text-faint hover:border-line-strong focus:border-accent-dim"
280 />
281 </Field>
282 <ErrorText>{actionData && "requestError" in actionData ? actionData.requestError : undefined}</ErrorText>
283 <div className="*:w-full">
Merge branch 'worktree-agent-a985c01f43538af01'284 <SubmitButton pending="Sending…" match={{ intent: "request-access" }}>
285 Request access
286 </SubmitButton>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look287 </div>
288 </Form>
289 </>
290 )}
291 </section>
292 </div>
293 );
294}
295
296export default function Register({ loaderData, actionData }: Route.ComponentProps) {
297 const { inviteOnly, invite } = loaderData;
298 const error = actionData && "error" in actionData ? actionData.error : undefined;
299 // Invite-only: the sign-up form only appears with a good code (or a
300 // submitted one that failed, so the person can correct the rest).
301 const showForm = !inviteOnly || invite !== null;
302 return (
303 <AuthCard
304 title={showForm ? "Create your account" : "g1t is invite-only for now"}
Chat and workspace agents: channels, DMs and named agents you talk to305 subtitle={showForm ? "Your team and its agents, working in one place" : "Enter your invite, or ask for one"}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look306 footer={
307 <>
308 Already have an account?{" "}
309 <Link to="/login" className="text-fg underline underline-offset-4">
310 Sign in
311 </Link>
312 </>
313 }
314 >
315 {showForm ? <SignUpForm loaderData={loaderData} error={error} /> : <InviteOnly loaderData={loaderData} actionData={actionData} />}
API and MCP server, Rust identity service, registration, site redesign316 </AuthCard>
317 );
318}

This file's history is long; its oldest lines are credited to the oldest commit read.