| 1 | # g1t for Android and iOS |
| 2 | |
| 3 | The phone app: [Expo](https://expo.dev) (SDK 57, React Native 0.86) with |
| 4 | Expo Router, in TypeScript. Chat, Code and Customize, signed in with g1t's |
| 5 | own OAuth. Android is the first target. What it does for people is in the |
| 6 | guide, [The phone app](../docs/src/content/docs/guides/mobile.md). |
| 7 | |
| 8 | ## Layout |
| 9 | |
| 10 | ```text |
| 11 | app.config.ts name, ids and icons per build variant (APP_VARIANT) |
| 12 | eas.json EAS Build profiles: development, preview, production |
| 13 | metro.config.js one React for the app in the monorepo |
| 14 | scripts/icons.mjs draws the icons from packages/theme/mark.svg |
| 15 | src/ |
| 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 | |
| 37 | Shared types and catalogs come from `@g1t/contracts` (by subpath, such as |
| 38 | `@g1t/contracts/oauth`, so the app does not bundle every module). |
| 39 | Customize's three parts are defined there too (`customize.ts`), so the |
| 40 | site 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 | |
| 51 | The OAuth client is not registered anywhere: its id encodes its name and |
| 52 | redirect (`encodeOAuthClient`), so `g1t for Android` with |
| 53 | `g1t://oauth/callback` is the same client on every phone. Access tokens |
| 54 | last 30 days and are refreshed a minute before they expire; a refresh |
| 55 | token works once, so `lib/client.ts` shares one refresh between |
| 56 | requests. |
| 57 | |
| 58 | ### Open: Chat with the app's OAuth sign-in |
| 59 | |
| 60 | OAuth tokens are made without the website permission |
| 61 | (`services/identity/src/oauth.rs` → `issue_oauth_tokens`), and Chat is |
| 62 | only on the site, so **an OAuth sign-in reaches Code but not Chat**. The |
| 63 | app says so on Chat and offers an access-token sign-in, which works today |
| 64 | with a token that has **Use the website as you**. The server needs one of |
| 65 | these before the app ships: |
| 66 | |
| 67 | 1. **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. |
| 72 | 2. **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 | |
| 77 | Turning `website` on for every OAuth token is not one of them: it would |
| 78 | let any approved application use the whole site as the person. |
| 79 | |
| 80 | ## Run it |
| 81 | |
| 82 | You need Node 22 (the repository's), and for Android, Android Studio with |
| 83 | an emulator or a phone with USB debugging. |
| 84 | |
| 85 | ```sh |
| 86 | npm install # at the repository's root: one workspace |
| 87 | cd apps/mobile |
| 88 | npx expo run:android # builds a development app and starts Metro |
| 89 | ``` |
| 90 | |
| 91 | `expo run:android` makes the native project (`android/`, ignored by git) |
| 92 | and installs `g1t (dev)`. After the first build, `npx expo start` and |
| 93 | the installed app are enough until a native module changes. |
| 94 | |
| 95 | Expo Go works for screens that need nothing native beyond what Go has, |
| 96 | and sign-in comes back to Go's own `exp://` address (allowed as a |
| 97 | redirect). `g1t://` links need a development build. |
| 98 | |
| 99 | Checks, which CI runs from the root (`npm run typecheck`, `npm test`): |
| 100 | |
| 101 | ```sh |
| 102 | npm run typecheck -w @g1t/mobile |
| 103 | npm test -w @g1t/mobile # lib/*.test.ts under Node |
| 104 | ``` |
| 105 | |
| 106 | ## Set up EAS and ship Android |
| 107 | |
| 108 | Once, by someone with access to the Expo account and Google Play: |
| 109 | |
| 110 | 1. `npm install -g eas-cli`, then `eas login`. |
| 111 | 2. 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. |
| 115 | 3. `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. |
| 118 | 4. 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. |
| 120 | 5. `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. |
| 123 | 6. 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 | |
| 129 | iOS is the same with `--platform ios`, an Apple Developer account and the |
| 130 | bundle 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`). |