Skip to content
160 linesCodeBlameRaw
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
82You need Node 22 (the repository's), and for Android, Android Studio with
83an emulator or a phone with USB debugging.
84
85```sh
86npm install # at the repository's root: one workspace
87cd apps/mobile
88npx expo run:android # builds a development app and starts Metro
89```
90
91`expo run:android` makes the native project (`android/`, ignored by git)
92and installs `g1t (dev)`. After the first build, `npx expo start` and
93the installed app are enough until a native module changes.
94
95Expo Go works for screens that need nothing native beyond what Go has,
96and sign-in comes back to Go's own `exp://` address (allowed as a
97redirect). `g1t://` links need a development build.
98
99Checks, which CI runs from the root (`npm run typecheck`, `npm test`):
100
101```sh
102npm run typecheck -w @g1t/mobile
103npm test -w @g1t/mobile # lib/*.test.ts under Node
104```
105
106## Set up EAS and ship Android
107
108Once, by someone with access to the Expo account and Google Play:
109
1101. `npm install -g eas-cli`, then `eas login`.
1112. In `apps/mobile`, `eas init`. It makes the EAS project and prints its
112 id: put it in `EAS_PROJECT` in `app.config.ts` (the config is code, so
113 `eas init` cannot write it there). Set `EAS_OWNER` in your shell if the
114 project belongs to an organization.
1153. `eas build --profile preview --platform android`. EAS makes and keeps
116 the upload keystore the first time; say yes. The build ends with a link
117 and a QR code to install the APK.
1184. In the [Google Play Console](https://play.google.com/console), create
119 the app with the package `sh.g1t.app`, and turn on Play App Signing.
1205. `npm run build:android` makes the production App Bundle. Upload the
121 first one by hand on Play's **Internal testing** track: Google requires
122 that before its API accepts uploads.
1236. For `eas submit`, make a Google Cloud service account with access to
124 the app in the Play Console, download its JSON key, and add it to the
125 EAS project's credentials (`eas credentials`). From then on,
126 `eas submit --platform android --latest` sends a build to internal
127 testing as a draft (`eas.json`'s `submit.production`).
128
129iOS is the same with `--platform ios`, an Apple Developer account and the
130bundle id `sh.g1t.app`; EAS makes the certificates.
131
132### Before the first store release
133
134- **Chat with OAuth**, above.
135- **Push notifications**: `expo-notifications`, and in `services/notify`
136 a subscription kind for Expo push tokens (or FCM and APNs) beside Web
137 Push, registered from the app after sign-in.
138- **App Links**: `https://g1t.sh/.well-known/assetlinks.json` naming
139 `sh.g1t.app` and Play's signing certificate's SHA-256, then an
140 `intentFilters` entry with `autoVerify` in `app.config.ts`, so g1t.sh
141 links open the app. iOS needs `apple-app-site-association` the same way.
142- **The brand's faces**: TTF copies of Hanken Grotesk, Bricolage
143 Grotesque and IBM Plex Mono (packages/theme ships woff2), loaded with
144 `expo-font`.
145- **Agents' faces**: `components/ui.tsx`'s `Avatar` draws a letter for an
146 agent; the site's `AgentFace` should be shared and drawn here.
147- **The store listing**: screenshots, a privacy policy URL, and Play's
148 data safety form (the app keeps the server and credentials in the
149 keystore, and sends nothing anywhere but the g1t it is pointed at).
150
151## Notes
152
153- **React is pinned.** The site uses a newer React than React Native 0.86
154 allows, so the app has its own `react` (19.2.3) and `metro.config.js`
155 resolves every `react` import to it. `expo-doctor` reports the root's
156 copy as a duplicate; that is expected here, and the bundle has one.
157- **Icons** are drawn from the mark: `npm run icons -w @g1t/mobile`.
158- **Versions**: `version` in `app.config.ts` is what people see;
159 EAS counts build numbers itself (`appVersionSource: remote`). Over-the-air
160 updates go to builds of the same `version` (`runtimeVersion`).