@terreno/mcp 57.19.2 → 57.20.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/CHANGELOG.md +393 -0
- package/README.md +32 -269
- package/dist/bootstrap.d.ts +12 -0
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +77 -122
- package/dist/bootstrap.js.map +1 -1
- package/dist/docs/guidelines/ui/guidelines/core.md +1 -1
- package/dist/docs/resources/overview.md +4 -2
- package/dist/docs/resources/patterns.md +237 -176
- package/dist/docs/resources/ui.md +48 -17
- package/dist/docs/templates/bootstrap/frontend/app/(tabs)/profile.template.tsx +0 -26
- package/dist/docs/templates/bootstrap/frontend/app/_layout.template.tsx +3 -2
- package/dist/docs/templates/bootstrap/frontend/app/login.template.tsx +1 -1
- package/dist/docs/upgrades/57.20.0.md +238 -0
- package/dist/docs/versioned/next/approval-rubric.md +30 -12
- package/dist/docs/versioned/next/explanation/README.md +5 -0
- package/dist/docs/versioned/next/explanation/account-linking.md +169 -0
- package/dist/docs/versioned/next/explanation/admin-interface.md +1 -1
- package/dist/docs/versioned/next/explanation/agent-guidelines.md +173 -0
- package/dist/docs/versioned/next/explanation/ai-development-loop.md +196 -0
- package/dist/docs/versioned/next/explanation/authentication.md +45 -1
- package/dist/docs/versioned/next/explanation/consent-forms.md +105 -0
- package/dist/docs/versioned/next/explanation/example-coverage.md +1 -0
- package/dist/docs/versioned/next/explanation/local-first-data.md +1 -1
- package/dist/docs/versioned/next/explanation/organizations.md +37 -0
- package/dist/docs/versioned/next/explanation/planning-plugins.md +4 -0
- package/dist/docs/versioned/next/explanation/responsive-design.md +143 -0
- package/dist/docs/versioned/next/explanation/roadmap-seed-issues.md +11 -0
- package/dist/docs/versioned/next/explanation/schema-metadata.md +5 -0
- package/dist/docs/versioned/next/how-to/README.md +7 -2
- package/dist/docs/versioned/next/how-to/audit-log.md +22 -3
- package/dist/docs/versioned/next/how-to/automate-terreno-upgrades.md +169 -0
- package/dist/docs/versioned/next/how-to/build-list-detail-screens.md +160 -0
- package/dist/docs/versioned/next/how-to/circleci.md +15 -5
- package/dist/docs/versioned/next/how-to/configure-better-auth.md +673 -53
- package/dist/docs/versioned/next/how-to/create-a-terreno-app.md +8 -5
- package/dist/docs/versioned/next/how-to/debug-with-mcp.md +393 -23
- package/dist/docs/versioned/next/how-to/enable-mcp-oauth.md +54 -3
- package/dist/docs/versioned/next/how-to/migrate-rtk-to-syncdb.md +9 -0
- package/dist/docs/versioned/next/how-to/password-reset.md +1 -1
- package/dist/docs/versioned/next/how-to/recover-locked-out-user.md +112 -0
- package/dist/docs/versioned/next/how-to/run-tests-locally.md +37 -8
- package/dist/docs/versioned/next/how-to/set-up-social-login.md +424 -0
- package/dist/docs/versioned/next/how-to/set-up-terreno-mcp.md +241 -36
- package/dist/docs/versioned/next/how-to/sign-consent-on-behalf.md +139 -0
- package/dist/docs/versioned/next/how-to/upgrade-terreno.md +4 -0
- package/dist/docs/versioned/next/implementationPlans/ai-dev-loop-boost.md +47 -32
- package/dist/docs/versioned/next/implementationPlans/auth-experience.md +952 -0
- package/dist/docs/versioned/next/implementationPlans/consent-forms.md +8 -0
- package/dist/docs/versioned/next/implementationPlans/list-detail-layout.md +297 -0
- package/dist/docs/versioned/next/implementationPlans/login-redesign/HANDOFF.md +211 -0
- package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/AnotherWay.html +121 -0
- package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/BackupCode.html +72 -0
- package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/Cancelled.html +87 -0
- package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/ContinueEmail.html +64 -0
- package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/FirstVisit.html +81 -0
- package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/Main.html +88 -0
- package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/ManageAccounts.html +77 -0
- package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/NotLinked.html +92 -0
- package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/SigningIn.html +60 -0
- package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/TwoFactor.html +76 -0
- package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/TwoFactorError.html +81 -0
- package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/WrongPassword.html +69 -0
- package/dist/docs/versioned/next/implementationPlans/mcp-boost-parity.md +1 -1
- package/dist/docs/versioned/next/plans/2026-10-23-provisional-consent.md +297 -0
- package/dist/docs/versioned/next/plans/2026-10-29-fix-mcp-oauth-native-and-machine.md +68 -0
- package/dist/docs/versioned/next/reference/README.md +1 -0
- package/dist/docs/versioned/next/reference/account-security.md +700 -0
- package/dist/docs/versioned/next/reference/admin-backend.md +3 -1
- package/dist/docs/versioned/next/reference/admin-frontend.md +225 -1
- package/dist/docs/versioned/next/reference/api.md +461 -35
- package/dist/docs/versioned/next/reference/cli.md +1 -1
- package/dist/docs/versioned/next/reference/comms.md +8 -0
- package/dist/docs/versioned/next/reference/create-terreno-app.md +3 -1
- package/dist/docs/versioned/next/reference/environment-variables.md +9 -3
- package/dist/docs/versioned/next/reference/legacy/rtk.md +19 -3
- package/dist/docs/versioned/next/reference/lifecycle-plugin.md +6 -0
- package/dist/docs/versioned/next/reference/mcp-server.md +1158 -441
- package/dist/docs/versioned/next/reference/syncdb.md +8 -1
- package/dist/docs/versioned/next/reference/ui.md +1271 -27
- package/dist/docs/versioned/next/tasks/ai-dev-loop-boost.md +28 -16
- package/dist/docs/versioned/next/tutorials/README.md +4 -0
- package/dist/httpApp.d.ts +14 -0
- package/dist/httpApp.d.ts.map +1 -0
- package/dist/httpApp.js +99 -0
- package/dist/httpApp.js.map +1 -0
- package/dist/index.js +8 -90
- package/dist/index.js.map +1 -1
- package/dist/local/localTools.d.ts.map +1 -1
- package/dist/local/localTools.js +44 -14
- package/dist/local/localTools.js.map +1 -1
- package/dist/local/metro/metroDevSession.d.ts +18 -0
- package/dist/local/metro/metroDevSession.d.ts.map +1 -1
- package/dist/local/metro/metroDevSession.js +42 -3
- package/dist/local/metro/metroDevSession.js.map +1 -1
- package/dist/local/tools/databaseQuery.d.ts.map +1 -1
- package/dist/local/tools/databaseQuery.js +3 -13
- package/dist/local/tools/databaseQuery.js.map +1 -1
- package/dist/local/tools/databaseSchema.d.ts.map +1 -1
- package/dist/local/tools/databaseSchema.js +3 -13
- package/dist/local/tools/databaseSchema.js.map +1 -1
- package/dist/local/tools/localMongo.d.ts +10 -0
- package/dist/local/tools/localMongo.d.ts.map +1 -0
- package/dist/local/tools/localMongo.js +30 -0
- package/dist/local/tools/localMongo.js.map +1 -0
- package/dist/local/tools/runtime.d.ts +18 -2
- package/dist/local/tools/runtime.d.ts.map +1 -1
- package/dist/local/tools/runtime.js +75 -20
- package/dist/local/tools/runtime.js.map +1 -1
- package/dist/local/tools/syncDb.d.ts +11 -0
- package/dist/local/tools/syncDb.d.ts.map +1 -1
- package/dist/local/tools/syncDb.js +15 -3
- package/dist/local/tools/syncDb.js.map +1 -1
- package/dist/rateLimit.d.ts +35 -0
- package/dist/rateLimit.d.ts.map +1 -0
- package/dist/rateLimit.js +112 -0
- package/dist/rateLimit.js.map +1 -0
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +42 -60
- package/dist/tools.js.map +1 -1
- package/package.json +4 -3
- package/src/docs/guidelines/ui/guidelines/core.md +1 -1
- package/src/docs/resources/overview.md +4 -2
- package/src/docs/resources/patterns.md +237 -176
- package/src/docs/resources/ui.md +48 -17
- package/src/docs/templates/bootstrap/frontend/app/(tabs)/profile.template.tsx +0 -26
- package/src/docs/templates/bootstrap/frontend/app/_layout.template.tsx +3 -2
- package/src/docs/templates/bootstrap/frontend/app/login.template.tsx +1 -1
- package/src/docs/upgrades/57.20.0.md +238 -0
- package/src/docs/versioned/next/approval-rubric.md +30 -12
- package/src/docs/versioned/next/explanation/README.md +5 -0
- package/src/docs/versioned/next/explanation/account-linking.md +169 -0
- package/src/docs/versioned/next/explanation/admin-interface.md +1 -1
- package/src/docs/versioned/next/explanation/agent-guidelines.md +173 -0
- package/src/docs/versioned/next/explanation/ai-development-loop.md +196 -0
- package/src/docs/versioned/next/explanation/authentication.md +45 -1
- package/src/docs/versioned/next/explanation/consent-forms.md +105 -0
- package/src/docs/versioned/next/explanation/example-coverage.md +1 -0
- package/src/docs/versioned/next/explanation/local-first-data.md +1 -1
- package/src/docs/versioned/next/explanation/organizations.md +37 -0
- package/src/docs/versioned/next/explanation/planning-plugins.md +4 -0
- package/src/docs/versioned/next/explanation/responsive-design.md +143 -0
- package/src/docs/versioned/next/explanation/roadmap-seed-issues.md +11 -0
- package/src/docs/versioned/next/explanation/schema-metadata.md +5 -0
- package/src/docs/versioned/next/how-to/README.md +7 -2
- package/src/docs/versioned/next/how-to/audit-log.md +22 -3
- package/src/docs/versioned/next/how-to/automate-terreno-upgrades.md +169 -0
- package/src/docs/versioned/next/how-to/build-list-detail-screens.md +160 -0
- package/src/docs/versioned/next/how-to/circleci.md +15 -5
- package/src/docs/versioned/next/how-to/configure-better-auth.md +673 -53
- package/src/docs/versioned/next/how-to/create-a-terreno-app.md +8 -5
- package/src/docs/versioned/next/how-to/debug-with-mcp.md +393 -23
- package/src/docs/versioned/next/how-to/enable-mcp-oauth.md +54 -3
- package/src/docs/versioned/next/how-to/migrate-rtk-to-syncdb.md +9 -0
- package/src/docs/versioned/next/how-to/password-reset.md +1 -1
- package/src/docs/versioned/next/how-to/recover-locked-out-user.md +112 -0
- package/src/docs/versioned/next/how-to/run-tests-locally.md +37 -8
- package/src/docs/versioned/next/how-to/set-up-social-login.md +424 -0
- package/src/docs/versioned/next/how-to/set-up-terreno-mcp.md +241 -36
- package/src/docs/versioned/next/how-to/sign-consent-on-behalf.md +139 -0
- package/src/docs/versioned/next/how-to/upgrade-terreno.md +4 -0
- package/src/docs/versioned/next/implementationPlans/ai-dev-loop-boost.md +47 -32
- package/src/docs/versioned/next/implementationPlans/auth-experience.md +952 -0
- package/src/docs/versioned/next/implementationPlans/consent-forms.md +8 -0
- package/src/docs/versioned/next/implementationPlans/list-detail-layout.md +297 -0
- package/src/docs/versioned/next/implementationPlans/login-redesign/HANDOFF.md +211 -0
- package/src/docs/versioned/next/implementationPlans/login-redesign/boards/AnotherWay.html +121 -0
- package/src/docs/versioned/next/implementationPlans/login-redesign/boards/BackupCode.html +72 -0
- package/src/docs/versioned/next/implementationPlans/login-redesign/boards/Cancelled.html +87 -0
- package/src/docs/versioned/next/implementationPlans/login-redesign/boards/ContinueEmail.html +64 -0
- package/src/docs/versioned/next/implementationPlans/login-redesign/boards/FirstVisit.html +81 -0
- package/src/docs/versioned/next/implementationPlans/login-redesign/boards/Main.html +88 -0
- package/src/docs/versioned/next/implementationPlans/login-redesign/boards/ManageAccounts.html +77 -0
- package/src/docs/versioned/next/implementationPlans/login-redesign/boards/NotLinked.html +92 -0
- package/src/docs/versioned/next/implementationPlans/login-redesign/boards/SigningIn.html +60 -0
- package/src/docs/versioned/next/implementationPlans/login-redesign/boards/TwoFactor.html +76 -0
- package/src/docs/versioned/next/implementationPlans/login-redesign/boards/TwoFactorError.html +81 -0
- package/src/docs/versioned/next/implementationPlans/login-redesign/boards/WrongPassword.html +69 -0
- package/src/docs/versioned/next/implementationPlans/mcp-boost-parity.md +1 -1
- package/src/docs/versioned/next/plans/2026-10-23-provisional-consent.md +297 -0
- package/src/docs/versioned/next/plans/2026-10-29-fix-mcp-oauth-native-and-machine.md +68 -0
- package/src/docs/versioned/next/reference/README.md +1 -0
- package/src/docs/versioned/next/reference/account-security.md +700 -0
- package/src/docs/versioned/next/reference/admin-backend.md +3 -1
- package/src/docs/versioned/next/reference/admin-frontend.md +225 -1
- package/src/docs/versioned/next/reference/api.md +461 -35
- package/src/docs/versioned/next/reference/cli.md +1 -1
- package/src/docs/versioned/next/reference/comms.md +8 -0
- package/src/docs/versioned/next/reference/create-terreno-app.md +3 -1
- package/src/docs/versioned/next/reference/environment-variables.md +9 -3
- package/src/docs/versioned/next/reference/legacy/rtk.md +19 -3
- package/src/docs/versioned/next/reference/lifecycle-plugin.md +6 -0
- package/src/docs/versioned/next/reference/mcp-server.md +1158 -441
- package/src/docs/versioned/next/reference/syncdb.md +8 -1
- package/src/docs/versioned/next/reference/ui.md +1271 -27
- package/src/docs/versioned/next/tasks/ai-dev-loop-boost.md +28 -16
- package/src/docs/versioned/next/tutorials/README.md +4 -0
|
@@ -100,7 +100,6 @@ import { Text, Heading, Title } from "@terreno/ui";
|
|
|
100
100
|
import {
|
|
101
101
|
TextField,
|
|
102
102
|
EmailField,
|
|
103
|
-
PasswordField,
|
|
104
103
|
NumberField,
|
|
105
104
|
TextArea,
|
|
106
105
|
SelectField,
|
|
@@ -110,19 +109,26 @@ import {
|
|
|
110
109
|
RadioField,
|
|
111
110
|
} from "@terreno/ui";
|
|
112
111
|
|
|
112
|
+
// Fields share `title`, `value`, `onChange`, `errorText`, and `helperText`.
|
|
113
113
|
<TextField
|
|
114
|
-
|
|
114
|
+
title="Name"
|
|
115
115
|
value={name}
|
|
116
|
-
|
|
116
|
+
onChange={setName}
|
|
117
117
|
placeholder="Enter your name"
|
|
118
|
-
|
|
118
|
+
errorText={errors.name}
|
|
119
119
|
helperText="Your full legal name"
|
|
120
120
|
/>
|
|
121
121
|
|
|
122
|
+
// Passwords: TextField with type="password" (adds a show/hide control).
|
|
123
|
+
<TextField title="Password" type="password" value={password} onChange={setPassword} />
|
|
124
|
+
|
|
125
|
+
// NumberField values are strings; type is "number" or "decimal".
|
|
126
|
+
<NumberField title="Age" type="number" value={age} onChange={setAge} />
|
|
127
|
+
|
|
122
128
|
<SelectField
|
|
123
|
-
|
|
129
|
+
title="Country"
|
|
124
130
|
value={country}
|
|
125
|
-
|
|
131
|
+
onChange={setCountry}
|
|
126
132
|
options={[
|
|
127
133
|
{ label: "United States", value: "US" },
|
|
128
134
|
{ label: "Canada", value: "CA" },
|
|
@@ -130,16 +136,16 @@ import {
|
|
|
130
136
|
/>
|
|
131
137
|
|
|
132
138
|
<DateTimeField
|
|
133
|
-
|
|
139
|
+
title="Date of Birth"
|
|
140
|
+
type="date"
|
|
134
141
|
value={dob}
|
|
135
142
|
onChange={setDob}
|
|
136
|
-
mode="date"
|
|
137
143
|
/>
|
|
138
144
|
|
|
139
145
|
<BooleanField
|
|
140
|
-
|
|
146
|
+
title="Accept Terms"
|
|
141
147
|
value={accepted}
|
|
142
|
-
|
|
148
|
+
onChange={setAccepted}
|
|
143
149
|
/>
|
|
144
150
|
```
|
|
145
151
|
|
|
@@ -202,7 +208,7 @@ import {
|
|
|
202
208
|
### Page & Layout
|
|
203
209
|
|
|
204
210
|
```typescript
|
|
205
|
-
import { Page,
|
|
211
|
+
import { Page, ScrollView } from "@terreno/ui";
|
|
206
212
|
|
|
207
213
|
<Page
|
|
208
214
|
navigation={navigation}
|
|
@@ -215,6 +221,32 @@ import { Page, SplitPage, ScrollView } from "@terreno/ui";
|
|
|
215
221
|
</Page>
|
|
216
222
|
```
|
|
217
223
|
|
|
224
|
+
List/detail screens (mail, inbox, records) use `ListDetailLayout` as a route group's
|
|
225
|
+
`_layout.tsx`. Phones push the detail route; tablets and wide browsers show the list in a left
|
|
226
|
+
pane beside it. The open item lives in the URL.
|
|
227
|
+
|
|
228
|
+
```typescript
|
|
229
|
+
import { ListDetailIndex, ListDetailLayout, useListDetail } from "@terreno/ui";
|
|
230
|
+
|
|
231
|
+
// app/mail/_layout.tsx
|
|
232
|
+
export const unstable_settings = { initialRouteName: "index" };
|
|
233
|
+
export default () => (
|
|
234
|
+
<ListDetailLayout
|
|
235
|
+
detailHref={(id) => `/mail/${id}`}
|
|
236
|
+
indexHref="/mail"
|
|
237
|
+
list={<MailList />}
|
|
238
|
+
/>
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
// app/mail/index.tsx
|
|
242
|
+
export default () => <ListDetailIndex />;
|
|
243
|
+
|
|
244
|
+
// MailList rows read and change the open item
|
|
245
|
+
const { selectedId, select } = useListDetail();
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Use `SplitPage` only for list/detail views without their own routes.
|
|
249
|
+
|
|
218
250
|
## Theme System
|
|
219
251
|
|
|
220
252
|
### Using Theme
|
|
@@ -253,17 +285,16 @@ radiusSm, radiusMd, radiusLg, radiusXl, radius2xl, radius3xl
|
|
|
253
285
|
## Utilities
|
|
254
286
|
|
|
255
287
|
```typescript
|
|
256
|
-
import {
|
|
288
|
+
import {isNarrowViewport, isSupportedDesktopWidth, useStoredState} from "@terreno/ui";
|
|
289
|
+
import {useWindowDimensions} from "react-native";
|
|
257
290
|
|
|
258
291
|
// Persist state to storage
|
|
259
292
|
const [value, setValue] = useStoredState("key", defaultValue);
|
|
260
293
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
<DesktopLayout />
|
|
264
|
-
</MediaQuery>
|
|
294
|
+
const {width} = useWindowDimensions();
|
|
295
|
+
const isDesktopLayout = isSupportedDesktopWidth({width});
|
|
265
296
|
|
|
266
|
-
// Current window is below the desktop
|
|
297
|
+
// Current window is below the desktop floor (web lg / native xl, 1024pt).
|
|
267
298
|
// isMobileDevice() is deprecated in favor of isNarrowViewport().
|
|
268
299
|
if (isNarrowViewport()) {
|
|
269
300
|
// Narrow-viewport code
|
|
@@ -11,7 +11,6 @@ const ProfileScreen: React.FC = () => {
|
|
|
11
11
|
const [updateProfile] = usePatchMeMutation();
|
|
12
12
|
const [name, setName] = useState<string>("");
|
|
13
13
|
const [email, setEmail] = useState<string>("");
|
|
14
|
-
const [password, setPassword] = useState<string>("");
|
|
15
14
|
const [currentPassword, setCurrentPassword] = useState<string>("");
|
|
16
15
|
const [saveError, setSaveError] = useState<string | null>(null);
|
|
17
16
|
|
|
@@ -75,23 +74,6 @@ const ProfileScreen: React.FC = () => {
|
|
|
75
74
|
[currentPassword, isEmailChanged, updateProfile]
|
|
76
75
|
);
|
|
77
76
|
|
|
78
|
-
const handleSavePassword = useCallback(
|
|
79
|
-
async (value: string): Promise<void> => {
|
|
80
|
-
if (!value) {
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
setSaveError(null);
|
|
84
|
-
try {
|
|
85
|
-
await updateProfile({password: value}).unwrap();
|
|
86
|
-
setPassword("");
|
|
87
|
-
} catch (err) {
|
|
88
|
-
console.error("Error updating password:", err);
|
|
89
|
-
setSaveError("Failed to update password");
|
|
90
|
-
}
|
|
91
|
-
},
|
|
92
|
-
[updateProfile]
|
|
93
|
-
);
|
|
94
|
-
|
|
95
77
|
if (isLoading) {
|
|
96
78
|
return (
|
|
97
79
|
<Page navigation={undefined} title="Profile">
|
|
@@ -132,14 +114,6 @@ const ProfileScreen: React.FC = () => {
|
|
|
132
114
|
value={currentPassword}
|
|
133
115
|
/>
|
|
134
116
|
)}
|
|
135
|
-
<TapToEdit
|
|
136
|
-
helperText="Leave blank to keep your current password"
|
|
137
|
-
onSave={handleSavePassword}
|
|
138
|
-
setValue={setPassword}
|
|
139
|
-
title="New Password"
|
|
140
|
-
type="password"
|
|
141
|
-
value={password}
|
|
142
|
-
/>
|
|
143
117
|
{saveError && <Text color="error">{saveError}</Text>}
|
|
144
118
|
</Box>
|
|
145
119
|
</Card>
|
|
@@ -5,14 +5,15 @@ import {Stack} from "expo-router";
|
|
|
5
5
|
import * as SplashScreen from "expo-splash-screen";
|
|
6
6
|
import {useEffect} from "react";
|
|
7
7
|
import "react-native-reanimated";
|
|
8
|
-
import {baseUrl,
|
|
8
|
+
import {baseUrl, selectBetterAuthUserId} from "@terreno/rtk";
|
|
9
|
+
import {installTerrenoDevConsoleLogger} from "@terreno/syncdb";
|
|
9
10
|
import {TerrenoProvider} from "@terreno/ui";
|
|
10
11
|
import {Provider, useSelector} from "react-redux";
|
|
11
12
|
import {PersistGate} from "redux-persist/integration/react";
|
|
12
13
|
import {useColorScheme} from "@/components/useColorScheme";
|
|
13
14
|
import store, {persistor} from "@/store/index";
|
|
14
15
|
|
|
15
|
-
installTerrenoDevConsoleLogger();
|
|
16
|
+
installTerrenoDevConsoleLogger({baseUrl});
|
|
16
17
|
|
|
17
18
|
export {ErrorBoundary} from "expo-router";
|
|
18
19
|
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
# Upgrading to 57.20.0
|
|
2
|
+
|
|
3
|
+
**Action required:** Yes
|
|
4
|
+
**Affected packages:** @terreno/api, @terreno/rtk, @terreno/ui, @terreno/admin-frontend, @terreno/mcp
|
|
5
|
+
|
|
6
|
+
## Summary
|
|
7
|
+
|
|
8
|
+
This release ships the Better Auth account suite: social login, account linking, passkeys,
|
|
9
|
+
two-factor with backup codes, closed sign-up, and staff account recovery (`AccountSecurityApp`,
|
|
10
|
+
org-admin recovery). It adds drop-in `TerrenoLoginScreen` and `TerrenoAccountSecurityScreen`
|
|
11
|
+
screens, `ListDetailLayout`, provisional consent signing, and MCP OAuth client credentials. The
|
|
12
|
+
app `User` claim guard now runs before a Better Auth user is created, which can refuse sign-ups
|
|
13
|
+
that 57.17.0 accepted. Several defaults also change: the desktop layout floor, sign-out timing,
|
|
14
|
+
web autofill, and the hosted MCP URL.
|
|
15
|
+
|
|
16
|
+
## Breaking changes
|
|
17
|
+
|
|
18
|
+
### Better Auth claims an existing app `User` only with a verified email or its password
|
|
19
|
+
|
|
20
|
+
**What changed**
|
|
21
|
+
|
|
22
|
+
A Better Auth sign-up or sign-in links to an unlinked app `User` with the same email (ignoring
|
|
23
|
+
case) only when the provider verified the email, or when the password verifies against that
|
|
24
|
+
`User`'s passport-local-mongoose hash. Otherwise the request answers 409 with
|
|
25
|
+
`code: "VERIFY_EMAIL_TO_USE_ACCOUNT"` and creates no Better Auth user, account, or verification
|
|
26
|
+
email. OAuth callbacks redirect with that `error`. A `User` already bound to another Better Auth
|
|
27
|
+
account is refused with `code: "ACCOUNT_ALREADY_EXISTS"`. `syncBetterAuthUser` throws `APIError`
|
|
28
|
+
409 instead of inserting a duplicate `User`, and takes a new `password` option.
|
|
29
|
+
|
|
30
|
+
`BetterAuthApp` registered without a `userModel` now adopts `TerrenoApp`'s user model, and the
|
|
31
|
+
`terreno-app-user-sync` plugin syncs the app `User` when each session is created. A
|
|
32
|
+
password-proven link that fails to save fails the sign-in with 500 `APP_USER_LINK_FAILED`.
|
|
33
|
+
|
|
34
|
+
**Why**
|
|
35
|
+
|
|
36
|
+
57.17.0 checked the claim only after the Better Auth user existed, so a later email verification
|
|
37
|
+
could still hand that user someone else's `User`.
|
|
38
|
+
|
|
39
|
+
**Migration**
|
|
40
|
+
|
|
41
|
+
Users who sign up with their existing password, or sign in with a provider that verified their
|
|
42
|
+
email, link at once. Users with no password hash (for example GitHub JWT sign-in) must reset their
|
|
43
|
+
password through JWT `POST /auth/forgotPassword` first, or sign in with a verified provider. Keep
|
|
44
|
+
the JWT auth routes mounted while users move. Handle the new 409 codes on your login screen:
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
import {getLoginFeedback} from "@terreno/rtk";
|
|
48
|
+
|
|
49
|
+
// Before: a generic error for any refused sign-up
|
|
50
|
+
setError(error.message);
|
|
51
|
+
|
|
52
|
+
// After: VERIFY_EMAIL_TO_USE_ACCOUNT reads as "verify your email"
|
|
53
|
+
const {notice, error: errorLine} = getLoginFeedback({
|
|
54
|
+
failure: {code: error.code, message: error.message, source: "response"},
|
|
55
|
+
mode: methods?.accountLinking.mode,
|
|
56
|
+
});
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
`TerrenoLoginScreen` from `@terreno/rtk` handles these codes for you. See
|
|
60
|
+
`docs/explanation/account-linking.md`.
|
|
61
|
+
|
|
62
|
+
### `accountLinking: {mode: "never"}` refuses `link-social` up front
|
|
63
|
+
|
|
64
|
+
**What changed**
|
|
65
|
+
|
|
66
|
+
With linking off (`accountLinking: {mode: "never"}` or
|
|
67
|
+
`options.account.accountLinking.enabled: false`), `POST {basePath}/link-social` answers 403
|
|
68
|
+
`LINKING_DISABLED` instead of failing at the provider callback.
|
|
69
|
+
|
|
70
|
+
**Why**
|
|
71
|
+
|
|
72
|
+
The user learns linking is off before leaving for the provider.
|
|
73
|
+
|
|
74
|
+
**Migration**
|
|
75
|
+
|
|
76
|
+
Hide the "Link" action when `useAuthMethods(authClient)` reports linking is off, or treat 403
|
|
77
|
+
`LINKING_DISABLED` as a final answer:
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
// Before
|
|
81
|
+
await authClient.linkSocial({provider: "google"}); // failed later at the callback
|
|
82
|
+
|
|
83
|
+
// After
|
|
84
|
+
const {methods} = useAuthMethods(authClient);
|
|
85
|
+
const canLink = methods?.accountLinking.mode !== "never";
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Desktop layout floor moves from 768pt to 1024pt
|
|
89
|
+
|
|
90
|
+
**What changed**
|
|
91
|
+
|
|
92
|
+
`Card`, `MarkdownEditor`, the admin shell, and the admin filter drawer use the narrow layout
|
|
93
|
+
below 1024pt instead of 768pt. Admin home stacks its panes below that floor.
|
|
94
|
+
`ADMIN_FILTER_MOBILE_BREAKPOINT` is now 1024, and `isSupportedDesktopWidth({width})` reports the
|
|
95
|
+
floor.
|
|
96
|
+
|
|
97
|
+
**Why**
|
|
98
|
+
|
|
99
|
+
Tablets between 768pt and 1023pt got cramped desktop layouts. See
|
|
100
|
+
`docs/explanation/responsive-design.md`.
|
|
101
|
+
|
|
102
|
+
**Migration**
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
// Before
|
|
106
|
+
const isDesktop = width >= 768;
|
|
107
|
+
|
|
108
|
+
// After
|
|
109
|
+
import {isSupportedDesktopWidth} from "@terreno/ui";
|
|
110
|
+
const isDesktop = isSupportedDesktopWidth({width});
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Check screens you tuned for 768–1023pt widths, such as iPad portrait.
|
|
114
|
+
|
|
115
|
+
### `logout()` waits for the server before clearing the session
|
|
116
|
+
|
|
117
|
+
**What changed**
|
|
118
|
+
|
|
119
|
+
In `@terreno/rtk` `generateBetterAuthSlice`, `logout()` keeps the session until
|
|
120
|
+
`authClient.signOut()` finishes (`signOutTimeoutMs`, default 10 seconds). Meanwhile
|
|
121
|
+
`isSigningOut` (`selectBetterAuthIsSigningOut`) is true. A sign-out that fails while the session
|
|
122
|
+
is still live stays signed in and sets `error` to `SIGN_OUT_FAILED_MESSAGE`. With `apisToReset`,
|
|
123
|
+
the cache purges when the session clears, not at dispatch.
|
|
124
|
+
|
|
125
|
+
**Why**
|
|
126
|
+
|
|
127
|
+
A login screen that opened at once could find the still-live session and sign the user straight
|
|
128
|
+
back in.
|
|
129
|
+
|
|
130
|
+
**Migration**
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
// Before: navigate as soon as logout is dispatched
|
|
134
|
+
dispatch(betterAuth.actions.logout());
|
|
135
|
+
router.replace("/login");
|
|
136
|
+
|
|
137
|
+
// After: navigate when the session clears; show progress meanwhile
|
|
138
|
+
const isSigningOut = useSelector(selectBetterAuthIsSigningOut);
|
|
139
|
+
const isAuthenticated = useSelector(selectBetterAuthIsAuthenticated);
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### `TextField` `autoComplete` now reaches the browser
|
|
143
|
+
|
|
144
|
+
**What changed**
|
|
145
|
+
|
|
146
|
+
`TextField` sets `autoComplete` as the input's `autocomplete` attribute on web, and as
|
|
147
|
+
`autoComplete` / `textContentType` on native. `autoComplete="off"` now turns autofill off on web.
|
|
148
|
+
`LoginScreen` and `SignUpScreen` default email to `"username"`.
|
|
149
|
+
|
|
150
|
+
**Why**
|
|
151
|
+
|
|
152
|
+
Password managers could not offer saved credentials or new passwords.
|
|
153
|
+
|
|
154
|
+
**Migration**
|
|
155
|
+
|
|
156
|
+
```tsx
|
|
157
|
+
// Before
|
|
158
|
+
<TextField autoComplete="off" type="email" ... />
|
|
159
|
+
<TextField type="password" ... /> // on a set-new-password form
|
|
160
|
+
|
|
161
|
+
// After
|
|
162
|
+
<TextField autoComplete="username" type="email" ... /> // "email" on sign-up
|
|
163
|
+
<TextField autoComplete="new-password" type="password" ... />
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Hosted Terreno MCP server moves to `https://mcp.terreno.app/mcp`
|
|
167
|
+
|
|
168
|
+
**What changed**
|
|
169
|
+
|
|
170
|
+
Docs and `create-terreno-app` now use `https://mcp.terreno.app/mcp`.
|
|
171
|
+
`https://mcp.terreno.flourish.health/mcp` keeps serving for at least one more release.
|
|
172
|
+
|
|
173
|
+
**Why**
|
|
174
|
+
|
|
175
|
+
The hosted server has a Terreno-owned domain.
|
|
176
|
+
|
|
177
|
+
**Migration**
|
|
178
|
+
|
|
179
|
+
```json
|
|
180
|
+
// Before (.cursor/mcp.json, .mcp.json)
|
|
181
|
+
{"url": "https://mcp.terreno.flourish.health/mcp"}
|
|
182
|
+
|
|
183
|
+
// After
|
|
184
|
+
{"url": "https://mcp.terreno.app/mcp"}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Re-run `claude mcp add` registrations with the new URL. See `docs/how-to/set-up-terreno-mcp.md`.
|
|
188
|
+
|
|
189
|
+
### `SyncStatusBanner` waits before showing Offline
|
|
190
|
+
|
|
191
|
+
**What changed**
|
|
192
|
+
|
|
193
|
+
The Offline badge appears only after `isOnline` stays false for `offlineGraceMs` (default 3000 ms).
|
|
194
|
+
|
|
195
|
+
**Why**
|
|
196
|
+
|
|
197
|
+
It flashed Offline during the first socket connect and brief drops.
|
|
198
|
+
|
|
199
|
+
**Migration**
|
|
200
|
+
|
|
201
|
+
```tsx
|
|
202
|
+
// Before: shown immediately
|
|
203
|
+
<SyncStatusBanner />
|
|
204
|
+
|
|
205
|
+
// After: pass 0 to keep the old behavior
|
|
206
|
+
<SyncStatusBanner offlineGraceMs={0} />
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## Deprecations
|
|
210
|
+
|
|
211
|
+
- `installTerrenoDevConsoleLogger` moves to `@terreno/syncdb` (also
|
|
212
|
+
`@terreno/syncdb/devConsoleLogger`) and takes `{baseUrl}`. The `@terreno/rtk` export is a
|
|
213
|
+
deprecated wrapper that keeps working with no arguments.
|
|
214
|
+
- `terreno-mcp-local` `get_rtk_state` is a deprecated alias of the new `get_client_state` for one
|
|
215
|
+
release. It returns the same wrapped response, now with a `layer` field.
|
|
216
|
+
|
|
217
|
+
## New capabilities
|
|
218
|
+
|
|
219
|
+
- Better Auth features as config: `accountLinking`, `twoFactor`, `passkey`, `signUp: {mode: "closed"}`,
|
|
220
|
+
`oauthProxy`, `oauthHandoff` linking, and the Sign in with Apple key form. Login screens read
|
|
221
|
+
`GET {basePath}/terreno/methods` through `useAuthMethods`.
|
|
222
|
+
- Drop-in screens in `@terreno/rtk`: `TerrenoLoginScreen` (remembered accounts, passkeys, inline
|
|
223
|
+
two-factor) and `TerrenoAccountSecurityScreen`, built on new `@terreno/ui` security cards,
|
|
224
|
+
`OtpField`, `TwoFactorChallengeScreen`, and `FormContainer`.
|
|
225
|
+
- `AccountSecurityApp` for staff account recovery under `/users/:id/security`, org-admin recovery
|
|
226
|
+
under `/orgs/:orgId/members/:userId/security`, security notification emails, and
|
|
227
|
+
`UserSecurityScreen` / `MemberSecurityPanel` in `@terreno/admin-frontend`.
|
|
228
|
+
- `ListDetailLayout` in `@terreno/ui`, provisional consent signing, and MCP OAuth
|
|
229
|
+
`client_credentials` with loopback redirect ports.
|
|
230
|
+
|
|
231
|
+
## Verification
|
|
232
|
+
|
|
233
|
+
- `bun run compile` and `bun run lint` at the app root.
|
|
234
|
+
- `cd <your frontend> && bun run sdk` to pick up the new auth and security routes.
|
|
235
|
+
- Sign up with an email that matches an existing unverified `User` and confirm a 409
|
|
236
|
+
`VERIFY_EMAIL_TO_USE_ACCOUNT`; sign up with that `User`'s password and confirm it links.
|
|
237
|
+
- Sign out on web and confirm the app does not sign back in before the server answers.
|
|
238
|
+
- Load key screens at 900pt width and confirm the narrow layout reads well.
|
|
@@ -5,11 +5,25 @@ enforced in code before and after this judgment; your level moves only inside th
|
|
|
5
5
|
|
|
6
6
|
## Levels
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
|
11
|
-
|
|
|
12
|
-
|
|
|
8
|
+
Most features are medium. High is reserved for security-related code.
|
|
9
|
+
|
|
10
|
+
| Level | Human approvals | Meaning | Examples |
|
|
11
|
+
| --- | --- | --- | --- |
|
|
12
|
+
| low | None, once automated review and security checks are green | A mistake is cheap and visible; the PR proves itself | docs; copy and i18n strings; tests, snapshots, fixtures; internal-only features (admin screens, dev tooling, scripts no user runs); additive changes behind a feature flag that defaults off; lint autofix; regenerated files whose source changed in the same PR; changelog fragments; dependency patch bumps with green CI; one-line bug fixes with a regression test; small presentational UI tweaks (see below) |
|
|
13
|
+
| medium | One person | Ordinary product changes, and changes to sensitive areas | new or changed behavior with tests; UI changes with screenshots beyond a small presentational tweak; new endpoints with permission tests; schema changes, migrations, and backfills; caching (RTK tags, syncdb, HTTP/CDN caching); app updates (expo-updates, `eas.json`, `app.json`, release channels); AI system prompts and tool definitions; billing; infra, Dockerfiles, CI; public API shape (removed, renamed, or retyped exports, props, or endpoints); dependency minor and major bumps; feature-flag removals; hotfix branches; deleted or skipped tests |
|
|
14
|
+
| high | One person | Security-related code, where a mistake is costly, hidden, or hard to undo | auth, sessions, tokens, Better Auth and Passport config; permissions, RBAC, `queryFilter`s that scope data to a user; MCP OAuth; `socketAuth` and `sessionRevalidation`; secrets and env files; crypto, hashing, signature verification (webhook verifiers); PHI handling, consent, and audit logging; rate limits on unauthenticated routes; CORS and trusted origins; IAM, network, and firewall rules in infra; dependencies added for auth, crypto, or parsing untrusted input; reverts of security changes |
|
|
15
|
+
|
|
16
|
+
A PR is high only when it changes how the system decides who may see or do something,
|
|
17
|
+
or how secrets, credentials, or PHI are handled. Touching a security-adjacent file for an
|
|
18
|
+
unrelated reason (a typo, a log message, a rename) is judged by what the change does.
|
|
19
|
+
|
|
20
|
+
A **small presentational UI tweak** is low. It changes how one component looks or when it
|
|
21
|
+
shows something (layout, spacing, copy, color, a display delay or debounce, an animation)
|
|
22
|
+
and nothing else: no data fetching or syncdb/RTK changes, no navigation, no change to what
|
|
23
|
+
a user can submit or do, no auth or consent screens. It may add an optional prop with a
|
|
24
|
+
default, and that default may change what every consumer sees, as long as the old look is
|
|
25
|
+
one prop away. It still needs tests that would fail without the change and before/after
|
|
26
|
+
screenshots or video. A changelog fragment and docs update do not raise it.
|
|
13
27
|
|
|
14
28
|
## Dimensions
|
|
15
29
|
|
|
@@ -27,7 +41,8 @@ Score each 0–3 (0 absent, 1 weak, 2 adequate, 3 strong) and give reasons a rev
|
|
|
27
41
|
or weakened; snapshot updates explained; a coverage gate check green when present.
|
|
28
42
|
- **Evidence**: the `Verification` table names commands and outcomes, not just commands;
|
|
29
43
|
UI changes carry screenshots or video; CI is green on this head; `⚠️ Not tested` rows
|
|
30
|
-
are absent or
|
|
44
|
+
are absent or each carries an explicit reviewer instruction (the blocked behavior and the
|
|
45
|
+
exact steps to check it); each plan acceptance criterion
|
|
31
46
|
has its named verification present.
|
|
32
47
|
|
|
33
48
|
## Architecture: Terreno rules
|
|
@@ -99,13 +114,14 @@ A finding here is blocking when it is certain and should-fix when it is likely.
|
|
|
99
114
|
- A new runtime dependency is named in the PR body with the reason and is on the catalog
|
|
100
115
|
in `.rulesync/rules/01-dependency-catalog.md` or justified against it; a missing reason
|
|
101
116
|
lowers Architecture. Dev-only additions need no reason.
|
|
102
|
-
- Patch bumps of catalog packages with green CI stay low. Minor bumps
|
|
103
|
-
|
|
104
|
-
|
|
117
|
+
- Patch bumps of catalog packages with green CI stay low. Minor and major bumps, Expo SDK
|
|
118
|
+
and React Native upgrades, Mongoose majors, and lockfile-only changes without a
|
|
119
|
+
`package.json` change are medium. Better Auth and other auth or crypto majors are high.
|
|
105
120
|
- A bump that changes generated output (`bun.lock`, snapshots, `openApiSdk.ts`,
|
|
106
121
|
`plugins/terreno-claude/`) ships the regenerated output in the same PR.
|
|
107
122
|
- A new dependency's license passes `bun run check:licenses`.
|
|
108
|
-
- A `patches/` change is high
|
|
123
|
+
- A `patches/` change is medium (high when it patches an auth, crypto, or parsing
|
|
124
|
+
dependency): it edits a third party and needs the upstream issue linked.
|
|
109
125
|
- Removing a dependency the code still imports is a correctness finding; `knip` must stay
|
|
110
126
|
at zero findings.
|
|
111
127
|
|
|
@@ -136,11 +152,13 @@ A finding here is blocking when it is certain and should-fix when it is likely.
|
|
|
136
152
|
|
|
137
153
|
## Signals that move the level
|
|
138
154
|
|
|
139
|
-
- Raise one level
|
|
155
|
+
- Raise one level, never above medium unless the change is security-related: more than
|
|
156
|
+
two packages touched; deletions outnumber additions in
|
|
140
157
|
source; a `TODO`, `FIXME`, or commented-out code added; explicit `any` added; PR body
|
|
141
158
|
missing `Why`, `What changed`, or `Verification`; `WIP` in the title; the plan edited
|
|
142
159
|
in the same PR to match the code.
|
|
143
|
-
- Never low: new logic without a test; a `⚠️ Not tested` row without
|
|
160
|
+
- Never low: new logic without a test; a `⚠️ Not tested` row without an explicit reviewer instruction
|
|
161
|
+
(a row that does not name the blocked behavior and the exact steps to check it); an unresolved bot thread;
|
|
144
162
|
a revert.
|
|
145
163
|
- Size never moves the level. Changed lines and file counts say nothing about risk on
|
|
146
164
|
their own: judge what the lines do. Pure renames and moves, regenerated SDK or
|
|
@@ -15,12 +15,15 @@ Understanding-oriented documentation: concepts, architecture, and context.
|
|
|
15
15
|
- [Loop engineering](loop-engineering.md) — Fresh-invocation lifecycle, state, evidence, and orchestration boundaries
|
|
16
16
|
- [GitHub issue lifecycle](../how-to/github-issue-lifecycle.md) — Pick-ready issues and plan comments for Pick/Roast
|
|
17
17
|
- [Install agent skills](../how-to/install-agent-skills.md) — `npx skills add TerrenoLabs/terreno`
|
|
18
|
+
- [AI development loop](ai-development-loop.md) — The five-stage Ask, Generate, Run, Observe, Fix loop, what the agent can and cannot see, and how it composes with expo-mcp, Playwright MCP, and Maestro
|
|
19
|
+
- [Agent guidelines](agent-guidelines.md) — How hosted docs, per-package `.ai/` guidelines, and `.rulesync/` rules reach an agent, and how apps extend them
|
|
18
20
|
- [AI observability](ai-observability.md) — Two planes (telemetry vs control), plugins vs LangfuseApp, SOP loop
|
|
19
21
|
- [Durable agent harness](durable-agent-harness.md) — Phases, checkpoints, and why the audit span shares the checkpoint transaction
|
|
20
22
|
- [AI-powered workflows](ai-workflows.md) — Autonomous documentation, testing, and maintenance workflows
|
|
21
23
|
- [Agent UI Asks](agent-ui-asks.md) — Why agent questions are client-side tool calls, the pause and resume round trip, why cards carry exact answers, the watch paths, asks vs blocks
|
|
22
24
|
- [Agent UI blocks](agent-ui-blocks.md) — Why chat replies use a closed YAML catalog instead of free-form UI, why steppers and checklists call the server, and the rich-blocks rollout
|
|
23
25
|
- [Authentication architecture](authentication.md) — Better Auth, JWT, and optional MCP service tokens
|
|
26
|
+
- [Account linking](account-linking.md) — Linking modes, why both emails must be verified, Apple relay emails, and migrating JWT users
|
|
24
27
|
- [Organization tenancy](organizations.md) — Optional Membership-backed tenancy, context, and isolation boundaries
|
|
25
28
|
- [Configuration system](configuration-system.md) — Runtime configuration with database persistence
|
|
26
29
|
- [Dependency management](dependency-management.md) — Catalog pins, exercise tests, fingerprint freeze
|
|
@@ -34,8 +37,10 @@ Understanding-oriented documentation: concepts, architecture, and context.
|
|
|
34
37
|
- [Versioning policy](versioning-policy.md) — Lockstep `@terreno/*` versions, pre-1.0 breaks, deprecation window
|
|
35
38
|
- [How admin interfaces are shaped](admin-interface.md) — Screens, sidebar, `apiBase` vs `routeBase`
|
|
36
39
|
- [Admin plugin frontend widgets](admin-plugin-frontend.md) — Widget IDs from backend plugins
|
|
40
|
+
- [Consent forms](consent-forms.md) — Versions, responses, the consent gate, and why provisional signatures don't clear it
|
|
37
41
|
- [Consent admin migration](admin-consent-migration.md) — Which consent screens stay hand-written
|
|
38
42
|
- [Public roadmap process](roadmap-process.md) — GitHub roadmap vs Linear execution
|
|
39
43
|
- [Example app coverage](example-coverage.md) — Which framework capabilities the example apps exercise
|
|
40
44
|
- [Repository settings](repository-settings.md) — Maintainer GitHub settings that cannot be committed
|
|
41
45
|
- [Why Terreno owns chart SVG](charts.md) — Owned `react-native-svg` vs victory-native
|
|
46
|
+
- [Responsive design](responsive-design.md) — Support levels, breakpoints, and the desktop floor
|