@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
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# Responsive design
|
|
2
|
+
|
|
3
|
+
Terreno apps run on desktop web and native mobile. Responsive behavior follows the workflow, the available viewport, and how important that workflow is on that surface.
|
|
4
|
+
|
|
5
|
+
The goal is not to make every screen work everywhere. The goal is the best experience on the devices and workflows people actually use. Design specs name the supported breakpoints and the support level for each feature. Implement and test only those behaviors.
|
|
6
|
+
|
|
7
|
+
Breakpoints are viewport width in points, not a device model and not a physical screen resolution. Browser chrome, a resized window, split screen, and display scaling all change the available width.
|
|
8
|
+
|
|
9
|
+
## Surfaces
|
|
10
|
+
|
|
11
|
+
Primary devices define the design target. Support for other devices comes from the breakpoint system.
|
|
12
|
+
|
|
13
|
+
| Surface | Who it is for | Where it runs | Design target | Role |
|
|
14
|
+
| --- | --- | --- | --- | --- |
|
|
15
|
+
| Desktop web | People doing data-heavy work | Web, validated in Chrome | 13" laptop at `xl` (≥ 1280pt), such as a MacBook Air 13" | Primary experience. Full functionality, including complex review and multi-panel workflows. |
|
|
16
|
+
| Primary mobile | People whose main experience is the phone or tablet | iOS and Android | Large phone / small tablet at native `lg` (600–1023pt), such as a Samsung A11 | Full supported mobile workflows. |
|
|
17
|
+
| Reduced mobile | People who need emergency or lightweight access | iOS and Android | Common phones | A reduced set of workflows. Not a replacement for desktop. |
|
|
18
|
+
|
|
19
|
+
Other browsers are outside the desktop support contract. Do not create a layout for an individual phone or tablet model.
|
|
20
|
+
|
|
21
|
+
## Support levels
|
|
22
|
+
|
|
23
|
+
Every feature names one of these levels at each breakpoint it claims.
|
|
24
|
+
|
|
25
|
+
| Level | Definition | Example |
|
|
26
|
+
| --- | --- | --- |
|
|
27
|
+
| Fully supported | Designed and tested at this breakpoint. Intended workflows work. | A filter with search, groups, saved filters, and bulk actions on desktop `lg` and `xl`. |
|
|
28
|
+
| Supported with adaptation | Still fully usable. Layout or interaction is simpler so it fits. | The same filter on a tighter width becomes one category at a time, or moves advanced options behind an "Advanced filters" action. |
|
|
29
|
+
| Functionally available | Essential workflows can be finished. Complexity is intentionally omitted. | On reduced mobile, the filter keeps search and one or two high-value filters. |
|
|
30
|
+
| Not supported | Not available, not optimized, or not guaranteed. | The advanced filter below the minimum supported breakpoint. The app may still open. Layout and functionality are not guaranteed. |
|
|
31
|
+
|
|
32
|
+
### What a feature spec must define
|
|
33
|
+
|
|
34
|
+
| Item | Required? |
|
|
35
|
+
| --- | --- |
|
|
36
|
+
| Supported surfaces (desktop web, primary mobile, reduced mobile) | Yes |
|
|
37
|
+
| Supported breakpoints (`sm`, `md`, `lg`, `xl`) | Yes |
|
|
38
|
+
| Support level at each of those breakpoints | Yes |
|
|
39
|
+
| Behavior when a breakpoint is not supported | Yes |
|
|
40
|
+
| Layout changes at supported breakpoints | When the layout changes |
|
|
41
|
+
|
|
42
|
+
## Breakpoints
|
|
43
|
+
|
|
44
|
+
Tokens are platform-specific. `lg` is not the same width on web and native. The tables and the constants `WEB_BREAKPOINT_MIN_WIDTH` and `NATIVE_BREAKPOINT_MIN_WIDTH` are the same contract. API details: [Media query helpers](../reference/ui.md#media-query-helpers).
|
|
45
|
+
|
|
46
|
+
### Desktop web
|
|
47
|
+
|
|
48
|
+
The desktop web app is optimized for desktop. The minimum supported desktop breakpoint is `lg`.
|
|
49
|
+
|
|
50
|
+
| Category | Token | Viewport | Meaning |
|
|
51
|
+
| --- | --- | --- | --- |
|
|
52
|
+
| Primary desktop | `xl` | ≥ 1280pt | Full desktop layouts, multi-panel workflows, dense data. |
|
|
53
|
+
| Smaller desktop | `lg` | 1024–1279pt | Minimum supported desktop. Collapse secondary content, adjust panels, or simplify controls. Keep core workflows. |
|
|
54
|
+
| Below supported desktop | below `lg` | < 1024pt | Not a supported desktop experience. The app may stay reachable. Layouts are not guaranteed. |
|
|
55
|
+
|
|
56
|
+
On web, `sm` (320) and `md` (375) still classify widths below 1024 so a layout can stay reachable.
|
|
57
|
+
|
|
58
|
+
Test `xl` fully, including complex layouts and multi-panel interactions. Test `lg` to confirm critical workflows stay usable when space shrinks. Do not do breakpoint-specific testing below `lg`. Degrade gracefully there when it is cheap.
|
|
59
|
+
|
|
60
|
+
### Native mobile
|
|
61
|
+
|
|
62
|
+
| Category | Token | Viewport | Meaning |
|
|
63
|
+
| --- | --- | --- | --- |
|
|
64
|
+
| Extra large | `xl` | ≥ 1024pt | Not a supported mobile layout. Use desktop web where it exists. |
|
|
65
|
+
| Large mobile / tablet | `lg` | 600–1023pt | Primary tablet experience. Optimize supported mobile workflows here. |
|
|
66
|
+
| Standard mobile | `md` | 375–599pt | Common phone. Supported workflows stay usable. |
|
|
67
|
+
| Small mobile | `sm` | 320–374pt | Small phone. Critical workflows stay functional. Extra scrolling or a simpler layout is expected. |
|
|
68
|
+
| Below supported mobile | below `sm` | < 320pt | Not supported. The app may stay reachable. Layouts are not guaranteed. |
|
|
69
|
+
|
|
70
|
+
Test native `lg`, `md`, and `sm` unless the spec says otherwise. Do not do breakpoint-specific testing below `sm`.
|
|
71
|
+
|
|
72
|
+
A larger mobile screen does not automatically get the desktop layout. Use a breakpoint only when the layout actually changes.
|
|
73
|
+
|
|
74
|
+
`isSupportedDesktopWidth({width})` is the desktop floor: web `lg` and native `xl`, both 1024pt. Below that, do not ship a desktop multi-panel layout.
|
|
75
|
+
|
|
76
|
+
## Adapting a layout
|
|
77
|
+
|
|
78
|
+
### Desktop
|
|
79
|
+
|
|
80
|
+
Support a primary laptop display, a resized browser window, split screen, and a smaller window that is still inside `lg` or `xl`. Keep the workflow as width drops inside that range.
|
|
81
|
+
|
|
82
|
+
When width drops:
|
|
83
|
+
|
|
84
|
+
- Collapse secondary panels.
|
|
85
|
+
- Allow horizontal scrolling for a dense table.
|
|
86
|
+
- Move secondary actions into an overflow menu.
|
|
87
|
+
- Reduce spacing before removing content.
|
|
88
|
+
|
|
89
|
+
Do not shrink content until it is unreadable, compress a layout into a strip, or hide a critical workflow with no other way to reach it.
|
|
90
|
+
|
|
91
|
+
### Native
|
|
92
|
+
|
|
93
|
+
Mobile layouts favor finishing the task over matching desktop feature parity.
|
|
94
|
+
|
|
95
|
+
Reduced mobile, when a desktop workflow is too complex:
|
|
96
|
+
|
|
97
|
+
- Simplify the workflow.
|
|
98
|
+
- Reduce the available actions.
|
|
99
|
+
- Keep the high-value, time-sensitive tasks.
|
|
100
|
+
- Send people to desktop when the workflow needs it.
|
|
101
|
+
|
|
102
|
+
Do not recreate a desktop layout on mobile, support every desktop workflow, show a dense table or a multi-panel layout, or require a lot of horizontal scrolling.
|
|
103
|
+
|
|
104
|
+
Primary mobile should stay clear, readable, touch-friendly, and focused on the supported workflows.
|
|
105
|
+
|
|
106
|
+
## Checklist
|
|
107
|
+
|
|
108
|
+
Before building a new experience, answer these:
|
|
109
|
+
|
|
110
|
+
1. Is this desktop-only, mobile-only, or both? Is the workflow required on reduced mobile, or only on primary mobile?
|
|
111
|
+
2. What is the minimum supported width? When space decreases, does content collapse, stack, scroll, or hide?
|
|
112
|
+
3. What is the primary goal, which information is essential, and which actions are secondary?
|
|
113
|
+
4. Does this need a breakpoint, or a mobile variant of a component? Does it match an existing responsive pattern?
|
|
114
|
+
|
|
115
|
+
## Framework component audit
|
|
116
|
+
|
|
117
|
+
Audited against this policy. Pixel cutoffs that were not a breakpoint token were moved onto `isSupportedDesktopWidth`.
|
|
118
|
+
|
|
119
|
+
| Component | Result |
|
|
120
|
+
| --- | --- |
|
|
121
|
+
| `Box` responsive props (`sm` / `md` / `lg` / `xl`) | Compliant. One shared dimension listener. The largest matching token wins. |
|
|
122
|
+
| `DashboardGrid` | Compliant. Column counts follow `sm`, `md`, and `lg` (`xl` uses the `lg` count). |
|
|
123
|
+
| `SplitPage` (web) | Compliant. Side-by-side layout follows `isNarrowViewport()` unless the caller passes `narrowBelowWidth`. |
|
|
124
|
+
| `SplitPage` (native) | Compliant. Stacked list/detail at every native width, including tablet `lg`. Native tablets do not inherit the desktop split. |
|
|
125
|
+
| `Modal` | Compliant. Action sheet on native, centered dialog on web. |
|
|
126
|
+
| `DataTable` | Partial. Web keeps inline filters. Native collapses filters into one sheet. Horizontal scroll stays on for wide tables, including native, which is more scrolling than reduced mobile should require. |
|
|
127
|
+
| `Text` / `Heading` | Compliant. Separate web and native type scales. Type does not shrink again inside `sm`. |
|
|
128
|
+
| `Card` | Fixed. Container and display variants used a 768pt cutoff. They now follow the desktop floor. |
|
|
129
|
+
| `MarkdownEditor` | Fixed. Side-by-side edit/preview used a 768pt cutoff. It now follows the desktop floor. |
|
|
130
|
+
| `AdminShell` | Fixed. The sidebar became a hamburger drawer below 768pt. It now does that below the desktop floor. |
|
|
131
|
+
| `AdminFilterDrawer` | Fixed. The bottom sheet used the same 768pt cutoff. It now follows the desktop floor. `ADMIN_FILTER_MOBILE_BREAKPOINT` is 1024. |
|
|
132
|
+
| `AdminHome` | Fixed. Main and sidebar stay side by side only at or above the desktop floor, and only when the dashboard is not embedded. Below the floor they stack. |
|
|
133
|
+
| Admin SPA shell | Compliant. It wraps `AdminShell`, so it uses the same floor. |
|
|
134
|
+
| Example app screens | Compliant. `maxWidth={800}` is a content measure. Toolbars that sit in a row also wrap. No second pixel cutoff. |
|
|
135
|
+
| Sync debugger | Fixed. The event list and JSON inspector stack below the desktop floor. The screen still uses raw `View` and `Text` on purpose. |
|
|
136
|
+
| Demo headers | Fixed. Back-button visibility and header margin followed `isNarrowViewport()` once at module load. They now follow the desktop floor and update when the window resizes. |
|
|
137
|
+
| Demo stories (Tooltip, Multiselect, Accordion) | Fixed. Width switches followed a one-time `isNarrowViewport()` read. They now follow the desktop floor and update on resize. |
|
|
138
|
+
| Admin action rows | Partial. Some rows (`ConsentHistory`, consent editors) stay in one row without `wrap`. They are not a second breakpoint. |
|
|
139
|
+
| `DateTimeField` | Partial. Inline controls switch to an action sheet when the parent is narrower than 395pt, and also when `isNarrowViewport()` is true. 395pt is the field's own minimum, not a viewport token. |
|
|
140
|
+
| `Image` (`fullWidth`) | Gap. Width is read from `Dimensions` once at module load, so it does not track resize or rotation. |
|
|
141
|
+
| `SideDrawer` | Partial. Width is 40% on web and 95% on native, by platform rather than by breakpoint token. |
|
|
142
|
+
|
|
143
|
+
Do not add a new raw pixel cutoff. Use `Box` breakpoint props, `useResponsiveBreakpoint`, or `isSupportedDesktopWidth`.
|
|
@@ -1342,6 +1342,17 @@ One easy-to-use harness inside `@terreno/ai` that runs long-lived agentic work d
|
|
|
1342
1342
|
|
|
1343
1343
|
---
|
|
1344
1344
|
|
|
1345
|
+
## list-detail-layout
|
|
1346
|
+
|
|
1347
|
+
**Title:** `ListDetailLayout: a route-driven list/detail layout for phone and tablet`
|
|
1348
|
+
|
|
1349
|
+
**Labels:** `area:ui`, `type:feature`
|
|
1350
|
+
**Project fields:** Area=`ui`, Target=`Next`, Impact=`Feature`, IP=`list-detail-layout`, Status=`Planned`
|
|
1351
|
+
|
|
1352
|
+
- **Implementation plan:** [list-detail-layout.md](https://github.com/TerrenoLabs/terreno/blob/master/docs/implementationPlans/list-detail-layout.md)
|
|
1353
|
+
|
|
1354
|
+
---
|
|
1355
|
+
|
|
1345
1356
|
# Shipped, umbrella, and declined IPs
|
|
1346
1357
|
|
|
1347
1358
|
Tracking issues created for IPs that previously lacked a `**Roadmap issue:**` header.
|
|
@@ -43,6 +43,11 @@ const routerDescription = describeModelForRouter(Todo, {
|
|
|
43
43
|
|
|
44
44
|
System paths (`_id`, `__v`, `created`, `updated`, `deleted`) are marked with `system: true`.
|
|
45
45
|
|
|
46
|
+
`required` is true only when the field is required on every document. A conditional
|
|
47
|
+
`required` (a function, such as "required when `provisional` is true") leaves it false, so
|
|
48
|
+
OpenAPI and generated SDKs type the field as optional; Mongoose still enforces the condition
|
|
49
|
+
on save.
|
|
50
|
+
|
|
46
51
|
## Views (not canonical)
|
|
47
52
|
|
|
48
53
|
| Surface | Function | Notes |
|
|
@@ -27,15 +27,18 @@ Problem-oriented, practical steps. Use these when you know what you want to do.
|
|
|
27
27
|
- [Durable background jobs](background-jobs.md) — `@terreno/jobs`: enqueue, workers, schedules, Cloud Tasks / Vercel Queues
|
|
28
28
|
- [Create a Mongoose model](create-a-model.md) — Define models with proper conventions
|
|
29
29
|
- [Enable the framework audit log](audit-log.md) — `AuditApp`, per-router `audit`, append-only events
|
|
30
|
+
- [Sign consent on a user's behalf](sign-consent-on-behalf.md) — Provisional consent signatures: RBAC grant, signer screen, status checks, void, print
|
|
30
31
|
- [Seed a database](seed-a-database.md) — Idempotent sync, dry runs, and guarded reset-and-reseed
|
|
31
32
|
- [Run MongoDB migrations](run-mongodb-migrations.md) — Versioned `up`/`down` files, `terreno-migrate`, production gate
|
|
32
33
|
- [Add GitHub OAuth authentication](add-github-oauth.md) — Enable GitHub login for your API
|
|
33
34
|
- [Configure Better Auth](configure-better-auth.md) — Set up Better Auth with social OAuth (Google, GitHub, Apple)
|
|
35
|
+
- [Set up social login](set-up-social-login.md) — Register Google, GitHub, and Apple once on the stable deploy, proxy previews through it, and move stable onto one domain
|
|
34
36
|
- [Password reset and email verification](password-reset.md) — JWT routes, comms templates, Better Auth hooks
|
|
37
|
+
- [Recover a locked-out user](recover-locked-out-user.md) — Platform and org-admin recovery: 2FA, passkeys, providers, sessions, suspend
|
|
35
38
|
- [Add WebSocket integration](websocket-integration.md) — Set up real-time Socket.io connections
|
|
36
39
|
- [Expose Model Context Protocol tools](expose-mcp-tools.md) — Turn `modelRouter` models into MCP tools an LLM can call
|
|
37
|
-
- [Set up Terreno MCP](set-up-terreno-mcp.md) —
|
|
38
|
-
- [Debug with MCP](debug-with-mcp.md) —
|
|
40
|
+
- [Set up Terreno MCP](set-up-terreno-mcp.md) — Connect Cursor or Claude Code to the hosted docs/codegen server and the local runtime server
|
|
41
|
+
- [Debug with MCP](debug-with-mcp.md) — Follow a failure from symptom to verified fix with the local MCP tools
|
|
39
42
|
- [Connect an MCP client with a service token](connect-mcp-service-token.md) — Mint a personal `mcp_` key for Perplexity and other remote clients
|
|
40
43
|
- [Enable MCP OAuth for a JWT app](enable-mcp-oauth.md) — Let Claude.ai and ChatGPT connect to `/mcp` through OAuth 2.1 with your own consent screen
|
|
41
44
|
- [Install agent skills](install-agent-skills.md) — `npx skills`, the Cursor plugin, the Codex plugin, or the Claude Code plugin
|
|
@@ -48,7 +51,9 @@ Problem-oriented, practical steps. Use these when you know what you want to do.
|
|
|
48
51
|
- [Call external APIs](call-external-apis.md) — Authenticated HTTP client, retries, and error normalization for third-party integrations
|
|
49
52
|
- [Upgrade banner](upgrade-banner.md) — Soft warning and hard-block app update UX
|
|
50
53
|
- [Upgrade Terreno](upgrade-terreno.md) — Lockstep `@terreno/*` bumps, note range, order, rollback
|
|
54
|
+
- [Automate Terreno upgrades](automate-terreno-upgrades.md) — Hourly workflow that launches one upgrade agent when a week-old release is out and no upgrade is open
|
|
51
55
|
- [Track upstream Expo](track-upstream-expo.md) — Morning probe for SDK betas; `release-X.Y.Z` loop log + native fingerprint deps
|
|
56
|
+
- [Build a list/detail screen](build-list-detail-screens.md) — `ListDetailLayout` route group: list on a phone, list + detail panes on a tablet, selection in the URL
|
|
52
57
|
- [Show loading skeletons](show-loading-skeletons.md) — `loading`, `loadingComponent`, and `loadingAnimation` on Box, Card, charts, and Page; `withLoading` for custom components
|
|
53
58
|
- [Compose charts in a dashboard grid](charts-and-dashboards.md) — `LineChart` / `BarChart` / `AreaChart` / `DonutChart` inside `Card`s in `DashboardGrid` (ops-dashboard scorecards and spans: [dashboard-chart-parity](../implementationPlans/dashboard-chart-parity.md))
|
|
54
59
|
- [Compare rendered chart snapshots](compare-chart-rendered-snapshots.md) — PNG goldens for the chart visual gallery, not JSON snapshots
|
|
@@ -38,7 +38,10 @@ modelRouter("/todos", Todo, {
|
|
|
38
38
|
| PATCH `/:id/:field/:itemId` | `updated` | `arrayUpdate` | Changed fields; `recordId` set |
|
|
39
39
|
| DELETE `/:id/:field/:itemId` | `updated` | `arrayRemove` | Changed fields; `recordId` set |
|
|
40
40
|
|
|
41
|
-
`source` is `modelRouter` for these writes.
|
|
41
|
+
`source` is `modelRouter` for these writes. `ConsentApp` also writes `modelRouter` events for
|
|
42
|
+
provisional consent actions: `created`/`create` for a provisional signature, `updated`/`update` for a void, and
|
|
43
|
+
`viewed`/`read` when staff read another user's consent statuses. `read` and `viewed` mark
|
|
44
|
+
audited reads; no other surface writes them. Secrets (`password`, `hash`, `salt`, `token`,
|
|
42
45
|
`secret`, `refreshToken`) are omitted at every object and array depth, including compound
|
|
43
46
|
keys such as `tokenHash`. Extra `redact` names merge with that list as exact field names.
|
|
44
47
|
|
|
@@ -51,6 +54,22 @@ Admin diffs also omit each model's `hiddenFields` and `excludeFields`.
|
|
|
51
54
|
|
|
52
55
|
RBAC mutations fan into the same collection when `createAccess({auditSink: persistRbacAuditToAuditEvent})` is set. `source` is `rbac`.
|
|
53
56
|
|
|
57
|
+
### 4. Account security actions
|
|
58
|
+
|
|
59
|
+
`AccountSecurityApp` writes the same collection for account recovery. It refuses to start
|
|
60
|
+
unless `AuditApp` is registered (or you pass `audit: false`).
|
|
61
|
+
|
|
62
|
+
| `source` | Written by | `modelName` / `recordId` | `after` |
|
|
63
|
+
| --- | --- | --- | --- |
|
|
64
|
+
| `admin` | AdminApp POST/PATCH/DELETE | Model / document id | Changed fields |
|
|
65
|
+
| `modelRouter` | `modelRouter` with `audit` | Model / document id | Changed fields |
|
|
66
|
+
| `rbac` | `createAccess({auditSink: persistRbacAuditToAuditEvent})` | RBAC target | Role change |
|
|
67
|
+
| `security` | `AccountSecurityApp` recovery actions (2FA reset and later actions) | `User` / target user id | `{securityAction, organizationId?}` |
|
|
68
|
+
|
|
69
|
+
A recovery call that changes nothing (resetting 2FA that is already off) writes no row. List
|
|
70
|
+
one user's security history with `GET /audit-events?source=security&recordId=<userId>`. Routes
|
|
71
|
+
and action names: [Account security reference](../reference/account-security.md).
|
|
72
|
+
|
|
54
73
|
`organizationId` is copied onto the event when `req.organization` is set (`id` or `_id`), otherwise from the request org AsyncLocalStorage, then `X-Organization-Id`, then the mutated document's `organizationId` string. Socket admin-window mutates attach `organizationId` on the payload so non-tenant models still land in the selected organization's audit log. The field is omitted when none of those exist. Admin list/read for `AuditEvent` is not organization-scoped, so unscoped rows stay visible when no organization is selected.
|
|
55
74
|
|
|
56
75
|
`actorId` comes from `req.user.id` (or `_id`). Only a 24-hex Mongo ObjectId is stored;
|
|
@@ -60,7 +79,7 @@ shape still writes the event with `actorId` omitted and a warning logged.
|
|
|
60
79
|
Update and delete snapshot `before` only when `audit` is on. If that `toJSON` throws, the
|
|
61
80
|
mutation still completes and the event is recorded without a `before` snapshot.
|
|
62
81
|
|
|
63
|
-
###
|
|
82
|
+
### 5. Fire-and-forget persist
|
|
64
83
|
|
|
65
84
|
HTTP handlers do not await Mongo. Diffs still run before the response so `before` / `after` are captured. Failures log; the mutation stays 2xx.
|
|
66
85
|
|
|
@@ -80,6 +99,6 @@ Authenticated malformed queue payloads are acknowledged without persistence. The
|
|
|
80
99
|
become valid on retry, and returning a non-2xx response would make Cloud Tasks retry the
|
|
81
100
|
same poison task indefinitely.
|
|
82
101
|
|
|
83
|
-
###
|
|
102
|
+
### 6. Retention
|
|
84
103
|
|
|
85
104
|
Omit `retentionDays` or set `0` to keep events forever (no TTL index). `new AuditApp({retentionDays: 90})` creates a Mongo TTL index on `created` with `expireAfterSeconds = 90 * 86400`, replacing the default `{created: 1}` field index so Mongo does not reject a duplicate key pattern. Mongo expires documents in the background; lowering or removing TTL later requires dropping that index yourself (`db.auditevents.dropIndex(...)`) — Mongoose will not remove it.
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# How to automate Terreno upgrades with a scheduled agent
|
|
2
|
+
|
|
3
|
+
Run a GitHub Actions job every working hour that checks whether your app is behind the latest Terreno release. When it is, and no upgrade is already in progress, the job starts one cloud agent. The agent runs the [`upgrading-terreno`](upgrade-terreno.md) skill and opens a pull request. The job never edits the tree and cannot merge. A person reviews the pull request.
|
|
4
|
+
|
|
5
|
+
```text
|
|
6
|
+
cron tick ──► in working hours? ──► behind the latest GitHub release?
|
|
7
|
+
│
|
|
8
|
+
▼
|
|
9
|
+
release ≥ 7 days old? ──► no open upgrade PR? ──► no upgrade agent running or in cooldown?
|
|
10
|
+
│
|
|
11
|
+
▼
|
|
12
|
+
npm integrity for every installed package ──► launch one agent ──► agent opens PR
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
This page is an example. Copy the workflow and adapt the script to your repository. The Terreno repository does not run it.
|
|
16
|
+
|
|
17
|
+
## Why the job only decides
|
|
18
|
+
|
|
19
|
+
A GitHub runner usually has no MongoDB, no running backend, and no browser. The upgrade skill needs all three: it starts the upgraded backend, regenerates the typed client from `/openapi.json`, and runs the app. So the job decides and enqueues. The agent runs in a cloud environment that already has your app's services, such as a Cursor Cloud Agent environment.
|
|
20
|
+
|
|
21
|
+
## Example workflow
|
|
22
|
+
|
|
23
|
+
`.github/workflows/upgrade-terreno.yml`:
|
|
24
|
+
|
|
25
|
+
```yaml
|
|
26
|
+
name: Upgrade Terreno
|
|
27
|
+
|
|
28
|
+
on:
|
|
29
|
+
schedule:
|
|
30
|
+
# 13:00–22:00 UTC covers 9:00am–5:59pm America/New_York in both EST and EDT.
|
|
31
|
+
# The script drops ticks outside the local window.
|
|
32
|
+
- cron: "0 13-22 * * 1-5"
|
|
33
|
+
workflow_dispatch:
|
|
34
|
+
inputs:
|
|
35
|
+
version:
|
|
36
|
+
description: "Target @terreno/* version (empty = latest GitHub release)"
|
|
37
|
+
required: false
|
|
38
|
+
type: string
|
|
39
|
+
dry_run:
|
|
40
|
+
description: "Decide only — do not launch an upgrade agent"
|
|
41
|
+
required: false
|
|
42
|
+
default: false
|
|
43
|
+
type: boolean
|
|
44
|
+
|
|
45
|
+
permissions:
|
|
46
|
+
contents: read
|
|
47
|
+
pull-requests: read
|
|
48
|
+
|
|
49
|
+
# A second tick waits instead of racing the pull-request and active-agent checks.
|
|
50
|
+
concurrency:
|
|
51
|
+
group: upgrade-terreno
|
|
52
|
+
cancel-in-progress: false
|
|
53
|
+
|
|
54
|
+
jobs:
|
|
55
|
+
upgrade-terreno:
|
|
56
|
+
runs-on: ubuntu-latest
|
|
57
|
+
timeout-minutes: 15
|
|
58
|
+
steps:
|
|
59
|
+
- uses: actions/checkout@v6
|
|
60
|
+
with:
|
|
61
|
+
persist-credentials: false
|
|
62
|
+
|
|
63
|
+
- uses: oven-sh/setup-bun@v2
|
|
64
|
+
with:
|
|
65
|
+
bun-version: "1.4.4"
|
|
66
|
+
|
|
67
|
+
- run: bun install --frozen-lockfile
|
|
68
|
+
|
|
69
|
+
- name: Launch Terreno upgrade when one is not already open
|
|
70
|
+
env:
|
|
71
|
+
CURSOR_API_KEY: ${{ secrets.CURSOR_API_KEY }}
|
|
72
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
73
|
+
TERRENO_UPGRADE_CLOUD_ENVIRONMENT: ${{ vars.TERRENO_UPGRADE_CLOUD_ENVIRONMENT }}
|
|
74
|
+
TERRENO_UPGRADE_DRY_RUN: ${{ github.event_name == 'workflow_dispatch' && inputs.dry_run || 'false' }}
|
|
75
|
+
TERRENO_UPGRADE_VERSION: ${{ inputs.version }}
|
|
76
|
+
run: bun .github/scripts/upgrade-terreno.ts
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
The job needs read access to contents and pull requests only. The agent pushes with its own credentials.
|
|
80
|
+
|
|
81
|
+
## What the script decides
|
|
82
|
+
|
|
83
|
+
`.github/scripts/upgrade-terreno.ts` checks these in order and stops at the first one that says no. Order matters: the cheap checks run first, and no agent API call happens until every gate passes.
|
|
84
|
+
|
|
85
|
+
| Order | Check | Skip when |
|
|
86
|
+
| --- | --- | --- |
|
|
87
|
+
| 1 | Working hours | A scheduled tick lands outside weekdays 9:00am–5:59pm in your team's timezone. Use Luxon with a named zone so daylight saving does not move the window. Manual runs are not gated. |
|
|
88
|
+
| 2 | Installed versions | No `@terreno/*` pin is found. This is an error, not a skip. Read every manifest that pins Terreno. With a Bun catalog, the versions live in the root `package.json` under `workspaces.catalog` (and `workspaces.catalogs.<name>`); workspace packages say `catalog:`. |
|
|
89
|
+
| 3 | Target version | The lowest installed pin already equals the target, or any pin is newer than it. Never downgrade. Resolving the target fails the job when `@terreno/api` at that version is not on npm yet; see below. |
|
|
90
|
+
| 4 | Release age | A scheduled run targets a release under 7 days old. A manual run is the human gate and is not held. |
|
|
91
|
+
| 5 | Open pull request | Any open pull request is an upgrade: its title names a Terreno upgrade, its branch contains `upgrade-terreno`, or its body has the `<!-- terreno-upgrade -->` marker. |
|
|
92
|
+
| 6 | Running agent | An upgrade agent (name starts with `Upgrade Terreno`) is still running. |
|
|
93
|
+
| 7 | Cooldown | An agent for the same target version was created in the last 24 hours, even one that finished or failed. Without this, a broken upgrade is retried every hour. |
|
|
94
|
+
| 8 | npm integrity | Any installed package has no published `dist.integrity` for the target. |
|
|
95
|
+
|
|
96
|
+
Take the target from the latest stable [TerrenoLabs/terreno GitHub release](https://github.com/TerrenoLabs/terreno/releases), not the npm `latest` dist-tag. Reject drafts, prereleases, and tags that are not semver. Validate a manual `version` input as semver before using it.
|
|
97
|
+
|
|
98
|
+
Write the decision to `$GITHUB_STEP_SUMMARY` so each run says why it skipped:
|
|
99
|
+
|
|
100
|
+
```text
|
|
101
|
+
## Terreno upgrade
|
|
102
|
+
|
|
103
|
+
- Decision: skipped (open-pull-request)
|
|
104
|
+
- Detail: Open Terreno upgrade pull request #261 (Upgrade Terreno to 57.19.2).
|
|
105
|
+
- From: 57.16.2
|
|
106
|
+
- To: 57.19.2
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
The job fails, rather than skips, in two cases:
|
|
110
|
+
|
|
111
|
+
- A launch would happen and `CURSOR_API_KEY` is missing. A skip with no key is still green.
|
|
112
|
+
- The latest GitHub release exists but `@terreno/api` at that version is not on npm. This check runs while the target is resolved, before the release-age and open-pull-request gates, so every in-hours run is red until publishing finishes. That points at a stuck release. If you would rather skip quietly, return a skip from that check instead of throwing.
|
|
113
|
+
|
|
114
|
+
## The agent prompt
|
|
115
|
+
|
|
116
|
+
The skill stops for confirmation unless the invoking task names both versions and authorizes the bump. The prompt does that, so the agent prints its plan into the transcript and continues:
|
|
117
|
+
|
|
118
|
+
```text
|
|
119
|
+
Upgrade Terreno from 57.16.2 to 57.19.2.
|
|
120
|
+
|
|
121
|
+
This unattended task names fromVersion 57.16.2 and toVersion 57.19.2 and authorizes the
|
|
122
|
+
lockstep bump. Follow the upgrading-terreno skill. Print the plan into the transcript, then
|
|
123
|
+
perform the upgrade. Do not wait for confirmation.
|
|
124
|
+
|
|
125
|
+
Check out the latest origin/master and create a feature branch before editing. Do not commit
|
|
126
|
+
to master.
|
|
127
|
+
|
|
128
|
+
Bump every already-installed @terreno/* dependency to 57.19.2. Do not add @terreno packages
|
|
129
|
+
that are not already installed.
|
|
130
|
+
|
|
131
|
+
Installed pins:
|
|
132
|
+
package.json workspaces.catalog @terreno/api@57.16.2
|
|
133
|
+
package.json workspaces.catalog @terreno/ui@57.16.2
|
|
134
|
+
|
|
135
|
+
The target is the TerrenoLabs/terreno GitHub release 57.19.2, not the npm latest dist-tag.
|
|
136
|
+
Install these exact versions. npm dist.integrity:
|
|
137
|
+
@terreno/api@57.19.2 sha512-…
|
|
138
|
+
@terreno/ui@57.19.2 sha512-…
|
|
139
|
+
|
|
140
|
+
Open exactly one pull request against master. Put <!-- terreno-upgrade --> in its body.
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Add the repo-specific steps the skill cannot know, such as which apps run `bun run sdk`. Install the skill in the app first ([install agent skills](install-agent-skills.md)); otherwise the agent has nothing to follow.
|
|
144
|
+
|
|
145
|
+
Name the agent `Upgrade Terreno to <version>` so checks 6 and 7 can find it.
|
|
146
|
+
|
|
147
|
+
## Test the script
|
|
148
|
+
|
|
149
|
+
Inject the I/O (manifest reads, GitHub, npm, the agent API, and the clock) as dependencies and test the decision function with fakes. Cover at least:
|
|
150
|
+
|
|
151
|
+
- A scheduled tick at 8:59am and 6:00pm local time in both winter and summer
|
|
152
|
+
- Already current, and a pin newer than the target
|
|
153
|
+
- A 6-day-old release on a schedule, and the same release on a manual run
|
|
154
|
+
- Each open pull request signal on its own
|
|
155
|
+
- A finished agent for the same version inside and outside the cooldown
|
|
156
|
+
- A package with no npm integrity
|
|
157
|
+
- A dry run that reports `launch` without calling the agent API
|
|
158
|
+
|
|
159
|
+
## Try it
|
|
160
|
+
|
|
161
|
+
Run the workflow manually with `dry_run` checked. The summary shows the decision without launching anything. Then run it locally:
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
GITHUB_EVENT_NAME=workflow_dispatch \
|
|
165
|
+
GITHUB_REPOSITORY=<owner>/<repo> \
|
|
166
|
+
GITHUB_TOKEN=$(gh auth token) \
|
|
167
|
+
TERRENO_UPGRADE_DRY_RUN=true \
|
|
168
|
+
bun .github/scripts/upgrade-terreno.ts
|
|
169
|
+
```
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# Build a list/detail screen
|
|
2
|
+
|
|
3
|
+
Put the list and its detail route in one route group, and make `ListDetailLayout` the group's
|
|
4
|
+
layout. On a phone the list pushes the detail. On a tablet or a wide browser the list stays in a
|
|
5
|
+
left pane and the detail opens beside it. The URL always names the open item.
|
|
6
|
+
|
|
7
|
+
```text
|
|
8
|
+
app/mail/
|
|
9
|
+
_layout.tsx ListDetailLayout
|
|
10
|
+
index.tsx ListDetailIndex
|
|
11
|
+
[id].tsx the detail screen
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
```tsx
|
|
15
|
+
// app/mail/_layout.tsx
|
|
16
|
+
import {ListDetailLayout, Text} from "@terreno/ui";
|
|
17
|
+
import type React from "react";
|
|
18
|
+
|
|
19
|
+
import {MailList} from "@/components/MailList";
|
|
20
|
+
|
|
21
|
+
// A deep link to /mail/3 on a phone still has the list to go back to.
|
|
22
|
+
export const unstable_settings = {initialRouteName: "index"};
|
|
23
|
+
|
|
24
|
+
const MailLayout: React.FC = () => (
|
|
25
|
+
<ListDetailLayout
|
|
26
|
+
detailHref={(id) => `/mail/${id}`}
|
|
27
|
+
emptyDetail={<Text>Select a message</Text>}
|
|
28
|
+
indexHref="/mail"
|
|
29
|
+
list={<MailList />}
|
|
30
|
+
screenOptions={{title: "Inbox"}}
|
|
31
|
+
/>
|
|
32
|
+
);
|
|
33
|
+
export default MailLayout;
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
```tsx
|
|
37
|
+
// app/mail/index.tsx
|
|
38
|
+
import {ListDetailIndex} from "@terreno/ui";
|
|
39
|
+
import type React from "react";
|
|
40
|
+
|
|
41
|
+
const MailIndex: React.FC = () => <ListDetailIndex />;
|
|
42
|
+
export default MailIndex;
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
46
|
+
// app/mail/[id].tsx
|
|
47
|
+
import {Page, Text} from "@terreno/ui";
|
|
48
|
+
import {useLocalSearchParams} from "expo-router";
|
|
49
|
+
import type React from "react";
|
|
50
|
+
|
|
51
|
+
const MailMessage: React.FC = () => {
|
|
52
|
+
const {id} = useLocalSearchParams<{id: string}>();
|
|
53
|
+
return (
|
|
54
|
+
<Page>
|
|
55
|
+
<Text>Message {id}</Text>
|
|
56
|
+
</Page>
|
|
57
|
+
);
|
|
58
|
+
};
|
|
59
|
+
export default MailMessage;
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Select rows from the list
|
|
63
|
+
|
|
64
|
+
Read the open item and open others with `useListDetail()`. Mark the open row as selected so
|
|
65
|
+
screen readers announce it. On web a clickable `Box` row sends this as `aria-current="true"`.
|
|
66
|
+
|
|
67
|
+
```tsx
|
|
68
|
+
import {Box, Text, useListDetail} from "@terreno/ui";
|
|
69
|
+
import type React from "react";
|
|
70
|
+
|
|
71
|
+
export const MailRow: React.FC<{id: string; subject: string}> = ({id, subject}) => {
|
|
72
|
+
const {select, selectedId} = useListDetail();
|
|
73
|
+
const isSelected = id === selectedId;
|
|
74
|
+
return (
|
|
75
|
+
<Box
|
|
76
|
+
accessibilityHint="Opens the message"
|
|
77
|
+
accessibilityLabel={subject}
|
|
78
|
+
accessibilityState={{selected: isSelected}}
|
|
79
|
+
color={isSelected ? "primary" : "base"}
|
|
80
|
+
onClick={() => select(id)}
|
|
81
|
+
padding={3}
|
|
82
|
+
testID={`mail-row-${id}`}
|
|
83
|
+
>
|
|
84
|
+
<Text color={isSelected ? "inverted" : "primary"}>{subject}</Text>
|
|
85
|
+
</Box>
|
|
86
|
+
);
|
|
87
|
+
};
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
| Call | Stacked (phone) | Split (tablet, wide web) |
|
|
91
|
+
| --- | --- | --- |
|
|
92
|
+
| `select(id)`, nothing open | Pushes `detailHref(id)` | Pushes `detailHref(id)` |
|
|
93
|
+
| `select(id)`, another item open | Pushes `detailHref(id)` | Replaces the open detail |
|
|
94
|
+
| `select(selectedId)` | Pushes again | Does nothing |
|
|
95
|
+
| `clear()` | `router.dismissTo(indexHref)`: pops back to the list inside this group | Same |
|
|
96
|
+
| `clear()`, nothing open | Does nothing | Does nothing |
|
|
97
|
+
|
|
98
|
+
Because split mode replaces the open detail, the stack is never deeper than the list plus one
|
|
99
|
+
detail, so back returns to the list after a rotation too. When split, the header's back button
|
|
100
|
+
(`list-detail-leave`) leaves the whole group instead, since the list is already on screen.
|
|
101
|
+
|
|
102
|
+
When another screen opens a detail directly, push it with `withAnchor` so the list sits below it
|
|
103
|
+
and back lands on the list instead of leaving the group:
|
|
104
|
+
|
|
105
|
+
```tsx
|
|
106
|
+
router.push("/mail/3", {withAnchor: true});
|
|
107
|
+
<Link href="/mail/3" withAnchor>Open message</Link>
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
`selectedId` is the `paramName` param (default `id`) of the route directly below `indexHref`
|
|
111
|
+
whose segment is `detailRoute` (default `[id]`). An `id` from a parent dynamic segment, such as
|
|
112
|
+
`/orgs/[id]/mail`, never counts.
|
|
113
|
+
|
|
114
|
+
## Publish the detail URL
|
|
115
|
+
|
|
116
|
+
A static web export (`web.output: "static"`) writes an HTML file only for paths
|
|
117
|
+
`generateStaticParams` returns. Without it, opening `/mail/3` directly is a 404 on a host that
|
|
118
|
+
does not rewrite unknown paths.
|
|
119
|
+
|
|
120
|
+
```tsx
|
|
121
|
+
// app/mail/[id].tsx
|
|
122
|
+
export const generateStaticParams = (): {id: string}[] =>
|
|
123
|
+
messages.map((message) => ({id: message.id}));
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Ids that are not known at build time need the host fallback from
|
|
127
|
+
[Build for web](build-for-web.md): Netlify `_redirects` with `/* /index.html 200`, or the Vercel
|
|
128
|
+
rewrite in [Deploy to Vercel](deploy-to-vercel.md). The demo inbox exports one page per fixture
|
|
129
|
+
message at `/demo/list-detail/:id` and `/demo/sidebar-navigation/mail/:id`. The Netlify demo
|
|
130
|
+
deploy also writes that fallback, so an id outside the fixtures still opens the missing-message
|
|
131
|
+
screen.
|
|
132
|
+
|
|
133
|
+
## Keep list state across the breakpoint
|
|
134
|
+
|
|
135
|
+
The open item survives a rotation or a browser resize. The `list` element does not: it moves
|
|
136
|
+
between the index screen and the left pane, so it remounts. Keep filters, search text, and other
|
|
137
|
+
list state in the route (`router.setParams`) or a store.
|
|
138
|
+
|
|
139
|
+
## Nest it inside a sidebar
|
|
140
|
+
|
|
141
|
+
`ListDetailLayout` measures its own width, so it splits correctly inside `SidebarNavigation` or
|
|
142
|
+
an admin rail. Make the group a screen of the sidebar navigator:
|
|
143
|
+
|
|
144
|
+
```text
|
|
145
|
+
app/workspace/
|
|
146
|
+
_layout.tsx SidebarNavigation with <SidebarNavigation.Screen name="mail" />
|
|
147
|
+
mail/_layout.tsx ListDetailLayout with indexHref="/workspace/mail"
|
|
148
|
+
mail/index.tsx
|
|
149
|
+
mail/[id].tsx
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Choose SplitPage instead
|
|
153
|
+
|
|
154
|
+
Use `SplitPage` when the list and detail do not have their own routes, such as a picker inside a
|
|
155
|
+
modal. `SplitPage` keeps the selection in component state, so it has no URL, deep links, or back
|
|
156
|
+
navigation for the open item.
|
|
157
|
+
|
|
158
|
+
## Reference
|
|
159
|
+
|
|
160
|
+
Props, defaults, and testIDs: [`ListDetailLayout`](../reference/ui.md#listdetaillayout).
|
|
@@ -106,7 +106,9 @@ when the package imports them. Coverage-script unit tests run in the cheap
|
|
|
106
106
|
Playwright runs five shards after `e2e-prepare` (`auth`, `app`, `admin-core`,
|
|
107
107
|
`admin-table`, `syncdb`) instead of one container per spec file. Each shard
|
|
108
108
|
first checks the [affected gate](#e2e-affected-gate) and halts when no changed
|
|
109
|
-
file can reach it.
|
|
109
|
+
file can reach it. The cross-site bearer specs run beside them in `e2e-cross-site`,
|
|
110
|
+
which builds its own web bundle and backend (`playwright.config.ts` with
|
|
111
|
+
`E2E_CROSS_SITE_BEARER=true`), has no affected gate, and is not a required check. Repository
|
|
110
112
|
policy checks share one `repo-policies` job so eight small checkouts do not
|
|
111
113
|
sit in the concurrency queue. `repo-policies` uses Node 22.14 because Knip's
|
|
112
114
|
oxc-parser throws `ERR_REQUIRE_ESM` on the shared 22.11 executor.
|
|
@@ -316,6 +318,7 @@ the same commands through the parameterized `packages-ci` job, gated by
|
|
|
316
318
|
| Admin SPA Build and E2E | `admin-spa-ci` |
|
|
317
319
|
| Lint, compile, and coverage (matrix package) | `packages-ci` (`admin-backend`, `admin-frontend`, `api-health`, `feature-flags`, `test`) |
|
|
318
320
|
| E2E · `<shard>` | `e2e` (matrix `shard`: `auth`, `app`, `admin-core`, `admin-table`, `syncdb`) |
|
|
321
|
+
| _(cross-site bearer specs, not required)_ | `e2e-cross-site` (after `e2e-prepare`; also the explicit-linking pass) |
|
|
319
322
|
| E2E Load · syncdb-loadlab | `e2e-load` (trigger-gated, see below) |
|
|
320
323
|
| Admin SPA Backend Integration E2E | `admin-spa-integration` |
|
|
321
324
|
| _(e2e compile+export once)_ | `e2e-prepare` |
|
|
@@ -368,10 +371,17 @@ To teach the agent from what reviewers actually say, run
|
|
|
368
371
|
<file>` of pasted comments): it opens one PR editing `docs/approval-rubric.md` and the
|
|
369
372
|
path rules from the review comments since the last pass.
|
|
370
373
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
374
|
+
Levels and human approvals (defined in `docs/approval-rubric.md`):
|
|
375
|
+
|
|
376
|
+
| Level | Human approvals | Examples |
|
|
377
|
+
| --- | --- | --- |
|
|
378
|
+
| low | None once automated review and security checks are green | docs, copy, internal-only features, additive changes behind a flag |
|
|
379
|
+
| medium | One person | ordinary product changes, and sensitive areas such as caching, app updates, and AI system prompts |
|
|
380
|
+
| high | One person | security-related code: auth, permissions, secrets, crypto, PHI handling |
|
|
381
|
+
|
|
382
|
+
`approveLevels` is `["low"]`, so the App's approval satisfies master's one-review rule
|
|
383
|
+
for low PRs. Medium and high PRs get a `COMMENT` review that says the risk level and
|
|
384
|
+
why, and a human approves. The prompts and answers behind each
|
|
375
385
|
verdict are stored as the `brewery-approve` job artifact. Full reference:
|
|
376
386
|
[brewery `docs/approve.md`](https://github.com/joshgachnang/brewery/blob/master/docs/approve.md).
|
|
377
387
|
|