@terreno/agent-plugin 0.0.0-stage → 57.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/.claude-plugin/plugin.json +32 -0
  2. package/CHANGELOG.md +2155 -0
  3. package/LICENSE +21 -0
  4. package/README.md +57 -2
  5. package/agents/pre-commit.md +65 -0
  6. package/agents/ui-verifier.md +93 -0
  7. package/package.json +32 -4
  8. package/references/async-review-bots.md +76 -0
  9. package/references/documentation-contract.md +61 -0
  10. package/references/execution-state.schema.json +77 -0
  11. package/references/github-attention-contract.md +165 -0
  12. package/references/independent-review.md +31 -0
  13. package/references/lifecycle-contract.md +211 -0
  14. package/references/loop-engineering.md +125 -0
  15. package/references/pick-roast-loop.md +112 -0
  16. package/references/pr-deployments.md +69 -0
  17. package/references/product-ci.md +164 -0
  18. package/references/push-ownership.md +91 -0
  19. package/references/stage-result.schema.json +82 -0
  20. package/references/subagent-briefing.md +43 -0
  21. package/references/testing-plan.md +163 -0
  22. package/references/user-guide.md +171 -0
  23. package/skills/1-grow/SKILL.md +145 -0
  24. package/skills/1-grow/references/grilling.md +243 -0
  25. package/skills/2-pick/SKILL.md +170 -0
  26. package/skills/2-pick/references/mocking.md +34 -0
  27. package/skills/2-pick/references/testing.md +43 -0
  28. package/skills/3-roast/SKILL.md +168 -0
  29. package/skills/4-brew/SKILL.md +201 -0
  30. package/skills/5-taste/SKILL.md +311 -0
  31. package/skills/ai-prompt-governance/SKILL.md +100 -0
  32. package/skills/attach-pr-media/SKILL.md +89 -0
  33. package/skills/attach-pr-media/scripts/upload-pr-media.sh +151 -0
  34. package/skills/backend-test-env/SKILL.md +64 -0
  35. package/skills/backfill-regression/SKILL.md +101 -0
  36. package/skills/backfill-user-guide/SKILL.md +96 -0
  37. package/skills/bootstrap-mcp-server/SKILL.md +244 -0
  38. package/skills/building-admin-interfaces/SKILL.md +83 -0
  39. package/skills/building-admin-interfaces/agents/openai.yaml +4 -0
  40. package/skills/building-admin-interfaces/references/nav-and-shell.md +42 -0
  41. package/skills/building-admin-interfaces/references/patterns-and-tools.md +73 -0
  42. package/skills/building-admin-interfaces/references/screens-and-routes.md +50 -0
  43. package/skills/building-terreno-apps/SKILL.md +280 -0
  44. package/skills/building-terreno-apps/agents/openai.yaml +4 -0
  45. package/skills/building-terreno-apps/references/navigation.md +102 -0
  46. package/skills/building-terreno-apps/references/route-structure.md +91 -0
  47. package/skills/building-terreno-apps/references/screen-patterns.md +191 -0
  48. package/skills/debug-syncdb-with-mcp/SKILL.md +75 -0
  49. package/skills/deploy-gcp/SKILL.md +73 -0
  50. package/skills/deploy-gcp/references/troubleshooting.md +29 -0
  51. package/skills/generate-release-announcements/SKILL.md +145 -0
  52. package/skills/generate-sdk/SKILL.md +156 -0
  53. package/skills/mongoose-schema-safety/SKILL.md +164 -0
  54. package/skills/pick-roast-loop/SKILL.md +152 -0
  55. package/skills/planning-loop/SKILL.md +161 -0
  56. package/skills/prd/SKILL.md +138 -0
  57. package/skills/set-up-social-login/SKILL.md +76 -0
  58. package/skills/store-assets/SKILL.md +221 -0
  59. package/skills/store-assets/examples/screenshots.yaml +31 -0
  60. package/skills/store-assets/examples/storeAssets.config.json +44 -0
  61. package/skills/store-assets/scripts/banner.ts +165 -0
  62. package/skills/store-assets/scripts/config.ts +255 -0
  63. package/skills/store-assets/scripts/devClient.ts +635 -0
  64. package/skills/store-assets/scripts/devices.ts +286 -0
  65. package/skills/store-assets/scripts/icon.ts +81 -0
  66. package/skills/store-assets/scripts/maestro/goHome.yaml +21 -0
  67. package/skills/store-assets/scripts/maestro/login.yaml +27 -0
  68. package/skills/store-assets/scripts/maestro/openApp.yaml +26 -0
  69. package/skills/store-assets/scripts/screenshots.ts +848 -0
  70. package/skills/taste-sweep/SKILL.md +172 -0
  71. package/skills/terreno-backend-api/SKILL.md +355 -0
  72. package/skills/terreno-backend-api/agents/openai.yaml +4 -0
  73. package/skills/terreno-backend-api/references/custom-routes.md +132 -0
  74. package/skills/terreno-backend-api/references/model-router.md +116 -0
  75. package/skills/terreno-backend-api/references/server-setup.md +116 -0
  76. package/skills/terreno-backend-tests/SKILL.md +176 -0
  77. package/skills/terreno-data-fetching/SKILL.md +276 -0
  78. package/skills/terreno-data-fetching/agents/openai.yaml +4 -0
  79. package/skills/terreno-data-fetching/references/auth-and-tokens.md +93 -0
  80. package/skills/terreno-data-fetching/references/realtime.md +45 -0
  81. package/skills/terreno-data-fetching/references/sdk-customization.md +84 -0
  82. package/skills/terreno-frontend-tests/SKILL.md +190 -0
  83. package/skills/terreno-mobile-tests/SKILL.md +127 -0
  84. package/skills/terreno-test-setup/SKILL.md +81 -0
  85. package/skills/terreno-test-setup/references/backend.md +131 -0
  86. package/skills/terreno-test-setup/references/frontend.md +169 -0
  87. package/skills/terreno-test-setup/references/mobile.md +134 -0
  88. package/skills/terreno-ui/SKILL.md +252 -0
  89. package/skills/terreno-ui/agents/openai.yaml +4 -0
  90. package/skills/terreno-ui/references/data-display.md +85 -0
  91. package/skills/terreno-ui/references/feedback.md +122 -0
  92. package/skills/terreno-ui/references/forms.md +119 -0
  93. package/skills/terreno-ui/references/layout.md +124 -0
  94. package/skills/terreno-ui/references/theming.md +87 -0
  95. package/skills/update-docs/SKILL.md +82 -0
  96. package/skills/upgrading-terreno/SKILL.md +86 -0
  97. package/skills/upgrading-terreno/references/ordering.md +40 -0
  98. package/skills/verify-ui-changes/SKILL.md +605 -0
