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.
| Projects live: fixes from walking them end to end | 1 | # Contributing to g1t |
| 2 | ||
| 3 | g1t is built the way it asks others to build: issues and pull requests on | |
| Fast pages, required checks on the branch, self-hosted runners, honest incidents | 4 | [g1t.sh/flagon-io/g1t](https://g1t.sh/flagon-io/g1t), required checks that prove |
| 5 | a change works, and a merge queue that keeps `main` passing. | |
| Projects live: fixes from walking them end to end | 6 | |
| 7 | ## A change ships with its docs | |
| 8 | ||
| 9 | Documentation is part of the product, held to the same bar as the code. | |
| 10 | A pull request that changes what someone can do, see or call changes the | |
| 11 | docs in the same pull request: | |
| 12 | ||
| 13 | | If you change | Update | | |
| 14 | | --- | --- | | |
| 15 | | Something a person does on g1t.sh | The guide for it in `apps/docs/src/content/docs/guides/` | | |
| 16 | | An API route, a field, or an MCP tool | `apps/api/src/operations.rs` descriptions (they feed the OpenAPI document and the API reference), the example and notes for it in `apps/api/src/reference.json`, `reference/mcp.md`, and any guide that shows the call. Then refresh the docs' copy of the OpenAPI document with `G1T_WRITE_OPENAPI=1 cargo test -p g1t-api openapi`; `cargo test` fails until you do. | | |
| g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent | 17 | | How agents behave | `guides/working-with-g1t.md`, and `apps/web/public/llms.txt` | |
| Projects live: fixes from walking them end to end | 18 | | Settings, limits or prices | The page that names them, and the table it is in | |
| 19 | | A new feature | A section in the guide that owns it, linked from the docs home if it is a new task | | |
| 20 | ||
| 21 | The style, in short: plain sentences, second person, no marketing words, | |
| 22 | sentence-case headings, a numbered list for steps, a table for options, and | |
| 23 | every API mention with its exact route or tool name. Examples are copyable | |
| 24 | and real. Nothing is documented that the code does not do. | |
| 25 | ||
| 26 | Check the docs build before you push: | |
| 27 | ||
| 28 | ```sh | |
| 29 | cd apps/docs && npm run build | |
| 30 | ``` | |
| 31 | ||
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 32 | ## Icons |
| 33 | ||
| 34 | - **Interface icons come from [Lucide](https://lucide.dev/icons).** In the | |
| 35 | apps, import them from `lucide-react`. In the docs, use `@lucide/astro`: | |
| 36 | import an icon component, or give `apps/docs/src/components/Card.astro` | |
| 37 | and `Aside.astro` a Lucide name such as `icon="git-branch"`. Starlight's | |
| 38 | own `<Card>`, `<LinkCard>` and `<Aside>` draw Starlight's icon set, so the | |
| 39 | docs import the wrappers in `apps/docs/src/components/` instead. | |
| 40 | - **Brand marks come from [Simple Icons](https://simpleicons.org)** | |
| 41 | (`simple-icons`), and follow each brand's own usage guidelines. Add the | |
| 42 | dependency with the first one you show. | |
| 43 | - **The g1t logo and illustrations are our own artwork**, in | |
| 44 | `apps/web/app/components/logo.tsx` and `art.tsx`. | |
| 45 | - Never hand-copy an icon's SVG paths into a file. If Lucide lacks the | |
| 46 | icon you need, pick the closest one that it has. | |
| 47 | ||
| CONTRIBUTING says how the site's interface is built: from the shadcn components in apps/web/app/components/ui, themed with g1t's tokens, adding a missing one there first, changing looks through variants rather than one-off class strings, and looking at every changed page in both themes on a desktop and a phone. | 48 | ## Interface components |
| 49 | ||
| 50 | - **Build the site from the components in `apps/web/app/components/ui`**, | |
| 51 | which are [shadcn/ui](https://ui.shadcn.com) components themed with | |
| 52 | g1t's tokens: buttons, cards, badges, tables, tabs, separators, | |
| 53 | breadcrumbs, menus, dialogs, sheets, tooltips, form controls. | |
| 54 | - **If shadcn has a component you need and `ui` doesn't, add it there** | |
| 55 | first, themed with the tokens, then use it. Don't rebuild it inline. | |
| 56 | - **Change how something looks through the component's variants**, not | |
| 57 | with a one-off class string at the call site. A long `className` on a | |
| 58 | `div` that draws a card, a pill or a button is a component that should | |
| 59 | exist. | |
| 60 | - Hover hints use the `Tooltip` component, never `title=`. Native | |
| 61 | `<select>`, file inputs, checkboxes and radios use their `ui` | |
| 62 | components. | |
| Agents have faces, and are never mistaken for people. Every agent wears a little bot face drawn from a look it owns, shape, colour, eyes, mouth, antenna, accessory and pattern, chosen in its builder and on its Profile tab with a live preview, Shuffle and a way back to the face its seed gives it; the face blinks on its own time, breathes, narrows its eyes while the agent works, shuts them asleep and bounces when it finishes, all of it still for anyone who asked for less motion. Wherever an agent shows, in chat, in a list, on a mention, on a review or a commit, its avatar carries an agent marker, and the people reading it are told so. In Chat, direct messages are two lists: People, and Agents, which also holds the agents you haven't talked to yet; a conversation with both a person and an agent in it is marked in the list, named in the conversation's header, spelled out by the composer and explained once the first time it opens. Agents keep their look in the agents service, which every service passes along. The chat and agents guides say so, and CONTRIBUTING makes the shared avatar the only way to draw an agent. | 63 | - **Every avatar is `Avatar`** (`ui/avatar.tsx`), and every agent's is |
| 64 | `Avatar` with `agent` set, or `AgentAvatar` (`components/agent-avatar.tsx`), | |
| 65 | which wraps it. That draws the agent's bot face (`AgentFace`, | |
| 66 | `components/agent-face.tsx`) from its `look` or its `avatar_seed`, puts | |
| 67 | the agent marker in its corner and names it "…, agent" to a screen | |
| 68 | reader. Never hand-draw an agent's face, a sparkle, a letter on a tile or | |
| 69 | the marker at a call site: a place that shows an agent differently is a | |
| 70 | bug. | |
| CONTRIBUTING says how the site's interface is built: from the shadcn components in apps/web/app/components/ui, themed with g1t's tokens, adding a missing one there first, changing looks through variants rather than one-off class strings, and looking at every changed page in both themes on a desktop and a phone. | 71 | - Look at every page you change, in light and dark, on a desktop and a |
| 72 | phone, before calling it done. | |
| 73 | ||
| Projects live: fixes from walking them end to end | 74 | ## Before you push |
| 75 | ||
| 76 | - `cargo test` in the crate or service you changed. | |
| 77 | - `npx tsc -b --force` in `apps/web` (the incremental build misses changes | |
| 78 | in `packages/contracts`). | |
| 79 | - Look at what you changed in a browser. Screenshots catch what type | |
| 80 | checks do not. | |
| Deploys as code: a manifest of every Worker, a deploy tool that ships only what changed in parallel stages, and a g1t Actions workflow | 81 | |
| 82 | ## Deploying | |
| 83 | ||
| 84 | Pushes to `main` deploy themselves: `.g1t/workflows/deploy.yml` runs | |
| 85 | `scripts/deploy.mjs`, which deploys only the parts that changed, migrations | |
| 86 | first. Every deployable part is listed in `deploy/stack.jsonc`; a new service | |
| 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. | 87 | or app goes there, and in the table on |
| 88 | [How a self-hosted g1t runs](https://docs.g1t.sh/guides/self-hosting-architecture/#each-part) | |
| 89 | (`npm run test:deploy` says what is missing). | |
| 90 | [Deploy g1t to Cloudflare](https://docs.g1t.sh/guides/deploy-to-cloudflare/) | |
| 91 | covers the tool, the workflow, rollbacks, adding a unit and first-time | |
| 92 | setup. | |
| 93 | ||
| 94 | - **Migrations run before the code**, so the old code reads the new schema | |
| 95 | for a minute or more. Add tables and columns; change what rows mean in | |
| 96 | two deploys (code that reads both forms first); never drop what live code | |
| 97 | still reads. | |
| 98 | - **The self-hosted runner** is released, not deployed: bump `version` in | |
| 99 | `crates/runner/Cargo.toml`, merge, and push a `runner-v<version>` tag. | |
| 100 | `.g1t/workflows/runner-release.yml` builds, signs and publishes it, and | |
| 101 | runners update themselves to it. `scripts/runner-release.mjs` does the | |
| 102 | same by hand. | |
| preparing for cloud work | 103 | - **The desktop app** is released the same way: bump `version` in |
| 104 | `apps/desktop/src-tauri/Cargo.toml`, merge, and push a | |
| 105 | `desktop-v<version>` tag. `.g1t/workflows/desktop-release.yml` builds | |
| 106 | the installers (Windows and Linux on Linux; macOS on a Mac runner when | |
| 107 | one is registered), signs the updater's files and publishes them, and | |
| 108 | installed apps update themselves. `scripts/desktop-release.mjs` does the | |
| 109 | same by hand. The app loads the live site, so a change to the site needs | |
| 110 | no release; only a change under `apps/desktop` does. The guide is | |
| 111 | [The desktop app](https://docs.g1t.sh/guides/desktop/). | |
| 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. | 112 | |
| 113 | ## Speed | |
| 114 | ||
| 115 | Pages are a few rounds of service calls, and each round costs a trip to | |
| 116 | the databases, so start calls together and add no rounds. | |
| 117 | ||
| 118 | - Every page answers with `Server-Timing`: each loader, each service's | |
| 119 | calls and their database time. Look at it in DevTools when a page feels | |
| 120 | slow. | |
| 121 | - `scripts/perf/measure.ps1` times pages from your machine (`-BrowserUA` | |
| 122 | for signed-out pages as a browser sees them). A page that only reads | |
| 123 | must not set the `g1t_d1` cookie: a new read-only service method goes in | |
| 124 | `READS` in `apps/web/app/lib/perf.ts`. | |
| 125 | - Targets from the US: signed-out pages under 200 ms to first byte, | |
| 126 | signed-in pages under 400 ms, streamed panels within a second. | |
| 127 | - Workers run without Smart Placement; `scripts/perf/placement-probe.mjs` | |
| 128 | measures a placement before you pin one. | |
| A doc opens at once: the page carries the document's saved state, so the editor mounts without waiting for its live room, which connects in the background and sends only what changed, with a quiet dot that turns green when it's synced; edits made before then are kept and sent once. The editor's code is preloaded from the page and fetched early when you hover a doc, the app's shared code ships in a few chunks instead of a hundred small ones, socket tickets come with the page instead of a round trip, and the artifacts service answers a page, a sidebar and a room in parallel lookups instead of a dozen in a row, reporting its database round trips in Server-Timing. The artifacts guide says how a doc opens, and CONTRIBUTING's Speed section has the numbers. | 129 | - A TypeScript service reports its database time too (`db;dur`, from |
| 130 | `openD1` in `@g1t/contracts`): the round trips a method makes are what | |
| 131 | it costs, so start them together (`Promise.all`), keep what never | |
| 132 | changes across requests in the isolate (the artifacts service keeps a | |
| 133 | workspace's slug for 30 s and whether its General space exists), and | |
| 134 | don't wake a Durable Object for a read D1 can answer. | |
| 135 | - Nothing live gates a page. A page carries what it shows (a doc's text | |
| 136 | and its saved document), opens its sockets as it hydrates rather than | |
| 137 | from inside the code they feed, keeps what is typed until they answer, | |
| 138 | and says so with a dot, never a spinner. A page opened with an access | |
| 139 | token gets its socket tickets with the page (the root loader mints the | |
| 140 | feed's, an artifact's loader its room's) rather than asking for each. | |
| 141 | - Code that only the browser runs (the doc editor) is still fetched with | |
| 142 | the page: `vite.config.ts` notes the chunk and what it imports for the | |
| 143 | route's `links`, and a link to such a page warms the chunk on hover. | |
| 144 | Modules several routes share are grouped by who shares them | |
| 145 | (`codeSplitting.groups`), so a page fetches tens of files, not a | |
| 146 | hundred and more. Before this, opening a doc fetched 124 files | |
| 147 | (1,049 KB) in three waves and the editor appeared at 2.8–3.3 s; a doc | |
| 148 | now fetches 71 files (1,026 KB) in one wave, and against the deployed | |
| 149 | services the editor appears at 2.1 s, of which the room's answer is | |
| A doc saved before g1t kept documents with the page opens as fast as any other from its second open: the page asks its room for the document once, sends it with the page and keeps it on the row, instead of leaving the editor to wait for the room's first message. The artifacts guide says so, and CONTRIBUTING's Speed section has today's measurements on g1t.sh and what is still to cut. | 150 | 0.65 s and the editor's own start 0.4 s. With the saved document in the |
| 151 | page, measured on g1t.sh signed in (2026-10-10, warm, 1440×900), the | |
| 152 | editor appears at 1.5 s: first byte 0.53 s, scripts by 0.75 s, the | |
| 153 | editor's own start the rest. A doc without a saved document (one last | |
| 154 | saved before documents were kept) waited for its room's first message | |
| 155 | instead, 0.9 s after the socket opened, and appeared at 2.0–2.5 s; the | |
| 156 | page now asks the room once and keeps the answer, so only its first | |
| 157 | open pays. Still to cut: the live socket's four service calls before | |
| 158 | the room answers, the page and sidebar's 27 database round trips in two | |
| 159 | calls, the root loader's billing and identity calls (0.7–1.3 s and | |
| 160 | 0.45 s inside), and the 3.4 MB of script a doc page decodes. | |
| 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. | 161 | |
| 162 | ## Rate limits | |
| 163 | ||
| 164 | `RATE_LIMITS` in `packages/contracts/src/rate-limits.ts` is the table of | |
| 165 | record, and the [rate limits page](https://docs.g1t.sh/reference/rate-limits/) | |
| 166 | is the public one: change both, and the binding in the Worker's | |
| 167 | `wrangler.jsonc`, together (`front-door-limits.test.ts` fails when they | |
| 168 | disagree). Limits fail open, keys hash anything secret, and each Worker | |
| 169 | takes its namespace ids from its own block of a hundred (41xx packages, | |
| 170 | 42xx web, 43xx repos, 44xx api, 45xx og, 46xx status). | |
| 171 | ||
| 172 | ## Operating g1t.sh | |
| 173 | ||
| 174 | For Flagon staff. | |
| 175 | ||
| 176 | - **Incidents** are declared, updated and resolved in sudo, under | |
| 177 | **Platform → Incidents**, and appear on status.g1t.sh within 30 seconds. | |
| 178 | Declare as soon as people are affected, and pick the higher severity | |
| 179 | when unsure. SEV1 (down for most people, or data at risk) gets an update | |
| 180 | at least every 30 minutes and SEV2 (a core part broken for many) at least | |
| 181 | hourly; both email subscribers and need a blameless postmortem within | |
| 182 | five working days. | |
| The artifacts service is services/artifacts, the Worker g1t-artifacts, bound as ARTIFACTS by the API, the site and the agents; its live rooms move to it with a Durable Object transfer from g1t-docs-service, and its database, bucket, indexes and queue keep their names. The git store's binding and settings are GITSTORE, its ops scripts gitstore-*, and workflow run artifacts keep their compatible API under run_artifacts modules. The deploy tool puts a Worker that has never deployed before the Workers in its stage that bind to it, and the deploy guide gives the cutover runbook. | 183 | - **An Artifacts outage:** `scripts/ops/gitstore-namespaces.mjs` shows a |
| 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. | 184 | failing namespace. After 15 minutes, serve it read-only from the fallback |
| 185 | git store (`scripts/ops/restore-to-gitstore.mjs restore`, then the repos | |
| 186 | Worker's secret `GIT_FALLBACK_NAMESPACES`) and open an incident. To | |
| 187 | switch back, `reconcile` anything the fallback took, then delete the | |
| 188 | secret. The script's header has every step. | |
| 189 | - **Costs and margin**, model prices, credits and the platform pause are | |
| 190 | in sudo, under **Costs & margin**. The code is in `services/billing/src` | |
| 191 | (`costs.rs`, `margin.rs`, `pricing.rs`, `budget.rs`, `platform.rs`). |
This file's history is long; its oldest lines are credited to the oldest commit read.