@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.
Files changed (197) hide show
  1. package/CHANGELOG.md +393 -0
  2. package/README.md +32 -269
  3. package/dist/bootstrap.d.ts +12 -0
  4. package/dist/bootstrap.d.ts.map +1 -1
  5. package/dist/bootstrap.js +77 -122
  6. package/dist/bootstrap.js.map +1 -1
  7. package/dist/docs/guidelines/ui/guidelines/core.md +1 -1
  8. package/dist/docs/resources/overview.md +4 -2
  9. package/dist/docs/resources/patterns.md +237 -176
  10. package/dist/docs/resources/ui.md +48 -17
  11. package/dist/docs/templates/bootstrap/frontend/app/(tabs)/profile.template.tsx +0 -26
  12. package/dist/docs/templates/bootstrap/frontend/app/_layout.template.tsx +3 -2
  13. package/dist/docs/templates/bootstrap/frontend/app/login.template.tsx +1 -1
  14. package/dist/docs/upgrades/57.20.0.md +238 -0
  15. package/dist/docs/versioned/next/approval-rubric.md +30 -12
  16. package/dist/docs/versioned/next/explanation/README.md +5 -0
  17. package/dist/docs/versioned/next/explanation/account-linking.md +169 -0
  18. package/dist/docs/versioned/next/explanation/admin-interface.md +1 -1
  19. package/dist/docs/versioned/next/explanation/agent-guidelines.md +173 -0
  20. package/dist/docs/versioned/next/explanation/ai-development-loop.md +196 -0
  21. package/dist/docs/versioned/next/explanation/authentication.md +45 -1
  22. package/dist/docs/versioned/next/explanation/consent-forms.md +105 -0
  23. package/dist/docs/versioned/next/explanation/example-coverage.md +1 -0
  24. package/dist/docs/versioned/next/explanation/local-first-data.md +1 -1
  25. package/dist/docs/versioned/next/explanation/organizations.md +37 -0
  26. package/dist/docs/versioned/next/explanation/planning-plugins.md +4 -0
  27. package/dist/docs/versioned/next/explanation/responsive-design.md +143 -0
  28. package/dist/docs/versioned/next/explanation/roadmap-seed-issues.md +11 -0
  29. package/dist/docs/versioned/next/explanation/schema-metadata.md +5 -0
  30. package/dist/docs/versioned/next/how-to/README.md +7 -2
  31. package/dist/docs/versioned/next/how-to/audit-log.md +22 -3
  32. package/dist/docs/versioned/next/how-to/automate-terreno-upgrades.md +169 -0
  33. package/dist/docs/versioned/next/how-to/build-list-detail-screens.md +160 -0
  34. package/dist/docs/versioned/next/how-to/circleci.md +15 -5
  35. package/dist/docs/versioned/next/how-to/configure-better-auth.md +673 -53
  36. package/dist/docs/versioned/next/how-to/create-a-terreno-app.md +8 -5
  37. package/dist/docs/versioned/next/how-to/debug-with-mcp.md +393 -23
  38. package/dist/docs/versioned/next/how-to/enable-mcp-oauth.md +54 -3
  39. package/dist/docs/versioned/next/how-to/migrate-rtk-to-syncdb.md +9 -0
  40. package/dist/docs/versioned/next/how-to/password-reset.md +1 -1
  41. package/dist/docs/versioned/next/how-to/recover-locked-out-user.md +112 -0
  42. package/dist/docs/versioned/next/how-to/run-tests-locally.md +37 -8
  43. package/dist/docs/versioned/next/how-to/set-up-social-login.md +424 -0
  44. package/dist/docs/versioned/next/how-to/set-up-terreno-mcp.md +241 -36
  45. package/dist/docs/versioned/next/how-to/sign-consent-on-behalf.md +139 -0
  46. package/dist/docs/versioned/next/how-to/upgrade-terreno.md +4 -0
  47. package/dist/docs/versioned/next/implementationPlans/ai-dev-loop-boost.md +47 -32
  48. package/dist/docs/versioned/next/implementationPlans/auth-experience.md +952 -0
  49. package/dist/docs/versioned/next/implementationPlans/consent-forms.md +8 -0
  50. package/dist/docs/versioned/next/implementationPlans/list-detail-layout.md +297 -0
  51. package/dist/docs/versioned/next/implementationPlans/login-redesign/HANDOFF.md +211 -0
  52. package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/AnotherWay.html +121 -0
  53. package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/BackupCode.html +72 -0
  54. package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/Cancelled.html +87 -0
  55. package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/ContinueEmail.html +64 -0
  56. package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/FirstVisit.html +81 -0
  57. package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/Main.html +88 -0
  58. package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/ManageAccounts.html +77 -0
  59. package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/NotLinked.html +92 -0
  60. package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/SigningIn.html +60 -0
  61. package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/TwoFactor.html +76 -0
  62. package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/TwoFactorError.html +81 -0
  63. package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/WrongPassword.html +69 -0
  64. package/dist/docs/versioned/next/implementationPlans/mcp-boost-parity.md +1 -1
  65. package/dist/docs/versioned/next/plans/2026-10-23-provisional-consent.md +297 -0
  66. package/dist/docs/versioned/next/plans/2026-10-29-fix-mcp-oauth-native-and-machine.md +68 -0
  67. package/dist/docs/versioned/next/reference/README.md +1 -0
  68. package/dist/docs/versioned/next/reference/account-security.md +700 -0
  69. package/dist/docs/versioned/next/reference/admin-backend.md +3 -1
  70. package/dist/docs/versioned/next/reference/admin-frontend.md +225 -1
  71. package/dist/docs/versioned/next/reference/api.md +461 -35
  72. package/dist/docs/versioned/next/reference/cli.md +1 -1
  73. package/dist/docs/versioned/next/reference/comms.md +8 -0
  74. package/dist/docs/versioned/next/reference/create-terreno-app.md +3 -1
  75. package/dist/docs/versioned/next/reference/environment-variables.md +9 -3
  76. package/dist/docs/versioned/next/reference/legacy/rtk.md +19 -3
  77. package/dist/docs/versioned/next/reference/lifecycle-plugin.md +6 -0
  78. package/dist/docs/versioned/next/reference/mcp-server.md +1158 -441
  79. package/dist/docs/versioned/next/reference/syncdb.md +8 -1
  80. package/dist/docs/versioned/next/reference/ui.md +1271 -27
  81. package/dist/docs/versioned/next/tasks/ai-dev-loop-boost.md +28 -16
  82. package/dist/docs/versioned/next/tutorials/README.md +4 -0
  83. package/dist/httpApp.d.ts +14 -0
  84. package/dist/httpApp.d.ts.map +1 -0
  85. package/dist/httpApp.js +99 -0
  86. package/dist/httpApp.js.map +1 -0
  87. package/dist/index.js +8 -90
  88. package/dist/index.js.map +1 -1
  89. package/dist/local/localTools.d.ts.map +1 -1
  90. package/dist/local/localTools.js +44 -14
  91. package/dist/local/localTools.js.map +1 -1
  92. package/dist/local/metro/metroDevSession.d.ts +18 -0
  93. package/dist/local/metro/metroDevSession.d.ts.map +1 -1
  94. package/dist/local/metro/metroDevSession.js +42 -3
  95. package/dist/local/metro/metroDevSession.js.map +1 -1
  96. package/dist/local/tools/databaseQuery.d.ts.map +1 -1
  97. package/dist/local/tools/databaseQuery.js +3 -13
  98. package/dist/local/tools/databaseQuery.js.map +1 -1
  99. package/dist/local/tools/databaseSchema.d.ts.map +1 -1
  100. package/dist/local/tools/databaseSchema.js +3 -13
  101. package/dist/local/tools/databaseSchema.js.map +1 -1
  102. package/dist/local/tools/localMongo.d.ts +10 -0
  103. package/dist/local/tools/localMongo.d.ts.map +1 -0
  104. package/dist/local/tools/localMongo.js +30 -0
  105. package/dist/local/tools/localMongo.js.map +1 -0
  106. package/dist/local/tools/runtime.d.ts +18 -2
  107. package/dist/local/tools/runtime.d.ts.map +1 -1
  108. package/dist/local/tools/runtime.js +75 -20
  109. package/dist/local/tools/runtime.js.map +1 -1
  110. package/dist/local/tools/syncDb.d.ts +11 -0
  111. package/dist/local/tools/syncDb.d.ts.map +1 -1
  112. package/dist/local/tools/syncDb.js +15 -3
  113. package/dist/local/tools/syncDb.js.map +1 -1
  114. package/dist/rateLimit.d.ts +35 -0
  115. package/dist/rateLimit.d.ts.map +1 -0
  116. package/dist/rateLimit.js +112 -0
  117. package/dist/rateLimit.js.map +1 -0
  118. package/dist/tools.d.ts.map +1 -1
  119. package/dist/tools.js +42 -60
  120. package/dist/tools.js.map +1 -1
  121. package/package.json +4 -3
  122. package/src/docs/guidelines/ui/guidelines/core.md +1 -1
  123. package/src/docs/resources/overview.md +4 -2
  124. package/src/docs/resources/patterns.md +237 -176
  125. package/src/docs/resources/ui.md +48 -17
  126. package/src/docs/templates/bootstrap/frontend/app/(tabs)/profile.template.tsx +0 -26
  127. package/src/docs/templates/bootstrap/frontend/app/_layout.template.tsx +3 -2
  128. package/src/docs/templates/bootstrap/frontend/app/login.template.tsx +1 -1
  129. package/src/docs/upgrades/57.20.0.md +238 -0
  130. package/src/docs/versioned/next/approval-rubric.md +30 -12
  131. package/src/docs/versioned/next/explanation/README.md +5 -0
  132. package/src/docs/versioned/next/explanation/account-linking.md +169 -0
  133. package/src/docs/versioned/next/explanation/admin-interface.md +1 -1
  134. package/src/docs/versioned/next/explanation/agent-guidelines.md +173 -0
  135. package/src/docs/versioned/next/explanation/ai-development-loop.md +196 -0
  136. package/src/docs/versioned/next/explanation/authentication.md +45 -1
  137. package/src/docs/versioned/next/explanation/consent-forms.md +105 -0
  138. package/src/docs/versioned/next/explanation/example-coverage.md +1 -0
  139. package/src/docs/versioned/next/explanation/local-first-data.md +1 -1
  140. package/src/docs/versioned/next/explanation/organizations.md +37 -0
  141. package/src/docs/versioned/next/explanation/planning-plugins.md +4 -0
  142. package/src/docs/versioned/next/explanation/responsive-design.md +143 -0
  143. package/src/docs/versioned/next/explanation/roadmap-seed-issues.md +11 -0
  144. package/src/docs/versioned/next/explanation/schema-metadata.md +5 -0
  145. package/src/docs/versioned/next/how-to/README.md +7 -2
  146. package/src/docs/versioned/next/how-to/audit-log.md +22 -3
  147. package/src/docs/versioned/next/how-to/automate-terreno-upgrades.md +169 -0
  148. package/src/docs/versioned/next/how-to/build-list-detail-screens.md +160 -0
  149. package/src/docs/versioned/next/how-to/circleci.md +15 -5
  150. package/src/docs/versioned/next/how-to/configure-better-auth.md +673 -53
  151. package/src/docs/versioned/next/how-to/create-a-terreno-app.md +8 -5
  152. package/src/docs/versioned/next/how-to/debug-with-mcp.md +393 -23
  153. package/src/docs/versioned/next/how-to/enable-mcp-oauth.md +54 -3
  154. package/src/docs/versioned/next/how-to/migrate-rtk-to-syncdb.md +9 -0
  155. package/src/docs/versioned/next/how-to/password-reset.md +1 -1
  156. package/src/docs/versioned/next/how-to/recover-locked-out-user.md +112 -0
  157. package/src/docs/versioned/next/how-to/run-tests-locally.md +37 -8
  158. package/src/docs/versioned/next/how-to/set-up-social-login.md +424 -0
  159. package/src/docs/versioned/next/how-to/set-up-terreno-mcp.md +241 -36
  160. package/src/docs/versioned/next/how-to/sign-consent-on-behalf.md +139 -0
  161. package/src/docs/versioned/next/how-to/upgrade-terreno.md +4 -0
  162. package/src/docs/versioned/next/implementationPlans/ai-dev-loop-boost.md +47 -32
  163. package/src/docs/versioned/next/implementationPlans/auth-experience.md +952 -0
  164. package/src/docs/versioned/next/implementationPlans/consent-forms.md +8 -0
  165. package/src/docs/versioned/next/implementationPlans/list-detail-layout.md +297 -0
  166. package/src/docs/versioned/next/implementationPlans/login-redesign/HANDOFF.md +211 -0
  167. package/src/docs/versioned/next/implementationPlans/login-redesign/boards/AnotherWay.html +121 -0
  168. package/src/docs/versioned/next/implementationPlans/login-redesign/boards/BackupCode.html +72 -0
  169. package/src/docs/versioned/next/implementationPlans/login-redesign/boards/Cancelled.html +87 -0
  170. package/src/docs/versioned/next/implementationPlans/login-redesign/boards/ContinueEmail.html +64 -0
  171. package/src/docs/versioned/next/implementationPlans/login-redesign/boards/FirstVisit.html +81 -0
  172. package/src/docs/versioned/next/implementationPlans/login-redesign/boards/Main.html +88 -0
  173. package/src/docs/versioned/next/implementationPlans/login-redesign/boards/ManageAccounts.html +77 -0
  174. package/src/docs/versioned/next/implementationPlans/login-redesign/boards/NotLinked.html +92 -0
  175. package/src/docs/versioned/next/implementationPlans/login-redesign/boards/SigningIn.html +60 -0
  176. package/src/docs/versioned/next/implementationPlans/login-redesign/boards/TwoFactor.html +76 -0
  177. package/src/docs/versioned/next/implementationPlans/login-redesign/boards/TwoFactorError.html +81 -0
  178. package/src/docs/versioned/next/implementationPlans/login-redesign/boards/WrongPassword.html +69 -0
  179. package/src/docs/versioned/next/implementationPlans/mcp-boost-parity.md +1 -1
  180. package/src/docs/versioned/next/plans/2026-10-23-provisional-consent.md +297 -0
  181. package/src/docs/versioned/next/plans/2026-10-29-fix-mcp-oauth-native-and-machine.md +68 -0
  182. package/src/docs/versioned/next/reference/README.md +1 -0
  183. package/src/docs/versioned/next/reference/account-security.md +700 -0
  184. package/src/docs/versioned/next/reference/admin-backend.md +3 -1
  185. package/src/docs/versioned/next/reference/admin-frontend.md +225 -1
  186. package/src/docs/versioned/next/reference/api.md +461 -35
  187. package/src/docs/versioned/next/reference/cli.md +1 -1
  188. package/src/docs/versioned/next/reference/comms.md +8 -0
  189. package/src/docs/versioned/next/reference/create-terreno-app.md +3 -1
  190. package/src/docs/versioned/next/reference/environment-variables.md +9 -3
  191. package/src/docs/versioned/next/reference/legacy/rtk.md +19 -3
  192. package/src/docs/versioned/next/reference/lifecycle-plugin.md +6 -0
  193. package/src/docs/versioned/next/reference/mcp-server.md +1158 -441
  194. package/src/docs/versioned/next/reference/syncdb.md +8 -1
  195. package/src/docs/versioned/next/reference/ui.md +1271 -27
  196. package/src/docs/versioned/next/tasks/ai-dev-loop-boost.md +28 -16
  197. package/src/docs/versioned/next/tutorials/README.md +4 -0
