Skip to content

Commit 74602fe

Browse files
Merge branch 'main' into fix/website-windows-shortcut-3526
2 parents c84f374 + ef943a2 commit 74602fe

250 files changed

Lines changed: 18716 additions & 5174 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.cursor/rules/cursor-cloud.mdc‎

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
---
2+
description: Cursor Cloud environment setup and run notes for T3 Code (Cursor-specific; not for other editors/CI)
3+
alwaysApply: true
4+
---
5+
6+
# Cursor Cloud specific instructions
7+
8+
These notes apply only to Cursor Cloud agents (kept out of the shared `AGENTS.md` since T3 Code is
9+
developed in many places). They cover the pre-provisioned VM environment and non-obvious run caveats.
10+
11+
## Toolchain
12+
13+
Pre-installed and wired into login shells (`~/.bashrc`/`~/.profile`): Node 24 (via nvm), the global
14+
`vp` (Vite+) CLI, and its bundled pnpm. `vp` is the package manager + task runner for this repo
15+
(`vp i`, `vp check`, `vp run typecheck`, `vp test`, `vp run --filter <pkg> <script>`).
16+
17+
- Node gotcha: the base image also ships an older Node on `/exec-daemon` that wins on `PATH` in
18+
non-login shells. Run commands in a login shell (`bash -lc '...'`) or prepend
19+
`$HOME/.nvm/versions/node/v24.13.1/bin` so Node 24 (`engines.node: ^24.13.1`) is used.
20+
- `vp check` also runs the formatter (not just lint); markdown edits must be formatter-clean. Run
21+
`vp check --fix` before committing.
22+
23+
## Running the web app
24+
25+
- `npm run dev` (from repo root) starts contracts (watch), web (Vite, port 5733), and the server
26+
(`node --watch`, port 13773) together, auto-wiring `VITE_HTTP_URL`/`VITE_WS_URL`. Use
27+
`npm run dev:server` / `npm run dev:web` for one side. If base ports are taken it auto-offsets to
28+
the next free pair, so read the actual ports from stdout. Avoid production `build`/`start` in dev.
29+
- Auth/pairing: the server is unauthenticated by default and prints a pairing URL to stdout on startup
30+
(e.g. `http://localhost:5733/pair#token=XXXX`). Open that URL in the browser to pair before the web
31+
UI can talk to the server. Server state (SQLite, auth, projects) lives under `~/.t3`
32+
(`T3CODE_HOME`); multiple `npm run dev` instances bind different ports but share that same DB.
33+
- Agent providers are external CLIs (`codex`, `claude`, `cursor-agent`, `opencode`) probed on `PATH`.
34+
Without one installed the UI loads but no agent can run. Claude Code
35+
(`npm i -g @anthropic-ai/claude-code`) works headlessly using `ANTHROPIC_API_KEY` from the
36+
environment (the server forwards `process.env` to the child; no interactive `claude auth login`
37+
needed). Provider probing runs at startup and refreshes roughly every 5 minutes — restart the dev
38+
server to pick up a newly installed provider CLI immediately.
39+
40+
## Android native builds (apps/mobile)
41+
42+
The Android toolchain is pre-installed and wired into login shells: OpenJDK 17
43+
(`JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64`; the base image's default `java` is 21, so builds must
44+
use `JAVA_HOME`), and the Android SDK at `$HOME/Android/Sdk` (`ANDROID_HOME`/`ANDROID_SDK_ROOT`) with
45+
platform-tools, `platforms;android-36`, `build-tools;36.0.0`, `ndk;27.1.12297006`, and `cmake;3.22.1`.
46+
Gradle auto-downloads any additional pinned SDK packages on demand.
47+
48+
- Generate the native project first: from `apps/mobile`,
49+
`EXPO_NO_GIT_STATUS=1 APP_VARIANT=development npx expo prebuild --clean --platform android`
50+
(the `android/` folder is generated, not committed). RN 0.85 defaults: compileSdk/targetSdk 36,
51+
minSdk 24, NDK `27.1.12297006` (see `react-native/gradle/libs.versions.toml`).
52+
- Build with Gradle from `apps/mobile/android`, e.g. `./gradlew :app:assembleDebug`. First build is
53+
slow (Kotlin + C++/NDK codegen). Limit ABIs to speed things up, e.g.
54+
`-PreactNativeArchitectures=arm64-v8a`. Verified working end-to-end on a native module:
55+
`./gradlew :react-native-worklets:assembleDebug` compiles Kotlin + C++ and emits an AAR + `.so`s.
56+
- No emulator: this VM has no `/dev/kvm` (nested virtualization), so a hardware-accelerated Android
57+
emulator will not run. Use a physical device (`adb`) or EAS cloud builds (`vp run eas:android:*`) to
58+
actually run the app. The full IDE GUI is unnecessary here — the SDK/NDK/Gradle toolchain that
59+
Android Studio bundles is what is installed.
60+
- Known blocker for the full-app `:app:assembleDebug` (pre-existing, app-level — not the environment):
61+
`:react-native-screens:compileDebugKotlin` fails because `patches/[email protected]`
62+
adds new codegen props (`subtitle`, `largeSubtitle`, `navigationItemStyle`,
63+
`headerCenterBarButtonItems`, `headerToolbarItems`) to the JS spec + iOS native code but includes no
64+
Android Kotlin setters, so the generated `ScreenStackHeaderConfigManagerInterface` is unimplemented.
65+
The mobile README documents only iOS for local builds and routes Android through EAS. Fixing local
66+
Android app builds requires an app/patch change (add the Android Kotlin setters), which is out of
67+
scope for environment setup.
68+
69+
## Known flaky tests (unrelated to setup)
70+
71+
- `apps/server/src/git/GitManager.test.ts` (cross-repo PR metadata, can time out at 12s).
72+
- `apps/server/src/provider/Layers/ProviderRegistry.test.ts` (codex binaryPath re-probe ordering).

