@terreno/mcp 0.31.0 → 56.0.0-beta.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 (209) hide show
  1. package/dist/bootstrap.d.ts.map +1 -1
  2. package/dist/bootstrap.js +15 -16
  3. package/dist/bootstrap.js.map +1 -1
  4. package/dist/docs/templates/bootstrap/backend/types/models/userTypes.template.md +4 -5
  5. package/dist/docs/templates/bootstrap/frontend/app/_layout.template.tsx +1 -1
  6. package/dist/docs/templates/tools/model.template.md +2 -4
  7. package/dist/docs/versioned/next/README.md +1 -0
  8. package/dist/docs/versioned/next/explanation/README.md +1 -7
  9. package/dist/docs/versioned/next/how-to/README.md +2 -8
  10. package/dist/docs/versioned/next/how-to/deploy-to-gcp.md +228 -20
  11. package/dist/docs/versioned/next/how-to/migrate-rtk-to-syncdb.md +445 -0
  12. package/dist/docs/versioned/next/implementationPlans/PLAN_INDEX.md +4 -57
  13. package/dist/docs/versioned/next/implementationPlans/README.md +30 -56
  14. package/dist/docs/versioned/next/implementationPlans/agentic-sdlc-plugin.md +1 -2
  15. package/dist/docs/versioned/next/implementationPlans/ai-dev-loop-boost.md +0 -1
  16. package/dist/docs/versioned/next/implementationPlans/build-terreno-app-validation.md +0 -1
  17. package/dist/docs/versioned/next/implementationPlans/deploy-to-gcp.md +0 -1
  18. package/dist/docs/versioned/next/implementationPlans/deploy-to-vercel.md +0 -1
  19. package/dist/docs/versioned/next/implementationPlans/deployment-foundation.md +0 -1
  20. package/dist/docs/versioned/next/implementationPlans/docs-reference-coverage.md +0 -1
  21. package/dist/docs/versioned/next/implementationPlans/docs-tutorials-ai-first.md +0 -1
  22. package/dist/docs/versioned/next/implementationPlans/examples-demo-coverage.md +0 -1
  23. package/dist/docs/versioned/next/implementationPlans/oss-governance-baseline.md +0 -1
  24. package/dist/docs/versioned/next/implementationPlans/positioning-django-rails-universal.md +0 -1
  25. package/dist/docs/versioned/next/implementationPlans/public-roadmap-github.md +0 -1
  26. package/dist/docs/versioned/next/implementationPlans/rtk-to-syncdb-migration-docs.md +52 -47
  27. package/dist/docs/versioned/next/implementationPlans/syncdb-api-inventory.md +303 -0
  28. package/dist/docs/versioned/next/implementationPlans/syncdb-codegen.md +252 -0
  29. package/dist/docs/versioned/next/implementationPlans/syncdb-local-first.md +468 -420
  30. package/dist/docs/versioned/next/implementationPlans/syncdb-phase-c-design.md +425 -0
  31. package/dist/docs/versioned/next/implementationPlans/terreno-syncdb-2.md +800 -0
  32. package/dist/docs/versioned/next/implementationPlans/upgrade-guides-and-skill.md +0 -1
  33. package/dist/docs/versioned/next/implementationPlans/web-ssr-and-admin-spa.md +0 -1
  34. package/dist/docs/versioned/next/reference/README.md +1 -4
  35. package/dist/docs/versioned/next/reference/environment-variables.md +382 -220
  36. package/dist/docs/versioned/next/reference/syncdb.md +456 -0
  37. package/dist/docs/versioned/next/superpowers/plans/2026-07-28-admin-mobile-navigation.md +97 -0
  38. package/dist/docs/versioned/next/superpowers/specs/2026-07-28-admin-mobile-navigation-design.md +36 -0
  39. package/dist/docs/versioned/next/tasks/agentic-sdlc-plugin.md +5 -5
  40. package/dist/docs/versioned/next/tasks/deploy-to-gcp.md +11 -11
  41. package/dist/docs/versioned/next/tasks/deployment-foundation.md +12 -12
  42. package/dist/docs/versioned/next/tasks/docs-reference-coverage.md +4 -4
  43. package/dist/docs/versioned/next/tasks/oss-governance-baseline.md +18 -18
  44. package/dist/docs/versioned/next/tasks/positioning-django-rails-universal.md +4 -4
  45. package/dist/docs/versioned/next/tasks/public-roadmap-github.md +15 -15
  46. package/dist/docs/versioned/next/tasks/rtk-to-syncdb-migration-docs.md +10 -10
  47. package/dist/docs/versioned/next/tasks/syncdb-codegen.md +65 -0
  48. package/dist/docs/versioned/next/tasks/syncdb-local-first.md +434 -117
  49. package/package.json +4 -5
  50. package/src/docs/templates/bootstrap/backend/types/models/userTypes.template.md +4 -5
  51. package/src/docs/templates/bootstrap/frontend/app/_layout.template.tsx +1 -1
  52. package/src/docs/templates/tools/model.template.md +2 -4
  53. package/src/docs/versioned/next/README.md +1 -0
  54. package/src/docs/versioned/next/explanation/README.md +1 -7
  55. package/src/docs/versioned/next/how-to/README.md +2 -8
  56. package/src/docs/versioned/next/how-to/deploy-to-gcp.md +228 -20
  57. package/src/docs/versioned/next/how-to/migrate-rtk-to-syncdb.md +445 -0
  58. package/src/docs/versioned/next/implementationPlans/PLAN_INDEX.md +4 -57
  59. package/src/docs/versioned/next/implementationPlans/README.md +30 -56
  60. package/src/docs/versioned/next/implementationPlans/agentic-sdlc-plugin.md +1 -2
  61. package/src/docs/versioned/next/implementationPlans/ai-dev-loop-boost.md +0 -1
  62. package/src/docs/versioned/next/implementationPlans/build-terreno-app-validation.md +0 -1
  63. package/src/docs/versioned/next/implementationPlans/deploy-to-gcp.md +0 -1
  64. package/src/docs/versioned/next/implementationPlans/deploy-to-vercel.md +0 -1
  65. package/src/docs/versioned/next/implementationPlans/deployment-foundation.md +0 -1
  66. package/src/docs/versioned/next/implementationPlans/docs-reference-coverage.md +0 -1
  67. package/src/docs/versioned/next/implementationPlans/docs-tutorials-ai-first.md +0 -1
  68. package/src/docs/versioned/next/implementationPlans/examples-demo-coverage.md +0 -1
  69. package/src/docs/versioned/next/implementationPlans/oss-governance-baseline.md +0 -1
  70. package/src/docs/versioned/next/implementationPlans/positioning-django-rails-universal.md +0 -1
  71. package/src/docs/versioned/next/implementationPlans/public-roadmap-github.md +0 -1
  72. package/src/docs/versioned/next/implementationPlans/rtk-to-syncdb-migration-docs.md +52 -47
  73. package/src/docs/versioned/next/implementationPlans/syncdb-api-inventory.md +303 -0
  74. package/src/docs/versioned/next/implementationPlans/syncdb-codegen.md +252 -0
  75. package/src/docs/versioned/next/implementationPlans/syncdb-local-first.md +468 -420
  76. package/src/docs/versioned/next/implementationPlans/syncdb-phase-c-design.md +425 -0
  77. package/src/docs/versioned/next/implementationPlans/terreno-syncdb-2.md +800 -0
  78. package/src/docs/versioned/next/implementationPlans/upgrade-guides-and-skill.md +0 -1
  79. package/src/docs/versioned/next/implementationPlans/web-ssr-and-admin-spa.md +0 -1
  80. package/src/docs/versioned/next/reference/README.md +1 -4
  81. package/src/docs/versioned/next/reference/environment-variables.md +382 -220
  82. package/src/docs/versioned/next/reference/syncdb.md +456 -0
  83. package/src/docs/versioned/next/superpowers/plans/2026-07-28-admin-mobile-navigation.md +97 -0
  84. package/src/docs/versioned/next/superpowers/specs/2026-07-28-admin-mobile-navigation-design.md +36 -0
  85. package/src/docs/versioned/next/tasks/agentic-sdlc-plugin.md +5 -5
  86. package/src/docs/versioned/next/tasks/deploy-to-gcp.md +11 -11
  87. package/src/docs/versioned/next/tasks/deployment-foundation.md +12 -12
  88. package/src/docs/versioned/next/tasks/docs-reference-coverage.md +4 -4
  89. package/src/docs/versioned/next/tasks/oss-governance-baseline.md +18 -18
  90. package/src/docs/versioned/next/tasks/positioning-django-rails-universal.md +4 -4
  91. package/src/docs/versioned/next/tasks/public-roadmap-github.md +15 -15
  92. package/src/docs/versioned/next/tasks/rtk-to-syncdb-migration-docs.md +10 -10
  93. package/src/docs/versioned/next/tasks/syncdb-codegen.md +65 -0
  94. package/src/docs/versioned/next/tasks/syncdb-local-first.md +434 -117
  95. package/LICENSE +0 -21
  96. package/dist/__tests__/bootstrap.test.d.ts +0 -2
  97. package/dist/__tests__/bootstrap.test.d.ts.map +0 -1
  98. package/dist/__tests__/bootstrap.test.js +0 -299
  99. package/dist/__tests__/bootstrap.test.js.map +0 -1
  100. package/dist/__tests__/databaseQuery.test.d.ts +0 -2
  101. package/dist/__tests__/databaseQuery.test.d.ts.map +0 -1
  102. package/dist/__tests__/databaseQuery.test.js +0 -83
  103. package/dist/__tests__/databaseQuery.test.js.map +0 -1
  104. package/dist/__tests__/docsRoot.test.d.ts +0 -2
  105. package/dist/__tests__/docsRoot.test.d.ts.map +0 -1
  106. package/dist/__tests__/docsRoot.test.js +0 -59
  107. package/dist/__tests__/docsRoot.test.js.map +0 -1
  108. package/dist/__tests__/inferPackages.test.d.ts +0 -2
  109. package/dist/__tests__/inferPackages.test.d.ts.map +0 -1
  110. package/dist/__tests__/inferPackages.test.js +0 -35
  111. package/dist/__tests__/inferPackages.test.js.map +0 -1
  112. package/dist/__tests__/preload.d.ts +0 -2
  113. package/dist/__tests__/preload.d.ts.map +0 -1
  114. package/dist/__tests__/preload.js +0 -12
  115. package/dist/__tests__/preload.js.map +0 -1
  116. package/dist/__tests__/prompts.test.d.ts +0 -2
  117. package/dist/__tests__/prompts.test.d.ts.map +0 -1
  118. package/dist/__tests__/prompts.test.js +0 -295
  119. package/dist/__tests__/prompts.test.js.map +0 -1
  120. package/dist/__tests__/resources.test.d.ts +0 -2
  121. package/dist/__tests__/resources.test.d.ts.map +0 -1
  122. package/dist/__tests__/resources.test.js +0 -352
  123. package/dist/__tests__/resources.test.js.map +0 -1
  124. package/dist/__tests__/search.test.d.ts +0 -2
  125. package/dist/__tests__/search.test.d.ts.map +0 -1
  126. package/dist/__tests__/search.test.js +0 -163
  127. package/dist/__tests__/search.test.js.map +0 -1
  128. package/dist/__tests__/tools.test.d.ts +0 -2
  129. package/dist/__tests__/tools.test.d.ts.map +0 -1
  130. package/dist/__tests__/tools.test.js +0 -540
  131. package/dist/__tests__/tools.test.js.map +0 -1
  132. package/dist/__tests__/upgradeGuide.test.d.ts +0 -2
  133. package/dist/__tests__/upgradeGuide.test.d.ts.map +0 -1
  134. package/dist/__tests__/upgradeGuide.test.js +0 -22
  135. package/dist/__tests__/upgradeGuide.test.js.map +0 -1
  136. package/dist/docs/upgrades/0.31.0.md +0 -60
  137. package/dist/docs/versioned/next/explanation/deployment-architecture-gcp.md +0 -65
  138. package/dist/docs/versioned/next/explanation/deployment-baseline.md +0 -78
  139. package/dist/docs/versioned/next/explanation/positioning.md +0 -164
  140. package/dist/docs/versioned/next/explanation/repository-settings.md +0 -44
  141. package/dist/docs/versioned/next/explanation/roadmap-process.md +0 -330
  142. package/dist/docs/versioned/next/explanation/roadmap-seed-issues.md +0 -866
  143. package/dist/docs/versioned/next/how-to/build-for-web.md +0 -69
  144. package/dist/docs/versioned/next/how-to/deploy-backend-to-cloud-run.md +0 -115
  145. package/dist/docs/versioned/next/how-to/deploy-web-to-gcs-cdn.md +0 -183
  146. package/dist/docs/versioned/next/implementationPlans/b2b-platform-program.md +0 -114
  147. package/dist/docs/versioned/next/implementationPlans/billing-stripe.md +0 -158
  148. package/dist/docs/versioned/next/implementationPlans/comms-abstraction.md +0 -240
  149. package/dist/docs/versioned/next/implementationPlans/comms-adapter-expo-push.md +0 -107
  150. package/dist/docs/versioned/next/implementationPlans/comms-adapter-sendgrid.md +0 -107
  151. package/dist/docs/versioned/next/implementationPlans/comms-adapter-twilio-sms.md +0 -102
  152. package/dist/docs/versioned/next/implementationPlans/comms-adapter-twilio-verify.md +0 -93
  153. package/dist/docs/versioned/next/implementationPlans/infra-mcp-research.md +0 -188
  154. package/dist/docs/versioned/next/implementationPlans/infra-mcp.md +0 -269
  155. package/dist/docs/versioned/next/implementationPlans/native-module-baseline.md +0 -105
  156. package/dist/docs/versioned/next/implementationPlans/orgs-and-teams.md +0 -158
  157. package/dist/docs/versioned/next/implementationPlans/password-reset-and-email-verification.md +0 -147
  158. package/dist/docs/versioned/next/reference/admin-spa.md +0 -130
  159. package/dist/docs/versioned/next/reference/ai.md +0 -519
  160. package/dist/docs/versioned/next/reference/test.md +0 -108
  161. package/dist/docs/versioned/next/roadmap.md +0 -9
  162. package/dist/docs/versioned/next/tasks/README.md +0 -42
  163. package/dist/docs/versioned/next/tasks/billing-stripe.md +0 -75
  164. package/dist/docs/versioned/next/tasks/comms-abstraction.md +0 -77
  165. package/dist/docs/versioned/next/tasks/comms-adapter-expo-push.md +0 -29
  166. package/dist/docs/versioned/next/tasks/comms-adapter-sendgrid.md +0 -29
  167. package/dist/docs/versioned/next/tasks/comms-adapter-twilio-sms.md +0 -29
  168. package/dist/docs/versioned/next/tasks/comms-adapter-twilio-verify.md +0 -19
  169. package/dist/docs/versioned/next/tasks/infra-mcp.md +0 -43
  170. package/dist/docs/versioned/next/tasks/native-module-baseline.md +0 -42
  171. package/dist/docs/versioned/next/tasks/orgs-and-teams.md +0 -70
  172. package/dist/docs/versioned/next/tasks/password-reset-and-email-verification.md +0 -60
  173. package/src/docs/upgrades/0.31.0.md +0 -60
  174. package/src/docs/versioned/next/explanation/deployment-architecture-gcp.md +0 -65
  175. package/src/docs/versioned/next/explanation/deployment-baseline.md +0 -78
  176. package/src/docs/versioned/next/explanation/positioning.md +0 -164
  177. package/src/docs/versioned/next/explanation/repository-settings.md +0 -44
  178. package/src/docs/versioned/next/explanation/roadmap-process.md +0 -330
  179. package/src/docs/versioned/next/explanation/roadmap-seed-issues.md +0 -866
  180. package/src/docs/versioned/next/how-to/build-for-web.md +0 -69
  181. package/src/docs/versioned/next/how-to/deploy-backend-to-cloud-run.md +0 -115
  182. package/src/docs/versioned/next/how-to/deploy-web-to-gcs-cdn.md +0 -183
  183. package/src/docs/versioned/next/implementationPlans/b2b-platform-program.md +0 -114
  184. package/src/docs/versioned/next/implementationPlans/billing-stripe.md +0 -158
  185. package/src/docs/versioned/next/implementationPlans/comms-abstraction.md +0 -240
  186. package/src/docs/versioned/next/implementationPlans/comms-adapter-expo-push.md +0 -107
  187. package/src/docs/versioned/next/implementationPlans/comms-adapter-sendgrid.md +0 -107
  188. package/src/docs/versioned/next/implementationPlans/comms-adapter-twilio-sms.md +0 -102
  189. package/src/docs/versioned/next/implementationPlans/comms-adapter-twilio-verify.md +0 -93
  190. package/src/docs/versioned/next/implementationPlans/infra-mcp-research.md +0 -188
  191. package/src/docs/versioned/next/implementationPlans/infra-mcp.md +0 -269
  192. package/src/docs/versioned/next/implementationPlans/native-module-baseline.md +0 -105
  193. package/src/docs/versioned/next/implementationPlans/orgs-and-teams.md +0 -158
  194. package/src/docs/versioned/next/implementationPlans/password-reset-and-email-verification.md +0 -147
  195. package/src/docs/versioned/next/reference/admin-spa.md +0 -130
  196. package/src/docs/versioned/next/reference/ai.md +0 -519
  197. package/src/docs/versioned/next/reference/test.md +0 -108
  198. package/src/docs/versioned/next/roadmap.md +0 -9
  199. package/src/docs/versioned/next/tasks/README.md +0 -42
  200. package/src/docs/versioned/next/tasks/billing-stripe.md +0 -75
  201. package/src/docs/versioned/next/tasks/comms-abstraction.md +0 -77
  202. package/src/docs/versioned/next/tasks/comms-adapter-expo-push.md +0 -29
  203. package/src/docs/versioned/next/tasks/comms-adapter-sendgrid.md +0 -29
  204. package/src/docs/versioned/next/tasks/comms-adapter-twilio-sms.md +0 -29
  205. package/src/docs/versioned/next/tasks/comms-adapter-twilio-verify.md +0 -19
  206. package/src/docs/versioned/next/tasks/infra-mcp.md +0 -43
  207. package/src/docs/versioned/next/tasks/native-module-baseline.md +0 -42
  208. package/src/docs/versioned/next/tasks/orgs-and-teams.md +0 -70
  209. package/src/docs/versioned/next/tasks/password-reset-and-email-verification.md +0 -60
