@terreno/agent-plugin 0.0.0-stage → 58.0.0-beta.2
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 +2213 -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 +86 -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 +74 -0
- package/skills/building-admin-interfaces/references/screens-and-routes.md +50 -0
- package/skills/building-terreno-apps/SKILL.md +294 -0
- package/skills/building-terreno-apps/agents/openai.yaml +4 -0
- package/skills/building-terreno-apps/references/navigation.md +108 -0
- package/skills/building-terreno-apps/references/route-structure.md +104 -0
- package/skills/building-terreno-apps/references/screen-patterns.md +174 -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 +165 -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 +112 -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 +288 -0
- package/skills/terreno-data-fetching/agents/openai.yaml +4 -0
- package/skills/terreno-data-fetching/references/auth-and-tokens.md +89 -0
- package/skills/terreno-data-fetching/references/realtime.md +50 -0
- package/skills/terreno-data-fetching/references/sdk-customization.md +55 -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 +87 -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 +93 -0
- package/skills/upgrading-terreno/references/ordering.md +40 -0
- package/skills/verify-ui-changes/SKILL.md +605 -0
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# Screen Patterns — Terreno Apps
|
|
2
|
+
|
|
3
|
+
## List screen (synced collection)
|
|
4
|
+
|
|
5
|
+
Synced collections read from the local store: no loading spinner after bootstrap, and
|
|
6
|
+
writes apply immediately.
|
|
7
|
+
|
|
8
|
+
```tsx
|
|
9
|
+
import {generateMutationId} from "@terreno/syncdb";
|
|
10
|
+
import {useMutate, useQuery} from "@terreno/syncdb/react";
|
|
11
|
+
import {Box, Button, Card, Heading, Page, Text} from "@terreno/ui";
|
|
12
|
+
import {useCallback} from "react";
|
|
13
|
+
|
|
14
|
+
const TodoListScreen: React.FC = () => {
|
|
15
|
+
const todos = useQuery<Todo>("todos", {sort: "-created"});
|
|
16
|
+
const {create} = useMutate("todos");
|
|
17
|
+
|
|
18
|
+
const handleCreate = useCallback((): void => {
|
|
19
|
+
create({data: {_id: generateMutationId(), title: "New todo", completed: false}});
|
|
20
|
+
}, [create]);
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<Page title="Todos" scroll>
|
|
24
|
+
<Box gap={3} padding={4}>
|
|
25
|
+
<Box direction="row" justifyContent="between" alignItems="center">
|
|
26
|
+
<Heading size="lg">Todos</Heading>
|
|
27
|
+
<Button text="Add" onClick={handleCreate} iconName="plus" />
|
|
28
|
+
</Box>
|
|
29
|
+
{todos.length === 0 ? (
|
|
30
|
+
<Text color="secondaryLight">No todos yet</Text>
|
|
31
|
+
) : (
|
|
32
|
+
todos.map((todo) => (
|
|
33
|
+
<Card key={todo._id} padding={3}>
|
|
34
|
+
<Text>{todo.title}</Text>
|
|
35
|
+
</Card>
|
|
36
|
+
))
|
|
37
|
+
)}
|
|
38
|
+
</Box>
|
|
39
|
+
</Page>
|
|
40
|
+
);
|
|
41
|
+
};
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Gate the empty state on the stream's bootstrap (see `docs/how-to/use-syncdb-correctly.md`).
|
|
45
|
+
|
|
46
|
+
## Profile screen
|
|
47
|
+
|
|
48
|
+
Use `TapToEdit` for per-field profile edits. Save each field with the generated `usePatchAuthMeMutation` — do not collect every field behind one Save button.
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
import {Box, Card, Page, TapToEdit} from "@terreno/ui";
|
|
52
|
+
import {useCallback, useEffect, useState} from "react";
|
|
53
|
+
import {useGetAuthMeQuery, usePatchAuthMeMutation} from "@/store/apiSdk";
|
|
54
|
+
|
|
55
|
+
const ProfileScreen: React.FC = () => {
|
|
56
|
+
const {data: profile} = useGetAuthMeQuery();
|
|
57
|
+
const [updateProfile] = usePatchAuthMeMutation();
|
|
58
|
+
const [name, setName] = useState<string>("");
|
|
59
|
+
|
|
60
|
+
// Seed only this field from the server so saving another TapToEdit does not wipe the draft.
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
if (!profile) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
setName(profile.name || "");
|
|
66
|
+
}, [profile?.name]);
|
|
67
|
+
|
|
68
|
+
const handleSaveName = useCallback(
|
|
69
|
+
async (value: string): Promise<void> => {
|
|
70
|
+
await updateProfile({name: value}).unwrap();
|
|
71
|
+
},
|
|
72
|
+
[updateProfile]
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<Page title="Profile" scroll>
|
|
77
|
+
<Box gap={4} padding={4}>
|
|
78
|
+
<Card>
|
|
79
|
+
<TapToEdit
|
|
80
|
+
onSave={handleSaveName}
|
|
81
|
+
setValue={setName}
|
|
82
|
+
title="Name"
|
|
83
|
+
type="text"
|
|
84
|
+
value={name}
|
|
85
|
+
/>
|
|
86
|
+
</Card>
|
|
87
|
+
</Box>
|
|
88
|
+
</Page>
|
|
89
|
+
);
|
|
90
|
+
};
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Form screen
|
|
94
|
+
|
|
95
|
+
```tsx
|
|
96
|
+
import {generateMutationId} from "@terreno/syncdb";
|
|
97
|
+
import {useMutate} from "@terreno/syncdb/react";
|
|
98
|
+
import {Box, Button, Page, TextField} from "@terreno/ui";
|
|
99
|
+
import {useCallback, useState} from "react";
|
|
100
|
+
import {useRouter} from "expo-router";
|
|
101
|
+
|
|
102
|
+
const CreateTodoScreen: React.FC = () => {
|
|
103
|
+
const router = useRouter();
|
|
104
|
+
const [title, setTitle] = useState<string>("");
|
|
105
|
+
const [titleError, setTitleError] = useState<string | undefined>();
|
|
106
|
+
const {create} = useMutate("todos");
|
|
107
|
+
|
|
108
|
+
const handleSubmit = useCallback((): void => {
|
|
109
|
+
if (!title.trim()) {
|
|
110
|
+
setTitleError("Title is required");
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
create({data: {_id: generateMutationId(), title: title.trim(), completed: false}});
|
|
114
|
+
router.back();
|
|
115
|
+
}, [create, router, title]);
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<Page title="New Todo" scroll>
|
|
119
|
+
<Box gap={4} padding={4}>
|
|
120
|
+
<TextField
|
|
121
|
+
title="Title"
|
|
122
|
+
value={title}
|
|
123
|
+
onChange={setTitle}
|
|
124
|
+
errorText={titleError}
|
|
125
|
+
/>
|
|
126
|
+
<Button text="Save" onClick={handleSubmit} fullWidth />
|
|
127
|
+
</Box>
|
|
128
|
+
</Page>
|
|
129
|
+
);
|
|
130
|
+
};
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Server rejections arrive later in `useConflicts()` / `useSyncStatus().failedCount`, not
|
|
134
|
+
from `create`.
|
|
135
|
+
|
|
136
|
+
## Detail screen with params
|
|
137
|
+
|
|
138
|
+
```tsx
|
|
139
|
+
import {useEntity} from "@terreno/syncdb/react";
|
|
140
|
+
import {Page, Text} from "@terreno/ui";
|
|
141
|
+
import {useLocalSearchParams} from "expo-router";
|
|
142
|
+
|
|
143
|
+
const TodoDetailScreen: React.FC = () => {
|
|
144
|
+
const {id} = useLocalSearchParams<{id: string}>();
|
|
145
|
+
const {data, deleted} = useEntity<Todo>("todos", id ?? "");
|
|
146
|
+
|
|
147
|
+
if (!data || deleted) {
|
|
148
|
+
return <Page title="Todo"><Text color="error">Not found</Text></Page>;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return (
|
|
152
|
+
<Page title={data.title} scroll>
|
|
153
|
+
<Text>{data.title}</Text>
|
|
154
|
+
</Page>
|
|
155
|
+
);
|
|
156
|
+
};
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Admin table screen
|
|
160
|
+
|
|
161
|
+
Prefer `@terreno/admin-frontend` over hand-rolled tables. Use `AdminScreenRouter` so
|
|
162
|
+
custom screens and models share `[model]/index`. Full nav and custom-screen rules:
|
|
163
|
+
skill `building-admin-interfaces`.
|
|
164
|
+
|
|
165
|
+
```tsx
|
|
166
|
+
import {AdminScreenRouter} from "@terreno/admin-frontend";
|
|
167
|
+
import {useLocalSearchParams} from "expo-router";
|
|
168
|
+
|
|
169
|
+
// Rendered inside the AdminProvider in app/admin/_layout.tsx.
|
|
170
|
+
const AdminModelScreen: React.FC = () => {
|
|
171
|
+
const {model} = useLocalSearchParams<{model: string}>();
|
|
172
|
+
return <AdminScreenRouter apiBase="/admin" name={model ?? ""} routeBase="/admin" />;
|
|
173
|
+
};
|
|
174
|
+
```
|
|
@@ -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,165 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: generate-sdk
|
|
3
|
+
description: >-
|
|
4
|
+
Regenerate the example-frontend SDKs from the example-backend's OpenAPI spec
|
|
5
|
+
with terreno-syncdb-codegen: store/apiSdk.ts (typed request hooks for every
|
|
6
|
+
non-synced route, `bun run sdk`) and store/syncDbSdk.ts (synced collection
|
|
7
|
+
hooks, `bun run sync-sdk`). Handles dependency install, backend startup,
|
|
8
|
+
codegen, and cleanup from a fresh checkout. Use whenever the backend's API
|
|
9
|
+
surface changes — new modelRouter routes or actions, custom routes registered
|
|
10
|
+
via createOpenApiBuilder, new/modified Mongoose models or field descriptions
|
|
11
|
+
in example-backend, sync config changes, changes to
|
|
12
|
+
permissions/queryFields/populatePaths/responseHandler, edits to @terreno/api
|
|
13
|
+
routing or OpenAPI generation, or any time a route is added/removed/renamed.
|
|
14
|
+
Also use when the user asks to "regenerate the SDK", "update apiSdk.ts" or
|
|
15
|
+
"syncDbSdk.ts", "run bun run sdk" or "bun run sync-sdk", or reports
|
|
16
|
+
stale/missing generated hooks. Lifecycle composition: Pick after API-shape
|
|
17
|
+
changes; Roast to verify generated-client compatibility.
|
|
18
|
+
targets:
|
|
19
|
+
- '*'
|
|
20
|
+
---
|
|
21
|
+
# Generate Frontend SDK
|
|
22
|
+
|
|
23
|
+
Regenerates the two generated files in `example-frontend` from the live backend spec at
|
|
24
|
+
`http://localhost:4000/openapi.json` (or `OPENAPI_URL`). Both come from
|
|
25
|
+
`terreno-syncdb-codegen` in `@terreno/syncdb`.
|
|
26
|
+
|
|
27
|
+
| Command | Output | Covers |
|
|
28
|
+
|---------|--------|--------|
|
|
29
|
+
| `bun run sdk` (runs `api-sdk`) | `store/apiSdk.ts` | Every operation without `x-terreno-sync`: `/auth/me`, modelRouter actions, AI, consents, announcements, version check |
|
|
30
|
+
| `bun run sync-sdk` | `store/syncDbSdk.ts` | `SYNC_COLLECTIONS` + collection hooks for the collections in the script's `--collections` list |
|
|
31
|
+
|
|
32
|
+
**Never edit `store/apiSdk.ts` or `store/syncDbSdk.ts` by hand — codegen overwrites them.**
|
|
33
|
+
Codegen options live in `example-frontend/syncdb-codegen.json` (`excludePaths` drops
|
|
34
|
+
routes such as the SSE streams; `overrides` sets per-collection options). Built-in admin
|
|
35
|
+
uses admin-frontend's own fetch client and is not generated here.
|
|
36
|
+
|
|
37
|
+
## Documentation
|
|
38
|
+
|
|
39
|
+
Read `docs/reference/syncdb.md#codegen` and `docs/how-to/generate-the-client-sdk.md`
|
|
40
|
+
before regenerating. After a public API change, run `update-docs` in the same slice.
|
|
41
|
+
|
|
42
|
+
## When to run this skill
|
|
43
|
+
|
|
44
|
+
Run `sdk` when the non-synced OpenAPI surface changes; run `sync-sdk` when a synced
|
|
45
|
+
collection's schema or `sync` config changes. Run both when unsure. Triggers:
|
|
46
|
+
|
|
47
|
+
- New or removed `modelRouter` registration, `collectionActions`, or `instanceActions`
|
|
48
|
+
- Edits to `permissions`, `queryFields`, `populatePaths`, `defaultQueryParams`, `sort`, `responseHandler`, `sync`, or `openApiOverwrite`
|
|
49
|
+
- New custom routes built with `createOpenApiBuilder`
|
|
50
|
+
- Mongoose schema changes that affect the public shape (fields, types, required/enum/default, `description`, refs)
|
|
51
|
+
- Auth route changes (`addAuthRoutes`, `addMeRoutes`, GitHub OAuth, Better Auth)
|
|
52
|
+
- Edits to `@terreno/api` OpenAPI generation (`openApi.ts`, `openApiBuilder.ts`, `api.ts`, `populate.ts`) or to `syncdb/src/codegen/`
|
|
53
|
+
- A new synced collection: also add it to the `--collections` list in the `sync-sdk` script
|
|
54
|
+
|
|
55
|
+
Skip it for backend-internal changes (logging, refactors that keep routes and models, tests).
|
|
56
|
+
|
|
57
|
+
## Prerequisites
|
|
58
|
+
|
|
59
|
+
- Bun on PATH (run `bun` directly; do not modify PATH)
|
|
60
|
+
- A MongoDB **replica set** at `MONGO_URI` (the backend's change streams need one)
|
|
61
|
+
- Port 4000 free
|
|
62
|
+
|
|
63
|
+
## Procedure
|
|
64
|
+
|
|
65
|
+
Run the steps in order. Stop and report on the first failure.
|
|
66
|
+
|
|
67
|
+
### 1. Verify the worktree is bootstrapped
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
test -d node_modules && test -d example-backend/node_modules && test -d example-frontend/node_modules && echo "installed" || echo "missing"
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
If `missing`, run `bun run bootstrap` from the repo root in the background and wait for it.
|
|
74
|
+
It installs deps and compiles every workspace package.
|
|
75
|
+
|
|
76
|
+
### 2. Start MongoDB if it is not running
|
|
77
|
+
|
|
78
|
+
`mongosh --quiet --eval 'rs.status().ok'` (or a `node` MongoClient ping) succeeds when a
|
|
79
|
+
replica set is up. Otherwise start one from the `mongodb-memory-server` binary cache and
|
|
80
|
+
initiate it once, as in `/workspace/CLAUDE.md` ("Example full stack"):
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
mkdir -p .devdata/mongo
|
|
84
|
+
"$(ls ~/.cache/mongodb-binaries/mongod-* | head -1)" \
|
|
85
|
+
--replSet rs0 --port 27017 --bind_ip 127.0.0.1 --dbpath .devdata/mongo
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Run `mongod` in the background. Initiate the replica set with `node` (not `bun`) from
|
|
89
|
+
`example-backend/`. If no `mongod` binary exists, tell the user and stop.
|
|
90
|
+
|
|
91
|
+
### 3. Ensure backend env vars are set
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
test -f example-backend/.env || cp example-backend/.env.example example-backend/.env
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
The example values (`MONGO_URI=mongodb://127.0.0.1:27017/terreno-example?replicaSet=rs0`,
|
|
98
|
+
token and session secrets) are fine for codegen. Never overwrite an existing `.env`.
|
|
99
|
+
|
|
100
|
+
### 4. Start the example backend on port 4000
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
bun run backend:dev
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Use `run_in_background: true`.
|
|
107
|
+
|
|
108
|
+
### 5. Wait for the OpenAPI spec
|
|
109
|
+
|
|
110
|
+
Poll until the spec returns 200 (5–20 s on a cold start). Codegen against a backend that is
|
|
111
|
+
not ready fails or emits an empty SDK.
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
until curl -sf -o /dev/null http://localhost:4000/openapi.json; do sleep 2; done
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
If the backend logs an error (Mongo connection refused, port in use, compile error), stop
|
|
118
|
+
and report it.
|
|
119
|
+
|
|
120
|
+
### 6. Generate
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
cd example-frontend && bun run sdk && bun run sync-sdk
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Both scripts format their output with Biome. A non-zero exit names the problem: two
|
|
127
|
+
operations sharing a hook name (give one an `operationId`), a missing collection path, or a
|
|
128
|
+
list response without `data.items`.
|
|
129
|
+
|
|
130
|
+
### 7. Stop the backend
|
|
131
|
+
|
|
132
|
+
Kill the background backend you started (and `mongod`, if you started it):
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
pkill -f "example-backend.*src/index.ts" || true
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### 8. Verify and report
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
git diff --stat example-frontend/store/apiSdk.ts example-frontend/store/syncDbSdk.ts
|
|
142
|
+
cd example-frontend && bunx tsc --noEmit
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Report which backend changes triggered the run, the diff stat, and any codegen or compile
|
|
146
|
+
errors. Do not commit the generated files automatically.
|
|
147
|
+
|
|
148
|
+
## Common failure modes
|
|
149
|
+
|
|
150
|
+
| Symptom | Cause | Fix |
|
|
151
|
+
|---|---|---|
|
|
152
|
+
| `ECONNREFUSED 127.0.0.1:27017` | MongoDB not running | Step 2 |
|
|
153
|
+
| Backend boots but change streams fail | Mongo is not a replica set | Start `mongod --replSet rs0` and initiate it |
|
|
154
|
+
| `EADDRINUSE :::4000` | Stale backend process | `pkill -f "example-backend.*src/index.ts"`, then retry |
|
|
155
|
+
| `fetch failed` / empty output | Backend not ready when codegen ran | Redo step 5 |
|
|
156
|
+
| Duplicate hook name error | Two operations map to one name | Add an `operationId` to one route |
|
|
157
|
+
| Screen imports a hook that no longer exists | Route renamed or excluded | Update the import; check `excludePaths` in `syncdb-codegen.json` |
|
|
158
|
+
|
|
159
|
+
## Notes
|
|
160
|
+
|
|
161
|
+
- Script definitions: `example-frontend/package.json` (`api-sdk`, `sdk`, `sync-sdk`). They run
|
|
162
|
+
`syncdb/src/codegen/cli.ts` directly; consumer apps call the `terreno-syncdb-codegen` bin.
|
|
163
|
+
- Hook names match the old RTK codegen rule (`GET /auth/me` → `useGetAuthMeQuery`), so
|
|
164
|
+
migrated screens keep their names. Apps upgrading from `@terreno/rtk` follow
|
|
165
|
+
`docs/how-to/migrate-rtk-to-syncdb.md`.
|