‎.github/workflows/mobile-eas-preview.yml‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,10 @@ jobs:
5757
with:
5858
eas-version: latest
5959
token: ${{ secrets.EXPO_TOKEN }}
60-
packager: pnpm
60+
# npm, not pnpm: this only installs eas-cli into the action's own
61+
# tool dir, and pnpm 11 hard-fails that install on dtrace-provider's
62+
# ignored build script (no allowBuilds config outside the repo).
63+
packager: npm
6164

6265
- name: Pull preview environment variables
6366
if: steps.expo-token.outputs.present == 'true'
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
name: Mobile EAS Production
2+
3+
# Production builds and OTA updates run from CI (Linux) — never from a laptop.
4+
# Under the fingerprint runtime-version policy the fingerprint must be computed
5+
# in the same OS/pnpm as the EAS build; a macOS `eas build` computes a different
6+
# fingerprint (platform-specific deps + pnpm version) and errors. On this Linux
7+
# runner, with corepack pinning pnpm 10.24 in eas.json, local == build.
8+
on:
9+
workflow_dispatch:
10+
inputs:
11+
mode:
12+
description: "build (+ auto-submit to TestFlight) or update (OTA)"
13+
required: true
14+
type: choice
15+
default: build
16+
options:
17+
- build
18+
- update
19+
platform:
20+
description: "Target platform"
21+
required: true
22+
type: choice
23+
default: ios
24+
options:
25+
- ios
26+
- android
27+
- all
28+
message:
29+
description: "OTA update message (mode=update only)"
30+
required: false
31+
type: string
32+
33+
jobs:
34+
production:
35+
name: EAS Production ${{ inputs.mode }}
36+
runs-on: blacksmith-8vcpu-ubuntu-2404
37+
permissions:
38+
contents: read
39+
env:
40+
APP_VARIANT: production
41+
NODE_OPTIONS: --max-old-space-size=8192
42+
steps:
43+
- id: expo-token
44+
name: Check for EXPO_TOKEN
45+
env:
46+
EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }}
47+
run: |
48+
if [ -n "$EXPO_TOKEN" ]; then
49+
echo "present=true" >> "$GITHUB_OUTPUT"
50+
else
51+
echo "present=false" >> "$GITHUB_OUTPUT"
52+
echo "EXPO_TOKEN is not available; skipping EAS production job."
53+
fi
54+
55+
- name: Checkout
56+
if: steps.expo-token.outputs.present == 'true'
57+
uses: actions/checkout@v6
58+
with:
59+
fetch-depth: 0
60+
61+
- name: Setup Vite+
62+
if: steps.expo-token.outputs.present == 'true'
63+
uses: voidzero-dev/setup-vp@v1
64+
with:
65+
node-version-file: package.json
66+
cache: true
67+
run-install: true
68+
69+
- name: Expose pnpm
70+
if: steps.expo-token.outputs.present == 'true'
71+
run: |
72+
pnpm_version="$(node --print "require('./package.json').packageManager.split('@').pop()")"
73+
vp_pnpm_bin="$HOME/.vite-plus/package_manager/pnpm/$pnpm_version/pnpm/bin"
74+
echo "$vp_pnpm_bin" >> "$GITHUB_PATH"
75+
"$vp_pnpm_bin/pnpm" --version
76+
77+
- name: Setup EAS
78+
if: steps.expo-token.outputs.present == 'true'
79+
uses: expo/expo-github-action@v8
80+
with:
81+
eas-version: latest
82+
token: ${{ secrets.EXPO_TOKEN }}
83+
# npm, not pnpm: this only installs eas-cli into the action's own
84+
# tool dir, and pnpm 11 hard-fails that install on dtrace-provider's
85+
# ignored build script (no allowBuilds config outside the repo).
86+
packager: npm
87+
88+
- name: Pull production environment variables
89+
if: steps.expo-token.outputs.present == 'true'
90+
working-directory: apps/mobile
91+
env:
92+
EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }}
93+
run: eas env:pull production --non-interactive
94+
95+
- name: Build and submit
96+
if: steps.expo-token.outputs.present == 'true' && inputs.mode == 'build'
97+
working-directory: apps/mobile
98+
env:
99+
EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }}
100+
run: eas build --platform ${{ inputs.platform }} --profile production --auto-submit --non-interactive --no-wait
101+
102+
- name: Publish OTA update
103+
if: steps.expo-token.outputs.present == 'true' && inputs.mode == 'update'
104+
working-directory: apps/mobile
105+
env:
106+
EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }}
107+
run: |
108+
eas update \
109+
--channel production \
110+
--environment production \
111+
--platform ${{ inputs.platform }} \
112+
--message "${{ inputs.message || format('Production OTA ({0})', github.sha) }}" \
113+
--non-interactive

