@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.
- package/.claude-plugin/plugin.json +32 -0
- package/CHANGELOG.md +2155 -0
- package/LICENSE +21 -0
- package/README.md +57 -2
- package/agents/pre-commit.md +65 -0
- package/agents/ui-verifier.md +93 -0
- package/package.json +32 -4
- package/references/async-review-bots.md +76 -0
- package/references/documentation-contract.md +61 -0
- package/references/execution-state.schema.json +77 -0
- package/references/github-attention-contract.md +165 -0
- package/references/independent-review.md +31 -0
- package/references/lifecycle-contract.md +211 -0
- package/references/loop-engineering.md +125 -0
- package/references/pick-roast-loop.md +112 -0
- package/references/pr-deployments.md +69 -0
- package/references/product-ci.md +164 -0
- package/references/push-ownership.md +91 -0
- package/references/stage-result.schema.json +82 -0
- package/references/subagent-briefing.md +43 -0
- package/references/testing-plan.md +163 -0
- package/references/user-guide.md +171 -0
- package/skills/1-grow/SKILL.md +145 -0
- package/skills/1-grow/references/grilling.md +243 -0
- package/skills/2-pick/SKILL.md +170 -0
- package/skills/2-pick/references/mocking.md +34 -0
- package/skills/2-pick/references/testing.md +43 -0
- package/skills/3-roast/SKILL.md +168 -0
- package/skills/4-brew/SKILL.md +201 -0
- package/skills/5-taste/SKILL.md +311 -0
- package/skills/ai-prompt-governance/SKILL.md +100 -0
- package/skills/attach-pr-media/SKILL.md +89 -0
- package/skills/attach-pr-media/scripts/upload-pr-media.sh +151 -0
- package/skills/backend-test-env/SKILL.md +64 -0
- package/skills/backfill-regression/SKILL.md +101 -0
- package/skills/backfill-user-guide/SKILL.md +96 -0
- package/skills/bootstrap-mcp-server/SKILL.md +244 -0
- package/skills/building-admin-interfaces/SKILL.md +83 -0
- package/skills/building-admin-interfaces/agents/openai.yaml +4 -0
- package/skills/building-admin-interfaces/references/nav-and-shell.md +42 -0
- package/skills/building-admin-interfaces/references/patterns-and-tools.md +73 -0
- package/skills/building-admin-interfaces/references/screens-and-routes.md +50 -0
- package/skills/building-terreno-apps/SKILL.md +280 -0
- package/skills/building-terreno-apps/agents/openai.yaml +4 -0
- package/skills/building-terreno-apps/references/navigation.md +102 -0
- package/skills/building-terreno-apps/references/route-structure.md +91 -0
- package/skills/building-terreno-apps/references/screen-patterns.md +191 -0
- package/skills/debug-syncdb-with-mcp/SKILL.md +75 -0
- package/skills/deploy-gcp/SKILL.md +73 -0
- package/skills/deploy-gcp/references/troubleshooting.md +29 -0
- package/skills/generate-release-announcements/SKILL.md +145 -0
- package/skills/generate-sdk/SKILL.md +156 -0
- package/skills/mongoose-schema-safety/SKILL.md +164 -0
- package/skills/pick-roast-loop/SKILL.md +152 -0
- package/skills/planning-loop/SKILL.md +161 -0
- package/skills/prd/SKILL.md +138 -0
- package/skills/set-up-social-login/SKILL.md +76 -0
- package/skills/store-assets/SKILL.md +221 -0
- package/skills/store-assets/examples/screenshots.yaml +31 -0
- package/skills/store-assets/examples/storeAssets.config.json +44 -0
- package/skills/store-assets/scripts/banner.ts +165 -0
- package/skills/store-assets/scripts/config.ts +255 -0
- package/skills/store-assets/scripts/devClient.ts +635 -0
- package/skills/store-assets/scripts/devices.ts +286 -0
- package/skills/store-assets/scripts/icon.ts +81 -0
- package/skills/store-assets/scripts/maestro/goHome.yaml +21 -0
- package/skills/store-assets/scripts/maestro/login.yaml +27 -0
- package/skills/store-assets/scripts/maestro/openApp.yaml +26 -0
- package/skills/store-assets/scripts/screenshots.ts +848 -0
- package/skills/taste-sweep/SKILL.md +172 -0
- package/skills/terreno-backend-api/SKILL.md +355 -0
- package/skills/terreno-backend-api/agents/openai.yaml +4 -0
- package/skills/terreno-backend-api/references/custom-routes.md +132 -0
- package/skills/terreno-backend-api/references/model-router.md +116 -0
- package/skills/terreno-backend-api/references/server-setup.md +116 -0
- package/skills/terreno-backend-tests/SKILL.md +176 -0
- package/skills/terreno-data-fetching/SKILL.md +276 -0
- package/skills/terreno-data-fetching/agents/openai.yaml +4 -0
- package/skills/terreno-data-fetching/references/auth-and-tokens.md +93 -0
- package/skills/terreno-data-fetching/references/realtime.md +45 -0
- package/skills/terreno-data-fetching/references/sdk-customization.md +84 -0
- package/skills/terreno-frontend-tests/SKILL.md +190 -0
- package/skills/terreno-mobile-tests/SKILL.md +127 -0
- package/skills/terreno-test-setup/SKILL.md +81 -0
- package/skills/terreno-test-setup/references/backend.md +131 -0
- package/skills/terreno-test-setup/references/frontend.md +169 -0
- package/skills/terreno-test-setup/references/mobile.md +134 -0
- package/skills/terreno-ui/SKILL.md +252 -0
- package/skills/terreno-ui/agents/openai.yaml +4 -0
- package/skills/terreno-ui/references/data-display.md +85 -0
- package/skills/terreno-ui/references/feedback.md +122 -0
- package/skills/terreno-ui/references/forms.md +119 -0
- package/skills/terreno-ui/references/layout.md +124 -0
- package/skills/terreno-ui/references/theming.md +87 -0
- package/skills/update-docs/SKILL.md +82 -0
- package/skills/upgrading-terreno/SKILL.md +86 -0
- package/skills/upgrading-terreno/references/ordering.md +40 -0
- package/skills/verify-ui-changes/SKILL.md +605 -0
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# Screen Patterns — Terreno Apps
|
|
2
|
+
|
|
3
|
+
## List screen
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
import {Box, Button, Card, Heading, Page, Spinner, Text} from "@terreno/ui";
|
|
7
|
+
import {useCallback} from "react";
|
|
8
|
+
import {useGetTodosQuery} from "@/store/sdk";
|
|
9
|
+
|
|
10
|
+
const TodoListScreen: React.FC = () => {
|
|
11
|
+
const {data, isLoading, error, refetch} = useGetTodosQuery({});
|
|
12
|
+
|
|
13
|
+
const handleCreate = useCallback((): void => {
|
|
14
|
+
// navigate or open modal
|
|
15
|
+
}, []);
|
|
16
|
+
|
|
17
|
+
if (isLoading) {
|
|
18
|
+
return <Page title="Todos"><Spinner /></Page>;
|
|
19
|
+
}
|
|
20
|
+
if (error) {
|
|
21
|
+
return (
|
|
22
|
+
<Page title="Todos">
|
|
23
|
+
<Text color="error">Failed to load</Text>
|
|
24
|
+
<Button text="Retry" onClick={refetch} />
|
|
25
|
+
</Page>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const items = data?.data ?? [];
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<Page title="Todos" scroll>
|
|
33
|
+
<Box gap={3} padding={4}>
|
|
34
|
+
<Box direction="row" justifyContent="between" alignItems="center">
|
|
35
|
+
<Heading size="lg">Todos</Heading>
|
|
36
|
+
<Button text="Add" onClick={handleCreate} iconName="plus" />
|
|
37
|
+
</Box>
|
|
38
|
+
{items.length === 0 ? (
|
|
39
|
+
<Text color="secondaryLight">No todos yet</Text>
|
|
40
|
+
) : (
|
|
41
|
+
items.map((todo) => (
|
|
42
|
+
<Card key={todo.id} padding={3}>
|
|
43
|
+
<Text>{todo.title}</Text>
|
|
44
|
+
</Card>
|
|
45
|
+
))
|
|
46
|
+
)}
|
|
47
|
+
</Box>
|
|
48
|
+
</Page>
|
|
49
|
+
);
|
|
50
|
+
};
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Profile screen
|
|
54
|
+
|
|
55
|
+
Use `TapToEdit` for per-field profile edits. Save each field with `usePatchMeMutation` — do not collect every field behind one Save button.
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
import {Box, Card, Page, TapToEdit} from "@terreno/ui";
|
|
59
|
+
import {useCallback, useEffect, useState} from "react";
|
|
60
|
+
import {useGetMeQuery, usePatchMeMutation} from "@/store/sdk";
|
|
61
|
+
|
|
62
|
+
const ProfileScreen: React.FC = () => {
|
|
63
|
+
const {data: profile} = useGetMeQuery();
|
|
64
|
+
const [updateProfile] = usePatchMeMutation();
|
|
65
|
+
const [name, setName] = useState<string>("");
|
|
66
|
+
|
|
67
|
+
// Seed only this field from the server so saving another TapToEdit does not wipe the draft.
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
if (!profile) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
setName(profile.name || "");
|
|
73
|
+
}, [profile?.name]);
|
|
74
|
+
|
|
75
|
+
const handleSaveName = useCallback(
|
|
76
|
+
async (value: string): Promise<void> => {
|
|
77
|
+
await updateProfile({name: value}).unwrap();
|
|
78
|
+
},
|
|
79
|
+
[updateProfile]
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<Page title="Profile" scroll>
|
|
84
|
+
<Box gap={4} padding={4}>
|
|
85
|
+
<Card>
|
|
86
|
+
<TapToEdit
|
|
87
|
+
onSave={handleSaveName}
|
|
88
|
+
setValue={setName}
|
|
89
|
+
title="Name"
|
|
90
|
+
type="text"
|
|
91
|
+
value={name}
|
|
92
|
+
/>
|
|
93
|
+
</Card>
|
|
94
|
+
</Box>
|
|
95
|
+
</Page>
|
|
96
|
+
);
|
|
97
|
+
};
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Form screen
|
|
101
|
+
|
|
102
|
+
```tsx
|
|
103
|
+
import {Box, Button, Page, TextField} from "@terreno/ui";
|
|
104
|
+
import {useCallback, useState} from "react";
|
|
105
|
+
import {useRouter} from "expo-router";
|
|
106
|
+
import {usePostTodosMutation} from "@/store/sdk";
|
|
107
|
+
|
|
108
|
+
const CreateTodoScreen: React.FC = () => {
|
|
109
|
+
const router = useRouter();
|
|
110
|
+
const [title, setTitle] = useState<string>("");
|
|
111
|
+
const [titleError, setTitleError] = useState<string | undefined>();
|
|
112
|
+
const [createTodo, {isLoading}] = usePostTodosMutation();
|
|
113
|
+
|
|
114
|
+
const handleSubmit = useCallback(async (): Promise<void> => {
|
|
115
|
+
if (!title.trim()) {
|
|
116
|
+
setTitleError("Title is required");
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
try {
|
|
120
|
+
await createTodo({title: title.trim(), completed: false}).unwrap();
|
|
121
|
+
router.back();
|
|
122
|
+
} catch (err) {
|
|
123
|
+
console.error("Create failed", err);
|
|
124
|
+
}
|
|
125
|
+
}, [createTodo, router, title]);
|
|
126
|
+
|
|
127
|
+
return (
|
|
128
|
+
<Page title="New Todo" scroll>
|
|
129
|
+
<Box gap={4} padding={4}>
|
|
130
|
+
<TextField
|
|
131
|
+
title="Title"
|
|
132
|
+
value={title}
|
|
133
|
+
onChange={setTitle}
|
|
134
|
+
errorText={titleError}
|
|
135
|
+
/>
|
|
136
|
+
<Button text="Save" onClick={handleSubmit} loading={isLoading} fullWidth />
|
|
137
|
+
</Box>
|
|
138
|
+
</Page>
|
|
139
|
+
);
|
|
140
|
+
};
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Detail screen with params
|
|
144
|
+
|
|
145
|
+
```tsx
|
|
146
|
+
import {Page, Spinner, Text} from "@terreno/ui";
|
|
147
|
+
import {useLocalSearchParams} from "expo-router";
|
|
148
|
+
import {useGetTodosByIdQuery} from "@/store/sdk";
|
|
149
|
+
|
|
150
|
+
const TodoDetailScreen: React.FC = () => {
|
|
151
|
+
const {id} = useLocalSearchParams<{id: string}>();
|
|
152
|
+
const {data, isLoading, error} = useGetTodosByIdQuery({id: id ?? ""}, {skip: !id});
|
|
153
|
+
|
|
154
|
+
if (isLoading) {
|
|
155
|
+
return <Page title="Todo"><Spinner /></Page>;
|
|
156
|
+
}
|
|
157
|
+
if (error || !data) {
|
|
158
|
+
return <Page title="Todo"><Text color="error">Not found</Text></Page>;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return (
|
|
162
|
+
<Page title={data.title} scroll>
|
|
163
|
+
<Text>{data.title}</Text>
|
|
164
|
+
</Page>
|
|
165
|
+
);
|
|
166
|
+
};
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Admin table screen
|
|
170
|
+
|
|
171
|
+
Prefer `@terreno/admin-frontend` over hand-rolled tables. Use `AdminScreenRouter` so
|
|
172
|
+
custom screens and models share `[model]/index`. Full nav and custom-screen rules:
|
|
173
|
+
skill `building-admin-interfaces`.
|
|
174
|
+
|
|
175
|
+
```tsx
|
|
176
|
+
import {AdminScreenRouter} from "@terreno/admin-frontend";
|
|
177
|
+
import {useLocalSearchParams} from "expo-router";
|
|
178
|
+
import {terrenoApi} from "@/store/sdk";
|
|
179
|
+
|
|
180
|
+
const AdminModelScreen: React.FC = () => {
|
|
181
|
+
const {model} = useLocalSearchParams<{model: string}>();
|
|
182
|
+
return (
|
|
183
|
+
<AdminScreenRouter
|
|
184
|
+
api={terrenoApi}
|
|
185
|
+
apiBase="/admin"
|
|
186
|
+
name={model ?? ""}
|
|
187
|
+
routeBase="/admin"
|
|
188
|
+
/>
|
|
189
|
+
);
|
|
190
|
+
};
|
|
191
|
+
```
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: debug-syncdb-with-mcp
|
|
3
|
+
description: >-
|
|
4
|
+
Invoke when debugging SyncDB local state, stale data, outbox stalls,
|
|
5
|
+
conflicts, cursor/stream drift, reconciliation, or offline behavior in a
|
|
6
|
+
running Terreno app. Uses terreno-mcp-local to inspect, snapshot, compare,
|
|
7
|
+
mutate, flush, merge, and repair SyncDB state.
|
|
8
|
+
---
|
|
9
|
+
# Debug SyncDB with MCP
|
|
10
|
+
|
|
11
|
+
Use the fixed SyncDB MCP tools instead of arbitrary `evaluate` snippets.
|
|
12
|
+
|
|
13
|
+
## 1. Establish the live client
|
|
14
|
+
|
|
15
|
+
1. Confirm the app creates its SyncDB client with `debug: true` in development.
|
|
16
|
+
2. Start the app and authenticate the affected user.
|
|
17
|
+
3. Call `get_syncdb_state` without filters.
|
|
18
|
+
|
|
19
|
+
Continue only when the response names a registered client and includes `status`,
|
|
20
|
+
`collections`, `outbox`, `conflicts`, `cursors`, `knownStreams`, `needsRepair`,
|
|
21
|
+
and `debug`.
|
|
22
|
+
|
|
23
|
+
## 2. Capture the baseline
|
|
24
|
+
|
|
25
|
+
Call `syncdb_snapshot` with `action: "capture"`. Keep the returned snapshot id.
|
|
26
|
+
Use `collection`, `entityId`, or `limit` only when the full state is too large.
|
|
27
|
+
|
|
28
|
+
The capture retains mergeable TinyBase content inside the local MCP process.
|
|
29
|
+
Returned JSON redacts sensitive fields and does not expose that merge payload.
|
|
30
|
+
|
|
31
|
+
## 3. Reproduce and inspect
|
|
32
|
+
|
|
33
|
+
Reproduce the issue once, then call:
|
|
34
|
+
|
|
35
|
+
- `get_syncdb_state` for the current state and debugger event stream
|
|
36
|
+
- `syncdb_snapshot` with `action: "capture"` for the post-reproduction state
|
|
37
|
+
- `syncdb_snapshot` with `action: "compare"` and both snapshot ids
|
|
38
|
+
|
|
39
|
+
Use the diff plus debug events to identify whether divergence begins at the
|
|
40
|
+
local mutation, outbox send, ack/nack, delta, cursor, or reconcile phase.
|
|
41
|
+
|
|
42
|
+
## 4. Change state only when required
|
|
43
|
+
|
|
44
|
+
Restart `terreno-mcp-local` with `TERRENO_MCP_EVAL=1`, then use
|
|
45
|
+
`syncdb_action`:
|
|
46
|
+
|
|
47
|
+
| Action | Purpose |
|
|
48
|
+
| --- | --- |
|
|
49
|
+
| `mutate` | Normal optimistic create/update/delete through the outbox |
|
|
50
|
+
| `setLocalEntity` / `deleteLocalEntity` | Direct local-only fault injection or repair |
|
|
51
|
+
| `flush` | Drain the durable outbox now |
|
|
52
|
+
| `reconcile` / `forceResync` | Catch up or purge and rebuild from server snapshots |
|
|
53
|
+
| `resolveConflict` / `retryFailed` | Unblock conflicted or terminally failed entities |
|
|
54
|
+
| `goOffline` / `goOnline` | Exercise queueing and reconnect behavior |
|
|
55
|
+
| `mergeSnapshot` | CRDT-merge a prior capture into the current TinyBase store; later HLCs win, including deletes |
|
|
56
|
+
| `clearDebug` | Reset retained debugger events and counters |
|
|
57
|
+
|
|
58
|
+
Prefer `mutate` over direct local edits. Capture another snapshot before
|
|
59
|
+
`setLocalEntity`, `deleteLocalEntity`, or `mergeSnapshot`; those actions bypass
|
|
60
|
+
normal server validation and outbox semantics.
|
|
61
|
+
|
|
62
|
+
## 5. Prove the diagnosis
|
|
63
|
+
|
|
64
|
+
Capture and compare a final snapshot, then verify:
|
|
65
|
+
|
|
66
|
+
- the expected entity payload, tombstone, seq, and stream
|
|
67
|
+
- no unintended pending mutation remains
|
|
68
|
+
- queued/conflict/failed counts match the expected outcome
|
|
69
|
+
- cursors and known streams are coherent
|
|
70
|
+
- debugger events show the expected send/ack/delta/reconcile sequence
|
|
71
|
+
|
|
72
|
+
The workflow is complete when the state diff and event sequence explain the
|
|
73
|
+
failure or prove the repair. See
|
|
74
|
+
[`docs/how-to/debug-with-mcp.md`](../../../../docs/how-to/debug-with-mcp.md) for
|
|
75
|
+
setup and examples.
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: deploy-gcp
|
|
3
|
+
description: Deploy a Terreno app to GCP — Cloud Run backend and GCS+CDN static web. Trigger phrases include "deploy to GCP", "deploy to Cloud Run", and "ship the backend".
|
|
4
|
+
targets: ['*']
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Deploy to GCP
|
|
8
|
+
|
|
9
|
+
Deploy `example-backend` (or a bootstrapped `backend/`) to Cloud Run and the web export to GCS + CDN.
|
|
10
|
+
|
|
11
|
+
**Never deploy to a project the user did not name in the request.** Never create or modify secrets without echoing which secret names will change.
|
|
12
|
+
|
|
13
|
+
## When to use
|
|
14
|
+
|
|
15
|
+
- User asks to deploy a Terreno backend or full stack to Google Cloud
|
|
16
|
+
- User names a specific GCP `$PROJECT_ID` and wants Cloud Run + static web hosting
|
|
17
|
+
|
|
18
|
+
## When not to use
|
|
19
|
+
|
|
20
|
+
- User has not named a target GCP project
|
|
21
|
+
- User wants Vercel, Netlify-only, or non-GCP hosting
|
|
22
|
+
- User wants Terraform apply without explicit confirmation
|
|
23
|
+
|
|
24
|
+
## Preflight checks
|
|
25
|
+
|
|
26
|
+
1. **Project layout** — detect monorepo (`example-backend/`, `example-frontend/`) vs bootstrapped app (`backend/`, `frontend/`).
|
|
27
|
+
2. **gcloud** — `gcloud config get-value project` must match the user-named project (or set it with user confirmation).
|
|
28
|
+
3. **APIs** — verify `run.googleapis.com`, `artifactregistry.googleapis.com`, `secretmanager.googleapis.com` are enabled.
|
|
29
|
+
4. **MongoDB** — `MONGO_URI` must point at a replica set (Atlas or `replSet=`). Standalone `mongod` breaks change streams.
|
|
30
|
+
5. **Web build** — confirm `EXPO_PUBLIC_API_URL` will be set to the Cloud Run URL **before** `bun run export`.
|
|
31
|
+
|
|
32
|
+
## Plan and confirm (required)
|
|
33
|
+
|
|
34
|
+
Before any mutating `gcloud`, `docker push`, or `gsutil` command, print:
|
|
35
|
+
|
|
36
|
+
- Target `$PROJECT_ID`, `$REGION`, `$SERVICE_NAME`
|
|
37
|
+
- Web bucket `$WEB_BUCKET` and CDN site name
|
|
38
|
+
- Every secret to create or update (`MONGO_URI`, `TOKEN_SECRET`, etc.)
|
|
39
|
+
- Every resource to create or update (Artifact Registry repo, Cloud Run service, GCS bucket, CDN resources)
|
|
40
|
+
|
|
41
|
+
**Stop and require explicit user confirmation** before proceeding.
|
|
42
|
+
|
|
43
|
+
## Backend deploy
|
|
44
|
+
|
|
45
|
+
Follow [docs/how-to/deploy-backend-to-cloud-run.md](../../../../docs/how-to/deploy-backend-to-cloud-run.md):
|
|
46
|
+
|
|
47
|
+
1. Create Artifact Registry repository
|
|
48
|
+
2. `docker build -f example-backend/Dockerfile .` from repo root
|
|
49
|
+
3. Push image to `$REGION-docker.pkg.dev/$PROJECT_ID/$REPOSITORY/$SERVICE_NAME`
|
|
50
|
+
4. Create Secret Manager secrets (values out of band)
|
|
51
|
+
5. Create runtime service account with `secretmanager.secretAccessor`
|
|
52
|
+
6. `gcloud run deploy` with `--session-affinity`, `--timeout=3600`, `--min-instances=1`, `--set-secrets`, `--port=3000`
|
|
53
|
+
|
|
54
|
+
## Frontend deploy
|
|
55
|
+
|
|
56
|
+
Follow [docs/how-to/deploy-web-to-gcs-cdn.md](../../../../docs/how-to/deploy-web-to-gcs-cdn.md):
|
|
57
|
+
|
|
58
|
+
1. `EXPO_PUBLIC_API_URL=$SERVICE_URL bun run export` in the frontend package
|
|
59
|
+
2. Upload `dist/` to `$WEB_BUCKET` with correct cache headers
|
|
60
|
+
3. Run `./scripts/setup-gcs-hosting.sh --project ... --site-name ... --bucket ... --domain ...` if CDN not provisioned
|
|
61
|
+
4. Invalidate CDN cache on the URL map
|
|
62
|
+
|
|
63
|
+
## Verification
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
curl -s "$SERVICE_URL/health" | jq .
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Expect `"healthy": true`. Fetch the web root URL and confirm the SPA loads and API calls hit `$SERVICE_URL`.
|
|
70
|
+
|
|
71
|
+
## Troubleshooting
|
|
72
|
+
|
|
73
|
+
See [references/troubleshooting.md](references/troubleshooting.md).
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# GCP deploy troubleshooting
|
|
2
|
+
|
|
3
|
+
Symptom → cause → fix for Terreno on Cloud Run + GCS/CDN.
|
|
4
|
+
|
|
5
|
+
## Runtime constraints
|
|
6
|
+
|
|
7
|
+
| Symptom | Cause | Fix |
|
|
8
|
+
|---------|-------|-----|
|
|
9
|
+
| Websockets disconnect every few requests | No session affinity on Cloud Run | Add `--session-affinity` to `gcloud run deploy` |
|
|
10
|
+
| Socket connections drop after ~5 minutes | Default Cloud Run request timeout (300s) | Raise `--timeout` (for example `3600`) |
|
|
11
|
+
| Log: `FeatureFlag.watch() failed — live updates require MongoDB as a replica set` | `MONGO_URI` points at standalone `mongod` | Use Atlas or a replica set — change streams required |
|
|
12
|
+
| First request after idle is slow; sockets reconnect | Cloud Run scaled to zero | Set `--min-instances=1` for user-facing services |
|
|
13
|
+
| Browser: `Access to fetch at '...' from origin '...' has been blocked by CORS policy` | `corsOrigin` / Better Auth `trustedOrigins` missing web CDN origin | Add CDN URL to backend CORS and auth config |
|
|
14
|
+
|
|
15
|
+
## Deploy-time failures
|
|
16
|
+
|
|
17
|
+
| Symptom | Cause | Fix |
|
|
18
|
+
|---------|-------|-----|
|
|
19
|
+
| `denied: Permission "artifactregistry.repositories.uploadArtifacts" denied` | Deploy identity lacks Artifact Registry write | Grant `roles/artifactregistry.writer` to the deploy service account |
|
|
20
|
+
| `Permission denied on secret ...` or `Secret Manager access denied` at cold start | Runtime SA lacks `secretmanager.secretAccessor` | Grant role on each secret or project |
|
|
21
|
+
| Cloud Run revision unhealthy; logs show connection refused on health check | Container not listening on `$PORT` | Terreno reads `process.env.PORT` (`api/src/terrenoApp.ts`); set `ENV PORT=3000` in Dockerfile and `--port=3000` on deploy |
|
|
22
|
+
| Web app loads but API calls go to `localhost` | `EXPO_PUBLIC_API_URL` not set at build time | Rebuild with `EXPO_PUBLIC_API_URL=$SERVICE_URL bun run export` |
|
|
23
|
+
| CDN serves old UI after deploy | CDN cache not invalidated | `gcloud compute url-maps invalidate-cdn-cache SITE-url-map --path "/*" --async` |
|
|
24
|
+
|
|
25
|
+
## Health check
|
|
26
|
+
|
|
27
|
+
| Symptom | Cause | Fix |
|
|
28
|
+
|---------|-------|-----|
|
|
29
|
+
| `curl $SERVICE_URL/health` returns `503` with `"database": "disconnected"` | MongoDB unreachable from Cloud Run | Check `MONGO_URI`, VPC/Atlas IP allowlist, and credentials |
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: generate-release-announcements
|
|
3
|
+
description: >-
|
|
4
|
+
Generate, validate, and optionally upload a multi-announcement Markdown release
|
|
5
|
+
pack for @terreno/announcements. Use when asked to announce a product release,
|
|
6
|
+
turn release notes or a Linear Release into in-app announcements, create an
|
|
7
|
+
announcement pack, or upload release announcements as drafts or live.
|
|
8
|
+
targets:
|
|
9
|
+
- '*'
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Generate release announcements
|
|
13
|
+
|
|
14
|
+
Create a source-controlled `terreno.announcement-pack/v1` directory from release notes. Human reference: `docs/reference/announcement-release-packs.md` in Terreno or the published Terreno announcement release-pack reference.
|
|
15
|
+
|
|
16
|
+
## 1. Collect the release
|
|
17
|
+
|
|
18
|
+
Determine:
|
|
19
|
+
|
|
20
|
+
- product identifier
|
|
21
|
+
- user-facing version
|
|
22
|
+
- client build number when one exists
|
|
23
|
+
- channel (`production` unless the user names another)
|
|
24
|
+
- release notes source: provided text/file, GitHub release/tag, changelog range, or Linear Release
|
|
25
|
+
|
|
26
|
+
For a Linear URL, use the available Linear connector to read the release and completed issues. If Linear is unavailable, stop only that source lookup and ask for the exported release notes; do not invent issue contents.
|
|
27
|
+
|
|
28
|
+
The collection step is complete when every feature, fix, breaking change, and operator action in the source is accounted for or explicitly excluded.
|
|
29
|
+
|
|
30
|
+
## 2. Choose the surfaces
|
|
31
|
+
|
|
32
|
+
Generate one or more focused announcements rather than copying raw issue lists:
|
|
33
|
+
|
|
34
|
+
1. Always create a `feed` changelog containing the complete user-relevant release.
|
|
35
|
+
2. Create a `modal` only for required action, breaking workflow, or high-impact staff rollout.
|
|
36
|
+
3. Create a `banner` for a short, non-blocking user-facing highlight.
|
|
37
|
+
4. Split staff and patient/user copy when their actions or vocabulary differ.
|
|
38
|
+
|
|
39
|
+
Default to `dismiss-only`. Use `required` only when the reader must take an action or confirm operational information.
|
|
40
|
+
|
|
41
|
+
The surface step is complete when each item has one audience, one purpose, and a title containing the release version.
|
|
42
|
+
|
|
43
|
+
## 3. Write the pack
|
|
44
|
+
|
|
45
|
+
Create:
|
|
46
|
+
|
|
47
|
+
```text
|
|
48
|
+
announcements/releases/<version>/
|
|
49
|
+
pack.yaml
|
|
50
|
+
<one-or-more-slugs>.md
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Write `pack.yaml`:
|
|
54
|
+
|
|
55
|
+
```yaml
|
|
56
|
+
schema: terreno.announcement-pack/v1
|
|
57
|
+
release:
|
|
58
|
+
product: <product>
|
|
59
|
+
version: "<version>"
|
|
60
|
+
buildNumber: <positive integer; omit when unknown>
|
|
61
|
+
channel: production
|
|
62
|
+
defaults:
|
|
63
|
+
platforms: [ios, android, web]
|
|
64
|
+
audienceType: all
|
|
65
|
+
displayMode: feed
|
|
66
|
+
acknowledgementPolicy: dismiss-only
|
|
67
|
+
priority: 0
|
|
68
|
+
announcements:
|
|
69
|
+
- changelog.md
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Each listed file uses YAML frontmatter plus Markdown:
|
|
73
|
+
|
|
74
|
+
```markdown
|
|
75
|
+
---
|
|
76
|
+
slug: changelog
|
|
77
|
+
title: "Version <version>"
|
|
78
|
+
displayMode: feed
|
|
79
|
+
audienceType: all
|
|
80
|
+
acknowledgementPolicy: dismiss-only
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## What changed in <version>
|
|
84
|
+
|
|
85
|
+
<reader-focused release copy>
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Frontmatter may override `platforms`, `priority`, `minBuildNumber`, `publishAt`, `expiresAt`, `audience`, and `primaryAction` (`label` plus absolute `url`). The release build number becomes `minBuildNumber` unless an item overrides it.
|
|
89
|
+
|
|
90
|
+
Do not write the server-managed numeric announcement `version`; that is a content revision, not the product release version.
|
|
91
|
+
|
|
92
|
+
The writing step is complete when every manifest file exists and every body is useful without reading another announcement.
|
|
93
|
+
|
|
94
|
+
## 4. Validate
|
|
95
|
+
|
|
96
|
+
Fail before upload when any check fails:
|
|
97
|
+
|
|
98
|
+
- manifest `schema` is exactly `terreno.announcement-pack/v1`
|
|
99
|
+
- product and release version are non-empty
|
|
100
|
+
- build number, when present, is a positive integer
|
|
101
|
+
- every listed Markdown file exists exactly once
|
|
102
|
+
- every item has a unique lowercase hyphenated `slug`, a title, and a non-empty body
|
|
103
|
+
- enum values match the announcement reference
|
|
104
|
+
- platforms are non-empty
|
|
105
|
+
- timestamps are ISO 8601 and primary-action URLs are absolute
|
|
106
|
+
- required modals state the required action clearly
|
|
107
|
+
|
|
108
|
+
Print the product, release version, build number, channel, item count, and a table of slug/surface/audience/policy. The validation step is complete only when all checks pass.
|
|
109
|
+
|
|
110
|
+
## 5. Optionally upload
|
|
111
|
+
|
|
112
|
+
Do not upload unless the user asked for API posting. Convert manifest defaults and each file's frontmatter/body into the JSON request documented by `POST /announcements/import-release`.
|
|
113
|
+
|
|
114
|
+
Use:
|
|
115
|
+
|
|
116
|
+
- API base URL from the user or app configuration
|
|
117
|
+
- token from `ANNOUNCEMENTS_UPLOAD_TOKEN` unless the user names another environment variable
|
|
118
|
+
- `Authorization: Bearer <token>`
|
|
119
|
+
- `"publish": false` by default
|
|
120
|
+
|
|
121
|
+
Write request JSON to a temporary file outside the repository, then:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
curl --fail-with-body \
|
|
125
|
+
-H "Authorization: Bearer $ANNOUNCEMENTS_UPLOAD_TOKEN" \
|
|
126
|
+
-H "Content-Type: application/json" \
|
|
127
|
+
--data @/tmp/terreno-announcement-release.json \
|
|
128
|
+
"$API_URL/announcements/import-release"
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Never print the token or commit the temporary JSON.
|
|
132
|
+
|
|
133
|
+
Set `"publish": true` only when the user explicitly says `live`, `publish`, or equivalent. Before a live upload, show the validated item table and require confirmation unless the original unattended task explicitly authorized live publication. A normal request to “upload” means drafts.
|
|
134
|
+
|
|
135
|
+
The upload step is complete when the API reports every item as created, updated, or unchanged. Report returned IDs and statuses. Delete the temporary JSON.
|
|
136
|
+
|
|
137
|
+
## Completion
|
|
138
|
+
|
|
139
|
+
Return:
|
|
140
|
+
|
|
141
|
+
- pack directory
|
|
142
|
+
- product version and build number
|
|
143
|
+
- generated item count and surfaces
|
|
144
|
+
- validation result
|
|
145
|
+
- upload result (`not requested`, `draft`, or `live`)
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: generate-sdk
|
|
3
|
+
description: >-
|
|
4
|
+
Regenerate the example-frontend RTK Query SDK (store/openApiSdk.ts) from the
|
|
5
|
+
example-backend's OpenAPI spec. Handles dependency install, backend startup,
|
|
6
|
+
SDK codegen, and cleanup from a fresh checkout. Use whenever the backend's API
|
|
7
|
+
surface changes — new modelRouter routes, custom routes registered via
|
|
8
|
+
createOpenApiBuilder, new/modified Mongoose models or field descriptions in
|
|
9
|
+
example-backend, changes to
|
|
10
|
+
permissions/queryFields/populatePaths/responseHandler, edits to @terreno/api
|
|
11
|
+
routing or OpenAPI generation, or any time a route is added/removed/renamed.
|
|
12
|
+
Also use when the user asks to "regenerate the SDK", "update openApiSdk.ts",
|
|
13
|
+
"run bun run sdk", or reports stale/missing generated hooks. Lifecycle
|
|
14
|
+
composition: Pick after API-shape changes; Roast to verify generated-client
|
|
15
|
+
compatibility.
|
|
16
|
+
targets:
|
|
17
|
+
- '*'
|
|
18
|
+
---
|
|
19
|
+
# Generate Frontend SDK
|
|
20
|
+
|
|
21
|
+
Regenerates `example-frontend/store/openApiSdk.ts` from the backend OpenAPI spec via RTK Query codegen.
|
|
22
|
+
|
|
23
|
+
**Scope:** This skill covers **non-synced** routes only (auth, profile, AI,
|
|
24
|
+
feature flags, custom RPC). Synced collections use `@terreno/syncdb` hooks
|
|
25
|
+
(`useQuery`, `useMutate`) — they are **not** in the generated SDK. Built-in
|
|
26
|
+
admin RPC uses admin-frontend's host-bound fetch client; do not add admin
|
|
27
|
+
`injectEndpoints`. See [syncdb reference](../../../../docs/reference/syncdb.md)
|
|
28
|
+
and [migrate-rtk-to-syncdb.md](../../../../docs/how-to/migrate-rtk-to-syncdb.md).
|
|
29
|
+
|
|
30
|
+
### Legacy RTK data path
|
|
31
|
+
|
|
32
|
+
During the `@terreno/rtk` support window, some screens may still use generated `useGetXQuery` / `usePostXMutation` for collections. Do not add new collection hooks — migrate to syncdb instead.
|
|
33
|
+
|
|
34
|
+
The `example-frontend` consumes the `example-backend` API via auto-generated RTK Query hooks in `store/openApiSdk.ts`. The codegen pulls from the live backend at `http://localhost:4000/openapi.json`, so the backend must be running while `bun run sdk` executes.
|
|
35
|
+
|
|
36
|
+
**Never edit `example-frontend/store/openApiSdk.ts` by hand — it is fully overwritten by this skill.**
|
|
37
|
+
|
|
38
|
+
## Documentation
|
|
39
|
+
|
|
40
|
+
Read `docs/reference/syncdb.md` and the backend/OpenAPI reference before regenerating. After a public API change, run `update-docs` in the same slice so reference and how-to pages match the new hooks. Generated SDK files are not hand-edited docs.
|
|
41
|
+
|
|
42
|
+
## When to run this skill
|
|
43
|
+
|
|
44
|
+
Run it whenever the backend's OpenAPI surface changes. Common triggers:
|
|
45
|
+
|
|
46
|
+
- New or removed `modelRouter` registration
|
|
47
|
+
- Edits to `permissions`, `queryFields`, `populatePaths`, `defaultQueryParams`, `sort`, `responseHandler`, or `openApiOverwrite` on an existing `modelRouter`
|
|
48
|
+
- New custom routes built with `createOpenApiBuilder` (path params, query params, request body, response schema changes)
|
|
49
|
+
- Mongoose schema changes that affect the public shape: added/removed fields, type changes, required/enum/default changes, `description` updates, ref changes
|
|
50
|
+
- Changes to auth routes (e.g. `addAuthRoutes`, `addMeRoutes`, GitHub OAuth, Better Auth)
|
|
51
|
+
- Edits to `@terreno/api` files that produce the OpenAPI spec (`openApi.ts`, `openApiBuilder.ts`, `api.ts`, `populate.ts`)
|
|
52
|
+
- User asks: "regenerate SDK", "update the SDK", "run bun run sdk", "openApiSdk.ts is out of date"
|
|
53
|
+
|
|
54
|
+
If the change is purely backend-internal (logging, refactor that doesn't alter routes/models, test-only edits), the SDK does **not** need regeneration — skip this skill.
|
|
55
|
+
|
|
56
|
+
## Prerequisites
|
|
57
|
+
|
|
58
|
+
- Bun is on PATH (run `bun` directly; do not modify PATH)
|
|
59
|
+
- MongoDB reachable at `mongodb://localhost:27017` (the example backend connects on boot)
|
|
60
|
+
- Ports 4000 (backend) must be free
|
|
61
|
+
|
|
62
|
+
If MongoDB is not running, tell the user and stop — do not try to install or start it automatically.
|
|
63
|
+
|
|
64
|
+
## Procedure
|
|
65
|
+
|
|
66
|
+
Run the steps in order. Stop and report on the first failure.
|
|
67
|
+
|
|
68
|
+
### 1. Verify the worktree is bootstrapped
|
|
69
|
+
|
|
70
|
+
Detect whether dependencies are installed. The repo uses Bun workspaces, so a single root `node_modules` is sufficient.
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
test -d node_modules && test -d example-backend/node_modules && test -d example-frontend/node_modules && echo "installed" || echo "missing"
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
If `missing`, run the full bootstrap from the repo root. This installs deps **and** compiles every workspace package (needed because `example-frontend` imports built artifacts from `@terreno/rtk` and other workspaces):
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
bun run bootstrap
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Bootstrap can take a few minutes — run it in the background and wait for it to finish before continuing.
|
|
83
|
+
|
|
84
|
+
### 2. Ensure backend env vars are set
|
|
85
|
+
|
|
86
|
+
The backend reads `example-backend/.env`. If that file is missing, copy the example:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
test -f example-backend/.env || cp example-backend/.env.example example-backend/.env
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
The default `.env.example` values are fine for local SDK generation. Do not invent secrets or overwrite an existing `.env`.
|
|
93
|
+
|
|
94
|
+
### 3. Start the example backend on port 4000
|
|
95
|
+
|
|
96
|
+
The dev script watches files, which is fine for a one-shot codegen run. Start it in the background from the repo root:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
bun run backend:dev
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Use the Bash tool's `run_in_background: true` so the process keeps running while you continue.
|
|
103
|
+
|
|
104
|
+
### 4. Wait for the OpenAPI spec to be served
|
|
105
|
+
|
|
106
|
+
Poll `http://localhost:4000/openapi.json` until it returns HTTP 200. The backend has to connect to MongoDB and register all routes before the spec is available, which can take 5–20 seconds on a cold start. Do not skip this wait — running `bun run sdk` before the spec is ready produces an empty/broken `openApiSdk.ts`.
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
until curl -sf -o /dev/null -w "%{http_code}" http://localhost:4000/openapi.json | grep -q 200; do sleep 2; done
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
If the backend logs an error (Mongo connection refused, port already in use, compile error), stop and surface the error to the user. Do not retry blindly.
|
|
113
|
+
|
|
114
|
+
### 5. Generate the SDK
|
|
115
|
+
|
|
116
|
+
Run the codegen from `example-frontend`. The script also runs Biome formatting on the output and strips an empty re-export line, so no follow-up formatting is needed.
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
cd example-frontend && bun run sdk
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
If the script fails with a missing module error (e.g. `@rtk-query/codegen-openapi`), dependencies are stale — re-run `bun run bootstrap` from the repo root and try again.
|
|
123
|
+
|
|
124
|
+
### 6. Stop the backend
|
|
125
|
+
|
|
126
|
+
Kill the background backend process you started in step 3. Use the PID from the background shell, or:
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
pkill -f "example-backend.*src/index.ts" || true
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### 7. Verify and report
|
|
133
|
+
|
|
134
|
+
Confirm `example-frontend/store/openApiSdk.ts` exists and was modified. Show the user a short summary:
|
|
135
|
+
|
|
136
|
+
- Which routes/models triggered the regeneration
|
|
137
|
+
- A `git diff --stat example-frontend/store/openApiSdk.ts` to show the size of the change
|
|
138
|
+
- Any warnings or errors emitted by the backend or codegen
|
|
139
|
+
|
|
140
|
+
Do **not** commit `openApiSdk.ts` automatically — leave that to the user's normal commit flow.
|
|
141
|
+
|
|
142
|
+
## Common failure modes
|
|
143
|
+
|
|
144
|
+
| Symptom | Cause | Fix |
|
|
145
|
+
|---|---|---|
|
|
146
|
+
| `ECONNREFUSED 127.0.0.1:27017` | MongoDB not running | Ask the user to start Mongo; do not auto-start |
|
|
147
|
+
| `EADDRINUSE :::4000` | Stale backend process | `pkill -f "example-backend.*src/index.ts"` then retry |
|
|
148
|
+
| `Cannot find module '@rtk-query/codegen-openapi'` | Missing/stale deps | Re-run `bun run bootstrap` |
|
|
149
|
+
| `openApiSdk.ts` has no endpoints | Backend wasn't ready when codegen ran | Step 4 wait was skipped — start over with a proper poll |
|
|
150
|
+
| Codegen errors on `$ref` | Backend OpenAPI spec is malformed | Inspect `/openapi.json` manually; check recent changes to `openApi.ts` or model schemas |
|
|
151
|
+
|
|
152
|
+
## Notes
|
|
153
|
+
|
|
154
|
+
- The codegen config lives at `example-frontend/openapi-config.ts`. The schema URL respects `OPENAPI_URL` if set, otherwise defaults to `http://localhost:4000/openapi.json`.
|
|
155
|
+
- The script entry point is `example-frontend/scripts/generate-sdk.ts` — it shells out to the `@rtk-query/codegen-openapi` CLI, then post-processes the output with Biome.
|
|
156
|
+
- Custom endpoints and cache tag types are layered on top in `example-frontend/store/sdk.ts` via `injectEndpoints`/`enhanceEndpoints`; that file is hand-maintained and is **not** overwritten by codegen.
|