@@ -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, installTerrenoDevConsoleLogger, selectBetterAuthUserId} from "@terreno/rtk";
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
 
@@ -68,7 +68,7 @@ const LoginScreen: React.FC = () => {
68
68
  />
69
69
  )}
70
70
  <TextField
71
- autoComplete="off"
71
+ autoComplete="username"
72
72
  disabled={isLoading}
73
73
  onChange={setEmail}
74
74
  placeholder="Email"
@@ -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
- | Level | Meaning | Examples |
9
- | --- | --- | --- |
10
- | low | A mistake is cheap and visible; the PR proves itself | docs; tests, snapshots, fixtures; copy and i18n strings; 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 |
11
- | medium | Ordinary product logic a reviewer would read once | new or changed behavior with tests; UI changes with screenshots; new endpoints with permission tests; minor dependency bumps; refactors inside one package |
12
- | high | A mistake is costly, hidden, or hard to undo | auth, sessions, permissions; schema and migrations; data backfills; billing; PHI handling; secrets, env, infra, Dockerfiles; CI; public API shape; lockstep framework bumps; feature-flag removals; reverts of security changes; hotfix branches with commits not on master; deleted or skipped tests |
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 covered by an explicit reviewer instruction; each plan acceptance criterion
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 are medium. Major
103
- bumps, Expo SDK and React Native upgrades, Better Auth and Mongoose majors, and
104
- lockfile-only changes without a `package.json` change are high.
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: it edits a third party and needs the upstream issue linked.
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: more than two packages touched; deletions outnumber additions in
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 a reviewer action; an unresolved bot thread;
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
@@ -0,0 +1,169 @@
1
+ # Account linking
2
+
3
+ One person can sign in to a Terreno app in several ways: email and password, Google, GitHub,
4
+ Apple, or a passkey. Account linking decides what happens when a new sign-in method arrives with
5
+ an email that already belongs to an account. Done carelessly, it is an account takeover. This page
6
+ explains the three modes, why Terreno asks for proof of the email before it links anything, and
7
+ how apps that started on JWT auth move their users over.
8
+
9
+ Setup: [Auth features](../how-to/configure-better-auth.md#auth-features). Reference:
10
+ [`GET {basePath}/terreno/methods`](../reference/api.md#better-auth-authentication).
11
+
12
+ ## Two layers of accounts
13
+
14
+ A Terreno app with Better Auth has two records per person:
15
+
16
+ - **The Better Auth user** (`user` collection) owns sign-in. Each sign-in method is an `account`
17
+ row under it: `credential` for email and password, `google`, `github`, and so on. Better Auth
18
+ keeps emails unique, so there is at most one Better Auth user per email.
19
+ - **The app `User`** (your Mongoose model) owns everything else: permissions, `admin`,
20
+ `disabled`, org memberships, and your data. It points at its Better Auth user through
21
+ `betterAuthId`.
22
+
23
+ Linking happens at both layers. Better Auth decides whether a new provider `account` joins an
24
+ existing Better Auth user. Terreno decides whether a Better Auth user may claim an existing app
25
+ `User` (`syncBetterAuthUser`).
26
+
27
+ ## The three modes
28
+
29
+ Set `accountLinking: {mode}` on `BetterAuthConfig`. The mode controls the Better Auth layer.
30
+
31
+ | Mode | A same-email sign-in with a new method | `POST {basePath}/link-social` |
32
+ | --- | --- | --- |
33
+ | `verified-email` (default) | Joins the account when both emails are verified | Links |
34
+ | `explicit` | Refused with `account_not_linked`; nothing is created | Links, from a signed-in session |
35
+ | `never` | Refused with `account_not_linked`; nothing is created | Refused with 403 `LINKING_DISABLED` |
36
+
37
+ - **`verified-email`** is the least friction: someone who signed up with email and password and
38
+ later taps "Continue with Google" on the same address lands in the same account.
39
+ - **`explicit`** makes the user prove they own the existing account first. They sign in the old
40
+ way, open the Security screen, and link the provider there. A login screen should turn
41
+ `account_not_linked` into "sign in with your original method, then link this one".
42
+ - **`never`** keeps every sign-in method separate. Because Better Auth keeps emails unique, a
43
+ second method on the same email cannot sign in at all. Pick it when a shared inbox must not
44
+ join two people's records.
45
+
46
+ `/terreno/methods` reports the mode so clients can word their guidance. Unlinking the only
47
+ remaining sign-in method is refused in every mode (400 `FAILED_TO_UNLINK_LAST_ACCOUNT`), so a user
48
+ cannot lock themselves out. rtk's `TerrenoAccountSecurityScreen` checks the same rule before it
49
+ asks, and a social-only user can add a password there (`POST {basePath}/terreno/set-password`) to
50
+ gain a second method before unlinking a provider.
51
+
52
+ ### What the refusals look like
53
+
54
+ | Path | `account_not_linked` | Unverified email matches an app `User` |
55
+ | --- | --- | --- |
56
+ | OAuth redirect (`/callback/{provider}`) | Redirect with `?error=account_not_linked` | Redirect with `?error=VERIFY_EMAIL_TO_USE_ACCOUNT` |
57
+ | ID-token sign-in (`/sign-in/social` with `idToken`) | 401, `message: "account not linked"` | 409, `code: "VERIFY_EMAIL_TO_USE_ACCOUNT"` |
58
+ | Email sign-up or sign-in | n/a | 409, `code: "VERIFY_EMAIL_TO_USE_ACCOUNT"`, `message: "Verify your email to use this account"` |
59
+
60
+ ## Why both emails must be verified
61
+
62
+ Linking by email trusts that whoever controls the email controls the account. That trust has to
63
+ hold on both sides:
64
+
65
+ - **The new method's email.** A provider that reports an unverified email has not checked that
66
+ the person owns it. Linking on it lets anyone who types your address into that provider into
67
+ your account. An unverified provider email never links, in any mode. `trustedProviders` turns
68
+ this check off for the providers it lists, so list only providers that prove email ownership.
69
+ - **The existing account's email.** If the existing account was itself created without proving
70
+ the email, an attacker can **pre-register**: sign up with the victim's address and a password
71
+ the attacker knows, wait for the victim to sign in with Google, and share the account.
72
+ Terreno keeps Better Auth's `requireLocalEmailVerified: true` in every mode, so an unverified
73
+ local account never absorbs a new method.
74
+
75
+ The same rule applies one layer down. A Better Auth user claims an existing app `User` only when
76
+ the provider verified its email, or when the user proves they own that `User` another way (see
77
+ the migration below). Terreno checks this **before the Better Auth user is created**. An
78
+ unverified sign-up for someone else's address gets 409 "Verify your email to use this account"
79
+ and leaves nothing behind: no Better Auth user, no `account` row, no verification email. That
80
+ matters because a Better Auth user left behind would be a pre-registration one layer down: once
81
+ anyone verified that email (the victim clicking the mail the attacker's sign-up triggered, or an
82
+ admin), the attacker's password would open the victim's `User`. Terreno never inserts a second
83
+ `User` for the same email either.
84
+
85
+ Two more rules hold whichever way the claim is proven. A `User` that already carries another
86
+ Better Auth account's `betterAuthId` is never relinked by email (409 `ACCOUNT_ALREADY_EXISTS`).
87
+ And when a verified email links a `User` whose own `emailVerified` is not `true`, the `User`'s
88
+ legacy password is removed and its `tokenEpoch` bumped: whoever set that password never proved
89
+ they own the mailbox, so it and the JWT refresh tokens issued from it stop working. A link proven
90
+ by the legacy password itself keeps that password. Operator seeds pass `trustEmail` to
91
+ `syncBetterAuthUser`, which skips these checks for accounts the seed created itself.
92
+
93
+ Emails are compared ignoring case, so a legacy `User` stored as `Jane@Example.com` is protected
94
+ from a sign-up as `jane@example.com`, and claiming it never creates a second `User`.
95
+
96
+ **Trade-off: the 409 reveals that an account exists.** A sign-up that gets 409
97
+ `VERIFY_EMAIL_TO_USE_ACCOUNT` tells the caller that an app `User` already has that email. Terreno
98
+ accepts this account-existence signal: refusing silently would either leave the
99
+ pre-registration hole open or leave legitimate users stuck with no explanation, and Better Auth's
100
+ own sign-up already answers `USER_ALREADY_EXISTS` for emails it knows. Apps that must hide
101
+ membership (for example, where an account implies care) should rate-limit sign-up (the `auth`
102
+ bucket) and word the client message neutrally.
103
+
104
+ The guard needs a user model on the Better Auth instance. `BetterAuthApp` uses its own
105
+ `userModel`, or adopts `TerrenoApp`'s when it was given none, before it creates Better Auth. If you
106
+ call `createBetterAuth` yourself, pass `userModel` to it whenever a session middleware links app
107
+ `User`s.
108
+
109
+ Terreno then syncs the app `User` as each Better Auth session is created, while the request that
110
+ proves who the user is (its password, its provider) is still in hand. That is also when
111
+ `oauthProvider` is recorded. The session check repeats the claim rule as a second guard, and a
112
+ password-proven link that fails to save fails the sign-in (500 `APP_USER_LINK_FAILED`) instead of
113
+ leaving a session that every request would refuse.
114
+
115
+ ## Apple private-relay emails
116
+
117
+ Sign in with Apple lets users hide their address. Apple then reports a relay address such as
118
+ `abc123@privaterelay.appleid.com`, marked verified, that forwards to the real inbox. It never
119
+ matches the email the person used elsewhere, so Apple sign-in creates a separate account in every
120
+ mode. Users who want one account sign in their usual way and link Apple from the Security screen
121
+ (`/link-social`). That link works only when Apple shares the real address ("Share My Email"):
122
+ Better Auth refuses a link whose email differs from the account's unless `allowDifferentEmails`
123
+ is on. Do not merge relay accounts by name: a name proves nothing.
124
+
125
+ ## Several accounts on one device
126
+
127
+ Linking answers "is this the same person?". Some people really have several accounts, for example
128
+ a parent with their own login and a child's. They should not merge them. `multiSession` lets one
129
+ device hold several signed-in accounts and switch between them without signing out, and
130
+ `signUp: {mode: "closed"}` stops new accounts altogether. Together with the linking mode, these
131
+ are the three knobs an app has over "multiple accounts".
132
+
133
+ ## Migrating users from JWT auth
134
+
135
+ Apps that started on Terreno's JWT and passport auth already have app `User`s with
136
+ passport-local-mongoose password hashes. When such a user first signs in through Better Auth with
137
+ the same email, Terreno links the new Better Auth user to their existing `User` if either:
138
+
139
+ - the sign-in comes from a provider (Google, GitHub, Apple, ...) that reports the email as
140
+ verified, or
141
+ - the password they sign up with verifies against the `User`'s existing passport hash. The
142
+ plaintext is checked only during that request and never stored. The check does not count as a
143
+ failed JWT login, so with passport's `limitAttempts` a stranger signing up with the victim's
144
+ email cannot lock the victim out.
145
+
146
+ Otherwise the sign-up answers 409 "Verify your email to use this account" and nothing is created.
147
+ Verifying the email afterwards cannot link anything, because there is no Better Auth user to
148
+ verify. Before this change, any unverified sign-up claimed the `User` with the same email, which
149
+ let anyone take over a legacy account by signing up with its address.
150
+
151
+ Users who signed in with GitHub through JWT have no password hash. They get in one of three ways:
152
+
153
+ - a provider sign-in whose email the provider verified;
154
+ - a password reset sent to their email through the JWT forgot-password flow
155
+ (`POST /auth/forgotPassword`), then a Better Auth sign-up with the new password;
156
+ - an admin-issued account recovery link (see [account security](../reference/api.md#better-auth-authentication)
157
+ once recovery is enabled) that sets their password and creates the Better Auth user.
158
+
159
+ To migrate smoothly:
160
+
161
+ 1. Keep the JWT auth routes mounted while users move over, so forgot-password still works.
162
+ 2. Tell users to sign up with the email and password they already use; those link at once.
163
+ 3. Point users without a password at a provider sign-in or at JWT forgot-password.
164
+
165
+ ## Learn more
166
+
167
+ - [Authentication architecture](authentication.md)
168
+ - [Configure Better Auth](../how-to/configure-better-auth.md)
169
+ - [Better Auth: users and accounts](https://www.better-auth.com/docs/concepts/users-accounts#account-linking)
@@ -55,7 +55,7 @@ row is `AuditEvent` when `AuditApp` is registered. `isAuditLogModel` also matche
55
55
  legacy `AdminAuditLog`, `audit-log`, and `audit-events` names. Jobs is a custom
56
56
  screen (`name: "jobs"`); the shell lifts it the same way.
57
57
 
58
- Below 768px the rail becomes a hamburger drawer. The main column is a body-style
58
+ Below the desktop floor (1024pt) the rail becomes a hamburger drawer. The main column is a body-style
59
59
  canvas (`neutral-050`, or `neutral-900` in dark mode so `surface.base` cards stand out).
60
60
  The colorful teal rail has no dark palette, so dark mode always uses the clinical rail.
61
61
  Host apps must give React Navigation a theme that follows the Terreno color scheme