‎.macroscope/check-run-agents/effect-service-conventions.md‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,15 @@ Review changed TypeScript and directly affected call sites for the conventions b
4343
- Keep implementation-specific names when an abstract port module contains one of several possible implementations, for example `makeCloudflaredRelayClient` and `layerCloudflared` in `RelayClient.ts`.
4444
- `infra/relay/src/db.ts` is an intentional exception: an inline `Layer.succeed(RelayDb, db)` is acceptable without generic `make`/`layer` exports.
4545

46+
## Dependency acquisition and runtime boundaries
47+
48+
- Production service construction must acquire Effect service dependencies from the environment with `yield* Foo.Foo`, and its `make`/`layer` types must expose those requirements. Flag factories or constructors that accept `Foo["Service"]` (or a plain object whose methods return `Effect`) when that value is an implementation dependency owned by the service. Passing service instances explicitly is acceptable in tests and integration harnesses; passing pure configuration, immutable domain values, or deliberate callback strategies is not service injection.
49+
- Do not hide dependencies in module globals, closures over singleton services, or `Layer.succeed` implementations that call runtime-backed or imperative APIs. Trace helpers used by a supposedly synchronous layer far enough to verify that asynchronous services are represented in the Effect environment.
50+
- `ManagedRuntime.make`, `runPromise`, and `runPromiseExit` belong at explicit application/framework boundaries such as React, native callback, CLI, or HTTP adapters. Flag their use in domain services, repositories, persistence implementations, and service constructors. A clearly named imperative adapter may bridge an Effect service into a Promise API, but it must not become a dependency of another Effect service.
51+
- Do not create per-feature managed runtimes or Atom runtimes to smuggle the same owned resource into multiple consumers. Compose the resource once in an application-owned layer/runtime and provide its context to integration runtimes.
52+
- When acquisition can fail but a caller must retain fallback behavior, keep the failure typed in Effect rather than bypassing the layer through an imperative runtime. Model unavailability in service operations or with an explicit optional-service layer so downstream recovery remains visible and testable.
53+
- During review, search touched code and affected call sites for service-instance parameters, `Layer.succeed`, `ManagedRuntime.make`, and `.runPromise`/`.runPromiseExit`. Verify that each occurrence is a legitimate test seam, pure value injection, or application boundary—not fake dependency injection or a hidden runtime.
54+
4655
## Errors and predicates
4756