@@ -0,0 +1,456 @@
1
+ # @terreno/syncdb
2
+
3
+ Local-first data layer for Terreno frontends. A TinyBase `MergeableStore` on device is the UI source of truth: reads come from the local store, writes apply optimistically into a durable outbox, and the server reconciles asynchronously over a socket delta protocol with HTTP snapshot catch-up. Supersedes `@terreno/rtk` for data-synchronization concerns.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Installation](#installation)
8
+ - [Architecture](#architecture)
9
+ - [Backend setup](#backend-setup)
10
+ - [createSyncDb configuration](#createsyncdb-configuration)
11
+ - [SyncDb client methods](#syncdb-client-methods)
12
+ - [React hooks](#react-hooks)
13
+ - [Conflict API](#conflict-api)
14
+ - [Sync status API](#sync-status-api)
15
+ - [Stream scoping](#stream-scoping)
16
+ - [Sync protocol](#sync-protocol)
17
+ - [Encryption at rest](#encryption-at-rest)
18
+ - [Testing](#testing)
19
+ - [Environment variables](#environment-variables)
20
+ - [Conventions](#conventions)
21
+
22
+ ## Key exports
23
+
24
+ - `createSyncDb`, `SyncDb`, `SyncDbConfig`, `MutateArgs`
25
+ - `betterAuthAdapter`, `AuthProvider`
26
+ - `listConflicts`, `wipeLocalData`, `generateMutationId`
27
+ - React (`@terreno/syncdb/react`): `SyncDbProvider`, `useEntity`, `useQuery`, `useEntityIds`, `useMutate`, `useSyncStatus`, `useConflicts`, `useSyncDebugLog`
28
+ - Testing (`@terreno/syncdb/testing`): `createFakeTransport`
29
+
30
+ ## Installation
31
+
32
+ ```bash
33
+ bun install @terreno/syncdb
34
+ ```
35
+
36
+ **Peer dependencies:** `react` (optional — only for `@terreno/syncdb/react`), `luxon`, `tinybase`.
37
+
38
+ **Native persistence:** install `expo-sqlite` in the **app** (not only in a library):
39
+
40
+ ```bash
41
+ bunx expo install expo-sqlite
42
+ ```
43
+
44
+ Expo autolinking walks the app's own dependencies; a nested copy leaves the native module out of the build. Rebuild the native project after adding it.
45
+
46
+ **Why `@terreno/syncdb/react` is a separate subpath:** `react` is an optional peer dependency. Keeping React bindings off the main entry lets non-React consumers (Node tests, scripts) import `@terreno/syncdb` without loading React.
47
+
48
+ ## Architecture
49
+
50
+ ```
51
+ FRONTEND (@terreno/syncdb) BACKEND (@terreno/api)
52
+ ┌───────────────────────────────────┐ ┌────────────────────────────────────┐
53
+ │ React hooks (useQuery, useEntity, │ │ modelRouter(path, Model, {sync}) │
54
+ │ useMutate, useSyncStatus, ...) │ │ └─ sync registry + validation │
55
+ │ │ ▲ │ │ │
56
+ │ ▼ │ │ │ syncPlugin (schema) │
57
+ │ TinyBase MergeableStore │ │ └─ stamps _syncSeq per write │
58
+ │ {collection} tables + _outbox │ │ │
59
+ │ + _cursors + _conflicts │ │ SyncApp (HTTP) RealtimeApp │
60
+ │ │ ▲ │ │ /sync/snapshot (Socket.io + │
61
+ │ ▼ │ │ │ /sync/mutate change streams)│
62
+ │ Persister (AES-GCM IndexedDB on │ │ /sync/key │ │
63
+ │ web, expo-sqlite on native) │ └─────────┬───────────────┼──────────┘
64
+ └──────┬─────────────────▲──────────┘ │ │
65
+ │ │ │ │
66
+ │ sync:mutate ─────────────────────────────►│ │
67
+ │ sync:ack / sync:nack ◄────────────────────┘ │
68
+ │ sync:delta ◄──────────────────────────────────────────────┘
69
+ │ GET /sync/snapshot (bootstrap + catch-up, HTTP)
70
+ └── POST /sync/mutate (fallback while the socket is down)
71
+ ```
72
+
73
+ Every mutation executes the same `@terreno/api` modelRouter write path as REST — identical permissions, hooks, and validation.
74
+
75
+ ## Backend setup
76
+
77
+ Register sync on the backend before the frontend client can sync a collection.
78
+
79
+ ### Required schema plugins
80
+
81
+ ```typescript
82
+ import {isDeletedPlugin, syncPlugin} from "@terreno/api";
83
+
84
+ todoSchema.plugin(isDeletedPlugin); // soft delete — required for tombstone catch-up
85
+ todoSchema.plugin(syncPlugin); // stamps per-stream _syncSeq on every write
86
+ ```
87
+
88
+ Apply plugins **before** the model compiles. Registration validates their presence.
89
+
90
+ ### modelRouter sync config
91
+
92
+ Use the three-argument `modelRouter` form:
93
+
94
+ ```typescript
95
+ import {modelRouter, OwnerQueryFilter, Permissions} from "@terreno/api";
96
+
97
+ const todoRouter = modelRouter("/todos", Todo, {
98
+ permissions: {
99
+ create: [Permissions.IsAuthenticated],
100
+ delete: [Permissions.IsOwner],
101
+ list: [Permissions.IsAuthenticated],
102
+ read: [Permissions.IsOwner],
103
+ update: [Permissions.IsOwner],
104
+ },
105
+ preCreate: (body, req) => ({...body, ownerId: (req.user as unknown as UserDocument)?._id}),
106
+ queryFilter: OwnerQueryFilter,
107
+ sync: {
108
+ scope: {type: "owner"}, // stream = todos|owner:{ownerId}
109
+ },
110
+ });
111
+ ```
112
+
113
+ ### SyncApp and RealtimeApp
114
+
115
+ ```typescript
116
+ import {RealtimeApp, SyncApp, TerrenoApp} from "@terreno/api";
117
+
118
+ new TerrenoApp({userModel: User})
119
+ .register(todoRouter)
120
+ .register(new SyncApp()) // GET /sync/snapshot, POST /sync/mutate, GET /sync/key, ...
121
+ .register(new RealtimeApp()) // Socket.io; sync:subscribe, sync:delta, sync:mutate
122
+ .start();
123
+ ```
124
+
125
+ - **`SyncApp`** — HTTP sync routes (`/sync/snapshot`, `/sync/mutate`, `/sync/mutate/batch`, `/sync/key`, `/sync/streams`, `/sync/entities`).
126
+ - **`RealtimeApp`** — Socket.io server with change-stream-driven `sync:delta` emission. Requires a **MongoDB replica set** (change streams).
127
+ - **`ensureSyncIndexes`** — `TerrenoApp.start()` awaits index builds for snapshot queries and sync bookkeeping (`SyncMutation.mutationId` unique, `SyncCounter.stream` unique, etc.). Hosts that build Express without `TerrenoApp.start()` should await `ensureSyncIndexes()` themselves.
128
+
129
+ Socket auth accepts legacy JWTs by default. For Better Auth sessions:
130
+
131
+ ```typescript
132
+ new RealtimeApp({betterAuth: {auth, userModel: User}})
133
+ ```
134
+
135
+ ## createSyncDb configuration
136
+
137
+ ```typescript
138
+ import {betterAuthAdapter, createSyncDb} from "@terreno/syncdb";
139
+
140
+ export const syncDb = createSyncDb({
141
+ name: "myapp",
142
+ collections: ["todos"],
143
+ authProvider: betterAuthAdapter(authClient),
144
+ baseUrl: "http://localhost:4000",
145
+ });
146
+ ```
147
+
148
+ | Option | Type | Default | Description |
149
+ |--------|------|---------|-------------|
150
+ | `name` | `string` | — (required) | Persisted database name |
151
+ | `collections` | `string[]` | — (required) | Collection names to sync (local tables + subscriptions) |
152
+ | `authProvider` | `AuthProvider` | — (required) | `{getToken, getUserId, onAuthChange, refresh?}` |
153
+ | `baseUrl` | `string` | — | Server origin; required unless both `transport` and `httpChannel` are injected |
154
+ | `transport` | `SyncTransport` | socket transport from `baseUrl` | Override for tests or custom wiring |
155
+ | `httpChannel` | `HttpChannel` | built from `baseUrl` | HTTP fallback channel override |
156
+ | `persisterFactory` | `PersisterFactory` | platform default (IndexedDB web, expo-sqlite native) | Storage backend override |
157
+ | `keyProvider` | `KeyProvider` | server-derived via `GET /sync/key` | Web encryption key provider |
158
+ | `idbGetImpl` / `idbSetImpl` | test hooks | — | Simulate IndexedDB failures (tests only) |
159
+ | `reconcileIntervalMs` | `number` | `300000` (5 min); `0` disables | Periodic reconcile + outbox replay timer |
160
+ | `seqJumpReconcileMinIntervalMs` | `number` | `30000` (30s) | Per-stream rate limit for seq-jump-triggered reconciles |
161
+ | `now` | `() => number` | `Date.now` | Injectable clock (tests) |
162
+ | `random` | `() => number` | `Math.random` | Injectable RNG for backoff jitter (tests) |
163
+ | `debug` | `boolean \| SyncDebugLogOptions` | `false` | In-memory debug event log |
164
+ | `onAuthRequired` | `() => void` | — | Fires once per auth-pause episode (prompt re-login) |
165
+ | `wipeOnSignOut` | `boolean` | `false` | `signOut()` also wipes local data |
166
+ | `batchSize` | `number` | `50` (server caps at 100) | Max mutations per batched drain send |
167
+ | `haltQueueOnConflict` | `boolean` | `false` | `true` = conflict halts entire drain; `false` = per-entity blocking |
168
+ | `onDecryptFailure` | `() => void` | wipe + re-bootstrap (with `console.warn`) | Override web decrypt-failure recovery |
169
+ | `tombstoneRetentionMs` | `number` | `7776000000` (90 days); `0` disables | Client tombstone compaction after successful reconcile |
170
+ | `startAuthRetryAttempts` | `number` | `3`; `1` disables | `start()` attempts to resolve `getUserId()` |
171
+ | `startAuthRetryDelayMs` | `number` | `250` | Delay between those attempts |
172
+
173
+ `start()` needs an authenticated user. It retries `getUserId()` briefly (transient session races after login) before rejecting.
174
+
175
+ ## SyncDb client methods
176
+
177
+ | Method | Description |
178
+ |--------|-------------|
179
+ | `start()` | Resolve user, run wipe-on-user-change check, start persistence, connect transport, subscribe collections, start reconcile timer. Resolves even when offline. Idempotent while already started. |
180
+ | `stop()` | Disconnect, stop persistence, clear timers and listeners. |
181
+ | `goOffline()` | Simulated outage: disconnect transport, pause replay/reconcile/timer; local mutations keep queueing. |
182
+ | `goOnline()` | End simulated outage; reconnect triggers reconcile + outbox replay. |
183
+ | `mutate({collection, operation, id?, data?})` | Optimistic local write + durable outbox enqueue + fire-and-forget replay. Returns `{mutationId, id}`. |
184
+ | `reconcile()` | HTTP snapshot catch-up for every known stream; runs tombstone compaction on success. |
185
+ | `forceResync()` | Purge every known stream locally and re-bootstrap from cursor 0 (outbox/conflicts untouched). Returns `{ok, reason?, streams, purged, repaired}`. |
186
+ | `replayOutbox()` | Drain queued mutations for the current user now. |
187
+ | `resolveConflict({mutationId, strategy})` | Apply `"useServer"` or `"keepMine"` to a recorded conflict. |
188
+ | `retryFailed({entityId})` | Re-enable an entity's queued successors after a terminal validation failure. |
189
+ | `signOut()` | Explicit teardown like `stop()`; wipes local data only when `wipeOnSignOut: true`. |
190
+ | `getSyncStatus()` | Snapshot of aggregate sync state (see [Sync status API](#sync-status-api)). |
191
+ | `onStatusChange(callback)` | Subscribe to connectivity/syncing changes; returns unsubscribe. |
192
+ | `store` | Read surface over the local MergeableStore (`SyncStore`). |
193
+ | `outbox` | Durable mutation outbox (`Outbox`). |
194
+ | `debug` | `SyncDebugLog` when `debug` is enabled; otherwise `undefined`. |
195
+
196
+ ## React hooks
197
+
198
+ Import from `@terreno/syncdb/react`:
199
+
200
+ ```typescript
201
+ import {
202
+ SyncDbProvider,
203
+ useConflicts,
204
+ useEntity,
205
+ useEntityIds,
206
+ useMutate,
207
+ useQuery,
208
+ useSyncDbClient,
209
+ useSyncDebugLog,
210
+ useSyncStatus,
211
+ } from "@terreno/syncdb/react";
212
+ ```
213
+
214
+ Wrap the app (or a subtree) in `SyncDbProvider`:
215
+
216
+ ```tsx
217
+ <SyncDbProvider client={syncDb}>
218
+ <TodoList />
219
+ </SyncDbProvider>
220
+ ```
221
+
222
+ ### `useEntity(collection, id)`
223
+
224
+ ```typescript
225
+ const {data, deleted, seq, isPending} = useEntity<Todo>("todos", id);
226
+ ```
227
+
228
+ Subscribe to a single entity. Re-renders when that row changes. `isPending` is true while an outbox mutation protects optimistic state.
229
+
230
+ ### `useQuery(collection, options?)`
231
+
232
+ ```typescript
233
+ const todos = useQuery<Todo>("todos", {
234
+ filter: (t) => !t.completed,
235
+ sort: (a, b) => b.created.localeCompare(a.created),
236
+ includeDeleted: false,
237
+ });
238
+ ```
239
+
240
+ Returns decoded entity data arrays. Tombstones excluded unless `includeDeleted: true`. Filter and sort run in JS — memoize callbacks when collections are large.
241
+
242
+ ### `useEntityIds(collection, options?)`
243
+
244
+ ```typescript
245
+ const ids = useEntityIds<Todo>("todos", {filter: (t) => !t.completed, sort: byCreatedDesc});
246
+ return ids.map((id) => <TodoRow key={id} id={id} />);
247
+ ```
248
+
249
+ Same options as `useQuery`, but returns only ordered ids with **referential stability** — the array identity changes only when membership or order changes, not on field updates. Pair with per-row `useEntity` for large lists.
250
+
251
+ ### `useMutate(collection)`
252
+
253
+ ```typescript
254
+ const {create, update, remove} = useMutate("todos");
255
+
256
+ const {id, mutationId} = create({data: {title: "Milk", completed: false}});
257
+ update({id, data: {completed: true}});
258
+ remove({id});
259
+ ```
260
+
261
+ Each call applies locally, enqueues in the outbox, and kicks off replay.
262
+
263
+ ### `useSyncStatus()`
264
+
265
+ ```typescript
266
+ const status = useSyncStatus();
267
+ // status.isOnline, status.isSyncing, status.queuedCount, status.conflictCount, ...
268
+ ```
269
+
270
+ Reactive aggregate status; re-renders on outbox, conflict, cursor, connectivity, and drain-progress changes.
271
+
272
+ ### `useConflicts()`
273
+
274
+ ```typescript
275
+ const {conflicts, resolve} = useConflicts();
276
+ resolve({mutationId, strategy: "useServer"});
277
+ resolve({mutationId, strategy: "keepMine"});
278
+ ```
279
+
280
+ ### `useSyncDebugLog()`
281
+
282
+ ```typescript
283
+ const {enabled, events, stats, log, clear} = useSyncDebugLog();
284
+ ```
285
+
286
+ Available when the client was created with `debug: true`. `log?.snapshot()` returns a JSON-serializable object suitable for debug UIs or tooling.
287
+
288
+ ### `useSyncDbClient()`
289
+
290
+ Returns the `SyncDb` instance from context (escape hatch for imperative calls like `forceResync()`).
291
+
292
+ ## Conflict API
293
+
294
+ A **conflict** occurs when the client's `baseVersion` (last seen `_syncSeq`) does not match the server's current seq. The server responds with a `conflict` nack (HTTP **409** on `POST /sync/mutate`, or `sync:nack` with `code: "conflict"` on the socket) carrying the canonical server document.
295
+
296
+ The client records conflicts in the local `_conflicts` table. Read them with:
297
+
298
+ ```typescript
299
+ import {listConflicts} from "@terreno/syncdb";
300
+
301
+ const conflicts = listConflicts({store: client.store});
302
+ ```
303
+
304
+ In React, prefer `useConflicts()`.
305
+
306
+ ### Resolution strategies
307
+
308
+ | Strategy | Behavior |
309
+ |----------|----------|
310
+ | `"useServer"` | Overwrite local entity with canonical server data/seq; clear pending state; discard conflicted outbox row. Writes a **tombstone** when the server side is deleted. |
311
+ | `"keepMine"` | Re-enqueue the mutation under a **fresh** `mutationId` with `baseVersion` set to the server's seq; local optimistic data is kept. |
312
+
313
+ Resolve via `client.resolveConflict({mutationId, strategy})` or `useConflicts().resolve(...)`.
314
+
315
+ ### Conflict handling modes
316
+
317
+ - **Default (`haltQueueOnConflict: false`)** — per-entity blocking. Other entities keep draining.
318
+ - **`haltQueueOnConflict: true`** — any conflict halts the entire drain until resolved.
319
+
320
+ `getSyncStatus().blockedEntities` counts entities blocked by conflicts or terminal validation failures.
321
+
322
+ ## Sync status API
323
+
324
+ `getSyncStatus()` / `useSyncStatus()` return `SyncStatus`:
325
+
326
+ | Field | Type | Description |
327
+ |-------|------|-------------|
328
+ | `isOnline` | `boolean` | Transport connected |
329
+ | `isSyncing` | `boolean` | Reconcile or replay in flight |
330
+ | `queuedCount` | `number` | Outbox rows awaiting send |
331
+ | `conflictCount` | `number` | Unresolved conflicts |
332
+ | `failedCount` | `number` | Terminal `failed` outbox rows |
333
+ | `paused` | `"auth"?` | Replay paused for auth failure |
334
+ | `blockedEntities` | `number` | Entities blocked (conflict or validation) |
335
+ | `draining` | `boolean` | Outbox drain actively running |
336
+ | `sentThisDrain` | `number` | Mutations sent in current/most recent drain |
337
+ | `totalThisDrain` | `number` | Queue length when drain began |
338
+ | `streams` | `Record<string, number>` | Per-stream cursors (stream → highest applied seq) |
339
+ | `collections` | `Record<string, SyncCollectionStatus>` | Per-collection queued/conflict/failed counts |
340
+ | `persistence` | `"durable" \| "memory" \| "error"` | Local persistence health (web) |
341
+
342
+ `SyncCollectionStatus`: `{queuedCount, conflictCount, failedCount}`.
343
+
344
+ ## Stream scoping
345
+
346
+ Streams are the unit of ordered delivery, keyed `{collection}|{scope}`:
347
+
348
+ ```typescript
349
+ sync: {scope: {type: "owner"}} // todos|owner:{ownerId}
350
+ sync: {scope: {type: "owner", field: "userId"}} // todos|owner:{userId}
351
+ sync: {scope: {type: "tenant", field: "organizationId"}} // todos|tenant:{orgId}
352
+ sync: {scope: {type: "broadcast"}} // todos|all
353
+ sync: {
354
+ scope: (doc) => String(doc.workspaceId),
355
+ snapshotFilter: (user) => ({workspaceId: {$in: [...]}}), // required for custom
356
+ }
357
+ ```
358
+
359
+ - **Owner** streams use the authenticated socket's user id (client cannot pick another user's stream).
360
+ - **Tenant/custom** scopes resolve memberships via `SyncApp` `getUserScopes`.
361
+ - **`snapshotFilter`** restricts `GET /sync/snapshot` server-side. Auto-derived for owner/tenant; required for custom resolver scopes.
362
+
363
+ ## Sync protocol
364
+
365
+ ### HTTP routes (`SyncApp`, all authenticated)
366
+
367
+ | Endpoint | Purpose |
368
+ |----------|---------|
369
+ | `GET /sync/snapshot?collection=&stream=&cursor=&limit=` | Bootstrap + catch-up per stream |
370
+ | `GET /sync/streams` | Current stream membership for the user |
371
+ | `GET /sync/entities` | Point lookup for entity repair |
372
+ | `POST /sync/mutate` | Single mutation (HTTP fallback) |
373
+ | `POST /sync/mutate/batch` | Batched mutations (max 100, strict order, stop at first non-ack) |
374
+ | `GET /sync/key` | Per-user encryption key material (web) |
375
+
376
+ Conflict responses on mutate: **409** with `{nack}` body (`code: "conflict"`).
377
+
378
+ ### Socket events (`RealtimeApp`)
379
+
380
+ | Event | Direction | Payload |
381
+ |-------|-----------|---------|
382
+ | `sync:subscribe` / `sync:unsubscribe` | client → server | `{collections: string[]}` |
383
+ | `sync:subscribed` | server → client | `{collection, streams}` |
384
+ | `sync:error` | server → client | `{collection, message}` |
385
+ | `sync:delta` | server → client | `{collection, id, method, data?, seq, stream, deleted?, frontierSeq?}` |
386
+ | `sync:mutate` | client → server | `{mutationId, collection, operation, id?, data?, baseVersion?}` |
387
+ | `sync:ack` | server → client | `{mutationId, id, seq}` |
388
+ | `sync:nack` | server → client | `{mutationId, code, serverDoc?, serverSeq?, serverDeleted?, message?, retryAfterMs?}` |
389
+ | `sync:mutateBatch` | client → server | `{mutations: SyncMutateRequest[], batchId?}` |
390
+ | `sync:auth-expired` | server → client | `{reason: "expired" \| "disabled"}` — session re-validation sweep; client enters auth-pause |
391
+
392
+ Limits: 50 collection subscriptions per socket; 100 mutations/second per socket (each batch mutation counts); batches capped at 100.
393
+
394
+ ## Encryption at rest
395
+
396
+ **Web:** AES-256-GCM encrypted IndexedDB by default via pluggable `KeyProvider`:
397
+
398
+ - **`createServerKeyProvider`** (default) — derives key from `GET /sync/key` material (HKDF); server can rotate/revoke.
399
+ - **`createLocalKeyProvider`** — device-local random key; no server copy.
400
+
401
+ ```typescript
402
+ import {createLocalKeyProvider, createSyncDb} from "@terreno/syncdb";
403
+
404
+ const syncDb = createSyncDb({
405
+ name: "myapp",
406
+ collections,
407
+ authProvider,
408
+ baseUrl,
409
+ keyProvider: createLocalKeyProvider(),
410
+ });
411
+ ```
412
+
413
+ Undecryptable persisted data triggers wipe + re-bootstrap by default (`onDecryptFailure` to override). Storage read errors surface `persistence: "error"` without overwriting the blob.
414
+
415
+ **Native:** plaintext SQLite in the OS sandbox (by design).
416
+
417
+ ## Testing
418
+
419
+ ```typescript
420
+ import {createFakeTransport} from "@terreno/syncdb/testing";
421
+ import {createSyncDb} from "@terreno/syncdb";
422
+
423
+ const transport = createFakeTransport();
424
+ const client = createSyncDb({
425
+ name: "test",
426
+ collections: ["todos"],
427
+ authProvider: fakeAuth,
428
+ transport,
429
+ httpChannel: fakeHttp,
430
+ });
431
+
432
+ transport.respondWithAck();
433
+ transport.deliverDelta({...});
434
+ ```
435
+
436
+ `createFakeTransport` records sent mutations, simulates connectivity, delivers deltas, and queues ack/nack responders.
437
+
438
+ ## Environment variables
439
+
440
+ Syncdb reads no environment variables directly. Host apps typically set:
441
+
442
+ | Variable | Used by | Description |
443
+ |----------|---------|-------------|
444
+ | `EXPO_PUBLIC_API_URL` | `@terreno/rtk` `baseUrl` | Backend origin passed to `createSyncDb({baseUrl})` |
445
+
446
+ Backend sync requires a MongoDB replica set (`MONGO_URI` with `replicaSet=`) for `RealtimeApp` change streams.
447
+
448
+ ## Conventions
449
+
450
+ - **Local-first only** — there is no server-first mode. The local store is always the read source.
451
+ - **Synced models need `String` `_id`** — offline creates mint client ids (UUIDs); default ObjectId `_id` cast-fails.
452
+ - **Soft delete only** on synced models — `isDeletedPlugin` required; hard deletes break tombstone catch-up.
453
+ - **No `bulkWrite` / `updateMany` / `deleteMany`** on synced models — use per-document loops.
454
+ - **Do not write reserved tables** (`_outbox`, `_cursors`, `_conflicts`) directly — use `client.mutate`, hooks, and `resolveConflict`.
455
+ - **User switch wipes local data** — confirmed different-user login clears the previous user's store; bare logout/401 does not (INV-2).
456
+ - **Keep `@terreno/rtk` for non-synced routes** — generated OpenAPI hooks remain the right tool for custom REST endpoints, admin, and auth configuration. See [How to migrate from RTK to syncdb](../how-to/migrate-rtk-to-syncdb.md).
@@ -0,0 +1,97 @@
1
+ # Admin Mobile Navigation Implementation Plan
2
+
3
+ > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
4
+
5
+ **Goal:** On viewports below 768px, replace the admin shell’s fixed sidebar with a hamburger-triggered left slide-over drawer.
6
+
7
+ **Architecture:** Keep navigation content in one shared internal component. `AdminShell` uses `useWindowDimensions` to switch between desktop rail and mobile header + overlay drawer. Drawer closes on backdrop, close button, and nav selection.
8
+
9
+ **Tech Stack:** React Native / Expo, `@terreno/ui` (`Box`, `Heading`, `Text`, `IconButton`), bun test + `@testing-library/react-native`.
10
+
11
+ ## Global Constraints
12
+
13
+ - Breakpoint: below 768px → mobile; 768px and above → desktop sidebar.
14
+ - Menu style: left slide-over drawer with dismissible backdrop.
15
+ - Accessibility: hamburger labeled "Open navigation menu"; close labeled "Close navigation menu".
16
+ - Do not change public `AdminShellProps` API unless required.
17
+ - Always support React Native Web.
18
+
19
+ ---
20
+
21
+ ### Task 1: Failing AdminShell responsive tests
22
+
23
+ **Files:**
24
+ - Create: `admin-frontend/src/AdminShell.test.tsx`
25
+
26
+ **Interfaces:**
27
+ - Consumes: `AdminShell` from `./AdminShell`; mocked `./useAdminConfig` and `expo-router`
28
+ - Produces: failing tests that assert desktop sidebar at ≥768, mobile header below 768, drawer open/close, nav closes drawer, a11y labels
29
+
30
+ - [ ] **Step 1: Write the failing test file**
31
+
32
+ Mock `useWindowDimensions` via a mutable width, mock `useAdminConfig` with a small config (Home + one model), mock `expo-router.router.push`. Assert:
33
+
34
+ 1. width 1024 → `admin-shell-sidebar` present; hamburger absent
35
+ 2. width 500 → sidebar absent; `admin-shell-menu-button` present with accessibilityLabel "Open navigation menu"
36
+ 3. press menu → `admin-shell-drawer` and backdrop appear; close button has "Close navigation menu"
37
+ 4. press backdrop / close → drawer gone
38
+ 5. open drawer, press Home nav → `router.push` called and drawer closed
39
+
40
+ - [ ] **Step 2: Run tests and confirm failure**
41
+
42
+ Run: `cd admin-frontend && bun test src/AdminShell.test.tsx`
43
+ Expected: FAIL (missing mobile UI / testIDs)
44
+
45
+ ---
46
+
47
+ ### Task 2: Implement responsive AdminShell
48
+
49
+ **Files:**
50
+ - Modify: `admin-frontend/src/AdminShell.tsx`
51
+ - Test: `admin-frontend/src/AdminShell.test.tsx`
52
+
53
+ **Interfaces:**
54
+ - Consumes: nav data from `useAdminConfig` (existing)
55
+ - Produces: `MOBILE_BREAKPOINT = 768`; shared sidebar body; mobile header; overlay drawer with backdrop
56
+
57
+ - [ ] **Step 1: Extract shared sidebar body**
58
+
59
+ Create an internal `AdminShellSidebarNav` that receives navigate callbacks, grouped models, scripts, screens, configurationPath, footer, sidebarVariant, and optional `onNavigate` that runs after each nav press (used to close the drawer).
60
+
61
+ - [ ] **Step 2: Wire responsive chrome**
62
+
63
+ ```tsx
64
+ const MOBILE_BREAKPOINT = 768;
65
+ const {width} = useWindowDimensions();
66
+ const isMobileLayout = width < MOBILE_BREAKPOINT;
67
+ const [isNavOpen, setIsNavOpen] = useState(false);
68
+ ```
69
+
70
+ Desktop: existing fixed 280px sidebar.
71
+ Mobile: compact header (`admin-shell-mobile-header`) with `IconButton` (`iconName="bars"`, testID `admin-shell-menu-button`) + "Admin" heading; main content full width.
72
+ When `isNavOpen`: absolute full-screen overlay (`admin-shell-drawer`) with backdrop (`admin-shell-drawer-backdrop`) and 280px panel containing close `IconButton` (`iconName="xmark"`, testID `admin-shell-drawer-close`) + shared nav. Close on backdrop, close button, and after navigate.
73
+
74
+ - [ ] **Step 3: Close drawer when resizing to desktop**
75
+
76
+ `useEffect` clears `isNavOpen` when `!isMobileLayout`.
77
+
78
+ - [ ] **Step 4: Run tests**
79
+
80
+ Run: `cd admin-frontend && bun test src/AdminShell.test.tsx`
81
+ Expected: PASS
82
+
83
+ - [ ] **Step 5: Compile and lint**
84
+
85
+ Run: `cd admin-frontend && bun run compile && bun run lint`
86
+ Expected: clean
87
+
88
+ ---
89
+
90
+ ### Task 3: Manual UI verification
91
+
92
+ **Files:** none (runtime check in example-frontend admin)
93
+
94
+ - [ ] **Step 1:** With backend + `frontend:web` running, open `/admin` as `admin@example.com`
95
+ - [ ] **Step 2:** Narrow viewport below 768 → hamburger header, no fixed sidebar
96
+ - [ ] **Step 3:** Open drawer, navigate, confirm close; widen to ≥768 → sidebar returns
97
+ - [ ] **Step 4:** Capture screenshot evidence under `.devdata/artifacts/`
@@ -0,0 +1,36 @@
1
+ # Admin Mobile Navigation Design
2
+
3
+ ## Goal
4
+
5
+ Make the admin shell usable on narrow devices by replacing its fixed 280px sidebar with a hamburger-triggered navigation drawer below 768px.
6
+
7
+ ## Responsive behavior
8
+
9
+ - At widths of 768px and above, preserve the existing sidebar and content layout.
10
+ - Below 768px, hide the fixed sidebar and show a compact header with an accessible hamburger button and the Admin label.
11
+ - Opening the hamburger displays the same grouped navigation in a left slide-over drawer above the page.
12
+ - A backdrop covers the remaining page and dismisses the drawer when pressed.
13
+ - The drawer also closes from its close button and after any navigation item is selected.
14
+
15
+ ## Component structure
16
+
17
+ Extract the sidebar body into a shared internal component so desktop and mobile render the same links, groups, configuration entry, and footer. `AdminShell` owns responsive width detection and drawer open state. The existing main content, top bar, breadcrumbs, and header actions remain unchanged apart from the added mobile header.
18
+
19
+ ## Accessibility
20
+
21
+ - The hamburger button has an "Open navigation menu" label.
22
+ - The drawer close button has a "Close navigation menu" label.
23
+ - Backdrop dismissal is available in addition to the explicit close button.
24
+ - Existing navigation labels and hints remain intact.
25
+
26
+ ## Testing
27
+
28
+ Add focused `AdminShell` tests covering:
29
+
30
+ - Desktop sidebar rendering at 768px and above.
31
+ - Mobile header rendering below 768px.
32
+ - Opening and closing the drawer.
33
+ - Closing after selecting a navigation item.
34
+ - Hamburger and close-button accessibility labels.
35
+
36
+ Run the admin frontend test suite, compile, lint, and responsive UI verification.
@@ -14,25 +14,25 @@ See: [`docs/implementationPlans/agentic-sdlc-plugin.md`](../implementationPlans/
14
14
 
15
15
  ## Phase 1: Decide and sanitize
16
16
 
17
- - [x] **Task 1.1**: Audit the plugin for repo- and org-specific content
17
+ - [ ] **Task 1.1**: Audit the plugin for repo- and org-specific content
18
18
  - Description: Grep all five skills for Flourish-specific and monorepo-specific content: `rg -n -i "phi|hipaa|flourish|dcb3|example-frontend|admin-spa|bun run|docs/implementationPlans"` across `plugins/`. Produce a table: file, line, category (org-specific / repo-path / hardcoded value / tool assumption), and the proposed replacement. Include the `.cursor-plugin/*.json` files (owner email, marketplace description). Do not change anything yet.
19
19
  - Files: none (findings in the PR body)
20
20
  - Depends on: none
21
21
  - Acceptance: every hit is categorized with a proposed replacement; the table covers all five skills plus both JSON files.
22
22
 
23
- - [x] **Task 1.2**: Generalize sensitive-data handling
23
+ - [ ] **Task 1.2**: Generalize sensitive-data handling
24
24
  - Description: Per IP question AP2, replace PHI/HIPAA-specific language with generalized sensitive-data language across `terreno-1-blend`, `terreno-4-pour`, and `terreno-5-dialin`. The rule must survive the rewrite: PR text, review replies, and attached evidence media must not contain sensitive data (PHI, PII, credentials, customer data). Keep it concrete — a vague "be careful with data" is not a usable instruction for an agent.
25
25
  - Files: `plugins/terreno-planning/skills/terreno-1-blend/SKILL.md`, `terreno-4-pour/SKILL.md`, `terreno-5-dialin/SKILL.md`
26
26
  - Depends on: Task 1.1
27
27
  - Acceptance: `rg -n -i "phi|hipaa" plugins/` returns nothing; each rewritten rule names the specific artifacts it applies to (PR body, comments, screenshots, recordings) and the data categories.
28
28
 
29
- - [x] **Task 1.3**: Fix the hardcoded branch suffix
29
+ - [ ] **Task 1.3**: Fix the hardcoded branch suffix
30
30
  - Description: `terreno-4-pour` documents the branch convention as `cursor/<descriptive-name>-dcb3`. The suffix is per-agent-run, not a fixed literal, so this will produce wrong branch names. Replace it with a description of the convention (a `cursor/` prefix plus the run-specific suffix the agent was given) and instruct the agent to use the suffix from its own run instructions rather than any literal in the skill.
31
31
  - Files: `plugins/terreno-planning/skills/terreno-4-pour/SKILL.md`
32
32
  - Depends on: Task 1.1
33
33
  - Acceptance: no literal run suffix appears anywhere in `plugins/`; the convention is described such that an agent in any run produces a correctly-suffixed branch.
34
34
 
35
- - [x] **Task 1.4**: Clean up plugin metadata
35
+ - [ ] **Task 1.4**: Clean up plugin metadata
36
36
  - Description: Update `.cursor-plugin/marketplace.json` and `plugins/terreno-planning/.cursor-plugin/plugin.json`: adjust the marketplace description per the AP1 decision (it currently says "Team marketplace"), review whether a personal email is the right contact for a public plugin versus a role address, and add a field or README note declaring which Terreno versions the plugin targets (IP question AP7). Add `plugins/terreno-planning/LICENSE` matching the root license.
37
37
  - Files: `.cursor-plugin/marketplace.json`, `plugins/terreno-planning/.cursor-plugin/plugin.json`, `plugins/terreno-planning/LICENSE` (new)
38
38
  - Depends on: Task 1.1
@@ -99,7 +99,7 @@ See: [`docs/implementationPlans/agentic-sdlc-plugin.md`](../implementationPlans/
99
99
  - Acceptance: under 150 lines; every coffee name is glossed; the installation steps were performed from a fresh state; the quickstart matches a run you completed.
100
100
 
101
101
  - [ ] **Task 4.2**: Write `docs/explanation/agentic-sdlc.md`
102
- - Description: The conceptual case, no commands. Cover: the process layer versus the MCP tool layer, and why both are needed; why planning is a separate stage with a question-first gate (agents confidently plan the wrong thing); why review happens in a **fresh context** rather than by asking the implementing agent to check its own work; why verification is a separate stage from implementation; why the frontend gate demands evidence rather than a claim; and why flaky CI must be classified rather than guessed at. Then an honest limits section: what the pipeline does not do, where a human must decide, and why the review loop persists through slow CI but stops when no autonomous action can advance a terminal failure. Reuse the mermaid diagram from the IP.
102
+ - Description: The conceptual case, no commands. Cover: the process layer versus the MCP tool layer, and why both are needed; why planning is a separate stage with a question-first gate (agents confidently plan the wrong thing); why review happens in a **fresh context** rather than by asking the implementing agent to check its own work; why verification is a separate stage from implementation; why the frontend gate demands evidence rather than a claim; and why flaky CI must be classified rather than guessed at. Then an honest limits section: what the pipeline does not do, where a human must decide, and the 15-minute review timeout. Reuse the mermaid diagram from the IP.
103
103
  - Files: `docs/explanation/agentic-sdlc.md` (new), `docs/explanation/README.md`
104
104
  - Depends on: Task 4.1
105
105
  - Acceptance: no commands; all six design rationales explained; a limits section naming at least three things the pipeline will not do; linked from the explanation index.