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.
| Merge branch 'worktree-agent-a1b995daa94e4e1b7' | 1 | import { createRequestHandler } from "react-router"; |
| 2 | ||
| Merge branch 'worktree-agent-a8385d293d42c913a' | 3 | import { identityClient, isNamespaceShaped } from "@g1t/contracts"; |
| 4 | ||
| Merge g1tusercontent.com: registry answers run nothing in a browser, the site's pages run only their own scripts, repository files and avatars on their own origin, raw files rate limited per address | 5 | import { addressesFor } from "../app/lib/addresses"; |
| Merge site-audit: SEO routes, analytics with consent first in Europe, security page, founder and X account, docs header; the tour opens on the pull request and leads with Code, on the real shell, with Agents and Docs working today | 6 | import { visitorAsksFirst } from "../app/lib/analytics-consent"; |
| Merge g1tusercontent.com: registry answers run nothing in a browser, the site's pages run only their own scripts, repository files and avatars on their own origin, raw files rate limited per address | 7 | import { hardenRegistryHeaders } from "../app/lib/content-safety"; |
| 8 | import { withSiteHeaders } from "../app/lib/page-headers"; | |
| Merge branch 'worktree-agent-a1b995daa94e4e1b7' | 9 | import { finishResponse, withRequestPerf } from "../app/lib/perf.server"; |
| Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9 | 10 | import { READ_DEADLINE_MS, withDeadline } from "../app/lib/deadline"; |
| Developer mode: a page that fails can say why. Under Settings, Account, a Developer mode switch (kept per browser in a g1t_dev cookie, like Appearance) makes the error page show the error itself, its message and stack, the address, the time, whether it came from a page the server drew or a navigation in the browser, the routes it was drawn from, the site's build (its stylesheet's hashed name), the browser and the desktop app's version, with Copy details to paste it all into a report; with it off, the page says where to turn it on. A signed-in request with developer mode on is answered by React Router in its development mode (workers/app.ts, a second handler made the first time it is wanted), so an error on the server comes through in full instead of "Unexpected Server Error"; that mode differs from production only in that and in hooks only Vite's dev server sets, and only ever covers the person's own requests. Checked in a production build: a failing repository page shows "counts failed with status 503" and its stack with developer mode on, and the usual page with it off. The workspaces guide covers it under Developer mode. | 11 | import { wantsServerErrors } from "../app/lib/developer-mode"; |
| Merge g1tusercontent.com: registry answers run nothing in a browser, the site's pages run only their own scripts, repository files and avatars on their own origin, raw files rate limited per address | 12 | import { gitLimited, pageLimited, usercontentLimited } from "../app/lib/front-door-limits"; |
| Merge branch 'worktree-agent-a1b995daa94e4e1b7' | 13 | import { goImport } from "../app/lib/go-get"; |
| Signed-out page cache: a repository's kept page is served only while the repository is still public, so one made private or deleted never shows from any data centre's copy | 14 | import { repositoryOfPage, stillPublic } from "../app/lib/public-cache"; |
| Merge branch 'worktree-agent-a8385d293d42c913a' | 15 | import { registryWorkspace, servicePath } from "../app/lib/registry-paths"; |
| Merge g1tusercontent.com: registry answers run nothing in a browser, the site's pages run only their own scripts, repository files and avatars on their own origin, raw files rate limited per address | 16 | import { usercontentPath } from "../app/lib/usercontent"; |
| Merge main into Artifacts Phase 2 | 17 | import { TOKEN_CHALLENGE } from "../app/lib/website-token"; |
| Merge g1tusercontent.com: registry answers run nothing in a browser, the site's pages run only their own scripts, repository files and avatars on their own origin, raw files rate limited per address | 18 | import { serveUsercontent } from "./usercontent"; |
| Merge branch 'worktree-agent-a1b995daa94e4e1b7' | 19 | |
| Merge the site's CPU: code highlighted once per data centre by content, markdown parsed once per text, and the request handler built once per isolate | 20 | const loadBuild = () => import("virtual:react-router/server-build"); |
| 21 | ||
| 22 | /** | |
| 23 | * Given a function for the build, React Router derives everything from it | |
| 24 | * again on every request: each route wrapped for the timings in | |
| 25 | * entry.server.tsx, then the route table flattened and ranked, about a | |
| 26 | * millisecond and a half of CPU a page. The build never changes while an | |
| 27 | * isolate lives, so outside development it is loaded once and the handler | |
| 28 | * made once. Development keeps the function, so a changed file is picked up. | |
| 29 | * Only the made handler is kept, never a promise of one, so no request | |
| 30 | * waits on another's (a build that fails to load is loaded again next time). | |
| 31 | */ | |
| 32 | let handler: ReturnType<typeof createRequestHandler> | undefined; | |
| Developer mode: a page that fails can say why. Under Settings, Account, a Developer mode switch (kept per browser in a g1t_dev cookie, like Appearance) makes the error page show the error itself, its message and stack, the address, the time, whether it came from a page the server drew or a navigation in the browser, the routes it was drawn from, the site's build (its stylesheet's hashed name), the browser and the desktop app's version, with Copy details to paste it all into a report; with it off, the page says where to turn it on. A signed-in request with developer mode on is answered by React Router in its development mode (workers/app.ts, a second handler made the first time it is wanted), so an error on the server comes through in full instead of "Unexpected Server Error"; that mode differs from production only in that and in hooks only Vite's dev server sets, and only ever covers the person's own requests. Checked in a production build: a failing repository page shows "counts failed with status 503" and its stack with developer mode on, and the usual page with it off. The workspaces guide covers it under Developer mode. | 33 | /** |
| 34 | * The same, in React Router's development mode: for a signed-in request | |
| 35 | * with developer mode on (app/lib/developer-mode.ts), so a page that fails | |
| 36 | * shows the server's own error rather than "Unexpected Server Error". Made | |
| 37 | * the first time someone asks for it. | |
| 38 | */ | |
| 39 | let developerHandler: ReturnType<typeof createRequestHandler> | undefined; | |
| Merge the site's CPU: code highlighted once per data centre by content, markdown parsed once per text, and the request handler built once per isolate | 40 | async function requestHandler(request: Request): Promise<Response> { |
| 41 | if (import.meta.env.DEV) return createRequestHandler(loadBuild, import.meta.env.MODE)(request); | |
| Developer mode: a page that fails can say why. Under Settings, Account, a Developer mode switch (kept per browser in a g1t_dev cookie, like Appearance) makes the error page show the error itself, its message and stack, the address, the time, whether it came from a page the server drew or a navigation in the browser, the routes it was drawn from, the site's build (its stylesheet's hashed name), the browser and the desktop app's version, with Copy details to paste it all into a report; with it off, the page says where to turn it on. A signed-in request with developer mode on is answered by React Router in its development mode (workers/app.ts, a second handler made the first time it is wanted), so an error on the server comes through in full instead of "Unexpected Server Error"; that mode differs from production only in that and in hooks only Vite's dev server sets, and only ever covers the person's own requests. Checked in a production build: a failing repository page shows "counts failed with status 503" and its stack with developer mode on, and the usual page with it off. The workspaces guide covers it under Developer mode. | 42 | if (wantsServerErrors(request)) { |
| 43 | developerHandler ??= createRequestHandler(await loadBuild(), "development"); | |
| 44 | return developerHandler(request); | |
| 45 | } | |
| Merge the site's CPU: code highlighted once per data centre by content, markdown parsed once per text, and the request handler built once per isolate | 46 | handler ??= createRequestHandler(await loadBuild(), import.meta.env.MODE); |
| 47 | return handler(request); | |
| 48 | } | |
| Merge branch 'worktree-agent-a1b995daa94e4e1b7' | 49 | |
| 50 | const DOCS = "https://docs.g1t.sh"; | |
| 51 | ||
| 52 | /** Where the documentation pages that used to live under /docs are now. */ | |
| 53 | const MOVED_DOCS: Record<string, string> = { | |
| 54 | "/docs": "/quickstart/", | |
| 55 | "/docs/concepts": "/concepts/overview/", | |
| 56 | "/docs/authentication": "/guides/authentication/", | |
| 57 | "/docs/git": "/guides/git/", | |
| 58 | "/docs/agents": "/guides/bring-your-own-agent/", | |
| 59 | "/docs/api": "/reference/api/", | |
| 60 | "/docs/api/reference": "/reference/api/", | |
| 61 | }; | |
| 62 | ||
| 63 | export default { | |
| 64 | async fetch(request, env, ctx) { | |
| Merge g1tusercontent.com: registry answers run nothing in a browser, the site's pages run only their own scripts, repository files and avatars on their own origin, raw files rate limited per address | 65 | // Repository files and avatars, on their own origin |
| 66 | // (g1tusercontent.com): answered before anything of the site's runs, | |
| 67 | // so nothing there reads or sets the session cookie. | |
| 68 | const usercontent = usercontentPath(new URL(request.url), addressesFor(env).usercontent); | |
| 69 | if (usercontent !== null) { | |
| 70 | return (await usercontentLimited(env, request, usercontent)) ?? serveUsercontent(env, ctx, request, usercontent); | |
| Merge branch 'worktree-agent-a1b995daa94e4e1b7' | 71 | } |
| Merge g1tusercontent.com: registry answers run nothing in a browser, the site's pages run only their own scripts, repository files and avatars on their own origin, raw files rate limited per address | 72 | // Every answer: no sniffing, a referrer of the origin alone, and no |
| 73 | // framing of pages (app/lib/page-headers.ts). | |
| 74 | return withSiteHeaders(await site(request, env, ctx)); | |
| Merge branch 'worktree-agent-a1b995daa94e4e1b7' | 75 | }, |
| 76 | } satisfies ExportedHandler<Env>; | |
| 77 | ||
| Merge g1tusercontent.com: registry answers run nothing in a browser, the site's pages run only their own scripts, repository files and avatars on their own origin, raw files rate limited per address | 78 | async function site(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> { |
| 79 | const { pathname } = new URL(request.url); | |
| 80 | // Git over HTTPS shares this hostname but belongs to the repos service. | |
| 81 | // Its answer goes back to the git client as it is: a repository under a | |
| 82 | // renamed workspace's old name answers with a 301, which git follows and | |
| 83 | // must see, so the redirect is never followed here. | |
| 84 | // The container registry (`docker login g1t.sh`) and the npm registry | |
| 85 | // (`g1t.sh/-/npm/`) are the packages | |
| 86 | // service's, handed over the same way. | |
| 87 | // `go get g1t.sh/<workspace>/<repo>`: where its code is, from the | |
| 88 | // address alone, so it costs nothing and caches. | |
| 89 | const go = request.method === "GET" ? goImport(new URL(request.url)) : null; | |
| 90 | if (go) { | |
| 91 | return new Response(go, { | |
| 92 | headers: { "content-type": "text/html; charset=utf-8", "cache-control": "public, max-age=3600" }, | |
| 93 | }); | |
| 94 | } | |
| 95 | const service = servicePath(pathname); | |
| 96 | if (service === "git") { | |
| 97 | // Per address without credentials, per credential with them | |
| 98 | // (app/lib/front-door-limits.ts): anonymous clones are not free to | |
| 99 | // the repository's owner. | |
| 100 | return (await gitLimited(env, request)) ?? proxyGit(env, request); | |
| 101 | } | |
| 102 | if (service === "packages") { | |
| 103 | return proxyPackages(env, request); | |
| 104 | } | |
| 105 | // The documentation is its own site. | |
| 106 | if (pathname === "/docs" || pathname.startsWith("/docs/")) { | |
| 107 | const page = pathname.endsWith("/") ? pathname.slice(0, -1) : pathname; | |
| 108 | const target = MOVED_DOCS[page] ?? "/"; | |
| 109 | return Response.redirect(DOCS + target, 301); | |
| 110 | } | |
| 111 | // Pages and data requests, limited per address signed out and per | |
| 112 | // session signed in (app/lib/front-door-limits.ts). | |
| 113 | const limited = await pageLimited(env, request, pathname); | |
| 114 | if (limited) return limited; | |
| 115 | // Every page and data request says where its time went (Server-Timing) | |
| 116 | // and keeps the reader's D1 bookmarks (app/lib/perf.server.ts). | |
| 117 | const render = () => withRequestPerf(request, async () => finishResponse(request, await requestHandler(request))); | |
| 118 | if (anonymousPage(request, pathname)) return servePublic(env, request, ctx, render); | |
| Merge main into Artifacts Phase 2 | 119 | const answer = await render(); |
| 120 | // A token the site refused (app/lib/website-token.ts) is told so as the | |
| 121 | // API tells it: React Router drops the headers of what middleware throws. | |
| 122 | if (answer.status === 401 && request.headers.has("authorization")) answer.headers.set("www-authenticate", TOKEN_CHALLENGE); | |
| 123 | return answer; | |
| Merge g1tusercontent.com: registry answers run nothing in a browser, the site's pages run only their own scripts, repository files and avatars on their own origin, raw files rate limited per address | 124 | } |
| 125 | ||
| Merge branch 'worktree-agent-a1b995daa94e4e1b7' | 126 | /** |
| 127 | * Public pages as someone signed out sees them: the same for every such | |
| 128 | * visitor, so kept in this data centre's cache. Reserved first segments | |
| 129 | * (settings, sign-in, invitations and the like) and workspace pages (`-`) | |
| The docs folder is gone, and what it held lives where people read it: how a self-hosted g1t runs and how to deploy g1t to Cloudflare are pages on docs.g1t.sh under Run g1t yourself, and speed, rate limits and operating g1t.sh are sections of CONTRIBUTING.md; code that cited a file in docs/ now points to the page or section that covers it, or says what it means itself, and applied migrations and the runner images are left as they were. | 130 | * are never kept. A repository's kept page is served only while repos |
| 131 | * says the repository is still public: one made private or deleted is | |
| 132 | * never served from any data centre's copy. | |
| Merge branch 'worktree-agent-a1b995daa94e4e1b7' | 133 | */ |
| 134 | const PUBLIC_TOP = /^\/(?:|_root\.data|pricing|explore|security|support|policies(?:\/[a-z-]+)?)(?:\.data)?$/; | |
| 135 | const PUBLIC_PROJECT = | |
| Merge email confirmation gate: a code and a link, nothing until confirmed (identity 0036) | 136 | /^\/(?!(?:settings|u|auth|oauth|integrations|new|invite|workspaces|device|verify|confirm-email|login|register|logout|forgot|reset|search|status|avatars|docs)\/)[^/]+\/(?!-\/|-$)[^/]+(?:\/(?:code|commits|issues|pulls|pull\/\d+|issues\/\d+|commit\/[0-9a-f]+|tree\/.+|blob\/.+))?(?:\.data)?$/; |
| Merge branch 'worktree-agent-a1b995daa94e4e1b7' | 137 | /** Fresh for this long; then served once more while a new copy is made. */ |
| 138 | const PUBLIC_FRESH_SECONDS = 30; | |
| 139 | const PUBLIC_STALE_SECONDS = 300; | |
| Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9 | 140 | /** The longest a cache lookup holds up a public page. */ |
| 141 | const CACHE_DEADLINE_MS = 2_000; | |
| Merge branch 'worktree-agent-a1b995daa94e4e1b7' | 142 | |
| 143 | function anonymousPage(request: Request, pathname: string): boolean { | |
| 144 | if (request.method !== "GET") return false; | |
| 145 | if (/(?:^|;\s*)g1t_session=/.test(request.headers.get("cookie") ?? "")) return false; | |
| Merge main into Artifacts Phase 2 | 146 | // A token signs the request in (app/lib/website-token.ts): never kept, never served a kept page. |
| 147 | if (request.headers.has("authorization")) return false; | |
| Merge branch 'worktree-agent-a1b995daa94e4e1b7' | 148 | return PUBLIC_TOP.test(pathname) || PUBLIC_PROJECT.test(pathname); |
| 149 | } | |
| 150 | ||
| Signed-out page cache: a repository's kept page is served only while the repository is still public, so one made private or deleted never shows from any data centre's copy | 151 | async function servePublic(env: Env, request: Request, ctx: ExecutionContext, render: () => Promise<Response>): Promise<Response> { |
| Merge branch 'worktree-agent-a1b995daa94e4e1b7' | 152 | const cache = (caches as unknown as { default: Cache }).default; |
| Merge site-audit: SEO routes, analytics with consent first in Europe, security page, founder and X account, docs header; the tour opens on the pull request and leads with Code, on the real shell, with Agents and Docs working today | 153 | // Visitors asked about analytics first get a page that says so, kept apart. |
| 154 | const keyUrl = new URL(request.url); | |
| 155 | if (visitorAsksFirst(request)) keyUrl.searchParams.set("_g1t_consent", "1"); | |
| 156 | const key = new Request(keyUrl, { method: "GET" }); | |
| Signed-out page cache: a repository's kept page is served only while the repository is still public, so one made private or deleted never shows from any data centre's copy | 157 | const repository = PUBLIC_PROJECT.test(new URL(request.url).pathname) ? repositoryOfPage(new URL(request.url).pathname) : null; |
| 158 | // Asked alongside the cache, so a hit waits for one indexed read at most. | |
| Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9 | 159 | // A cache that does not answer is a miss: the page is drawn afresh. |
| 160 | const [cached, visible] = await Promise.all([ | |
| 161 | withDeadline("cache", CACHE_DEADLINE_MS, () => cache.match(key)).catch(() => undefined), | |
| 162 | repository ? isStillPublic(env, repository) : Promise.resolve(true), | |
| 163 | ]); | |
| Signed-out page cache: a repository's kept page is served only while the repository is still public, so one made private or deleted never shows from any data centre's copy | 164 | if (cached && !visible) { |
| 165 | ctx.waitUntil(cache.delete(key).then(() => undefined, () => undefined)); | |
| 166 | return render(); | |
| 167 | } | |
| Merge branch 'worktree-agent-a1b995daa94e4e1b7' | 168 | const keptAt = Number(cached?.headers.get("x-g1t-kept-at") ?? 0); |
| 169 | const age = Math.round((Date.now() - keptAt) / 1000); | |
| 170 | const refresh = async () => { | |
| 171 | const fresh = await render(); | |
| 172 | // Only a plain answer for everyone: nothing that sets a cookie or says | |
| 173 | // it is private. | |
| 174 | const cacheable = | |
| 175 | (fresh.status === 200 || fresh.status === 404) && | |
| 176 | !fresh.headers.has("set-cookie") && | |
| 177 | !/private|no-store/.test(fresh.headers.get("cache-control") ?? ""); | |
| 178 | if (cacheable) { | |
| 179 | const copy = new Response(fresh.clone().body, fresh); | |
| 180 | copy.headers.set("x-g1t-kept-at", String(Date.now())); | |
| 181 | copy.headers.set("cache-control", `public, max-age=${PUBLIC_STALE_SECONDS}`); | |
| 182 | ctx.waitUntil(cache.put(key, copy)); | |
| 183 | } | |
| 184 | return fresh; | |
| 185 | }; | |
| 186 | if (cached && keptAt > 0 && age < PUBLIC_STALE_SECONDS) { | |
| 187 | if (age >= PUBLIC_FRESH_SECONDS) ctx.waitUntil(refresh().then(() => undefined, () => undefined)); | |
| 188 | const answer = new Response(cached.body, cached); | |
| 189 | answer.headers.delete("x-g1t-kept-at"); | |
| 190 | answer.headers.delete("cache-control"); | |
| 191 | answer.headers.set("server-timing", `cache;desc="hit, ${age}s old"`); | |
| 192 | return answer; | |
| 193 | } | |
| 194 | return refresh(); | |
| 195 | } | |
| 196 | ||
| Signed-out page cache: a repository's kept page is served only while the repository is still public, so one made private or deleted never shows from any data centre's copy | 197 | /** Whether the repository is there and public; anything else, including no answer, is no. */ |
| 198 | async function isStillPublic(env: Env, repository: string): Promise<boolean> { | |
| 199 | try { | |
| Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9 | 200 | return await withDeadline("repos visibility", READ_DEADLINE_MS, async (signal) => { |
| 201 | const answer = await env.REPOS.fetch("https://service/rpc/visibility", { | |
| 202 | method: "POST", | |
| 203 | headers: { "content-type": "application/json" }, | |
| 204 | body: JSON.stringify({ paths: [repository] }), | |
| 205 | signal, | |
| 206 | }); | |
| 207 | return answer.ok && stillPublic(await answer.json(), repository); | |
| Signed-out page cache: a repository's kept page is served only while the repository is still public, so one made private or deleted never shows from any data centre's copy | 208 | }); |
| 209 | } catch { | |
| 210 | return false; | |
| 211 | } | |
| 212 | } | |
| 213 | ||
| Merge branch 'worktree-agent-a1b995daa94e4e1b7' | 214 | /** |
| 215 | * A git request, answered by the repos service. Its `Server-Timing` header | |
| 216 | * gains `repos`: how long the answer took to start from here, so the time | |
| 217 | * between this Worker and the repos service shows beside the steps the | |
| 218 | * repos service reports. | |
| 219 | */ | |
| 220 | async function proxyGit(env: Env, request: Request): Promise<Response> { | |
| 221 | const started = Date.now(); | |
| 222 | const answer = await env.REPOS.fetch(new Request(request, { redirect: "manual" })); | |
| 223 | const response = new Response(answer.body, answer); | |
| 224 | response.headers.append("server-timing", `repos;dur=${Date.now() - started}`); | |
| 225 | return response; | |
| 226 | } | |
| 227 | ||
| 228 | /** | |
| 229 | * A registry request, answered by the packages service as it is: its | |
| 230 | * redirects (a large blob sent to storage) go back to the client, which | |
| Merge branch 'worktree-agent-a8385d293d42c913a' | 231 | * follows them itself. One that found nothing under a workspace's old name |
| 232 | * or an alias staff set (`g1t` for `flagon-io`) is sent to the same path | |
| 233 | * under the workspace's name: only the not-found answer pays for the lookup. | |
| Merge g1tusercontent.com: registry answers run nothing in a browser, the site's pages run only their own scripts, repository files and avatars on their own origin, raw files rate limited per address | 234 | * Every answer runs nothing in a browser (app/lib/content-safety.ts): what |
| 235 | * a registry serves is its publisher's, on this origin. | |
| Merge branch 'worktree-agent-a1b995daa94e4e1b7' | 236 | */ |
| 237 | async function proxyPackages(env: Env, request: Request): Promise<Response> { | |
| 238 | const started = Date.now(); | |
| 239 | const answer = await env.PACKAGES.fetch(new Request(request, { redirect: "manual" })); | |
| Merge branch 'worktree-agent-a8385d293d42c913a' | 240 | const moved = answer.status === 404 ? await registryMoved(env, request) : null; |
| 241 | const response = moved ?? new Response(answer.body, answer); | |
| Merge g1tusercontent.com: registry answers run nothing in a browser, the site's pages run only their own scripts, repository files and avatars on their own origin, raw files rate limited per address | 242 | hardenRegistryHeaders(response.headers); |
| Merge branch 'worktree-agent-a1b995daa94e4e1b7' | 243 | response.headers.append("server-timing", `packages;dur=${Date.now() - started}`); |
| 244 | return response; | |
| 245 | } | |
| 246 | ||
| Merge branch 'worktree-agent-a8385d293d42c913a' | 247 | /** Where a registry request under an alias or old name goes now, or null. */ |
| 248 | async function registryMoved(env: Env, request: Request): Promise<Response | null> { | |
| 249 | const url = new URL(request.url); | |
| 250 | const named = registryWorkspace(url.pathname); | |
| 251 | if (!named || !isNamespaceShaped(named.slug)) return null; | |
| 252 | let current: string | null = null; | |
| 253 | try { | |
| 254 | current = await identityClient(env.IDENTITY).resolveSlug(named.slug); | |
| 255 | } catch { | |
| 256 | return null; | |
| 257 | } | |
| 258 | if (!current || current === named.slug) return null; | |
| 259 | const get = request.method === "GET" || request.method === "HEAD"; | |
| 260 | // 308 keeps a publish a PUT, for the clients that follow it. | |
| 261 | return new Response(null, { status: get ? 301 : 308, headers: { location: named.under(current) + url.search } }); | |
| Merge branch 'worktree-agent-a1b995daa94e4e1b7' | 262 | } |
This file's history is long; its oldest lines are credited to the oldest commit read.