@@ -0,0 +1,131 @@
1
+ # Backend test setup
2
+
3
+ Bun's test runner plus `@terreno/test`: in-memory MongoDB, canonical auth env, quiet logs, a
4
+ Sentry mock, and supertest helpers.
5
+
6
+ ## 1. Install
7
+
8
+ ```bash
9
+ cd backend
10
+ bun add -d @terreno/test@<same version as @terreno/api> supertest @types/supertest
11
+ ```
12
+
13
+ `mongoose` is a peer dependency and is already in a Terreno backend. `mongodb-memory-server`
14
+ downloads a `mongod` binary on first use (Bun blocks its postinstall by default; the first test
15
+ run downloads it instead, which can take a minute).
16
+
17
+ ## 2. Preload
18
+
19
+ `backend/bunfig.toml`:
20
+
21
+ ```toml
22
+ [test]
23
+ preload = ["./src/tests/bunSetup.ts"]
24
+ root = "./src"
25
+ ```
26
+
27
+ `backend/src/tests/bunSetup.ts` — one file: SDK mocks first (if any), then the Mongo preload:
28
+
29
+ ```typescript
30
+ import {registerBackendPreload, registerSendGridMock} from "@terreno/test";
31
+
32
+ // Only for SDKs the app imports (a fresh create-terreno-app backend imports none; skip these).
33
+ export const sendGrid = registerSendGridMock();
34
+
35
+ registerBackendPreload({
36
+ onBeforeEach: () => {
37
+ sendGrid.reset();
38
+ // Keys the app reads that must start every test unset.
39
+ Reflect.deleteProperty(process.env, "SENDGRID_API_KEY");
40
+ },
41
+ testEnv: {
42
+ extra: {APP_URL: "http://localhost:8082"},
43
+ tokenIssuer: "<app>.test",
44
+ },
45
+ });
46
+ ```
47
+
48
+ What the preload does:
49
+
50
+ | When | Effect |
51
+ |------|--------|
52
+ | once per run | starts one `mongodb-memory-server` `mongod` (or connects to `TERRENO_TEST_MONGODB_URI`), mocks `@sentry/bun`, silences Winston/console |
53
+ | `beforeEach` | resets `TOKEN_SECRET`, `TOKEN_ISSUER`, `REFRESH_TOKEN_SECRET`, `SESSION_SECRET`, `NODE_ENV=test`, `TZ=UTC`, plus `testEnv.extra`; runs `onBeforeEach` |
54
+ | `afterEach` | resets the system clock, clears mock calls, restores `spyOn` spies, re-applies the env |
55
+
56
+ - It does **not** wipe collections. Tests delete what they read (see `terreno-backend-tests`).
57
+ - A cold `mongod` start can take 20s+; the launch timeout defaults to 60s
58
+ (`TERRENO_TEST_MONGO_LAUNCH_TIMEOUT_MS` to change it). The first run also downloads the
59
+ `mongod` binary.
60
+ - Some lines still print (`debug: No options.openApi provided...` when a test builds a
61
+ `TerrenoApp`, request logs). They are harmless.
62
+ - Change streams, transactions, or `SyncApp` under test need a replica set: set
63
+ `process.env.TERRENO_TEST_USE_REPLSET = "true"` at the top of the preload.
64
+ `registerBackendPreload({useTransactions: true})` also wraps each test in a transaction
65
+ that is aborted afterwards (no cleanup code, but every DB call is serialized through one
66
+ session); use it only when cleanup cost dominates the suite.
67
+ - `registerSimpleMongoPreload` is the lighter variant the Terreno packages use; prefer
68
+ `registerBackendPreload` in apps.
69
+
70
+ ## 3. Third-party services
71
+
72
+ `mock.module` replaces a module for the whole `bun test` process, so a mock registered inside
73
+ one test file leaks into every later file. Register SDK mocks **only in the preload**, export
74
+ the handles so tests can assert on them, and `reset()` each in `onBeforeEach` (the preload's
75
+ `afterEach` clears call history but not stored state such as GCS objects or queued tasks).
76
+
77
+ | Registrar | Module |
78
+ |-----------|--------|
79
+ | `registerSendGridMock` | `@sendgrid/mail` |
80
+ | `registerTwilioMock` | `twilio` |
81
+ | `registerExpoPushMock` | `expo-server-sdk` |
82
+ | `registerCloudTasksMock` | `@google-cloud/tasks` |
83
+ | `registerGcsMock` | `@google-cloud/storage` |
84
+ | `registerGoogleAuthMock` | `google-auth-library` |
85
+ | `registerTerrenoNotifierMocks` | `sendToSlack` / `sendToZoom` / `sendToGoogleChat` from `@terreno/api` |
86
+
87
+ The AI SDK needs no module mock: pass `createMockLanguageModel()` to `AIService` (see
88
+ `terreno-backend-tests`). Prefer constructor injection over a module mock when the app already
89
+ accepts a client (for example `@terreno/comms` providers).
90
+
91
+ ## 4. First test
92
+
93
+ `backend/src/api/users.test.ts` — proves the preload, Mongo, and supertest work:
94
+
95
+ ```typescript
96
+ import {describe, expect, it} from "bun:test";
97
+ import {TerrenoApp} from "@terreno/api";
98
+ import supertest from "supertest";
99
+ import {User} from "../models/user";
100
+ import {userRouter} from "./users";
101
+
102
+ const buildApp = () =>
103
+ new TerrenoApp({skipListen: true, userModel: User as never}).register(userRouter).build();
104
+
105
+ describe("users", () => {
106
+ it("rejects anonymous list requests", async () => {
107
+ const res = await supertest(buildApp()).get("/users");
108
+ expect(res.status).toBe(401);
109
+ });
110
+ });
111
+ ```
112
+
113
+ Build a `TerrenoApp` with only the routers under test instead of calling the app's `start()`:
114
+ `start()` connects to the dev database and registers every plugin.
115
+
116
+ ## CI
117
+
118
+ ```json
119
+ "scripts": {
120
+ "test": "bun test",
121
+ "test:ci": "bun test",
122
+ "test:shard": "terreno-test-shard",
123
+ "test:hygiene": "terreno-test-hygiene --root src"
124
+ }
125
+ ```
126
+
127
+ - Sharding: `bun run test:shard --shard $INDEX/$TOTAL` in a CI matrix; `--list` prints a
128
+ shard's files; flags after `--` go to `bun test` (e.g. `-- --timeout 30000`).
129
+ - Hygiene: run `bun run test:hygiene --update-baseline` once to record existing violations,
130
+ commit the baseline, then run `bun run test:hygiene` in CI so only new ones fail.
131
+ - Unit jobs need no services: the in-memory `mongod` runs inside the job.
@@ -0,0 +1,169 @@
1
+ # Frontend test setup
2
+
3
+ Two layers: **unit/component tests** run in Bun with React Native mocked (fast, no device), and
4
+ **web E2E** runs the real app in Chromium with Playwright.
5
+
6
+ ## Unit tests (Bun + React Native Testing Library)
7
+
8
+ ### 1. Install
9
+
10
+ ```bash
11
+ cd frontend
12
+ bun add -d @terreno/test@<same version as @terreno/ui> @testing-library/react-native@^13 \
13
+ react-test-renderer@<exact react version>
14
+ ```
15
+
16
+ - Pin React Native Testing Library to **13**. Version 14 makes `render` async and drops
17
+ `react-test-renderer`; `renderWithTerreno` targets 13, and unpinned installs get 14 (tests
18
+ then fail with `findByText is not a function`).
19
+ - `react-test-renderer` must match `react` exactly (both `19.2.3` today). Each run prints
20
+ `react-test-renderer is deprecated`; that warning is expected with RNTL 13.
21
+ - `@terreno/test` also brings its backend dependencies (`mongodb-memory-server`, `express`);
22
+ they are only loaded by backend tests.
23
+
24
+ ### 2. Preload
25
+
26
+ `frontend/bunfig.toml`:
27
+
28
+ ```toml
29
+ [test]
30
+ preload = ["./tests/testPreload.ts"]
31
+ coveragePathIgnorePatterns = ["**/*.test.ts", "**/*.test.tsx", "tests/**", "store/openApiSdk.ts"]
32
+ ```
33
+
34
+ `frontend/tests/testPreload.ts`:
35
+
36
+ ```typescript
37
+ import {registerExpoTestEnvironment} from "@terreno/test/expo/registerExpoTestEnvironment";
38
+
39
+ registerExpoTestEnvironment({
40
+ expoConfig: {extra: {}, scheme: "<app scheme from app.json>"},
41
+ });
42
+ ```
43
+
44
+ It mocks `react-native` (Bun cannot parse its Flow sources), the common `expo-*` modules,
45
+ `expo-router`, `@expo/vector-icons`, reanimated, gesture handler, SVG, AsyncStorage, NetInfo,
46
+ and SecureStore; serves images and fonts as asset stubs; sets `__DEV__`,
47
+ `IS_REACT_ACT_ENVIRONMENT`, `TZ`, and `EXPO_OS`. After each test it restores spies, empties
48
+ stored tokens, and resets `__DEV__`.
49
+
50
+ Options when the defaults do not fit:
51
+
52
+ | Need | Option |
53
+ |------|--------|
54
+ | A module should load for real | `skip: ["expo-sqlite"]` |
55
+ | App-specific native module | `overrides: {"react-native-maps": () => ({MapView: () => null})}` |
56
+ | Android / web `Platform.OS` | `platform: "android"` (default `"ios"`) |
57
+ | Fixed clock in every test | `fixedNow: "2024-06-01T10:30:00.000Z"` |
58
+ | A package ships its own nested `react-native` | `nestedPackagePrefixes: ["some-lib/node_modules/"]` |
59
+
60
+ Put any further app-wide `mock.module` calls (e.g. a partial `@/store/sdk` mock) in a second
61
+ preload file listed **after** `testPreload.ts`, and load the real module there with
62
+ `await import(...)` — a static import would evaluate before React Native is mocked.
63
+
64
+ ### 3. First test
65
+
66
+ `frontend/components/Greeting.test.tsx`:
67
+
68
+ ```tsx
69
+ import {describe, expect, it} from "bun:test";
70
+ import {renderWithTerreno} from "@terreno/test/expo/renderWithTerreno";
71
+ import {Greeting} from "./Greeting";
72
+
73
+ describe("Greeting", () => {
74
+ it("shows the user's name", async () => {
75
+ const {findByText} = renderWithTerreno(<Greeting name="Ada" />);
76
+ expect(await findByText("Hello, Ada")).toBeTruthy();
77
+ });
78
+ });
79
+ ```
80
+
81
+ `renderWithTerreno(ui, {store?, theme?})` wraps the tree in `TerrenoProvider` (and a Redux
82
+ `Provider` when `store` is passed; needs `react-redux`).
83
+
84
+ Done when `cd frontend && bun test` passes.
85
+
86
+ ## Playwright web E2E
87
+
88
+ ### 1. Install
89
+
90
+ ```bash
91
+ cd frontend
92
+ bun add -d @playwright/test
93
+ bunx playwright install --with-deps chromium
94
+ ```
95
+
96
+ E2E needs a **replica-set** `mongod` (the backend's sync and realtime use change streams):
97
+
98
+ ```bash
99
+ mongod --replSet rs0 --port 27017 --bind_ip 127.0.0.1 --dbpath ./.devdata/mongo &
100
+ mongosh --quiet --eval 'try { rs.status() } catch { rs.initiate({_id: "rs0", members: [{_id: 0, host: "127.0.0.1:27017"}]}) }'
101
+ ```
102
+
103
+ ### 2. Config
104
+
105
+ `frontend/playwright.config.ts`:
106
+
107
+ ```typescript
108
+ import {defineConfig, devices} from "@playwright/test";
109
+
110
+ export default defineConfig({
111
+ projects: [
112
+ {name: "setup", testMatch: /auth\.setup\.ts/},
113
+ {dependencies: ["setup"], name: "chromium", use: {...devices["Desktop Chrome"]}},
114
+ ],
115
+ testDir: "./e2e",
116
+ use: {baseURL: "http://localhost:8082", trace: "retain-on-failure"},
117
+ webServer: [
118
+ {
119
+ command: "bun run --cwd ../backend src/index.ts",
120
+ env: {MONGO_URI: process.env.MONGO_URI ?? "mongodb://127.0.0.1/<app>-e2e?replicaSet=rs0", PORT: "4000"},
121
+ reuseExistingServer: !process.env.CI,
122
+ timeout: 120_000,
123
+ url: "http://localhost:4000/health",
124
+ },
125
+ {
126
+ command: "bunx expo start --web --port 8082",
127
+ env: {EXPO_PUBLIC_API_URL: "http://localhost:4000"},
128
+ reuseExistingServer: !process.env.CI,
129
+ timeout: 180_000,
130
+ url: "http://localhost:8082",
131
+ },
132
+ ],
133
+ });
134
+ ```
135
+
136
+ ### 3. Layout
137
+
138
+ ```
139
+ e2e/
140
+ fixtures/testUsers.ts # TEST_USER, ADMIN_USER: {email, name, password}
141
+ helpers/login.ts # loginAs(page, user = TEST_USER)
142
+ auth.setup.ts # creates the users through the API before any spec runs
143
+ <feature>.spec.ts
144
+ ```
145
+
146
+ `e2e/helpers/login.ts`:
147
+
148
+ ```typescript
149
+ import type {Page} from "@playwright/test";
150
+ import {TEST_USER} from "../fixtures/testUsers";
151
+
152
+ export const loginAs = async (page: Page, user = TEST_USER): Promise<void> => {
153
+ await page.goto("/login");
154
+ await page.getByTestId("login-screen").waitFor({state: "visible"});
155
+ await page.getByTestId("login-screen-email-input").fill(user.email);
156
+ await page.getByTestId("login-screen-password-input").fill(user.password);
157
+ await page.getByTestId("login-screen-submit-button").click();
158
+ await page.getByTestId("login-screen").waitFor({state: "hidden"});
159
+ };
160
+ ```
161
+
162
+ `e2e/auth.setup.ts` signs each fixture user up through the backend's auth API and ignores
163
+ "already exists", so reruns are idempotent. With Better Auth (the `create-terreno-app`
164
+ default) that is `request.post("http://localhost:4000/api/auth/sign-up/email", {data: {email,
165
+ name, password}})`; with JWT auth it is `POST /auth/signup`. `bun run --cwd ../backend` loads
166
+ `backend/.env`, so the backend gets its auth secrets. The `@terreno/ui` login screen already
167
+ carries the `login-screen*` testIDs; give a custom form the same IDs.
168
+
169
+ Done when `bunx playwright test` passes. Add `"test:e2e": "playwright test"` to the scripts.
@@ -0,0 +1,134 @@
1
+ # Mobile test setup (iOS / Android)
2
+
3
+ Two layers:
4
+
5
+ - **Platform unit tests** — the same Bun suite with `Platform.OS` set to `ios` or `android`.
6
+ Fast; no device.
7
+ - **Maestro flows** — drive the real native app on a simulator or emulator.
8
+
9
+ ## Platform unit tests
10
+
11
+ `registerExpoTestEnvironment` fixes `Platform.OS` for the whole run, so pick it from an env var
12
+ in the preload and run the suite once per platform:
13
+
14
+ ```typescript
15
+ // frontend/tests/testPreload.ts
16
+ import {registerExpoTestEnvironment} from "@terreno/test/expo/registerExpoTestEnvironment";
17
+
18
+ const platform = (process.env.TEST_PLATFORM ?? "ios") as "android" | "ios" | "web";
19
+ registerExpoTestEnvironment({expoConfig: {extra: {}, scheme: "<scheme>"}, platform});
20
+ ```
21
+
22
+ ```json
23
+ "scripts": {
24
+ "test": "bun test",
25
+ "test:android": "TEST_PLATFORM=android bun test",
26
+ "test:web": "TEST_PLATFORM=web bun test"
27
+ }
28
+ ```
29
+
30
+ Run the extra platforms in CI only when the app has `Platform.OS` / `Platform.select` branches.
31
+
32
+ ## Maestro
33
+
34
+ ### 1. Native identifiers
35
+
36
+ Maestro targets the app by bundle ID. Set both in `frontend/app.json` if missing
37
+ (`create-terreno-app` only sets `scheme`):
38
+
39
+ ```json
40
+ "ios": {"bundleIdentifier": "com.<org>.<app>"},
41
+ "android": {"package": "com.<org>.<app>"}
42
+ ```
43
+
44
+ ### 2. A build on a device
45
+
46
+ Use a **development build**, not Expo Go (Expo Go's app ID is Expo's, not yours):
47
+
48
+ | Platform | Command |
49
+ |----------|---------|
50
+ | iOS simulator (macOS + Xcode) | `bunx expo run:ios` |
51
+ | Android emulator | `bunx expo run:android` |
52
+ | Either, prebuilt in the cloud | `bunx eas-cli build --profile development --platform <ios\|android>` then `bunx eas-cli build:run -p <platform> --latest` |
53
+
54
+ The `store-assets` skill's `devclient:ensure` script keeps a dev client in sync with the
55
+ native fingerprint if the app already uses it.
56
+
57
+ Reach the local backend from the device:
58
+
59
+ - iOS simulator shares the Mac's `localhost`; nothing to do.
60
+ - Android emulator: `adb reverse tcp:8081 tcp:8081 && adb reverse tcp:4000 tcp:4000` (Metro and
61
+ the API). Repeat after every emulator restart.
62
+
63
+ ### 3. Install Maestro
64
+
65
+ ```bash
66
+ curl -fsSL "https://get.maestro.mobile.dev" | bash # needs Java 17
67
+ maestro --version
68
+ ```
69
+
70
+ ### 4. Flows
71
+
72
+ ```
73
+ frontend/maestro/
74
+ config.yaml # flow list and order
75
+ subflows/login.yaml # reused by other flows
76
+ login.yaml
77
+ ```
78
+
79
+ `frontend/maestro/subflows/login.yaml`:
80
+
81
+ ```yaml
82
+ appId: ${APP_ID}
83
+ ---
84
+ - extendedWaitUntil:
85
+ visible:
86
+ id: "login-screen-email-input"
87
+ timeout: 60000
88
+ - tapOn:
89
+ id: "login-screen-email-input"
90
+ - inputText: ${EMAIL}
91
+ - tapOn:
92
+ id: "login-screen-password-input"
93
+ - inputText: ${PASSWORD}
94
+ - hideKeyboard
95
+ - tapOn:
96
+ id: "login-screen-submit-button"
97
+ ```
98
+
99
+ `frontend/maestro/login.yaml`:
100
+
101
+ ```yaml
102
+ appId: ${APP_ID}
103
+ ---
104
+ - launchApp:
105
+ clearState: true
106
+ - runFlow: subflows/login.yaml
107
+ - extendedWaitUntil:
108
+ visible:
109
+ id: "home-screen"
110
+ timeout: 30000
111
+ ```
112
+
113
+ React Native `testID` becomes the iOS accessibility identifier and the Android resource ID, so
114
+ Maestro's `id:` selector works on both. `@terreno/ui` components accept a `testID` prop; check a custom component passes it to the underlying native view.
115
+
116
+ ### 5. Run
117
+
118
+ ```bash
119
+ maestro test -e APP_ID=com.<org>.<app> -e EMAIL=test@example.com -e PASSWORD=<password> maestro/
120
+ maestro --device <udid|emulator-serial> test ... # when several devices are booted
121
+ maestro hierarchy # list the testIDs on screen
122
+ ```
123
+
124
+ Seed the user first (`bun run seed` in the backend, or the E2E auth setup).
125
+
126
+ Add `"test:maestro": "maestro test maestro/"` to the scripts.
127
+
128
+ ### CI
129
+
130
+ - Android: a Linux runner with an emulator (e.g. `reactivecircus/android-emulator-runner`),
131
+ then build, install, `adb reverse`, and `maestro test`.
132
+ - iOS: needs a macOS runner and a simulator build (`eas build --profile development:simulator`
133
+ or `expo run:ios`).
134
+ - Maestro Cloud runs both from an uploaded build if the project has an account.
@@ -0,0 +1,252 @@
1
+ ---
2
+ name: terreno-ui
3
+ description: >-
4
+ Build UI with @terreno/ui — Terreno's React Native component library with a
5
+ large component set and a three-layer theming system. Use when adding or reviewing
6
+ layout, forms, tables, modals, feedback, or navigation chrome. Covers
7
+ component selection, Box/Page patterns, theme props, and when NOT to use raw
8
+ React Native primitives. Lifecycle composition: Grow for UI shape, Pick for
9
+ implementation, Roast for UI conformance review. Not for backend APIs, RTK
10
+ Query, or Expo Router setup.
11
+ ---
12
+ # @terreno/ui
13
+
14
+ `@terreno/ui` is Terreno's universal (iOS, Android, web) component library. Start with existing components before creating new ones. For full screen structure and navigation, see `building-terreno-apps`.
15
+
16
+ ## Choosing a component (read this first)
17
+
18
+ Work down this list and stop at the first layer that meets the need:
19
+
20
+ 1. **Layout primitives — start here.** `Box` for flex layout, `Page` for full screens, `Card` for grouped content with shadow.
21
+ 2. **Form fields.** `TextField`, `SelectField`, `DateTimeField`, `CheckBox`, etc. for user input.
22
+ 3. **Data display.** `DataTable`, `Badge`, `Text`, `Heading` for content.
23
+ 4. **Feedback.** `Modal`, `Toast`, `Spinner`, `Banner`, `ErrorPage` for states.
24
+ 5. **Admin screens.** Skill `building-admin-interfaces` (`AdminScreenRouter`,
25
+ `AdminModelTable`, `AdminModelForm`, `AdminShellLayout`) before hand-rolling
26
+ tables/forms.
27
+ 6. **New component — last resort.** Only when a concrete composition gap is proven. Add to `ui/` package and a demo story in `demo/`.
28
+
29
+ **Do not use `@expo/ui`** in Terreno apps unless there is an explicit requirement for native SwiftUI/Compose that `@terreno/ui` cannot satisfy.
30
+
31
+ ## Documentation
32
+
33
+ 1. Read `docs/reference/ui.md`, `docs/explanation/responsive-design.md`, `docs/reference/components/`, and the relevant explanation pages before changing components, width-dependent layout, or theme contracts.
34
+ 2. Implement against that design.
35
+ 3. Update those pages (and a demo story) in the same slice with `update-docs`.
36
+ 4. Ship without matching docs is a failed slice.
37
+
38
+ ## References
39
+
40
+ ```
41
+ references/
42
+ layout.md Box, Page, Card, ListDetailLayout, SplitPage — spacing and flex
43
+ forms.md TextField, SelectField, DateTimeField, validation
44
+ data-display.md DataTable, Badge, Text, Heading
45
+ feedback.md Modal, Toast, Spinner, Banner
46
+ theming.md TerrenoProvider, useTheme, color tokens
47
+ ```
48
+
49
+ ## Installation
50
+
51
+ In Terreno monorepo apps, `@terreno/ui` is a workspace dependency:
52
+
53
+ ```json
54
+ "@terreno/ui": "workspace:*"
55
+ ```
56
+
57
+ Wrap the app root in `TerrenoProvider`:
58
+
59
+ ```tsx
60
+ import {TerrenoProvider} from "@terreno/ui";
61
+
62
+ <TerrenoProvider>
63
+ {children}
64
+ </TerrenoProvider>
65
+ ```
66
+
67
+ ## Quick examples
68
+
69
+ ### Layout
70
+
71
+ ```tsx
72
+ import {Box, Card, Heading, Page, Text} from "@terreno/ui";
73
+
74
+ const ProfileScreen: React.FC = () => (
75
+ <Page title="Profile" scroll>
76
+ <Box gap={4} padding={4}>
77
+ <Heading size="lg">Account</Heading>
78
+ <Card padding={4}>
79
+ <Text>user@example.com</Text>
80
+ </Card>
81
+ </Box>
82
+ </Page>
83
+ );
84
+ ```
85
+
86
+ ### Button
87
+
88
+ ```tsx
89
+ import {Button} from "@terreno/ui";
90
+
91
+ <Button
92
+ text="Save"
93
+ variant="primary"
94
+ onClick={handleSave}
95
+ loading={isSaving}
96
+ iconName="check"
97
+ fullWidth
98
+ />
99
+ ```
100
+
101
+ Variants: `primary` | `secondary` | `muted` | `outline` | `destructive`
102
+
103
+ ### Form field
104
+
105
+ ```tsx
106
+ import {TextField} from "@terreno/ui";
107
+
108
+ <TextField
109
+ title="Email"
110
+ value={email}
111
+ onChange={setEmail}
112
+ type="email"
113
+ errorText={emailError}
114
+ helperText="We'll never share your email"
115
+ />
116
+ ```
117
+
118
+ ### Modal
119
+
120
+ ```tsx
121
+ import {Modal, Text} from "@terreno/ui";
122
+
123
+ <Modal
124
+ title="Confirm Delete"
125
+ visible={isVisible}
126
+ primaryButtonText="Delete"
127
+ secondaryButtonText="Cancel"
128
+ onDismiss={() => setIsVisible(false)}
129
+ onPrimaryAction={handleDelete}
130
+ >
131
+ <Text>Are you sure?</Text>
132
+ </Modal>
133
+ ```
134
+
135
+ ## Rules
136
+
137
+ - Use `Box` for layout — never raw `View` (unless inside a platform-specific workaround).
138
+ - Use `Text` / `Heading` from `@terreno/ui` — never raw React Native `Text`.
139
+ - Use theme props (`padding`, `gap`, `color`, `border`, `rounding`) — not inline hex when a token exists.
140
+ - Use `Page` for screen-level layout — not bare `ScrollView`.
141
+ - Icons use FontAwesome 6 via `iconName` props.
142
+ - Use Luxon for dates in display logic; `DateTimeField` handles input.
143
+ - Always support React Native Web.
144
+ - After component changes, verify in the demo app (`verify-ui-changes` skill).
145
+
146
+ ## Component map
147
+
148
+ | Need | Component |
149
+ |------|-----------|
150
+ | Flex layout | `Box` |
151
+ | Full screen | `Page` |
152
+ | Grouped content | `Card` |
153
+ | List/detail with routes (mail, inbox, records) | `ListDetailLayout` in the group's `_layout.tsx` |
154
+ | List/detail without routes (inside a modal) | `SplitPage` |
155
+ | Text input | `TextField`, `TextArea`, `EmailField`, `PasswordField` |
156
+ | Dropdown | `SelectField`, `MultiselectField` |
157
+ | Date/time | `DateTimeField` |
158
+ | Boolean | `CheckBox`, `BooleanField`, `RadioField` |
159
+ | Table | `DataTable` |
160
+ | Status chip | `Badge` |
161
+ | Action | `Button`, `IconButton`, `Link` |
162
+ | Dialog | `Modal`, `ActionSheet`, `ModalSheet` |
163
+ | Loading | `Spinner` |
164
+ | Notification | `Toast` (via TerrenoProvider), `Banner` |
165
+ | Error state | `ErrorPage`, `ErrorBoundary` |
166
+ | OAuth login | `SocialLoginButton` |
167
+ | Signature | `SignatureField` |
168
+ | Markdown | `MarkdownView` |
169
+
170
+ See `./references/` for detailed props and patterns.
171
+
172
+ ## vs Expo UI (`@expo/ui`)
173
+
174
+ | | @terreno/ui | @expo/ui |
175
+ |---|-------------|----------|
176
+ | Platform | RN + web (unified) | Native SwiftUI/Compose |
177
+ | Theming | Three-layer design tokens | Platform-native |
178
+ | Forms | Full field library | Limited universal set |
179
+ | Tables | `DataTable` with sort/pagination | List components |
180
+ | Admin | `@terreno/admin-frontend` | None |
181
+ | Terreno integration | First-class | Not integrated |
182
+
183
+ Default to `@terreno/ui` in all Terreno apps.
184
+
185
+ ## Decision Tree
186
+
187
+ ```
188
+ Need UI in a Terreno app?
189
+ |-- Screen structure?
190
+ | \-- Page + Box (references/layout.md)
191
+ |
192
+ |-- User input?
193
+ | \-- references/forms.md
194
+ |
195
+ |-- Show data (table, badge, text)?
196
+ | \-- references/data-display.md
197
+ |
198
+ |-- Loading/error/modal/toast?
199
+ | \-- references/feedback.md
200
+ |
201
+ |-- Colors/spacing/fonts?
202
+ | \-- references/theming.md
203
+ |
204
+ |-- Admin CRUD table/form, sidebar, or custom operator screen?
205
+ | \-- skill building-admin-interfaces
206
+ |
207
+ |-- Component doesn't exist?
208
+ | \-- Compose with Box + existing fields; add to ui/ only if gap is proven
209
+ |
210
+ \-- Changed a component?
211
+ \-- verify-ui-changes (demo app /dev route)
212
+ ```
213
+
214
+ ## Common Mistakes
215
+
216
+ **Wrong: inline styles for spacing**
217
+
218
+ ```tsx
219
+ <View style={{padding: 16, marginBottom: 8}}>
220
+ ```
221
+
222
+ **Right: theme props**
223
+
224
+ ```tsx
225
+ <Box padding={4} marginBottom={2}>
226
+ ```
227
+
228
+ **Wrong: raw Text**
229
+
230
+ ```tsx
231
+ import {Text} from "react-native";
232
+ <Text style={{fontSize: 24}}>Title</Text>
233
+ ```
234
+
235
+ **Right: Heading**
236
+
237
+ ```tsx
238
+ import {Heading} from "@terreno/ui";
239
+ <Heading size="lg">Title</Heading>
240
+ ```
241
+
242
+ **Wrong: StyleSheet.create for one-off styles**
243
+
244
+ ```tsx
245
+ const styles = StyleSheet.create({container: {flex: 1}});
246
+ ```
247
+
248
+ **Right: inline styles or Box props (Terreno convention)**
249
+
250
+ ```tsx
251
+ <Box flex="grow" padding={4}>
252
+ ```
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "Terreno UI"
3
+ short_description: "Choose and compose @terreno/ui components — Box, Page, forms, tables, modals, and theming for cross-platform apps"
4
+ default_prompt: "Use $terreno-ui to pick the right @terreno/ui components and theme props for a layout, form, or feedback pattern."