@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,73 @@
1
+ # Patterns and tools
2
+
3
+ ## Widget registry (`AdminProvider.widgets`)
4
+
5
+ Three buckets. Host entries override built-ins with the same ID.
6
+
7
+ | Bucket | IDs come from | Built-ins include |
8
+ | --- | --- | --- |
9
+ | `home` | `AdminApp.home.slots` | `modelStats` / `modelsGrid`, `scriptRunner`, `versionConfig`, `recentActivity`, `feature-flags-overrides` |
10
+ | `screens` | `customScreens[].name` | `comms`, `documents`, `ai-requests`, `version-config` |
11
+ | `fields` | `admin.fieldOverrides.widget` | `textarea`, `markdown`, consent widgets |
12
+
13
+ Unknown IDs render `MissingWidget` instead of crashing. Register host widgets once
14
+ on `AdminProvider`; do not pass the deprecated `customScreens` prop on
15
+ `AdminModelList`.
16
+
17
+ Plugin backend IDs arrive via `adminContribution()`. First-party React lives in
18
+ `@terreno/admin-frontend`. Third-party plugins ship their own frontend package;
19
+ the host spreads it into `widgets`. See
20
+ [Admin plugin frontend widgets](../../../../../docs/explanation/admin-plugin-frontend.md).
21
+
22
+ ## Model admin metadata that affects UI
23
+
24
+ Set these on `modelRouter({admin: ...})` rather than cloning tables:
25
+
26
+ - `listFields`, `searchFields`, `sortableFields`, `filters`
27
+ - `fieldsets`, `readonlyFields`, `hiddenFields`
28
+ - `group`, `recordTitleField`, `pageSize`
29
+ - `actions` / `bulkPatchAllowlist` for changelist bulk work
30
+
31
+ Reference: [admin-config](../../../../../docs/reference/admin-config.md).
32
+
33
+ ## Platform tools
34
+
35
+ | Tool | Config flag | Typical route |
36
+ | --- | --- | --- |
37
+ | Scripts | `platformTools.scripts` | `{routeBase}/__scripts` |
38
+ | Roles | `platformTools.roles` | `rolesPath` (embedded `/roles` or `/admin/roles`) |
39
+ | Version | `platformTools.version` | `versionConfigPath` |
40
+ | Configuration | `platformTools.configuration` | `configurationPath` |
41
+ | Audit Log / Feature Flags | model `read` | `/{model.name}` via Platform section |
42
+
43
+ ## Data and MCP
44
+
45
+ - Admin String-`_id` collection CRUD: windowed syncdb with `adminBroadcast`.
46
+ - Framework admin RPC: host-bound `adminRequest`; do not add `injectEndpoints`.
47
+ - ObjectId/API-only compatibility CRUD: `useAdminApi` until Terreno 58.
48
+ - After backend route changes: `cd example-frontend && bun run sdk` (non-synced
49
+ only). Do not generate RTK CRUD for synced product collections.
50
+ - MCP: `terreno_install_admin` scaffolds wiring; still follow this skill for
51
+ screens and nav. `terreno_search_docs` / `terreno_get_component_docs` for APIs.
52
+
53
+ ## Copy-paste custom screen
54
+
55
+ ```tsx
56
+ import {AdminScreenPage} from "@terreno/admin-frontend";
57
+ import type {AdminScreenWidgetProps} from "@terreno/admin-frontend";
58
+ import {Box, Text} from "@terreno/ui";
59
+ import React from "react";
60
+
61
+ export const ExampleOpsScreen: React.FC<AdminScreenWidgetProps> = ({routeBase}) => {
62
+ return (
63
+ <AdminScreenPage backHref={routeBase} color="transparent" padding={0} title="Ops">
64
+ <Box padding={4}>
65
+ <Text>Operator content</Text>
66
+ </Box>
67
+ </AdminScreenPage>
68
+ );
69
+ };
70
+ ```
71
+
72
+ Match `name: "example-ops"` on `AdminApp.customScreens` and
73
+ `widgets={{screens: {"example-ops": ExampleOpsScreen}}}`.
@@ -0,0 +1,50 @@
1
+ # Screens and routes
2
+
3
+ Canonical steps: [Build admin screens](../../../../../docs/how-to/build-admin-screens.md).
4
+
5
+ ## Embedded host (`example-frontend/app/admin`)
6
+
7
+ Wrap **once** in `_layout.tsx`: `AdminProvider` then `AdminShellLayout` then `Stack`.
8
+ Child routes must not wrap another shell.
9
+
10
+ | File | Component |
11
+ | --- | --- |
12
+ | `index.tsx` | `AdminHome` |
13
+ | `[model]/index.tsx` | `AdminScreenRouter` with `name={model}` |
14
+ | `[model]/create.tsx` | `AdminModelForm` `mode="create"` |
15
+ | `[model]/[id].tsx` | `AdminModelForm` |
16
+ | Extra folders (`comms/`, `consent-forms/`, `configuration.tsx`) | Only when generic CRUD is not enough |
17
+
18
+ Create is **`create`**, not `new`.
19
+
20
+ ## Standalone host (`admin-spa/app`)
21
+
22
+ Root `_layout.tsx` is providers + `AdminGate` only. **Each screen** wraps
23
+ `AdminShellLayout` because there is no shared admin stack layout. `routeBase=""`.
24
+ `apiBase` comes from `app-config.json` (`adminApiBasePath`, default `/admin`).
25
+
26
+ ## `AdminScreenRouter`
27
+
28
+ Resolves one URL segment:
29
+
30
+ 1. `__scripts` → `AdminScriptList`
31
+ 2. Config model `name` → `AdminModelTable`
32
+ 3. `widgets.screens[name]` → custom screen widget
33
+ 4. Config `customScreens` without a widget → missing-widget placeholder
34
+ 5. Else not-found
35
+
36
+ Model **names** (Mongoose model name, or a unique suffix like `Food-archived-foods`)
37
+ are the URL segment, not `routePath` (`/admin/users`).
38
+
39
+ ## Dedicated routes vs widgets
40
+
41
+ Prefer a `widgets.screens` component so `[model]/index` can render it. Add a
42
+ dedicated Expo file when:
43
+
44
+ - The path has more segments than `/admin/:name` (comms message `/comms/:id`).
45
+ - The screen must not share the generic create/edit form (consent editor).
46
+ - The host is admin-spa and you want a static export path (optional; the
47
+ `[model]` fallback already covers single-segment custom screens).
48
+
49
+ Expo file routes win over `[model]` when the folder name is literal
50
+ (`comms/index.tsx` beats `[model]/index.tsx` for `/admin/comms`).
@@ -0,0 +1,280 @@
1
+ ---
2
+ name: building-terreno-apps
3
+ description: >-
4
+ Complete guide for building Terreno full-stack Expo apps with @terreno/ui,
5
+ Expo Router, and @terreno/rtk. Covers layout, forms, navigation, theming,
6
+ screen patterns, and when to use Expo Go vs custom builds.
7
+ ---
8
+ # Building Terreno Apps
9
+
10
+ Use this skill when building or refactoring screens in a Terreno Expo app (`example-frontend`, `admin-spa`, or a consumer app using `@terreno/ui` + `@terreno/rtk`).
11
+
12
+ **Related skills:** `terreno-ui` (component selection), `terreno-data-fetching` (API hooks), `verify-ui-changes` (mandatory UI verification), `generate-sdk` (after backend changes), `building-admin-interfaces` (admin shell, custom screens, sidebar).
13
+
14
+ ## Documentation
15
+
16
+ 1. Read `docs/tutorials/`, `docs/explanation/`, and app-structure how-to pages before changing screens or navigation.
17
+ 2. Implement against that design.
18
+ 3. Update those pages in the same slice with `update-docs`.
19
+ 4. Ship without matching docs is a failed slice.
20
+
21
+ ## References
22
+
23
+ Consult these resources as needed:
24
+
25
+ ```
26
+ references/
27
+ route-structure.md Expo Router conventions adapted for Terreno apps
28
+ screen-patterns.md List, detail, form, and admin screen templates
29
+ navigation.md Stacks, tabs, modals, and auth-gated routing
30
+ ```
31
+
32
+ Admin CRUD, sidebar, custom screens, and `apiBase`/`routeBase`: use skill
33
+ `building-admin-interfaces` and `docs/how-to/build-admin-screens.md`.
34
+
35
+ ## Running the App
36
+
37
+ **CRITICAL: Always try Expo Go first before creating custom builds.**
38
+
39
+ 1. Start with `bun run frontend:web` (or `bun run start` in the app package) and test in Expo Go or the web dev server.
40
+ 2. For full-stack features, start the backend: `bun run backend:dev` with MongoDB and auth env vars (see `AGENTS.md`).
41
+ 3. Only create custom builds (`npx expo run:ios/android`, `eas build`) when you need local Expo modules, Apple targets, third-party native modules not in Expo Go, or native config that cannot be expressed in `app.json`.
42
+
43
+ ## Provider Stack
44
+
45
+ Every Terreno app root layout should wrap children in this order (outer → inner):
46
+
47
+ ```tsx
48
+ import {Provider} from "react-redux";
49
+ import {PersistGate} from "redux-persist/integration/react";
50
+ import {TerrenoProvider} from "@terreno/ui";
51
+ // Import from your app's store bootstrap module (see example-frontend/store/index.ts)
52
+ import store, {persistor} from "../store/index.ts";
53
+
54
+ const RootLayout: React.FC = () => {
55
+ return (
56
+ <Provider store={store}>
57
+ <PersistGate loading={null} persistor={persistor}>
58
+ <TerrenoProvider>
59
+ <Stack />
60
+ </TerrenoProvider>
61
+ </PersistGate>
62
+ </Provider>
63
+ );
64
+ };
65
+ ```
66
+
67
+ See `example-frontend/app/_layout.tsx` for the full pattern including auth routing, sockets, and feature flags.
68
+
69
+ ## Code Style
70
+
71
+ - Use **functional components** with `React.FC` and explicit return types.
72
+ - Import hooks directly: `import {useEffect, useCallback} from "react"` — not `React.useEffect`.
73
+ - Wrap event handlers in `useCallback`. Add a comment above every `useEffect`.
74
+ - Use **Luxon** for all date/time operations — never `Date` or dayjs.
75
+ - Use **camelCase** directories (e.g. `components/todoList/`).
76
+ - Use **kebab-case** for route file names when the Expo skill convention applies (e.g. `comment-card.tsx`).
77
+ - **No barrel imports** — import concrete module files, not directory `index` re-exports. Cross-package `@terreno/*` roots are allowed.
78
+ - Never co-locate components, types, or utilities in the `app/` directory.
79
+
80
+ ## Layout — Use @terreno/ui, Not Raw Primitives
81
+
82
+ | Instead of | Use |
83
+ |------------|-----|
84
+ | `View` | `Box` |
85
+ | React Native `Text` | `Text` or `Heading` from `@terreno/ui` |
86
+ | `ScrollView` as page wrapper | `Page` with `scroll={true}` |
87
+ | `StyleSheet.create` | Theme props (`padding`, `gap`, `color`) or inline styles |
88
+ | `SafeAreaView` | `Page` handles safe areas; or `contentInsetAdjustmentBehavior` on lists |
89
+ | Inline hex colors | Theme surface/text colors via props (`color="primary"`) |
90
+
91
+ ### Page layout
92
+
93
+ ```tsx
94
+ import {Box, Button, Heading, Page, Text} from "@terreno/ui";
95
+
96
+ const TodosScreen: React.FC = () => {
97
+ return (
98
+ <Page title="Todos" scroll loading={isLoading}>
99
+ <Box gap={3} padding={4}>
100
+ <Heading size="lg">My Todos</Heading>
101
+ <Text color="secondaryLight">Track your tasks</Text>
102
+ <Button text="Add Todo" onClick={handleAdd} variant="primary" />
103
+ </Box>
104
+ </Page>
105
+ );
106
+ };
107
+ ```
108
+
109
+ ### Box layout
110
+
111
+ ```tsx
112
+ <Box direction="row" padding={4} gap={2} alignItems="center">
113
+ <Text flex="grow">Content</Text>
114
+ <Button text="Action" onClick={handleAction} />
115
+ </Box>
116
+ ```
117
+
118
+ Use the spacing scale via props (`padding={4}` → 16px). Prefer `gap` over margin.
119
+
120
+ ## Data on Screens
121
+
122
+ Never call `fetch` or `axios` in screen components. Use generated RTK Query hooks:
123
+
124
+ ```tsx
125
+ import {useGetTodosQuery, usePostTodosMutation} from "@/store/sdk";
126
+
127
+ const {data, isLoading, error, refetch} = useGetTodosQuery({});
128
+ const [createTodo, {isLoading: isCreating}] = usePostTodosMutation();
129
+ ```
130
+
131
+ See `terreno-data-fetching` for auth, caching, and error handling.
132
+
133
+ ## Forms
134
+
135
+ Use `@terreno/ui` form fields with local state and RTK mutations:
136
+
137
+ ```tsx
138
+ import {Button, TextField} from "@terreno/ui";
139
+
140
+ const [title, setTitle] = useState<string>("");
141
+ const [titleError, setTitleError] = useState<string | undefined>();
142
+
143
+ const handleSubmit = useCallback(async (): Promise<void> => {
144
+ if (!title.trim()) {
145
+ setTitleError("Title is required");
146
+ return;
147
+ }
148
+ try {
149
+ await createTodo({title: title.trim(), completed: false}).unwrap();
150
+ setTitle("");
151
+ } catch (err) {
152
+ console.error("Failed to create todo", err);
153
+ }
154
+ }, [createTodo, title]);
155
+
156
+ <TextField
157
+ title="Title"
158
+ value={title}
159
+ onChange={setTitle}
160
+ errorText={titleError}
161
+ />
162
+ <Button text="Create" onClick={handleSubmit} loading={isCreating} />
163
+ ```
164
+
165
+ ## Navigation
166
+
167
+ - Use Expo Router file-based routing in `app/`.
168
+ - Gate routes on auth state in root `_layout.tsx` (redirect to `/login` when logged out).
169
+ - Use `Stack` from `expo-router` for stack navigation; set titles via `Stack.Screen options`.
170
+ - For admin screens, use `AdminShellLayout` from `@terreno/admin-frontend`.
171
+
172
+ See `./references/navigation.md` and `./references/route-structure.md`.
173
+
174
+ ## Loading, Error, and Empty States
175
+
176
+ Every data-driven screen must handle all three:
177
+
178
+ ```tsx
179
+ if (isLoading) {
180
+ return <Page title="Todos"><Spinner /></Page>;
181
+ }
182
+ if (error) {
183
+ return (
184
+ <Page title="Todos">
185
+ <Text color="error">Failed to load todos</Text>
186
+ <Button text="Retry" onClick={refetch} />
187
+ </Page>
188
+ );
189
+ }
190
+ if (!data?.data?.length) {
191
+ return (
192
+ <Page title="Todos">
193
+ <Text>No todos yet</Text>
194
+ <Button text="Create your first todo" onClick={handleAdd} />
195
+ </Page>
196
+ );
197
+ }
198
+ ```
199
+
200
+ ## Theming
201
+
202
+ - Wrap the app in `TerrenoProvider` (done in root layout).
203
+ - Use theme props on components: `color="primary"`, `border="default"`, `rounding="md"`.
204
+ - Access theme programmatically with `useTheme()` when needed.
205
+ - Do not hardcode hex colors when a theme token exists.
206
+
207
+ ## Verification
208
+
209
+ Any UI change **must** follow the `verify-ui-changes` skill before opening or updating a PR. Launch the correct app, log in with seeded credentials, exercise the feature, and attach screenshots/videos to the PR.
210
+
211
+ ## Decision Tree
212
+
213
+ ```
214
+ Building or changing a screen?
215
+ |-- Need layout/structure?
216
+ | \-- Page for full screens, Box for sections, Card for grouped content
217
+ |
218
+ |-- Need data from API?
219
+ | \-- terreno-data-fetching (RTK Query hooks, never raw fetch)
220
+ |
221
+ |-- Need a form?
222
+ | \-- @terreno/ui TextField/SelectField/DateTimeField + local state + mutation
223
+ |
224
+ |-- Need navigation?
225
+ | \-- references/navigation.md (Expo Router + auth gating)
226
+ |
227
+ |-- Need admin CRUD, sidebar, or a custom operator screen?
228
+ | \-- building-admin-interfaces + AdminShellLayout + AdminScreenRouter
229
+ |
230
+ |-- Backend API doesn't exist yet?
231
+ | \-- terreno-backend-api (modelRouter on Express, then generate-sdk)
232
+ |
233
+ \-- Done coding?
234
+ \-- verify-ui-changes (mandatory)
235
+ ```
236
+
237
+ ## Common Mistakes
238
+
239
+ **Wrong: Raw View/Text**
240
+
241
+ ```tsx
242
+ <View style={{padding: 16}}>
243
+ <Text style={{fontSize: 18}}>Hello</Text>
244
+ </View>
245
+ ```
246
+
247
+ **Right: @terreno/ui with theme props**
248
+
249
+ ```tsx
250
+ <Box padding={4}>
251
+ <Heading size="lg">Hello</Heading>
252
+ </Box>
253
+ ```
254
+
255
+ **Wrong: fetch in a screen**
256
+
257
+ ```tsx
258
+ const [todos, setTodos] = useState([]);
259
+ useEffect(() => {
260
+ fetch("/todos").then((r) => r.json()).then(setTodos);
261
+ }, []);
262
+ ```
263
+
264
+ **Right: Generated RTK Query hook**
265
+
266
+ ```tsx
267
+ const {data} = useGetTodosQuery({});
268
+ ```
269
+
270
+ **Wrong: Editing openApiSdk.ts by hand**
271
+
272
+ ```tsx
273
+ // Manually adding a hook to store/openApiSdk.ts
274
+ ```
275
+
276
+ **Right: Regenerate SDK after backend changes**
277
+
278
+ ```bash
279
+ cd example-frontend && bun run sdk
280
+ ```
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "Building Terreno Apps"
3
+ short_description: "Build Terreno Expo apps with @terreno/ui layout, RTK Query data, Expo Router navigation, and mandatory UI verification"
4
+ default_prompt: "Use $building-terreno-apps to build or refactor a Terreno screen with @terreno/ui components, generated RTK Query hooks, and Expo Router navigation."
@@ -0,0 +1,102 @@
1
+ # Navigation — Terreno Apps
2
+
3
+ Terreno apps use Expo Router. Use Expo Router's public documentation for low-level APIs
4
+ such as `Link`, `Stack.Screen`, modals, and form sheets. This reference covers
5
+ Terreno-specific navigation patterns.
6
+
7
+ ## Auth flow
8
+
9
+ 1. Root `_layout.tsx` checks `useSelectCurrentUserId()` from `@terreno/rtk`.
10
+ 2. Unauthenticated users redirect to `/login`.
11
+ 3. Login screen calls `useEmailLoginMutation()` (from generated SDK via `generateAuthSlice`).
12
+ 4. On success, tokens are stored automatically and `setUserId` is dispatched.
13
+ 5. Root layout redirects to `/(tabs)` or the intended deep link.
14
+
15
+ ```tsx
16
+ import {useEmailLoginMutation} from "@/store/sdk";
17
+ import {Button, TextField, Page, Box} from "@terreno/ui";
18
+
19
+ const LoginScreen: React.FC = () => {
20
+ const [email, setEmail] = useState<string>("");
21
+ const [password, setPassword] = useState<string>("");
22
+ const [login, {isLoading, error}] = useEmailLoginMutation();
23
+
24
+ const handleLogin = useCallback(async (): Promise<void> => {
25
+ try {
26
+ await login({email, password}).unwrap();
27
+ } catch (err) {
28
+ console.error("Login failed", err);
29
+ }
30
+ }, [email, login, password]);
31
+
32
+ return (
33
+ <Page title="Login" scroll>
34
+ <Box gap={3} padding={4}>
35
+ <TextField title="Email" value={email} onChange={setEmail} type="email" />
36
+ <TextField title="Password" value={password} onChange={setPassword} type="password" />
37
+ <Button text="Sign in" onClick={handleLogin} loading={isLoading} fullWidth />
38
+ </Box>
39
+ </Page>
40
+ );
41
+ };
42
+ ```
43
+
44
+ ## Tabs
45
+
46
+ Use Expo Router tab groups. Set tab icons via `@expo/vector-icons` or FontAwesome through `@terreno/ui` `Icon`:
47
+
48
+ ```tsx
49
+ import {Tabs} from "expo-router";
50
+ import FontAwesome from "@expo/vector-icons/FontAwesome";
51
+
52
+ const TabLayout: React.FC = () => {
53
+ return (
54
+ <Tabs screenOptions={{headerShown: false}}>
55
+ <Tabs.Screen
56
+ name="index"
57
+ options={{
58
+ title: "Todos",
59
+ tabBarIcon: ({color}) => <FontAwesome name="list" size={24} color={color} />,
60
+ }}
61
+ />
62
+ <Tabs.Screen
63
+ name="profile"
64
+ options={{
65
+ title: "Profile",
66
+ tabBarIcon: ({color}) => <FontAwesome name="user" size={24} color={color} />,
67
+ }}
68
+ />
69
+ </Tabs>
70
+ );
71
+ };
72
+ ```
73
+
74
+ ## Modals
75
+
76
+ Use Expo Router modal presentation for simple overlays:
77
+
78
+ ```tsx
79
+ // app/_layout.tsx
80
+ <Stack>
81
+ <Stack.Screen name="(tabs)" options={{headerShown: false}} />
82
+ <Stack.Screen name="modal" options={{presentation: "modal"}} />
83
+ </Stack>
84
+ ```
85
+
86
+ For confirmation dialogs and action sheets, prefer `@terreno/ui` `Modal` or `ActionSheet` — they render correctly on web and native.
87
+
88
+ ## Deep linking
89
+
90
+ Configure `scheme` in `app.json`. Terreno RTK resolves API base URLs from `EXPO_PUBLIC_API_URL` or `expo.extra.BASE_URL` — keep deep link schemes separate from API URLs.
91
+
92
+ ## Programmatic navigation
93
+
94
+ ```tsx
95
+ import {useRouter} from "expo-router";
96
+
97
+ const router = useRouter();
98
+ router.push(`/admin/${modelName}`);
99
+ router.back();
100
+ ```
101
+
102
+ Use `Link` from `expo-router` for declarative navigation when possible.
@@ -0,0 +1,91 @@
1
+ # Route Structure — Terreno Apps
2
+
3
+ Terreno apps use Expo Router with the same file conventions as standard Expo apps. Use
4
+ Expo Router's public documentation for dynamic routes, groups, and query parameters.
5
+
6
+ ## Terreno-specific conventions
7
+
8
+ ### Directory layout
9
+
10
+ ```
11
+ app/
12
+ _layout.tsx # Provider stack, auth gating, TerrenoProvider
13
+ login.tsx # Auth screen
14
+ (tabs)/
15
+ _layout.tsx # Tab navigator
16
+ index.tsx # Main screen (e.g. todos list)
17
+ profile.tsx
18
+ mail/
19
+ _layout.tsx # ListDetailLayout: list pane + Stack; unstable_settings.initialRouteName = "index"
20
+ index.tsx # ListDetailIndex: the list on a phone, the empty state on a tablet
21
+ [id].tsx # Detail screen; the open item lives in the URL
22
+ admin/
23
+ _layout.tsx # AdminShellLayout wrapper
24
+ index.tsx # AdminHome
25
+ [model]/
26
+ index.tsx # AdminScreenRouter (table or custom screen)
27
+ create.tsx # AdminModelForm (create)
28
+ [id].tsx # AdminModelForm (edit)
29
+ components/ # Screen components (NOT in app/)
30
+ hooks/ # Custom hooks
31
+ store/
32
+ index.ts # Redux store + generateAuthSlice
33
+ sdk.ts # Hand-maintained SDK extensions
34
+ openApiSdk.ts # Generated — never edit manually
35
+ ```
36
+
37
+ ### Rules
38
+
39
+ - Routes belong in `app/`; everything else lives outside.
40
+ - Never co-locate `components/`, `utils/`, or `types/` inside `app/`.
41
+ - Ensure a route matches `/` so the app is never blank.
42
+ - Use `(group)` routes to organize without affecting URLs.
43
+ - Use `_layout.tsx` for stacks and provider wrappers.
44
+ - Build list/detail screens (mail, inbox, records) as a route group with `ListDetailLayout` as its
45
+ `_layout.tsx`, `ListDetailIndex` as `index.tsx`, and the detail as `[id].tsx`. Phones push the
46
+ detail; tablets show the list and detail side by side.
47
+
48
+ ### Auth-gated routing
49
+
50
+ ```tsx
51
+ import {Redirect, Stack, useSegments} from "expo-router";
52
+ import {useSelectCurrentUserId} from "@terreno/rtk";
53
+
54
+ const RootLayoutNav: React.FC = () => {
55
+ const userId = useSelectCurrentUserId();
56
+ const segments = useSegments();
57
+
58
+ if (!userId && segments[0] !== "login") {
59
+ return <Redirect href="/login" />;
60
+ }
61
+ if (userId && segments[0] === "login") {
62
+ return <Redirect href="/(tabs)" />;
63
+ }
64
+
65
+ return <Stack screenOptions={{headerShown: false}} />;
66
+ };
67
+ ```
68
+
69
+ ### Admin routes
70
+
71
+ Wrap admin routes once in `app/admin/_layout.tsx`:
72
+
73
+ ```tsx
74
+ import {AdminShellLayout} from "@terreno/admin-frontend";
75
+ import {Stack} from "expo-router";
76
+ import {terrenoApi} from "@/store/sdk";
77
+
78
+ export default function AdminLayout(): React.ReactElement {
79
+ return (
80
+ <AdminShellLayout
81
+ api={terrenoApi}
82
+ apiBase="/admin"
83
+ configurationPath="/admin/configuration"
84
+ routeBase="/admin"
85
+ versionConfigPath="/version-config"
86
+ >
87
+ <Stack screenOptions={{headerShown: false, contentStyle: {flex: 1}}} />
88
+ </AdminShellLayout>
89
+ );
90
+ }
91
+ ```