@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,85 @@
|
|
|
1
|
+
# Data Display — @terreno/ui
|
|
2
|
+
|
|
3
|
+
## Text and Heading
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
<Text size="md" color="primary" bold truncate>
|
|
7
|
+
Body copy
|
|
8
|
+
</Text>
|
|
9
|
+
|
|
10
|
+
<Heading size="lg" color="primary" align="center">
|
|
11
|
+
Page Title
|
|
12
|
+
</Heading>
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Sizes: `sm`, `md`, `lg`, `xl`, `2xl` (responsive web/mobile values).
|
|
16
|
+
|
|
17
|
+
## Badge
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
<Badge status="success" value="Active" />
|
|
21
|
+
<Badge status="warning" value="Pending" />
|
|
22
|
+
<Badge status="error" value="Failed" />
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## DataTable
|
|
26
|
+
|
|
27
|
+
```tsx
|
|
28
|
+
<DataTable
|
|
29
|
+
columns={[
|
|
30
|
+
{key: "name", label: "Name", width: 200, sortable: true},
|
|
31
|
+
{key: "status", label: "Status", width: 100, columnType: "boolean"},
|
|
32
|
+
{key: "created", label: "Created", width: 150, columnType: "date"},
|
|
33
|
+
]}
|
|
34
|
+
data={rows}
|
|
35
|
+
onSortChange={setSort}
|
|
36
|
+
onRowClick={(row) => navigate(row.id)}
|
|
37
|
+
pageSize={20}
|
|
38
|
+
totalRowCount={total}
|
|
39
|
+
onPageChange={setPage}
|
|
40
|
+
sticky
|
|
41
|
+
loading={isLoading}
|
|
42
|
+
/>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Column types: `text`, `date`, `number`, `boolean`, `custom`.
|
|
46
|
+
|
|
47
|
+
For admin tables, prefer `AdminModelTable` from `@terreno/admin-frontend`.
|
|
48
|
+
|
|
49
|
+
## Link
|
|
50
|
+
|
|
51
|
+
```tsx
|
|
52
|
+
<Link href="https://example.com" text="Learn more" />
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Avatar
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
<Avatar name="Jane Doe" src={imageUrl} size="md" />
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Pagination
|
|
62
|
+
|
|
63
|
+
Standalone pagination when not using DataTable's built-in controls:
|
|
64
|
+
|
|
65
|
+
```tsx
|
|
66
|
+
<Pagination
|
|
67
|
+
page={currentPage}
|
|
68
|
+
totalPages={totalPages}
|
|
69
|
+
onPageChange={setPage}
|
|
70
|
+
/>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Date formatting
|
|
74
|
+
|
|
75
|
+
Always use Luxon:
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
import {DateTime} from "luxon";
|
|
79
|
+
|
|
80
|
+
const formatted = DateTime.fromISO(isoString).toLocaleString(DateTime.DATETIME_MED);
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Selectable text
|
|
84
|
+
|
|
85
|
+
Add `selectable` to `Text` when users may copy the content (IDs, error messages, data values).
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# Feedback — @terreno/ui
|
|
2
|
+
|
|
3
|
+
## Spinner
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
<Spinner />
|
|
7
|
+
<Spinner size="lg" />
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
Use in loading states inside `Page` or centered in `Box`.
|
|
11
|
+
|
|
12
|
+
## Modal
|
|
13
|
+
|
|
14
|
+
```tsx
|
|
15
|
+
<Modal
|
|
16
|
+
visible={isVisible}
|
|
17
|
+
onDismiss={() => setIsVisible(false)}
|
|
18
|
+
title="Confirm Action"
|
|
19
|
+
size="sm" // sm | md | lg
|
|
20
|
+
primaryButtonText="Confirm"
|
|
21
|
+
primaryButtonOnClick={handleConfirm}
|
|
22
|
+
secondaryButtonText="Cancel"
|
|
23
|
+
secondaryButtonOnClick={() => setIsVisible(false)}
|
|
24
|
+
persistOnBackgroundClick={false}
|
|
25
|
+
>
|
|
26
|
+
<Text>Are you sure?</Text>
|
|
27
|
+
</Modal>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Renders as ActionSheet on mobile, centered dialog on web.
|
|
31
|
+
|
|
32
|
+
## Button confirmation
|
|
33
|
+
|
|
34
|
+
```tsx
|
|
35
|
+
<Button
|
|
36
|
+
text="Delete"
|
|
37
|
+
variant="destructive"
|
|
38
|
+
withConfirmation
|
|
39
|
+
confirmationText="This cannot be undone."
|
|
40
|
+
onClick={handleDelete}
|
|
41
|
+
/>
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Toast
|
|
45
|
+
|
|
46
|
+
`TerrenoProvider` must wrap the app. Use the `useToast` hook from `@terreno/ui`:
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
import {useToast} from "@terreno/ui";
|
|
50
|
+
|
|
51
|
+
const MyScreen: React.FC = () => {
|
|
52
|
+
const toast = useToast();
|
|
53
|
+
|
|
54
|
+
const handleSave = useCallback(async (): Promise<void> => {
|
|
55
|
+
try {
|
|
56
|
+
await saveMutation().unwrap();
|
|
57
|
+
toast.success("Saved");
|
|
58
|
+
} catch (err) {
|
|
59
|
+
toast.catch(err, "Save failed");
|
|
60
|
+
}
|
|
61
|
+
}, [saveMutation, toast]);
|
|
62
|
+
};
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
`useToast` exposes `success`, `info`, `warn`, `error`, `show`, `hide`, and `catch` (for API errors).
|
|
66
|
+
|
|
67
|
+
## Banner
|
|
68
|
+
|
|
69
|
+
```tsx
|
|
70
|
+
<Banner
|
|
71
|
+
text="Service is temporarily unavailable."
|
|
72
|
+
status="warning"
|
|
73
|
+
/>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Use `OfflineBanner` from `@terreno/ui` when showing network connectivity status (see example-frontend todos screen).
|
|
77
|
+
|
|
78
|
+
## ErrorPage
|
|
79
|
+
|
|
80
|
+
Full-page error state:
|
|
81
|
+
|
|
82
|
+
```tsx
|
|
83
|
+
<ErrorPage
|
|
84
|
+
title="Something went wrong"
|
|
85
|
+
message="We couldn't load your data."
|
|
86
|
+
onRetry={refetch}
|
|
87
|
+
/>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## ErrorBoundary
|
|
91
|
+
|
|
92
|
+
Wrap risky subtrees:
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
<ErrorBoundary>
|
|
96
|
+
<RiskyComponent />
|
|
97
|
+
</ErrorBoundary>
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Loading + error pattern on Page
|
|
101
|
+
|
|
102
|
+
```tsx
|
|
103
|
+
if (isLoading) {
|
|
104
|
+
return (
|
|
105
|
+
<Page title="Items">
|
|
106
|
+
<Box alignItems="center" padding={8}>
|
|
107
|
+
<Spinner />
|
|
108
|
+
</Box>
|
|
109
|
+
</Page>
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (error) {
|
|
114
|
+
return (
|
|
115
|
+
<Page title="Items">
|
|
116
|
+
<ErrorPage title="Failed to load" onRetry={refetch} />
|
|
117
|
+
</Page>
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Never ship a screen without loading and error states.
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# Forms — @terreno/ui
|
|
2
|
+
|
|
3
|
+
All form fields share common props:
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
interface BaseFieldProps {
|
|
7
|
+
onChange: (value: string) => void;
|
|
8
|
+
value?: string;
|
|
9
|
+
title?: string; // label above field
|
|
10
|
+
placeholder?: string;
|
|
11
|
+
disabled?: boolean;
|
|
12
|
+
errorText?: string; // red error below field
|
|
13
|
+
helperText?: string; // gray helper below field
|
|
14
|
+
iconName?: IconName;
|
|
15
|
+
testID?: string;
|
|
16
|
+
}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## TextField
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
<TextField
|
|
23
|
+
title="Name"
|
|
24
|
+
value={name}
|
|
25
|
+
onChange={setName}
|
|
26
|
+
type="text" // text | email | password | phoneNumber | search | url
|
|
27
|
+
errorText={nameError}
|
|
28
|
+
helperText="Required"
|
|
29
|
+
multiline
|
|
30
|
+
rows={3}
|
|
31
|
+
grow // auto-expand height
|
|
32
|
+
trimOnBlur
|
|
33
|
+
/>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## SelectField
|
|
37
|
+
|
|
38
|
+
```tsx
|
|
39
|
+
<SelectField
|
|
40
|
+
title="Status"
|
|
41
|
+
options={[
|
|
42
|
+
{label: "Active", value: "active"},
|
|
43
|
+
{label: "Inactive", value: "inactive"},
|
|
44
|
+
]}
|
|
45
|
+
value={status}
|
|
46
|
+
onChange={setStatus}
|
|
47
|
+
requireValue
|
|
48
|
+
/>
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## DateTimeField
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
import {DateTime} from "luxon";
|
|
55
|
+
|
|
56
|
+
<DateTimeField
|
|
57
|
+
title="Due Date"
|
|
58
|
+
type="date" // date | datetime | time
|
|
59
|
+
value={dueDate} // ISO string
|
|
60
|
+
onChange={setDueDate}
|
|
61
|
+
showTimezone
|
|
62
|
+
/>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Always use Luxon for formatting displayed dates.
|
|
66
|
+
|
|
67
|
+
## Other fields
|
|
68
|
+
|
|
69
|
+
| Field | Use for |
|
|
70
|
+
|-------|---------|
|
|
71
|
+
| `TextArea` | Multi-line text |
|
|
72
|
+
| `NumberField` | Numeric input |
|
|
73
|
+
| `EmailField` | Email with validation |
|
|
74
|
+
| `PhoneNumberField` | Phone formatting |
|
|
75
|
+
| `BooleanField` | Toggle with label |
|
|
76
|
+
| `CheckBox` | Checkbox without field chrome |
|
|
77
|
+
| `RadioField` | Radio group |
|
|
78
|
+
| `MultiselectField` | Multiple selection |
|
|
79
|
+
| `AddressField` | Structured address |
|
|
80
|
+
| `PasswordField` | Password with show/hide |
|
|
81
|
+
| `SignatureField` | Draw signature (consent flows) |
|
|
82
|
+
|
|
83
|
+
## TapToEdit
|
|
84
|
+
|
|
85
|
+
Use `TapToEdit` for values that should stay read-only until the user taps the pencil. Pass `setValue` for the in-progress edit and `onSave` to persist.
|
|
86
|
+
|
|
87
|
+
```tsx
|
|
88
|
+
<TapToEdit
|
|
89
|
+
onSave={handleSaveName}
|
|
90
|
+
setValue={setName}
|
|
91
|
+
title="Name"
|
|
92
|
+
type="text"
|
|
93
|
+
value={name}
|
|
94
|
+
/>
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`onSave` may be async. Cancel restores the value from when editing started. Do not omit `setValue` when `editable` is true.
|
|
98
|
+
|
|
99
|
+
## Form pattern
|
|
100
|
+
|
|
101
|
+
```tsx
|
|
102
|
+
const [email, setEmail] = useState<string>("");
|
|
103
|
+
const [emailError, setEmailError] = useState<string | undefined>();
|
|
104
|
+
|
|
105
|
+
const handleSubmit = useCallback(async (): Promise<void> => {
|
|
106
|
+
if (!email.trim()) {
|
|
107
|
+
setEmailError("Email is required");
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
setEmailError(undefined);
|
|
111
|
+
await saveMutation({email}).unwrap();
|
|
112
|
+
}, [email, saveMutation]);
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Check validation at the start of handlers and return early.
|
|
116
|
+
|
|
117
|
+
## Admin forms
|
|
118
|
+
|
|
119
|
+
For admin CRUD, use `AdminModelForm` from `@terreno/admin-frontend` — it auto-generates fields from the backend `/admin/config` metadata.
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# Layout — @terreno/ui
|
|
2
|
+
|
|
3
|
+
## Box
|
|
4
|
+
|
|
5
|
+
Core flex primitive. All layout should use Box.
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
<Box
|
|
9
|
+
direction="row" // row | column
|
|
10
|
+
justifyContent="between" // start | end | center | between | around
|
|
11
|
+
alignItems="center" // start | end | center | baseline | stretch
|
|
12
|
+
flex="grow" // grow | shrink | none
|
|
13
|
+
gap={2} // 0-12 spacing scale
|
|
14
|
+
padding={4}
|
|
15
|
+
paddingX={2}
|
|
16
|
+
marginBottom={2}
|
|
17
|
+
width="100%"
|
|
18
|
+
color="base" // surface color token
|
|
19
|
+
border="default"
|
|
20
|
+
rounding="md"
|
|
21
|
+
shadow
|
|
22
|
+
onClick={handlePress} // makes it pressable
|
|
23
|
+
scroll // wraps in ScrollView
|
|
24
|
+
testID="my-box"
|
|
25
|
+
/>
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Spacing scale
|
|
29
|
+
|
|
30
|
+
`0→0px, 1→4px, 2→8px, 3→12px, 4→16px, 5→24px, 6→32px, 7→40px, 8→48px, 9→56px, 10→64px, 11→72px, 12→80px`
|
|
31
|
+
|
|
32
|
+
Prefer `gap` over margin. Prefer `padding` over margin.
|
|
33
|
+
|
|
34
|
+
## Page
|
|
35
|
+
|
|
36
|
+
Full-screen layout with optional header, back button, scroll, and footer.
|
|
37
|
+
|
|
38
|
+
```tsx
|
|
39
|
+
<Page
|
|
40
|
+
title="Settings"
|
|
41
|
+
backButton
|
|
42
|
+
scroll
|
|
43
|
+
loading={isLoading}
|
|
44
|
+
maxWidth={800}
|
|
45
|
+
padding={2}
|
|
46
|
+
footer={<Button text="Save" onClick={save} />}
|
|
47
|
+
>
|
|
48
|
+
{children}
|
|
49
|
+
</Page>
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Use `Page` instead of wrapping content in `ScrollView` manually.
|
|
53
|
+
|
|
54
|
+
## Card
|
|
55
|
+
|
|
56
|
+
Box wrapper with default rounding and shadow.
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
<Card padding={4} color="base">
|
|
60
|
+
<Text>Grouped content</Text>
|
|
61
|
+
</Card>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## ListDetailLayout
|
|
65
|
+
|
|
66
|
+
Prefer this for list/detail screens. Use it as a route group's `_layout.tsx`: on a phone the
|
|
67
|
+
list pushes the `[id]` route; on a tablet or wide browser (layout width ≥ 768) the list stays in
|
|
68
|
+
a left pane and the detail opens beside it. The open item is the URL, so deep links and back work,
|
|
69
|
+
and it stays open across rotation and resize.
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
// app/mail/_layout.tsx
|
|
73
|
+
export const unstable_settings = {initialRouteName: "index"};
|
|
74
|
+
const MailLayout: React.FC = () => (
|
|
75
|
+
<ListDetailLayout
|
|
76
|
+
detailHref={(id) => `/mail/${id}`}
|
|
77
|
+
emptyDetail={<Text>Select a message</Text>}
|
|
78
|
+
indexHref="/mail"
|
|
79
|
+
list={<MailList />}
|
|
80
|
+
/>
|
|
81
|
+
);
|
|
82
|
+
// app/mail/index.tsx
|
|
83
|
+
const MailIndex: React.FC = () => <ListDetailIndex />;
|
|
84
|
+
// In MailList rows: const {selectedId, select} = useListDetail();
|
|
85
|
+
// <Box accessibilityState={{selected: id === selectedId}} onClick={() => select(id)} ... />
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Keep list filters and search text in the route or a store: the list remounts at the breakpoint.
|
|
89
|
+
See `docs/how-to/build-list-detail-screens.md`.
|
|
90
|
+
|
|
91
|
+
## SplitPage
|
|
92
|
+
|
|
93
|
+
Use only for list/detail views without their own routes (for example inside a modal); prefer
|
|
94
|
+
`ListDetailLayout` otherwise. Master-detail layout. On large screens the list and detail sit side by side. On small
|
|
95
|
+
screens, selecting a list item replaces the list with the detail pane.
|
|
96
|
+
|
|
97
|
+
```tsx
|
|
98
|
+
<SplitPage
|
|
99
|
+
listViewData={items}
|
|
100
|
+
renderListViewItem={({item}) => <Text>{item.name}</Text>}
|
|
101
|
+
renderContent={(index) => <ItemDetail index={index} />}
|
|
102
|
+
/>
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Responsive breakpoints
|
|
106
|
+
|
|
107
|
+
Policy: `docs/explanation/responsive-design.md`. Width tables: `docs/reference/ui.md`.
|
|
108
|
+
|
|
109
|
+
Box supports `smDirection`, `mdDirection`, `lgDirection`, and `xlDirection`. Tokens are
|
|
110
|
+
platform-specific: native `sm` 320 / `md` 375 / `lg` 600 / `xl` 1024; web `lg` 1024 / `xl` 1280.
|
|
111
|
+
The desktop floor is `isSupportedDesktopWidth({width})`: web `lg` and native `xl`, both 1024pt.
|
|
112
|
+
Below that floor, keep the mobile layout. Native tablets (`lg`, 600–1023pt) do not get the desktop split.
|
|
113
|
+
|
|
114
|
+
```tsx
|
|
115
|
+
<Box direction="column" mdDirection="row" lgDirection="row" xlDirection="column">
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Do not
|
|
119
|
+
|
|
120
|
+
- Use raw `View` when Box props cover the need
|
|
121
|
+
- Use `Dimensions.get()` — prefer `useWindowDimensions` if sizing is needed
|
|
122
|
+
- Add a raw pixel cutoff such as 768. Use Box breakpoint props or `isSupportedDesktopWidth`
|
|
123
|
+
- Use `SafeAreaView` — Page handles safe areas
|
|
124
|
+
- Put hex colors in style when `color="primary"` works
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Theming — @terreno/ui
|
|
2
|
+
|
|
3
|
+
## Three-layer architecture
|
|
4
|
+
|
|
5
|
+
1. **Primitives** — raw color/spacing values (`neutral500`, `primary500`, `spacing4`)
|
|
6
|
+
2. **Config** — semantic maps (`text.primary`, `surface.base`, `border.default`)
|
|
7
|
+
3. **Computed theme** — resolved values consumed by components
|
|
8
|
+
|
|
9
|
+
## TerrenoProvider
|
|
10
|
+
|
|
11
|
+
```tsx
|
|
12
|
+
import {TerrenoProvider} from "@terreno/ui";
|
|
13
|
+
|
|
14
|
+
<TerrenoProvider colorScheme="system">
|
|
15
|
+
<App />
|
|
16
|
+
</TerrenoProvider>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Required at the app root. Provides theme context and toast support.
|
|
20
|
+
|
|
21
|
+
## useTheme
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
import {useTheme} from "@terreno/ui";
|
|
25
|
+
|
|
26
|
+
const {colorScheme, colorSchemeSetting, setColorScheme, theme, setTheme, setPrimitives, resetTheme} =
|
|
27
|
+
useTheme();
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Component color props
|
|
31
|
+
|
|
32
|
+
Use semantic props instead of hex:
|
|
33
|
+
|
|
34
|
+
```tsx
|
|
35
|
+
<Box color="base"> {/* theme.surface.base */}
|
|
36
|
+
<Text color="primary"> {/* theme.text.primary */}
|
|
37
|
+
<Text color="inverted"> {/* on dark backgrounds */}
|
|
38
|
+
<Text color="error"> {/* error state */}
|
|
39
|
+
</Box>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Surface colors: `base`, `primary`, `disabled`, `error`, etc.
|
|
43
|
+
Text colors: `primary`, `inverted`, `secondaryLight`, `error`, etc.
|
|
44
|
+
Border colors: `default`, `dark`, `focus`, `error`, `success`, `warning`.
|
|
45
|
+
|
|
46
|
+
## Spacing and radius
|
|
47
|
+
|
|
48
|
+
Use numeric spacing props (0-12) on Box, Page padding, and gap:
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
<Box padding={4} gap={3} rounding="md">
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Radius tokens: `sm`, `md`, `lg`, `xl`, `2xl`, `3xl`, `circle`.
|
|
55
|
+
|
|
56
|
+
## Customizing theme
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
setTheme({surface: {primary: "secondary500"}});
|
|
60
|
+
setPrimitives({accent500: "#FF6B35"});
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Reset with `resetTheme()`.
|
|
64
|
+
|
|
65
|
+
## Fonts
|
|
66
|
+
|
|
67
|
+
- Body: Nunito (`text` font family)
|
|
68
|
+
- Headings: Titillium Web (`title` font family)
|
|
69
|
+
|
|
70
|
+
Load fonts in root `_layout.tsx` via `useFonts` (see example-frontend).
|
|
71
|
+
|
|
72
|
+
## Dark mode
|
|
73
|
+
|
|
74
|
+
Set `TerrenoProvider colorScheme` to `"light"`, `"dark"`, or `"system"`.
|
|
75
|
+
The default is deterministic `"light"`; `"system"` follows React Native's
|
|
76
|
+
current system scheme. `themeColorSchemeOptions` exposes those three choices
|
|
77
|
+
as Light, Dark, and Follow system. Call `setColorScheme("system")` to change
|
|
78
|
+
the choice after startup. `colorSchemeSetting` keeps the selected choice, while
|
|
79
|
+
`useTheme().colorScheme` is always the resolved `"light"` or `"dark"` value.
|
|
80
|
+
The exported `lightThemeConfig` and `darkThemeConfig` match the Flourish Health
|
|
81
|
+
Figma modes.
|
|
82
|
+
|
|
83
|
+
## Do not
|
|
84
|
+
|
|
85
|
+
- Hardcode `#hex` when a theme token exists
|
|
86
|
+
- Use `style={{padding: 16}}` when `padding={4}` works
|
|
87
|
+
- Import colors from a separate constants file instead of theme props
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: update-docs
|
|
3
|
+
description: >-
|
|
4
|
+
Keep Terreno docs in sync whenever behavior, public API, architecture, or
|
|
5
|
+
operator workflow changes. Read architecture docs first; write Diátaxis pages
|
|
6
|
+
in the same slice; regenerate generated docs. Lifecycle composition: Pick when
|
|
7
|
+
behavior and docs change together; Roast proves docs; Brew is the final docs
|
|
8
|
+
gate.
|
|
9
|
+
---
|
|
10
|
+
# Update Docs
|
|
11
|
+
|
|
12
|
+
Use whenever behavior, public API, architecture, or operator workflow changes. Docs are
|
|
13
|
+
the architecture source. Code implements them.
|
|
14
|
+
|
|
15
|
+
## When to run
|
|
16
|
+
|
|
17
|
+
Always run in the same slice as the change. Skip only a pure internal refactor that does
|
|
18
|
+
not change APIs, UI, architecture, data flow, env/config, or operator steps. Record that
|
|
19
|
+
skip. Missing docs for a user-visible or architectural change is a failed slice.
|
|
20
|
+
|
|
21
|
+
## Workflow
|
|
22
|
+
|
|
23
|
+
1. **Read the current architecture**
|
|
24
|
+
- Open explanation and reference docs for the affected area before editing code or docs.
|
|
25
|
+
- If docs and code disagree, resolve both in this slice.
|
|
26
|
+
|
|
27
|
+
2. **Map the change to Diátaxis**
|
|
28
|
+
|
|
29
|
+
| Change | Page |
|
|
30
|
+
| --- | --- |
|
|
31
|
+
| First-run path | `docs/tutorials/` |
|
|
32
|
+
| Operator task | `docs/how-to/` |
|
|
33
|
+
| API, props, env, commands | `docs/reference/` |
|
|
34
|
+
| Why the system is shaped this way | `docs/explanation/` |
|
|
35
|
+
| UI component | generated `docs/reference/components/` plus a demo story |
|
|
36
|
+
|
|
37
|
+
The one exception to this layout is the
|
|
38
|
+
[`testing plan`](../../references/testing-plan.md) spots. The e2e guide stays at
|
|
39
|
+
`docs/testing/e2e.md` (with any pages it links) and the manual
|
|
40
|
+
regression suites stay in `docs/regression/<area>.md`, or wherever the repository's
|
|
41
|
+
`## Testing conventions` table points. Never move them into a Diátaxis directory.
|
|
42
|
+
|
|
43
|
+
The [`user guide`](../../references/user-guide.md) is the other exception. It is
|
|
44
|
+
written for the app's end users, not developers: one page per feature under
|
|
45
|
+
`docs/user-guide/<area>/<feature>.md`, or wherever the repository's
|
|
46
|
+
`## User guide conventions` table points. When a change alters what a user sees or
|
|
47
|
+
does, update its guide page with the Diátaxis pages, in the user-guide page format.
|
|
48
|
+
|
|
49
|
+
3. **Write the page**
|
|
50
|
+
- Update the existing section in place.
|
|
51
|
+
- Lead with the next action or canonical fact.
|
|
52
|
+
- Use the page's standard headings. Prefer tables.
|
|
53
|
+
- One minimal current example. No implementation chronology.
|
|
54
|
+
- Never hand-edit generated output.
|
|
55
|
+
|
|
56
|
+
4. **Regenerate generated docs** when UI types or OpenAPI-derived pages change:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
cd ui && bun run compile && bun run types
|
|
60
|
+
bun run website:generate
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
5. **Verify**
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
bun run website:build
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
6. **Sync agent copies** when agent-facing guidance changed:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
bun run rules
|
|
73
|
+
bun run skills:sync
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
7. **Record** every docs file created or updated in the PR `Verification` table.
|
|
77
|
+
|
|
78
|
+
## Integration
|
|
79
|
+
|
|
80
|
+
Pick, Roast, and Brew require this skill for user-visible or architectural work.
|
|
81
|
+
Brew does not open or update the PR until the docs in this slice match the shipped
|
|
82
|
+
behavior.
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: upgrading-terreno
|
|
3
|
+
description: >-
|
|
4
|
+
Upgrade Terreno lockstep @terreno/* packages. Trigger with "upgrade Terreno",
|
|
5
|
+
"bump @terreno packages", "update to the latest Terreno", or "upgrade terreno version".
|
|
6
|
+
targets: ['*']
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Upgrade Terreno
|
|
10
|
+
|
|
11
|
+
Perform a lockstep `@terreno/*` upgrade. Human-facing twin: `docs/how-to/upgrade-terreno.md`. Ordering detail: [references/ordering.md](references/ordering.md).
|
|
12
|
+
|
|
13
|
+
Do **not** duplicate Expo SDK steps. At step 6 invoke the `upgrading-expo` skill by name.
|
|
14
|
+
|
|
15
|
+
## Preconditions (refuse, do not warn)
|
|
16
|
+
|
|
17
|
+
Stop immediately and do not mutate the tree if any of these fail:
|
|
18
|
+
|
|
19
|
+
1. `git status --short` is **not empty** — refuse. Ask the user to commit or stash. A dirty tree has no safe rollback.
|
|
20
|
+
2. The current branch is the default branch (`master` or `main`) — refuse. Create or switch to a feature branch first.
|
|
21
|
+
3. Tests on this HEAD have not been shown passing — refuse. Run the app’s usual test command once so later failures are attributable to the upgrade.
|
|
22
|
+
|
|
23
|
+
## Determine versions
|
|
24
|
+
|
|
25
|
+
1. Prefer MCP `application_info` from `terreno-mcp-local`.
|
|
26
|
+
2. Else read every `package.json` that lists `@terreno/*`.
|
|
27
|
+
3. `fromVersion` = lowest installed `@terreno/*` semver. `toVersion` = user target or latest GitHub release.
|
|
28
|
+
|
|
29
|
+
## Fetch notes
|
|
30
|
+
|
|
31
|
+
Call hosted MCP `terreno_get_upgrade_guide` with `fromVersion` and `toVersion`. Read every concatenated note. A “no bundled notes” line means **unknown**, not no-op.
|
|
32
|
+
|
|
33
|
+
## Syncdb / RTK platform migration
|
|
34
|
+
|
|
35
|
+
If `fromVersion` is before `56.0.0-beta.2` and `toVersion` is `56.0.0-beta.2` or later, **stop**. This range crosses `@terreno/syncdb` and the RTK collection-CRUD deprecation. It is a platform migration, not a routine bump.
|
|
36
|
+
|
|
37
|
+
Offer exactly two options; do not automate the data-layer migration inside this flow:
|
|
38
|
+
|
|
39
|
+
1. Routine lockstep bump only (stay on RTK collection hooks for now).
|
|
40
|
+
2. Separate follow-up: `docs/how-to/migrate-rtk-to-syncdb.md`.
|
|
41
|
+
|
|
42
|
+
Wait for the user to pick.
|
|
43
|
+
|
|
44
|
+
## Plan and confirm (no mutations yet)
|
|
45
|
+
|
|
46
|
+
Print, then **stop until the user confirms**:
|
|
47
|
+
|
|
48
|
+
- `fromVersion` and `toVersion`
|
|
49
|
+
- Notes returned (or the gap warning)
|
|
50
|
+
- Packages affected
|
|
51
|
+
- Ordered steps 1–10 below
|
|
52
|
+
|
|
53
|
+
Do not install, edit lockfiles, or regenerate the client before confirmation.
|
|
54
|
+
|
|
55
|
+
If the caller is an unattended agent whose **invoking task already names** `fromVersion`, `toVersion`, and authorizes the bump (for example Pick Task 5.1), print the same plan into the transcript and treat that task as confirmation. Do not skip the printed plan. Do not skip the stop when a human is in the loop.
|
|
56
|
+
|
|
57
|
+
## Ordered steps (verification gate after each phase)
|
|
58
|
+
|
|
59
|
+
Match `docs/how-to/upgrade-terreno.md`. After each phase, run its gate. **If compile or tests fail, stop. Do not continue.**
|
|
60
|
+
|
|
61
|
+
Compile means a **typecheck** (`tsc --noEmit` or `bun run compile` when that script typechecks). `bun build` / bundlers that ignore TypeScript errors are not a passing gate.
|
|
62
|
+
|
|
63
|
+
1. Clean git tree + branch (already enforced).
|
|
64
|
+
2. Record current `@terreno/*` versions (`application_info` / `package.json`).
|
|
65
|
+
3. `terreno_get_upgrade_guide` for the range (already fetched). Apply note migrations that are code edits **after** confirmation, in this same order.
|
|
66
|
+
4. Bump **backend** packages: `@terreno/api`, `@terreno/test`, `@terreno/admin-backend`, `@terreno/blocks` (when the app lists it; `ai` and `ui` depend on it), `@terreno/ai`, `@terreno/api-health`, `@terreno/comms`, `@terreno/feature-flags`, `@terreno/mcp`. `bun install`.
|
|
67
|
+
5. Backend `bun run compile` and backend tests. Gate: both pass.
|
|
68
|
+
6. Expo SDK — invoke **`upgrading-expo`**. Skip only when Terreno majors do not require a new Expo (same Expo line). Gate: `npx expo-doctor` / install --fix as that skill requires; native rebuild if peers moved.
|
|
69
|
+
7. Bump **frontend** packages: `@terreno/ui`, `@terreno/rtk`, `@terreno/syncdb`, `@terreno/admin-frontend`, `@terreno/admin-spa`. `bun install`.
|
|
70
|
+
8. Start the **upgraded** backend. Regenerate the typed client (`bun run sdk` in the frontend).
|
|
71
|
+
9. `bun run compile`, `bun run lint`, and the app tests. Gate: all pass.
|
|
72
|
+
10. Run the app, log in, exercise changed screens.
|
|
73
|
+
|
|
74
|
+
## Failure handling
|
|
75
|
+
|
|
76
|
+
Report all three:
|
|
77
|
+
|
|
78
|
+
1. What succeeded (last passing step number).
|
|
79
|
+
2. What failed (command and first error line).
|
|
80
|
+
3. Rollback: `git reset --hard` on this branch, or `git checkout <pre-upgrade-sha> -- package.json bun.lock && bun install`.
|
|
81
|
+
|
|
82
|
+
On a **multi-version** jump failure: retry **version by version** (`from` → next recorded note version → …) to isolate the breaking version. Keep the failing consumer file. The first version whose typecheck fails is the isolate. Do not continue past a failed compile or test run.
|
|
83
|
+
|
|
84
|
+
## Final report
|
|
85
|
+
|
|
86
|
+
Print `fromVersion` → `toVersion`, notes applied, packages bumped, gates that passed, and remaining human follow-ups (native store binaries, RTK migration if deferred).
|