Skip to content
191 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.

The phone app's scaffolding, in apps/mobile: Expo SDK 57 with Expo Router, Android first, with Chat, Code and Customize. It signs in with g1t's own OAuth 2.1 (PKCE, a client id that encodes "g1t for Android" and g1t://oauth/callback, so nothing is registered), keeps the server and credentials in the phone's keystore, refreshes the 30-day token a minute early and shares the single-use refresh between requests. A personal access token is the other way in, and a self-hosted g1t the other server.1# g1t for Android and iOS
2
3The phone app: [Expo](https://expo.dev) (SDK 57, React Native 0.86) with
4Expo Router, in TypeScript. Chat, Code and Customize, signed in with g1t's
5own OAuth. Android is the first target. What it does for people is in the
6guide, [The phone app](../docs/src/content/docs/guides/mobile.md).
7
8## Layout
9
10```text
11app.config.ts name, ids and icons per build variant (APP_VARIANT)
12eas.json EAS Build profiles: development, preview, production
13metro.config.js one React for the app in the monorepo
14scripts/icons.mjs draws the icons from packages/theme/mark.svg
15src/
16 app/ the screens (Expo Router: one file, one route)
17 sign-in.tsx Sign in with g1t, an access token, or another server
18 oauth/callback.tsx where the site sends sign-in back (g1t://oauth/callback)
19 (app)/ signed in: the tabs
20 chat/ conversations, and one conversation, live
21 code/ repositories, pull requests, issues, diffs
22 customize/ Skills, Connectors, Plugins
23 you/ workspaces, how you signed in, sign out
24 +native-intent.tsx g1t:// links to screens (lib/links.ts)
25 components/ the building blocks, themed (ui.tsx), and Code's
26 lib/
27 server.ts which g1t: site and API addresses
28 oauth.ts OAuth 2.1 with PKCE against the server's metadata
29 credentials.ts the server and credentials, in the phone's keystore
30 client.ts every request: refresh, errors, the site or the API
31 session.tsx who is signed in, to which workspace
32 chat.ts, code.ts the data for Chat and Code (TanStack Query)
33 customize.ts Customize's listings, from @g1t/contracts' catalogs
34 theme/ packages/theme/tokens.css's colours, for React Native
35```
36
37Shared types and catalogs come from `@g1t/contracts` (by subpath, such as
38`@g1t/contracts/oauth`, so the app does not bundle every module).
39Customize's three parts are defined there too (`customize.ts`), so the
40site can move the Marketplace onto the same model.
41
42## How it talks to g1t
43
44| What | Where | How it is signed in |
45| --- | --- | --- |
46| Who you are, your workspaces | `GET {api}/user` | `Authorization: Bearer g1t_…` |
47| Code | `{api}/repos…` REST | the same |
48| Chat | `{site}/<workspace>/-/chat/api` and its live socket, as the site's own pages use them | the same header, which the site takes only from a token with **Use the website as you**; the socket gets a ticket from `{site}/-/live/ticket` first |
49| Sign-in | `{api}/.well-known/oauth-authorization-server`, `{site}/oauth/authorize`, `{api}/oauth/token` | OAuth 2.1, PKCE (S256), public client |
50
51The OAuth client is not registered anywhere: its id encodes its name and
52redirect (`encodeOAuthClient`), so `g1t for Android` with
53`g1t://oauth/callback` is the same client on every phone. Access tokens
54last 30 days and are refreshed a minute before they expire; a refresh
55token works once, so `lib/client.ts` shares one refresh between
56requests.
57
58### Open: Chat with the app's OAuth sign-in
59
60OAuth tokens are made without the website permission
61(`services/identity/src/oauth.rs` → `issue_oauth_tokens`), and Chat is
62only on the site, so **an OAuth sign-in reaches Code but not Chat**. The
63app says so on Chat and offers an access-token sign-in, which works today
64with a token that has **Use the website as you**. The server needs one of
65these before the app ships:
66
671. **Chat in the REST API** (recommended): `GET/POST /workspaces/:ws/chat…`
68 in `apps/api` with `chat:read` and `chat:write` scopes, calling the chat
69 service as the site does, and a socket ticket the API can mint. It is
70 what the API and MCP will want anyway; the app moves its three calls
71 in `lib/chat.ts` over.
722. **Let the site take OAuth tokens for Chat's routes alone**:
73 `website-token.ts` accepts a token without `website` on
74 `/<ws>/-/chat/api`, `/<ws>/-/chat/live` and `/-/live/ticket` when it
75 has new `chat:*` scopes. Smaller, but it widens what the site accepts.
76
77Turning `website` on for every OAuth token is not one of them: it would
78let any approved application use the whole site as the person.
79
80## Run it
81
The phone app signs in from Expo Go and stays signed in there. Go sends sign-in back to its own exp:// address, which follows the computer's network address, and a client id encodes its redirect, so a refresh after the address changed named another client and was refused. Credentials now keep the client id they were issued to and refresh with it; Go's client is named "g1t in Expo Go" on the approval page and in Connected applications. The README leads with running in Expo Go, and says what a development build adds and how to point the phone at a g1t on your computer.82You need Node 22 (the repository's).
The phone app's scaffolding, in apps/mobile: Expo SDK 57 with Expo Router, Android first, with Chat, Code and Customize. It signs in with g1t's own OAuth 2.1 (PKCE, a client id that encodes "g1t for Android" and g1t://oauth/callback, so nothing is registered), keeps the server and credentials in the phone's keystore, refreshes the 30-day token a minute early and shares the single-use refresh between requests. A personal access token is the other way in, and a self-hosted g1t the other server.83
The phone app signs in from Expo Go and stays signed in there. Go sends sign-in back to its own exp:// address, which follows the computer's network address, and a client id encodes its redirect, so a refresh after the address changed named another client and was refused. Credentials now keep the client id they were issued to and refresh with it; Go's client is named "g1t in Expo Go" on the approval page and in Connected applications. The README leads with running in Expo Go, and says what a development build adds and how to point the phone at a g1t on your computer.84### In Expo Go (quickest)
85
86Everything the app uses is in Expo Go for SDK 57, so no native build is
87needed:
88
The phone app's scaffolding, in apps/mobile: Expo SDK 57 with Expo Router, Android first, with Chat, Code and Customize. It signs in with g1t's own OAuth 2.1 (PKCE, a client id that encodes "g1t for Android" and g1t://oauth/callback, so nothing is registered), keeps the server and credentials in the phone's keystore, refreshes the 30-day token a minute early and shares the single-use refresh between requests. A personal access token is the other way in, and a self-hosted g1t the other server.89```sh
90npm install # at the repository's root: one workspace
91cd apps/mobile
The phone app signs in from Expo Go and stays signed in there. Go sends sign-in back to its own exp:// address, which follows the computer's network address, and a client id encodes its redirect, so a refresh after the address changed named another client and was refused. Credentials now keep the client id they were issued to and refresh with it; Go's client is named "g1t in Expo Go" on the approval page and in Connected applications. The README leads with running in Expo Go, and says what a development build adds and how to point the phone at a g1t on your computer.92npx expo start # then scan the QR code with Expo Go
The phone app's scaffolding, in apps/mobile: Expo SDK 57 with Expo Router, Android first, with Chat, Code and Customize. It signs in with g1t's own OAuth 2.1 (PKCE, a client id that encodes "g1t for Android" and g1t://oauth/callback, so nothing is registered), keeps the server and credentials in the phone's keystore, refreshes the 30-day token a minute early and shares the single-use refresh between requests. A personal access token is the other way in, and a self-hosted g1t the other server.93```
94
The phone app signs in from Expo Go and stays signed in there. Go sends sign-in back to its own exp:// address, which follows the computer's network address, and a client id encodes its redirect, so a refresh after the address changed named another client and was refused. Credentials now keep the client id they were issued to and refresh with it; Go's client is named "g1t in Expo Go" on the approval page and in Connected applications. The README leads with running in Expo Go, and says what a development build adds and how to point the phone at a g1t on your computer.95Install [Expo Go](https://expo.dev/go) on the phone, or press `a` for an
96Android emulator. The phone and the computer must be on the same network
97(or use `npx expo start --tunnel`).
The phone app's scaffolding, in apps/mobile: Expo SDK 57 with Expo Router, Android first, with Chat, Code and Customize. It signs in with g1t's own OAuth 2.1 (PKCE, a client id that encodes "g1t for Android" and g1t://oauth/callback, so nothing is registered), keeps the server and credentials in the phone's keystore, refreshes the 30-day token a minute early and shares the single-use refresh between requests. A personal access token is the other way in, and a self-hosted g1t the other server.98
The phone app signs in from Expo Go and stays signed in there. Go sends sign-in back to its own exp:// address, which follows the computer's network address, and a client id encodes its redirect, so a refresh after the address changed named another client and was refused. Credentials now keep the client id they were issued to and refresh with it; Go's client is named "g1t in Expo Go" on the approval page and in Connected applications. The README leads with running in Expo Go, and says what a development build adds and how to point the phone at a g1t on your computer.99Signing in works in Go: the site sends you back to Go's own address,
100`exp://<your computer>:8081/--/oauth/callback`, which g1t accepts as an
101application's own scheme. The approval page and Connected applications
102call it **g1t in Expo Go**. Its client id encodes that address, so a new
103network makes a new client: the app keeps the id it signed in with for
104refreshing, and signing in again after a move just adds a new entry to
105Connected applications. What Go can't do: open from `g1t://` links (they
106go to an installed build) and show the app's own name and icon.
107
108To develop against a g1t running on your computer, choose **Server** on
109the sign-in screen and give its address on your network, such as
110`http://192.168.1.20:5173` and the API's `http://192.168.1.20:8789`
111(`localhost` on a phone is the phone). Run that g1t with its `SITE_URL`
112and `API_URL` set to those addresses too: the API's OAuth metadata
113names the sign-in page by `SITE_URL`, and the phone opens what it names.
114
115### In a development build
116
117For `g1t://` links, the app's own icon and name, and anything native Go
118lacks later (push notifications):
119
120```sh
121npx expo run:android # builds g1t (dev) and starts Metro
122```
123
124`expo run:android` makes the native project (`android/`, ignored by git)
125and installs `g1t (dev)`, which signs in through `g1t://oauth/callback`.
126After the first build, `npx expo start` and the installed app are enough
127until a native module changes. `eas build --profile development` makes
128the same build in the cloud.
The phone app's scaffolding, in apps/mobile: Expo SDK 57 with Expo Router, Android first, with Chat, Code and Customize. It signs in with g1t's own OAuth 2.1 (PKCE, a client id that encodes "g1t for Android" and g1t://oauth/callback, so nothing is registered), keeps the server and credentials in the phone's keystore, refreshes the 30-day token a minute early and shares the single-use refresh between requests. A personal access token is the other way in, and a self-hosted g1t the other server.129
130Checks, which CI runs from the root (`npm run typecheck`, `npm test`):
131
132```sh
133npm run typecheck -w @g1t/mobile
134npm test -w @g1t/mobile # lib/*.test.ts under Node
135```
136
137## Set up EAS and ship Android
138
139Once, by someone with access to the Expo account and Google Play:
140
1411. `npm install -g eas-cli`, then `eas login`.
1422. In `apps/mobile`, `eas init`. It makes the EAS project and prints its
143 id: put it in `EAS_PROJECT` in `app.config.ts` (the config is code, so
144 `eas init` cannot write it there). Set `EAS_OWNER` in your shell if the
145 project belongs to an organization.
1463. `eas build --profile preview --platform android`. EAS makes and keeps
147 the upload keystore the first time; say yes. The build ends with a link
148 and a QR code to install the APK.
1494. In the [Google Play Console](https://play.google.com/console), create
150 the app with the package `sh.g1t.app`, and turn on Play App Signing.
1515. `npm run build:android` makes the production App Bundle. Upload the
152 first one by hand on Play's **Internal testing** track: Google requires
153 that before its API accepts uploads.
1546. For `eas submit`, make a Google Cloud service account with access to
155 the app in the Play Console, download its JSON key, and add it to the
156 EAS project's credentials (`eas credentials`). From then on,
157 `eas submit --platform android --latest` sends a build to internal
158 testing as a draft (`eas.json`'s `submit.production`).
159
160iOS is the same with `--platform ios`, an Apple Developer account and the
161bundle id `sh.g1t.app`; EAS makes the certificates.
162
163### Before the first store release
164
165- **Chat with OAuth**, above.
166- **Push notifications**: `expo-notifications`, and in `services/notify`
167 a subscription kind for Expo push tokens (or FCM and APNs) beside Web
168 Push, registered from the app after sign-in.
169- **App Links**: `https://g1t.sh/.well-known/assetlinks.json` naming
170 `sh.g1t.app` and Play's signing certificate's SHA-256, then an
171 `intentFilters` entry with `autoVerify` in `app.config.ts`, so g1t.sh
172 links open the app. iOS needs `apple-app-site-association` the same way.
173- **The brand's faces**: TTF copies of Hanken Grotesk, Bricolage
174 Grotesque and IBM Plex Mono (packages/theme ships woff2), loaded with
175 `expo-font`.
176- **Agents' faces**: `components/ui.tsx`'s `Avatar` draws a letter for an
177 agent; the site's `AgentFace` should be shared and drawn here.
178- **The store listing**: screenshots, a privacy policy URL, and Play's
179 data safety form (the app keeps the server and credentials in the
180 keystore, and sends nothing anywhere but the g1t it is pointed at).
181
182## Notes
183
184- **React is pinned.** The site uses a newer React than React Native 0.86
185 allows, so the app has its own `react` (19.2.3) and `metro.config.js`
186 resolves every `react` import to it. `expo-doctor` reports the root's
187 copy as a duplicate; that is expected here, and the bundle has one.
188- **Icons** are drawn from the mark: `npm run icons -w @g1t/mobile`.
189- **Versions**: `version` in `app.config.ts` is what people see;
190 EAS counts build numbers itself (`appVersionSource: remote`). Over-the-air
191 updates go to builds of the same `version` (`runtimeVersion`).