4857
- Define service failures with `Schema.TaggedErrorClass` and structured attributes. Derive `message` from those attributes rather than storing an unstructured message as the only data.

‎.vite-hooks/pre-commit‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
vp staged

‎apps/desktop/package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
"@effect/vitest": "catalog:",
3232
"@types/node": "catalog:",
3333
"cross-env": "^10.1.0",
34-
"electron-builder": "26.8.1",
34+
"electron-builder": "26.15.6",
3535
"tailwindcss": "^4.0.0",
3636
"vite-plus": "catalog:"
3737
},

‎apps/mobile/app.config.ts‎

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const VARIANT_CONFIG: Record<
1515
readonly appName: string;
1616
readonly scheme: string;
1717
readonly iosIcon: string;
18+
readonly splashIcon: string;
1819
readonly iosBundleIdentifier: string;
1920
readonly androidPackage: string;
2021
readonly relyingParty?: string;
@@ -24,6 +25,7 @@ const VARIANT_CONFIG: Record<
2425
appName: "T3 Code Dev",
2526
scheme: "t3code-dev",
2627
iosIcon: "./assets/icon-composer-dev.icon",
28+
splashIcon: "./assets/splash-icon-dev.png",
2729
iosBundleIdentifier: "com.t3tools.t3code.dev",
2830
androidPackage: "com.t3tools.t3code.dev",
2931
relyingParty: "clerk.t3.codes",
@@ -32,6 +34,7 @@ const VARIANT_CONFIG: Record<
3234
appName: "T3 Code Preview",
3335
scheme: "t3code-preview",
3436
iosIcon: "./assets/icon-composer-prod.icon",
37+
splashIcon: "./assets/splash-icon-prod.png",
3538
iosBundleIdentifier: "com.t3tools.t3code.preview",
3639
androidPackage: "com.t3tools.t3code.preview",
3740
relyingParty: "clerk.t3.codes",
@@ -40,6 +43,7 @@ const VARIANT_CONFIG: Record<
4043
appName: "T3 Code",
4144
scheme: "t3code",
4245
iosIcon: "./assets/icon-composer-prod.icon",
46+
splashIcon: "./assets/splash-icon-prod.png",
4347
iosBundleIdentifier: "com.t3tools.t3code",
4448
androidPackage: "com.t3tools.t3code",
4549
relyingParty: "clerk.t3.codes",
@@ -59,14 +63,28 @@ function resolveAppVariant(value: string | undefined): AppVariant {
5963

6064
const variant = VARIANT_CONFIG[APP_VARIANT];
6165

66+
const dmSansFonts = {
67+
regular: "@expo-google-fonts/dm-sans/400Regular/DMSans_400Regular.ttf",
68+
medium: "@expo-google-fonts/dm-sans/500Medium/DMSans_500Medium.ttf",
69+
bold: "@expo-google-fonts/dm-sans/700Bold/DMSans_700Bold.ttf",
70+
} as const;
71+
72+
// These aliases match the fonts' PostScript names on iOS. Register the same
73+
// names on Android so React Native and the native composer use one set of
74+
// family names without waiting for runtime font loading.
75+
6276
const config: ExpoConfig = {
6377
name: variant.appName,
6478
slug: "t3-code",
6579
platforms: ["ios", "android"],
6680
scheme: variant.scheme,
6781
version: "0.1.0",
6882
runtimeVersion: {
69-
policy: process.env.MOBILE_VERSION_POLICY ?? "appVersion",
83+
// Fingerprint (not appVersion) so an OTA only reaches binaries whose native
84+
// project — native deps, config plugins, AND patches/ — matches the update.
85+
// With appVersion, every 0.1.0 build shares a runtime version, so a JS update
86+
// could land on a binary missing the native changes it needs and crash.
87+
policy: process.env.MOBILE_VERSION_POLICY ?? "fingerprint",
7088
},
7189
orientation: "portrait",
7290
icon: "./assets/icon.png",
@@ -113,8 +131,32 @@ const config: ExpoConfig = {
113131
favicon: "./assets/favicon.png",
114132
},
115133
plugins: [
116-
"expo-font",
134+
[
135+
"expo-font",
136+
{
137+
ios: {
138+
fonts: [dmSansFonts.regular, dmSansFonts.medium, dmSansFonts.bold],
139+
},
140+
android: {
141+
fonts: [
142+
{
143+
fontFamily: "DMSans-Regular",
144+
fontDefinitions: [{ path: dmSansFonts.regular, weight: 400 }],
145+
},
146+
{
147+
fontFamily: "DMSans-Medium",
148+
fontDefinitions: [{ path: dmSansFonts.medium, weight: 500 }],
149+
},
150+
{
151+
fontFamily: "DMSans-Bold",
152+
fontDefinitions: [{ path: dmSansFonts.bold, weight: 700 }],
153+
},
154+
],
155+
},
156+
},
157+
],
117158
"expo-secure-store",
159+
"expo-sqlite",
118160
["@clerk/expo", { theme: "./clerk-theme.json" }],
119161
"expo-web-browser",
120162
[
@@ -127,12 +169,12 @@ const config: ExpoConfig = {
127169
[
128170
"expo-splash-screen",
129171
{
130-
image: "./assets/splash-icon.png",
172+
image: variant.splashIcon,
131173
resizeMode: "contain",
132174
backgroundColor: "#ffffff",
133175
imageWidth: 220,
134176
dark: {
135-
image: "./assets/splash-icon.png",
177+
image: variant.splashIcon,
136178
backgroundColor: "#0a0a0a",
137179
},
138180
},
@@ -151,12 +193,21 @@ const config: ExpoConfig = {
151193
},
152194
],
153195
"./plugins/withIosCocoaPodsUuidCache.cjs",
196+
// Must be listed BEFORE expo-widgets: same-type mods run last-registered-
197+
// first, so registering earlier makes this plugin's mods run AFTER
198+
// expo-widgets' — its dangerous mod wipes ios/ExpoWidgetsTarget/ (which
199+
// would delete the asset catalog) and its xcodeproj mod creates the widget
200+
// target (which must exist before the compile phase can be attached).
201+
"./plugins/withWidgetLogoAsset.cjs",
154202
[
155203
"expo-widgets",
156204
{
157205
bundleIdentifier: `${variant.iosBundleIdentifier}.widgets`,
158206
groupIdentifier: `group.${variant.iosBundleIdentifier}`,
159207
enablePushNotifications: true,
208+
// Agent activity can update many times an hour; without the
209+
// frequent-updates entitlement iOS throttles the update budget sooner.
210+
frequentUpdates: true,
160211
widgets: [
161212
{
162213
name: "AgentActivity",
@@ -179,6 +230,15 @@ const config: ExpoConfig = {
179230
publishableKey: repoEnv.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY ?? null,
180231
jwtTemplate: repoEnv.EXPO_PUBLIC_CLERK_JWT_TEMPLATE ?? null,
181232
},
233+
// Native Google sign-in credentials. @clerk/expo reads these from `extra`
234+
// under their exact env-var names (not nested), and its config plugin reads
235+
// the iOS URL scheme at prebuild to register it in Info.plist.
236+
// Unset values must be omitted (not null): the public manifest serializes
237+
// null to {}, which is truthy and would defeat Clerk's fallback checks.
238+
EXPO_PUBLIC_CLERK_GOOGLE_WEB_CLIENT_ID: repoEnv.EXPO_PUBLIC_CLERK_GOOGLE_WEB_CLIENT_ID,
239+
EXPO_PUBLIC_CLERK_GOOGLE_IOS_CLIENT_ID: repoEnv.EXPO_PUBLIC_CLERK_GOOGLE_IOS_CLIENT_ID,
240+
EXPO_PUBLIC_CLERK_GOOGLE_ANDROID_CLIENT_ID: repoEnv.EXPO_PUBLIC_CLERK_GOOGLE_ANDROID_CLIENT_ID,
241+
EXPO_PUBLIC_CLERK_GOOGLE_IOS_URL_SCHEME: repoEnv.EXPO_PUBLIC_CLERK_GOOGLE_IOS_URL_SCHEME,
182242
observability: {
183243
tracesUrl: repoEnv.EXPO_PUBLIC_OTLP_TRACES_URL ?? "https://api.axiom.co/v1/traces",
184244
tracesDataset: repoEnv.EXPO_PUBLIC_OTLP_TRACES_DATASET ?? null,
302 KB
Loading
Lines changed: 3 additions & 0 deletions
Loading

0 commit comments

Comments
 (0)