@terreno/mcp 57.0.0 → 57.1.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 (220) hide show
  1. package/dist/bootstrap.d.ts +1 -1
  2. package/dist/bootstrap.d.ts.map +1 -1
  3. package/dist/bootstrap.js +381 -191
  4. package/dist/bootstrap.js.map +1 -1
  5. package/dist/docs/guidelines/api/skills/mongoose-schema-safety/SKILL.md +8 -0
  6. package/dist/docs/resources/overview.md +6 -1
  7. package/dist/docs/resources/rtk.md +3 -1
  8. package/dist/docs/resources/syncdb.md +43 -0
  9. package/dist/docs/upgrades/56.0.0-beta.2.md +34 -0
  10. package/dist/docs/upgrades/57.1.0.md +126 -0
  11. package/dist/docs/versioned/next/README.md +15 -3
  12. package/dist/docs/versioned/next/explanation/README.md +3 -0
  13. package/dist/docs/versioned/next/explanation/admin-consent-migration.md +15 -0
  14. package/dist/docs/versioned/next/explanation/admin-plugin-frontend.md +12 -0
  15. package/dist/docs/versioned/next/explanation/announcements/rtk-deprecation.md +29 -0
  16. package/dist/docs/versioned/next/explanation/authentication.md +51 -58
  17. package/dist/docs/versioned/next/explanation/local-first-data.md +52 -0
  18. package/dist/docs/versioned/next/explanation/loop-engineering.md +117 -0
  19. package/dist/docs/versioned/next/explanation/repository-settings.md +8 -0
  20. package/dist/docs/versioned/next/explanation/roadmap-process.md +22 -9
  21. package/dist/docs/versioned/next/explanation/roadmap-seed-issues.md +39 -0
  22. package/dist/docs/versioned/next/how-to/README.md +3 -0
  23. package/dist/docs/versioned/next/how-to/admin-add-model.md +41 -0
  24. package/dist/docs/versioned/next/how-to/admin-custom-home.md +31 -0
  25. package/dist/docs/versioned/next/how-to/admin-custom-widget.md +42 -0
  26. package/dist/docs/versioned/next/how-to/admin-import-prebuilt.md +25 -0
  27. package/dist/docs/versioned/next/how-to/circleci.md +173 -0
  28. package/dist/docs/versioned/next/how-to/configure-better-auth.md +13 -10
  29. package/dist/docs/versioned/next/how-to/expose-mcp-tools.md +194 -0
  30. package/dist/docs/versioned/next/how-to/install-agent-skills.md +46 -0
  31. package/dist/docs/versioned/next/how-to/migrate-rtk-to-syncdb.md +26 -1
  32. package/dist/docs/versioned/next/how-to/websocket-integration.md +2 -1
  33. package/dist/docs/versioned/next/implementationPlans/ModularAPI.md +1 -0
  34. package/dist/docs/versioned/next/implementationPlans/README.md +31 -17
  35. package/dist/docs/versioned/next/implementationPlans/admin-improvements-research.md +136 -0
  36. package/dist/docs/versioned/next/implementationPlans/admin-improvements.md +157 -527
  37. package/dist/docs/versioned/next/implementationPlans/admin-only-research.md +4 -0
  38. package/dist/docs/versioned/next/implementationPlans/admin-only.md +3 -1
  39. package/dist/docs/versioned/next/implementationPlans/admin-script-runner.md +1 -0
  40. package/dist/docs/versioned/next/implementationPlans/admin-ui-v2-django-parity.md +1 -0
  41. package/dist/docs/versioned/next/implementationPlans/agentic-sdlc-plugin.md +160 -130
  42. package/dist/docs/versioned/next/implementationPlans/apierror-standard-error-redesign.md +1 -0
  43. package/dist/docs/versioned/next/implementationPlans/b2b-platform-program.md +5 -4
  44. package/dist/docs/versioned/next/implementationPlans/comms-abstraction-research.md +48 -0
  45. package/dist/docs/versioned/next/implementationPlans/comms-abstraction.md +152 -62
  46. package/dist/docs/versioned/next/implementationPlans/comms-adapter-sendgrid.md +9 -9
  47. package/dist/docs/versioned/next/implementationPlans/comms-admin-dashboard.md +1 -1
  48. package/dist/docs/versioned/next/implementationPlans/consent-forms.md +4 -0
  49. package/dist/docs/versioned/next/implementationPlans/design-blend-skill.md +1 -0
  50. package/dist/docs/versioned/next/implementationPlans/docs-site-and-versioning.md +1 -0
  51. package/dist/docs/versioned/next/implementationPlans/feature-flags-openfeature.md +1 -0
  52. package/dist/docs/versioned/next/implementationPlans/feature-flags.md +4 -0
  53. package/dist/docs/versioned/next/implementationPlans/infra-mcp-research.md +1 -0
  54. package/dist/docs/versioned/next/implementationPlans/infra-mcp.md +3 -3
  55. package/dist/docs/versioned/next/implementationPlans/mcp-boost-parity.md +1 -0
  56. package/dist/docs/versioned/next/implementationPlans/migrate-cicd-to-circleci-research.md +1 -0
  57. package/dist/docs/versioned/next/implementationPlans/migrate-cicd-to-circleci.md +2 -2
  58. package/dist/docs/versioned/next/implementationPlans/model-router-actions.md +4 -0
  59. package/dist/docs/versioned/next/implementationPlans/model-router-mcp.md +5 -4
  60. package/dist/docs/versioned/next/implementationPlans/offline-mode.md +3 -1
  61. package/dist/docs/versioned/next/implementationPlans/oss-launch-program.md +11 -2
  62. package/dist/docs/versioned/next/implementationPlans/pluggable-database-sqlite-research.md +150 -0
  63. package/dist/docs/versioned/next/implementationPlans/pluggable-database-sqlite.md +247 -0
  64. package/dist/docs/versioned/next/implementationPlans/positioning-django-rails-universal.md +1 -1
  65. package/dist/docs/versioned/next/implementationPlans/public-roadmap-github.md +2 -2
  66. package/dist/docs/versioned/next/implementationPlans/rbac-permissions.md +52 -15
  67. package/dist/docs/versioned/next/implementationPlans/rtk-to-syncdb-migration-docs.md +13 -12
  68. package/dist/docs/versioned/next/implementationPlans/syncdb-codegen-research.md +137 -0
  69. package/dist/docs/versioned/next/implementationPlans/syncdb-codegen.md +63 -59
  70. package/dist/docs/versioned/next/implementationPlans/syncdb-local-first.md +1 -0
  71. package/dist/docs/versioned/next/implementationPlans/syncdb-phase-c-design.md +1 -0
  72. package/dist/docs/versioned/next/implementationPlans/terreno-langfuse-integration.md +1 -0
  73. package/dist/docs/versioned/next/implementationPlans/terreno-syncdb-2.md +1 -0
  74. package/dist/docs/versioned/next/implementationPlans/upgrade-banner.md +4 -0
  75. package/dist/docs/versioned/next/reference/README.md +6 -1
  76. package/dist/docs/versioned/next/reference/admin-config.md +51 -0
  77. package/dist/docs/versioned/next/reference/ai.md +15 -1
  78. package/dist/docs/versioned/next/reference/api.md +18 -0
  79. package/dist/docs/versioned/next/reference/comms.md +88 -7
  80. package/dist/docs/versioned/next/reference/environment-variables.md +9 -1
  81. package/dist/docs/versioned/next/reference/{rtk.md → legacy/rtk.md} +11 -7
  82. package/dist/docs/versioned/next/reference/lifecycle-plugin.md +54 -0
  83. package/dist/docs/versioned/next/reference/mcp-server.md +7 -0
  84. package/dist/docs/versioned/next/reference/syncdb.md +30 -3
  85. package/dist/docs/versioned/next/tasks/README.md +21 -10
  86. package/dist/docs/versioned/next/tasks/admin-improvements.md +189 -353
  87. package/dist/docs/versioned/next/tasks/admin-only.md +2 -0
  88. package/dist/docs/versioned/next/tasks/agentic-sdlc-plugin.md +54 -160
  89. package/dist/docs/versioned/next/tasks/comms-abstraction.md +30 -24
  90. package/dist/docs/versioned/next/tasks/comms-adapter-sendgrid.md +4 -4
  91. package/dist/docs/versioned/next/tasks/consent-forms.md +2 -0
  92. package/dist/docs/versioned/next/tasks/feature-flags.md +2 -0
  93. package/dist/docs/versioned/next/tasks/migrate-cicd-to-circleci.md +14 -10
  94. package/dist/docs/versioned/next/tasks/model-router-actions.md +2 -0
  95. package/dist/docs/versioned/next/tasks/model-router-mcp.md +27 -27
  96. package/dist/docs/versioned/next/tasks/offline-mode.md +2 -0
  97. package/dist/docs/versioned/next/tasks/pluggable-database-sqlite.md +44 -0
  98. package/dist/docs/versioned/next/tasks/public-roadmap-github.md +1 -1
  99. package/dist/docs/versioned/next/tasks/rtk-to-syncdb-migration-docs.md +15 -15
  100. package/dist/docs/versioned/next/tasks/syncdb-codegen.md +37 -31
  101. package/dist/docs/versioned/next/tasks/upgrade-banner.md +2 -0
  102. package/dist/index.js +24 -55
  103. package/dist/index.js.map +1 -1
  104. package/dist/local/localServer.d.ts.map +1 -1
  105. package/dist/local/localServer.js +9 -8
  106. package/dist/local/localServer.js.map +1 -1
  107. package/dist/local/localTools.d.ts +1 -1
  108. package/dist/local/localTools.d.ts.map +1 -1
  109. package/dist/prompts.d.ts.map +1 -1
  110. package/dist/prompts.js +44 -45
  111. package/dist/prompts.js.map +1 -1
  112. package/dist/resources.d.ts.map +1 -1
  113. package/dist/resources.js +9 -2
  114. package/dist/resources.js.map +1 -1
  115. package/dist/tools.d.ts +1 -1
  116. package/dist/tools.d.ts.map +1 -1
  117. package/dist/tools.js +4 -2
  118. package/dist/tools.js.map +1 -1
  119. package/package.json +7 -3
  120. package/src/docs/guidelines/api/skills/mongoose-schema-safety/SKILL.md +8 -0
  121. package/src/docs/resources/overview.md +6 -1
  122. package/src/docs/resources/rtk.md +3 -1
  123. package/src/docs/resources/syncdb.md +43 -0
  124. package/src/docs/upgrades/56.0.0-beta.2.md +34 -0
  125. package/src/docs/upgrades/57.1.0.md +126 -0
  126. package/src/docs/versioned/next/README.md +15 -3
  127. package/src/docs/versioned/next/explanation/README.md +3 -0
  128. package/src/docs/versioned/next/explanation/admin-consent-migration.md +15 -0
  129. package/src/docs/versioned/next/explanation/admin-plugin-frontend.md +12 -0
  130. package/src/docs/versioned/next/explanation/announcements/rtk-deprecation.md +29 -0
  131. package/src/docs/versioned/next/explanation/authentication.md +51 -58
  132. package/src/docs/versioned/next/explanation/local-first-data.md +52 -0
  133. package/src/docs/versioned/next/explanation/loop-engineering.md +117 -0
  134. package/src/docs/versioned/next/explanation/repository-settings.md +8 -0
  135. package/src/docs/versioned/next/explanation/roadmap-process.md +22 -9
  136. package/src/docs/versioned/next/explanation/roadmap-seed-issues.md +39 -0
  137. package/src/docs/versioned/next/how-to/README.md +3 -0
  138. package/src/docs/versioned/next/how-to/admin-add-model.md +41 -0
  139. package/src/docs/versioned/next/how-to/admin-custom-home.md +31 -0
  140. package/src/docs/versioned/next/how-to/admin-custom-widget.md +42 -0
  141. package/src/docs/versioned/next/how-to/admin-import-prebuilt.md +25 -0
  142. package/src/docs/versioned/next/how-to/circleci.md +173 -0
  143. package/src/docs/versioned/next/how-to/configure-better-auth.md +13 -10
  144. package/src/docs/versioned/next/how-to/expose-mcp-tools.md +194 -0
  145. package/src/docs/versioned/next/how-to/install-agent-skills.md +46 -0
  146. package/src/docs/versioned/next/how-to/migrate-rtk-to-syncdb.md +26 -1
  147. package/src/docs/versioned/next/how-to/websocket-integration.md +2 -1
  148. package/src/docs/versioned/next/implementationPlans/ModularAPI.md +1 -0
  149. package/src/docs/versioned/next/implementationPlans/README.md +31 -17
  150. package/src/docs/versioned/next/implementationPlans/admin-improvements-research.md +136 -0
  151. package/src/docs/versioned/next/implementationPlans/admin-improvements.md +157 -527
  152. package/src/docs/versioned/next/implementationPlans/admin-only-research.md +4 -0
  153. package/src/docs/versioned/next/implementationPlans/admin-only.md +3 -1
  154. package/src/docs/versioned/next/implementationPlans/admin-script-runner.md +1 -0
  155. package/src/docs/versioned/next/implementationPlans/admin-ui-v2-django-parity.md +1 -0
  156. package/src/docs/versioned/next/implementationPlans/agentic-sdlc-plugin.md +160 -130
  157. package/src/docs/versioned/next/implementationPlans/apierror-standard-error-redesign.md +1 -0
  158. package/src/docs/versioned/next/implementationPlans/b2b-platform-program.md +5 -4
  159. package/src/docs/versioned/next/implementationPlans/comms-abstraction-research.md +48 -0
  160. package/src/docs/versioned/next/implementationPlans/comms-abstraction.md +152 -62
  161. package/src/docs/versioned/next/implementationPlans/comms-adapter-sendgrid.md +9 -9
  162. package/src/docs/versioned/next/implementationPlans/comms-admin-dashboard.md +1 -1
  163. package/src/docs/versioned/next/implementationPlans/consent-forms.md +4 -0
  164. package/src/docs/versioned/next/implementationPlans/design-blend-skill.md +1 -0
  165. package/src/docs/versioned/next/implementationPlans/docs-site-and-versioning.md +1 -0
  166. package/src/docs/versioned/next/implementationPlans/feature-flags-openfeature.md +1 -0
  167. package/src/docs/versioned/next/implementationPlans/feature-flags.md +4 -0
  168. package/src/docs/versioned/next/implementationPlans/infra-mcp-research.md +1 -0
  169. package/src/docs/versioned/next/implementationPlans/infra-mcp.md +3 -3
  170. package/src/docs/versioned/next/implementationPlans/mcp-boost-parity.md +1 -0
  171. package/src/docs/versioned/next/implementationPlans/migrate-cicd-to-circleci-research.md +1 -0
  172. package/src/docs/versioned/next/implementationPlans/migrate-cicd-to-circleci.md +2 -2
  173. package/src/docs/versioned/next/implementationPlans/model-router-actions.md +4 -0
  174. package/src/docs/versioned/next/implementationPlans/model-router-mcp.md +5 -4
  175. package/src/docs/versioned/next/implementationPlans/offline-mode.md +3 -1
  176. package/src/docs/versioned/next/implementationPlans/oss-launch-program.md +11 -2
  177. package/src/docs/versioned/next/implementationPlans/pluggable-database-sqlite-research.md +150 -0
  178. package/src/docs/versioned/next/implementationPlans/pluggable-database-sqlite.md +247 -0
  179. package/src/docs/versioned/next/implementationPlans/positioning-django-rails-universal.md +1 -1
  180. package/src/docs/versioned/next/implementationPlans/public-roadmap-github.md +2 -2
  181. package/src/docs/versioned/next/implementationPlans/rbac-permissions.md +52 -15
  182. package/src/docs/versioned/next/implementationPlans/rtk-to-syncdb-migration-docs.md +13 -12
  183. package/src/docs/versioned/next/implementationPlans/syncdb-codegen-research.md +137 -0
  184. package/src/docs/versioned/next/implementationPlans/syncdb-codegen.md +63 -59
  185. package/src/docs/versioned/next/implementationPlans/syncdb-local-first.md +1 -0
  186. package/src/docs/versioned/next/implementationPlans/syncdb-phase-c-design.md +1 -0
  187. package/src/docs/versioned/next/implementationPlans/terreno-langfuse-integration.md +1 -0
  188. package/src/docs/versioned/next/implementationPlans/terreno-syncdb-2.md +1 -0
  189. package/src/docs/versioned/next/implementationPlans/upgrade-banner.md +4 -0
  190. package/src/docs/versioned/next/reference/README.md +6 -1
  191. package/src/docs/versioned/next/reference/admin-config.md +51 -0
  192. package/src/docs/versioned/next/reference/ai.md +15 -1
  193. package/src/docs/versioned/next/reference/api.md +18 -0
  194. package/src/docs/versioned/next/reference/comms.md +88 -7
  195. package/src/docs/versioned/next/reference/environment-variables.md +9 -1
  196. package/src/docs/versioned/next/reference/{rtk.md → legacy/rtk.md} +11 -7
  197. package/src/docs/versioned/next/reference/lifecycle-plugin.md +54 -0
  198. package/src/docs/versioned/next/reference/mcp-server.md +7 -0
  199. package/src/docs/versioned/next/reference/syncdb.md +30 -3
  200. package/src/docs/versioned/next/tasks/README.md +21 -10
  201. package/src/docs/versioned/next/tasks/admin-improvements.md +189 -353
  202. package/src/docs/versioned/next/tasks/admin-only.md +2 -0
  203. package/src/docs/versioned/next/tasks/agentic-sdlc-plugin.md +54 -160
  204. package/src/docs/versioned/next/tasks/comms-abstraction.md +30 -24
  205. package/src/docs/versioned/next/tasks/comms-adapter-sendgrid.md +4 -4
  206. package/src/docs/versioned/next/tasks/consent-forms.md +2 -0
  207. package/src/docs/versioned/next/tasks/feature-flags.md +2 -0
  208. package/src/docs/versioned/next/tasks/migrate-cicd-to-circleci.md +14 -10
  209. package/src/docs/versioned/next/tasks/model-router-actions.md +2 -0
  210. package/src/docs/versioned/next/tasks/model-router-mcp.md +27 -27
  211. package/src/docs/versioned/next/tasks/offline-mode.md +2 -0
  212. package/src/docs/versioned/next/tasks/pluggable-database-sqlite.md +44 -0
  213. package/src/docs/versioned/next/tasks/public-roadmap-github.md +1 -1
  214. package/src/docs/versioned/next/tasks/rtk-to-syncdb-migration-docs.md +15 -15
  215. package/src/docs/versioned/next/tasks/syncdb-codegen.md +37 -31
  216. package/src/docs/versioned/next/tasks/upgrade-banner.md +2 -0
  217. package/dist/docs/versioned/next/implementationPlans/PLAN_INDEX.md +0 -73
  218. package/dist/docs/versioned/next/implementationPlans/syncdb-api-inventory.md +0 -303
  219. package/src/docs/versioned/next/implementationPlans/PLAN_INDEX.md +0 -73
  220. package/src/docs/versioned/next/implementationPlans/syncdb-api-inventory.md +0 -303
@@ -1,429 +1,265 @@
1
- # Task List: Admin Interface Overhaul (Django-Admin Parity)
1
+ # Task List: Admin Improvements (post–Admin UI v2)
2
2
 
3
- *Structured task breakdown for automated implementation. Each task is independently implementable and testable. Phases are ordered; tasks within a phase often run in parallel.*
3
+ *Executable checklist for the gap after [Admin UI v2](./admin-ui-v2-django-parity.md). See [admin-improvements.md](../implementationPlans/admin-improvements.md).*
4
4
 
5
- *Folded in from adversarial review:* tasks 1.0, 1.6a, 1.7a, 2.6a, 2.8a, 5.0, 6.0, 7.6, 7.7 are new. Tasks marked **(revised)** have updated descriptions or acceptance criteria.
5
+ **Decisions (2026-08-20):** Full IP; bulk actions = v2 `bulk-patch` only (no `/actions/:name`); `home.slots`; static permissions; `filterParser`; widgets in `admin-frontend/src/widgets/`; atomic example migration; new roadmap issue.
6
+
7
+ ---
8
+
9
+ ## Phase 0 — Admin UI v2 baseline (complete)
10
+
11
+ *Do not re-implement. Reference: `docs/implementationPlans/admin-ui-v2-django-parity.md`.*
12
+
13
+ - [x] **Task 0.1**: Config contract `schemaVersion: 2`, model meta, `home.slots`, scripts, customScreens
14
+ - Evidence: `admin-backend/src/adminApp.models.test.ts`
15
+
16
+ - [x] **Task 0.2**: `bulk-patch` + `background-tasks` + script runner
17
+ - Evidence: `admin-backend/src/adminApp.ts`, `admin-script-runner.md`
18
+
19
+ - [x] **Task 0.3**: `AdminShell`, `AdminHome`, list search/filters/bulk (inline), form fieldsets/readonly
20
+ - Evidence: `admin-frontend/src/AdminShell.tsx`, `AdminHome.tsx`, `AdminModelTable.tsx`, `AdminModelForm.tsx`
21
+
22
+ - [x] **Task 0.4**: Audit log + `recentActivity` widget
23
+ - Evidence: `example-backend` `AdminAuditLog`, `AdminHome.test.tsx`
24
+
25
+ - [x] **Task 0.5**: admin-spa + example v2 demo
26
+ - Evidence: `admin-spa/`, `example-backend/src/server.ts` v2 registrations
27
+
28
+ ---
6
29
 
7
30
  ## Phase 1 — Core API surface (`@terreno/api`)
8
31
 
9
- - [ ] **Task 1.0 (NEW)**: Extend `TerrenoPlugin.register` signature additively
10
- - Description: Change `TerrenoPlugin.register(app, openApi?)` to `register(app, openApi?, terrenoApp?)` in `api/src/terrenoPlugin.ts`. Update the call site in `api/src/terrenoApp.ts:321` to `registration.register(app, oapi, this)`. Existing plugins ignore the new arg — verify each one (`HealthApp`, `BetterAuthApp`, `ConfigurationApp`, `FeatureFlagsApp`, `LangfuseApp`, `DocumentStorageApp`, `ConsentApp`) compiles. Similarly: rename `ModelRouterRegistration._buildWithOpenApi(openApi)` to `_buildWithContext({openApi, terrenoApp})` (internal API). Update `TerrenoApp.build()` accordingly. Add `setMaxListeners(50)` to TerrenoApp's emitter.
11
- - Files: `api/src/terrenoPlugin.ts`, `api/src/terrenoApp.ts`, `api/src/api.ts`
12
- - Depends on: none
13
- - Acceptance: Existing tests pass unchanged. A new test verifies a plugin's `register` receives the `TerrenoApp` instance as the third arg. `_buildWithContext` is the public-internal API; `_buildWithOpenApi` no longer exists.
32
+ - [x] **Task 1.0**: Extend `TerrenoPlugin.register` additively
33
+ - `register(app, openApi?, terrenoApp?)`; `TerrenoApp.build()` passes `this`; rename `_buildWithOpenApi` → `_buildWithContext({openApi, terrenoApp})`; `setMaxListeners(50)`
34
+ - Files: `api/src/terrenoPlugin.ts`, `terrenoApp.ts`, `api.ts`
35
+ - Acceptance: Existing plugins compile; test receives `terrenoApp` as third arg.
14
36
 
15
- - [ ] **Task 1.1**: Define admin types in `@terreno/api`
16
- - Description: Create `api/src/adminTypes.ts` exporting `AdminConfig`, `AdminFilter`, `AdminFieldset`, `AdminFieldOverride`, `AdminAction`, `AdminContribution`, `AdminModelContribution`, `AdminCustomScreen`, `AdminHomeWidgetContribution`, `AdminHomeConfig`, `AdminHomeWidgetSpec`, `AdminChangeEvent`. These live in `api` (not `admin-backend`) so plugins can declare contributions without depending on `admin-backend`. No runtime code yet — types only.
17
- - Files: `api/src/adminTypes.ts`, `api/src/index.ts` (re-export)
18
- - Depends on: none
19
- - Acceptance: Types import successfully into `admin-backend`, `feature-flags`, and a new test file. No type errors across the monorepo (`bun run compile`).
37
+ - [x] **Task 1.1**: `api/src/adminTypes.ts`
38
+ - Export `AdminConfig`, `AdminFilter`, `AdminFieldset`, `AdminContribution`, `AdminModelContribution`, `AdminCustomScreen`, `AdminHomeWidgetContribution`, `AdminChangeEvent`, etc. (no `AdminAction.run` — bulk stays declarative v2 shape)
39
+ - Files: `api/src/adminTypes.ts`, `index.ts`
40
+ - Acceptance: `bun run compile` clean across monorepo.
20
41
 
21
- - [ ] **Task 1.2**: Extend `ModelRouterOptions` with `admin?: AdminConfig`
22
- - Description: Add the optional `admin` field to `ModelRouterOptions` in `api/src/api.ts`. No runtime use yet — it's a passthrough that consumers (AdminApp) read off the registration. Update both `modelRouter` signatures (Router and Registration). Type only — no behavioral change.
23
- - Files: `api/src/api.ts`
42
+ - [x] **Task 1.2**: `admin?: AdminConfig` on `ModelRouterOptions`
43
+ - Files: `api/src/api.ts`
24
44
  - Depends on: 1.1
25
- - Acceptance: Existing example-backend compiles unchanged. Adding `admin: {displayName: "X", listDisplay: ["name"]}` to a `modelRouter` call typechecks.
26
45
 
27
- - [ ] **Task 1.3**: `TerrenoApp` registration accessors
28
- - Description: Add `getRegistrations(): ModelRouterRegistration[]` and `getPlugins(): TerrenoPlugin[]` to `TerrenoApp` in `api/src/terrenoApp.ts`. AdminApp will call these from its own `register()` to discover admin contributions. Do not change existing behavior.
29
- - Files: `api/src/terrenoApp.ts`, `api/src/terrenoApp.test.ts` (add accessor tests)
46
+ - [x] **Task 1.3**: `TerrenoApp.getRegistrations()` + `getPlugins()`
47
+ - Files: `api/src/terrenoApp.ts`, tests
30
48
  - Depends on: none
31
- - Acceptance: Test verifies `app.getRegistrations()` returns each registered router and `getPlugins()` returns each registered plugin.
32
49
 
33
- - [ ] **Task 1.4**: `TerrenoPlugin.adminContribution?()` interface addition
34
- - Description: Add `adminContribution?(): AdminContribution` to the `TerrenoPlugin` interface in `api/src/terrenoPlugin.ts`. Optional — no existing plugins are affected.
35
- - Files: `api/src/terrenoPlugin.ts`
50
+ - [x] **Task 1.4**: `TerrenoPlugin.adminContribution?()`
51
+ - Files: `api/src/terrenoPlugin.ts`
36
52
  - Depends on: 1.1
37
- - Acceptance: Existing plugins (`FeatureFlagsApp`, `ConsentApp`, `DocumentStorageApp`, `LangfuseApp`, `BetterAuthApp`, `api-health`) compile unchanged.
38
53
 
39
- - [ ] **Task 1.5**: Typed event bus on `TerrenoApp`
40
- - Description: Add `on(event, listener)`, `off(event, listener)`, and internal `emit(event, payload)` to `TerrenoApp`. First event is `"admin:model.changed"` with the `AdminChangeEvent` payload. Implement as a small wrapper around node's `EventEmitter` typed via overloads. No transport — just an in-process bus.
41
- - Files: `api/src/terrenoApp.ts`, `api/src/terrenoApp.test.ts`
54
+ - [x] **Task 1.5**: Typed event bus (`admin:model.changed`)
55
+ - Files: `api/src/terrenoApp.ts`, tests
42
56
  - Depends on: 1.1
43
- - Acceptance: Tests verify (a) listeners receive emitted events, (b) `off` removes them, (c) typing prevents emitting unknown event names.
44
57
 
45
- - [ ] **Task 1.6 (revised)**: `modelRouter` emits scrubbed `admin:model.changed` when `admin.realtime` is true
46
- - Description: Inside `_buildWithContext({openApi, terrenoApp})`, wrap the existing `postCreate`/`postUpdate`/`postDelete` to additionally call `terrenoApp.emit("admin:model.changed", scrubbedEvent)` when `options.admin?.realtime === true`. Crucially: scrub the event's `document` field through a shared helper that strips both `admin.excludeFields` and `admin.hiddenFields` (same logic as `removeHiddenFields` in `admin-backend/src/adminApp.ts:132`; lift it into `api/src/api.ts` or a shared util). For delete events, omit `document` entirely. Event payload: `{type, modelName, routePath, documentId, document?, user: {id}, at}`.
47
- - Files: `api/src/api.ts`, `api/src/scrubAdminFields.ts` (NEW), `api/src/terrenoApp.ts`, `api/src/api.test.ts`
48
- - Depends on: 1.0, 1.2, 1.5
49
- - Acceptance: Test with `admin: {realtime: true, excludeFields: ["hash"]}` on a User-like model verifies events fire and the emitted `document` payload contains no `hash`. With `realtime: false`, no event. Delete event omits `document`.
50
-
51
- - [ ] **Task 1.6a (NEW)**: Shared scrub helper for excludeFields/hiddenFields with population
52
- - Description: Build the `scrubAdminFields(value, model, adminConfig, allModelAdmins)` helper. Recursive: when scrubbing a populated ref, look up the referenced model in `allModelAdmins` and apply *its* exclude+hidden union. If the referenced model has no admin config, return populated value as-is. Used by Task 1.6 emit AND by AdminApp's response handler (replaces today's `removeHiddenFields` in admin-backend/src/adminApp.ts:132).
53
- - Files: `api/src/scrubAdminFields.ts`, `api/src/scrubAdminFields.test.ts`
58
+ - [x] **Task 1.6**: `scrubAdminFields` helper + populated recursion
59
+ - Files: `api/src/scrubAdminFields.ts`, tests
54
60
  - Depends on: 1.1
55
- - Acceptance: Tests cover (a) top-level scrub, (b) nested arrays, (c) populated docs scrubbed by their own admin config, (d) unconfigured populated docs returned untouched.
56
-
57
- - [ ] **Task 1.7 (revised)**: `readonlyFields` server-side enforcement on ALL body-accepting routes
58
- - Description: Install a body-scrubbing middleware that drops `readonlyFields` keys from `req.body` for every body-accepting admin route — i.e., POST `/` (create), PATCH `/:id` (update), AND the array sub-routes `POST /:id/:field`, `PATCH /:id/:field/:itemId`, `DELETE /:id/:field/:itemId` (api/src/api.ts:1160-1174). Note POST `/` is a create — `readonlyFields` typically gates updates only, so make it config-driven: `readonlyFields` applies to PATCH+array sub-routes by default; `readonlyOnCreate?: boolean` (defaults false) gates the create path. Add a debug log when fields are dropped.
59
- - Files: `api/src/api.ts`, `api/src/api.test.ts`
60
- - Depends on: 1.0, 1.2
61
- - Acceptance: PATCH with `{created: "..."}` on a model with `readonlyFields: ["created"]` does not change the value. POST `/:id/checkboxes` with a body that includes a readonly key strips it. `readonlyOnCreate: true` causes POST `/` to also strip.
62
-
63
- - [ ] **Task 1.7a (NEW)**: `excludeFields` enforcement extends through `populatePaths` in responses
64
- - Description: When `removeHiddenFields` (now `scrubAdminFields`) processes a response, recursively apply each populated model's exclude+hidden set. Wired via AdminApp's response handler (which has access to the aggregated `allModelAdmins` map). Models without an admin config are passed through.
65
- - Files: `admin-backend/src/adminApp.ts`, `admin-backend/src/adminApp.test.ts`
66
- - Depends on: 1.6a, 2.2
67
- - Acceptance: A `Todo` admin populates `ownerId` → User. User admin has `excludeFields: ["hash", "salt"]`. GET `/admin/todos/:id` response's populated `ownerId.hash` is absent.
68
-
69
- - [ ] **Task 1.8**: `excludeFields` server-side enforcement
70
- - Description: Treat `admin.excludeFields` as a superset of `hiddenFields`: stripped from POST + PATCH bodies AND from list + detail responses. Existing `hiddenFields` continues to work and merges with `excludeFields` (union).
71
- - Files: `api/src/api.ts`, `api/src/api.test.ts`
72
- - Depends on: 1.2
73
- - Acceptance: A model with `excludeFields: ["hash"]` never includes `hash` in any admin response, and a POST/PATCH attempting to set `hash` does not persist it.
61
+
62
+ - [x] **Task 1.7**: Emit scrubbed `admin:model.changed` when `admin.realtime`
63
+ - Files: `api/src/api.ts`, tests
64
+ - Depends on: 1.0, 1.2, 1.5, 1.6
65
+
66
+ - [x] **Task 1.8**: `readonlyFields` / `excludeFields` enforcement on body-accepting routes
67
+ - Files: `api/src/api.ts`, tests
68
+ - Depends on: 1.0, 1.2, 1.6
69
+
70
+ - [x] **Task 1.9**: Wire `scrubAdminFields` into admin response paths (populated refs)
71
+ - Files: `admin-backend/src/adminApp.ts`, tests
72
+ - Depends on: 1.6, 2.2
73
+
74
+ ---
74
75
 
75
76
  ## Phase 2 — Admin backend aggregation
76
77
 
77
- - [ ] **Task 2.1**: Legacy `AdminModelConfig` adapter
78
- - Description: Create `admin-backend/src/legacy.ts` that converts the old shape (`{model, routePath, displayName, listFields, defaultSort, fieldOverrides, fieldOrder, hiddenFields}`) into an `AdminModelContribution` (with `admin: AdminConfig`). Emit exactly one deprecation warning per legacy entry per process: `[admin] AdminApp.models entry "<displayName>" uses the legacy shape (...). Migrate to modelRouter({admin: {...}}) — see docs/how-to/admin-add-model.md`. Use a `Set<string>` to dedupe.
79
- - Files: `admin-backend/src/legacy.ts`, `admin-backend/src/legacy.test.ts`
78
+ - [x] **Task 2.1**: `legacy.ts` adapter + deprecation warnings
79
+ - Files: `admin-backend/src/legacy.ts`, tests
80
80
  - Depends on: 1.1
81
- - Acceptance: `convertLegacy({model, listFields: ['email']})` returns an equivalent `AdminModelContribution`. Same input twice → warning logged once. Both `listFields` and `defaultSort` map to `listDisplay` and `ordering`.
82
81
 
83
- - [ ] **Task 2.2**: Aggregate admin contributions from registered routers + plugins + legacy `models`
84
- - Description: Rewrite `AdminApp.register()` to collect admin contributions from three sources: (a) `options.models` (legacy, run through the adapter), (b) `parentApp.getRegistrations()` filtered for those with `options.admin`, (c) `parentApp.getPlugins()` calling `adminContribution()` on each. Dedupe by `routePath` with precedence host > plugin > registered. Build a single internal `AdminModelContribution[]` to drive everything downstream.
85
- - Files: `admin-backend/src/adminApp.ts`, `admin-backend/src/adminApp.test.ts`
82
+ - [x] **Task 2.2**: Aggregate from modelRouter `admin:` + plugins + legacy `models`
83
+ - Precedence: registered router > plugin > legacy; throw on duplicate registered `routePath`
84
+ - Files: `admin-backend/src/adminApp.ts`, tests
86
85
  - Depends on: 1.3, 1.4, 2.1
87
- - Acceptance: Test with a `TerrenoApp` that has (a) one modelRouter with `admin:`, (b) a plugin that returns `{models: [...]}` from `adminContribution()`, (c) an AdminApp with legacy `models: [...]`. `/admin/config` returns all three with no duplicates and the right precedence.
88
86
 
89
- - [ ] **Task 2.3**: Aggregate `customScreens`, `homeWidgets`, `widgetIds`, `scripts` from plugin contributions
90
- - Description: Extend `AdminApp` aggregation to merge `customScreens`, `homeWidgets` (yielding `widgetIds`), and `scripts` from all plugin `adminContribution()` returns. Combine with `AdminApp`'s own `scripts` and existing `customScreens` from options. Host's `home.widgets` ordering wins; plugin widgets only surface if listed.
91
- - Files: `admin-backend/src/adminApp.ts`, `admin-backend/src/adminApp.test.ts`
87
+ - [x] **Task 2.3**: Merge `customScreens`, `homeWidgets` → `widgetIds`, plugin `scripts`
88
+ - Files: `admin-backend/src/adminApp.ts`, tests
92
89
  - Depends on: 2.2
93
- - Acceptance: A plugin contributing `{customScreens: [{name: "x"}], homeWidgets: [{id: "w"}]}` causes `/admin/config` to include those entries. Host without `home.widgets: ["w"]` does *not* surface the widget by default.
94
90
 
95
- - [ ] **Task 2.4**: Extend `/admin/config` response shape
96
- - Description: Update `AdminConfigResponse` to include the new fields (per `AdminModelMeta`: `listDisplay`, `listDisplayLinks`, `searchFields`, `sortableFields`, `ordering`, `pageSize`, `filters`, `fieldsets`, `readonlyFields`, `autocompleteFields`, `actions`, `permissions`, `realtime`, `group`, `icon`). At the top level: `home`, `widgetIds`, `capabilities`. Continue emitting `listFields` and `defaultSort` as deprecated aliases for one release.
97
- - Files: `admin-backend/src/adminApp.ts`, `admin-backend/src/adminApp.test.ts`
91
+ - [x] **Task 2.4**: Add `widgetIds`, `capabilities` to `/admin/config` (additive; v2 fields unchanged)
92
+ - Files: `admin-backend/src/adminApp.ts`, tests
98
93
  - Depends on: 2.2
99
- - Acceptance: GET /admin/config returns the extended shape. Snapshot test covers the response. `listFields === listDisplay` and `defaultSort === ordering` for back-compat.
100
94
 
101
- - [ ] **Task 2.5**: Per-action permission booleans in `/admin/config`
102
- - Description: For each model, evaluate the current user against `adminPermissions.{list,read,create,update,delete}` (falling back to `IsAdmin`) and emit booleans in the config response. Same for each action's `permissions`. `/admin/config` is per-user — set `Cache-Control: private, no-store`.
103
- - Files: `admin-backend/src/adminApp.ts`, `admin-backend/src/adminApp.test.ts`
95
+ - [x] **Task 2.5**: Static `permissions` flags on config (v2 behavior — **no per-user eval**)
96
+ - Verify/document: flags reflect `adminPermissions` config, not runtime user
97
+ - Files: `admin-backend/src/adminApp.ts` (docs/tests only if already correct)
104
98
  - Depends on: 2.4
105
- - Acceptance: Two users with different permissions receive different `permissions` blocks. `Cache-Control: private, no-store` header present.
106
99
 
107
- - [ ] **Task 2.6 (revised)**: Filter parser — per-type strict shape
108
- - Description: Create `admin-backend/src/filterParser.ts`. Function `parseFilters(query, filters: AdminFilter[]): {filter: FilterQuery<any>, errors: {[k: string]: string}}`. Each declared `AdminFilter.type` accepts EXACTLY one value shape (see "Filter param parsing" in the IP); anything else returns a 400 with field-level errors. Mongo operator keys (`$ne`, `$gt`, `$where`, `$regex`, `$in`, …) NEVER pass through — they are detected by key prefix `$` and rejected. Prototype-pollution attempts (`__proto__`, `constructor`, `prototype` as keys) dropped silently and logged at warn level. Fields not declared in `admin.filters[]` ignored.
109
- - Files: `admin-backend/src/filterParser.ts`, `admin-backend/src/filterParser.test.ts`
100
+ - [x] **Task 2.6**: `filterParser.ts` — strict per-type validation, injection hardening
101
+ - Accept v2-compatible query shapes (`field`, `field_gte`/`field_lte` for dateRange, etc.)
102
+ - Files: `admin-backend/src/filterParser.ts`, tests (matrix from original IP)
110
103
  - Depends on: 1.1
111
- - Acceptance: Test matrix covers:
112
- - boolean: accepts `"true"`/`"false"`, rejects arrays / `"yes"` / objects
113
- - text: accepts scalar string, rejects arrays / objects
114
- - choice: accepts declared values, rejects unknown values / arrays / objects
115
- - ref: accepts valid ObjectId, rejects non-ObjectId / arrays / objects
116
- - date: accepts ISO string, rejects arrays / objects / malformed
117
- - dateRange: accepts `{gte, lte}` only with optional keys, rejects scalars / arrays / extra keys / non-ISO values
118
- - Operator injection: `filter[role][$ne]=admin`, `filter[role][$where]=...`, `filter[role][$regex]=...` all return 400
119
- - Prototype pollution: `filter[__proto__][toString]=...`, `filter[constructor][prototype][toString]=...` dropped silently, warn-logged
120
- - Undeclared field: `filter[ssn]=123` silently ignored, no 400
121
-
122
- - [ ] **Task 2.7 (revised)**: Wire filter parser, `sortableFields`, and shared search helper
123
- - Description: In the admin's internal modelRouter creation, the modelRouter's `queryFilter` becomes an async adapter that awaits `adminFilter(req)` and merges with the parsed filter object. Validate `?sort=` against `sortableFields` (400 on unknown). Build a single `runSearch(model, q, searchFields)` helper used by both the list endpoint (`?q=`) and the existing `/search` endpoint — the two no longer have separate implementations. The list endpoint inlines search-as-filter using `$or` over `searchFields` (or `_id` for valid ObjectIds), avoiding a separate round-trip.
124
- - Files: `admin-backend/src/adminApp.ts`, `admin-backend/src/runSearch.ts` (NEW; extract from existing /search code), `admin-backend/src/adminApp.test.ts`
125
- - Depends on: 2.6
126
- - Acceptance: `GET /admin/users?filter[admin]=true&q=alice&sort=-created&pageSize=10` returns matching admin users named "alice", sorted newest first, 10 per page, with no extra DB round-trip beyond list + count. Unknown sort field → 400. `?filter[ssn]=...` (undeclared) ignored. `adminFilter` invoked once per request and properly awaited.
127
-
128
- - [ ] **Task 2.8 (revised)**: Action runner endpoint with frozen `AdminActionContext`
129
- - Description: Create `admin-backend/src/actionRunner.ts`. Mount `POST /admin/{routePath}/actions/:actionName` per model with declared actions. Validate body `{ids: string[]}` — reject `ids.length > MAX_ACTION_IDS` (default 1000) with HTTP 413. Look up action by name. Run `action.permissions` (or fall back to `adminPermissions.update`). Validate ids via ONE round-trip: `Model.find({_id: {$in: ids}, ...resolvedAdminFilter}).select("_id")`. Silently drop ids that don't pass; pass the survivors into `ctx.ids`. Build a frozen `AdminActionContext` (no live `req`). If `action.background: true`: persist the context onto a `BackgroundTask`, return `{taskId}`. Else invoke `action.run(ctx)` synchronously and return `{updatedIds?, message?, results?}`. Wrap errors → `APIError`.
130
- - Files: `admin-backend/src/actionRunner.ts`, `admin-backend/src/actionRunner.test.ts`, `admin-backend/src/adminApp.ts` (mount routes)
131
- - Depends on: 2.2, 2.8a
132
- - Acceptance: Sync action: returns updated rows. Background action: returns `{taskId}`, runner picks it up later, `GET /admin/scripts/tasks/:id` reports progress. Permission denied → 403. `ids.length > 1000` → 413. ids outside `adminFilter` scope are silently excluded from `ctx.ids` (action sees fewer rows than the user selected — caller decides whether to warn). Background runner does NOT use the live `req`.
133
104
 
134
- - [ ] **Task 2.8a (NEW)**: `AdminActionContext` type + `BackgroundTask` snapshot persistence
135
- - Description: Define `AdminActionContext<T>` in `api/src/adminTypes.ts` (per the IP). Extend `BackgroundTask` to carry an opaque `actionContext` field (JSON) for background-action runs. The background runner deserializes the context and invokes `action.run(ctx)` outside the request lifecycle. Provide a tagged logger constructed from `{actionName, requestId, userId}`.
136
- - Files: `api/src/adminTypes.ts`, `api/src/api.ts` (BackgroundTask schema extension), tests
137
- - Depends on: 1.1
138
- - Acceptance: A background action launched from request A and a background action launched from request B never see each other's context. Re-running `action.run` after the request closes does not crash (no `req.headers` access, etc.).
105
+ - [x] **Task 2.7**: Wire filterParser + `adminFilter` async + `sortableFields` enforcement
106
+ - Files: `admin-backend/src/adminApp.ts`, tests
107
+ - Depends on: 2.6
139
108
 
140
- - [ ] **Task 2.9 (revised)**: `routePath` normalization + collision precedence (registered > plugin > legacy)
141
- - Description: Trailing-slash normalization so `/users` and `/users/` are treated identically. Collision precedence: **registered modelRouter beats plugin contribution beats legacy `AdminApp.models`**. The most-specific source wins; legacy is purely additive. Two registered modelRouters with the same `routePath` is a fatal misconfiguration — throw at `AdminApp.register()` time with a clear message naming both registrations. Warn on plugin-vs-legacy and plugin-vs-registered collisions where the resolution is unambiguous.
142
- - Files: `admin-backend/src/adminApp.ts`, `admin-backend/src/adminApp.test.ts`
109
+ - [x] **Task 2.8**: `routePath` normalization + collision rules
110
+ - Files: `admin-backend/src/adminApp.ts`, tests
143
111
  - Depends on: 2.2
144
- - Acceptance: Two registered routers with `/users` → throw. Registered `/users` + plugin `/users` → registered wins, warn-logged. Plugin `/users` + legacy `/users` → plugin wins, warn-logged.
145
112
 
146
- - [ ] **Task 2.10**: Export new types from `admin-backend`
147
- - Description: Export `AdminApp`, `AdminContribution` (re-export from `api`), `AdminAction`, `AdminFilter`, `AdminFieldset` types from `admin-backend/src/index.ts`. Mark `AdminModelConfig` (legacy) and `AdminFieldOverride` for deprecation but keep exporting.
148
- - Files: `admin-backend/src/index.ts`
113
+ - [x] **Task 2.9**: Export types from `admin-backend/src/index.ts`
149
114
  - Depends on: 2.2
150
- - Acceptance: Consumers import `AdminAction` etc. without going through `@terreno/api`.
115
+
116
+ ---
151
117
 
152
118
  ## Phase 3 — Admin frontend foundations
153
119
 
154
- - [ ] **Task 3.1 (revised)**: `AdminProvider` context with locked three-bucket widget registry
155
- - Description: Create `admin-frontend/src/AdminProvider.tsx`. Context value `{ api, baseUrl, widgets: AdminWidgetRegistry, socket?: SocketLike }` where `AdminWidgetRegistry` is the three-bucket shape from the IP: `{ fields: Record<string, FieldWidgetComponent>; home: Record<string, HomeWidgetComponent>; screens: Record<string, ScreenWidgetComponent> }`. The three bucket types have distinct prop contracts:
156
- - `FieldWidgetComponent<TValue>`: `{value, onChange, fieldMeta, disabled, errorText, testID}`
157
- - `HomeWidgetComponent`: `{config?: unknown, modelMetas: AdminModelMeta[]}`
158
- - `ScreenWidgetComponent`: `{name, displayName, params?: Record<string, string>}`
159
- Provide `useAdminContext()` hook + sub-hooks `useFieldWidget(name)`, `useHomeWidget(name)`, `useScreenWidget(name)` that return a "MissingWidget" placeholder + one-time `console.warn` when the key isn't registered.
160
- - Files: `admin-frontend/src/AdminProvider.tsx`, `admin-frontend/src/AdminProvider.test.tsx`, `admin-frontend/src/types.ts`
120
+ - [x] **Task 3.1**: `AdminProvider` + three-bucket widget registry + hooks
121
+ - Files: `admin-frontend/src/AdminProvider.tsx`, `types.ts`, tests
161
122
  - Depends on: none
162
- - Acceptance: All three sub-hooks return registered components when present; return a placeholder component when missing and warn exactly once per (bucket, name) pair. `useAdminContext` throws with helpful message outside provider.
163
123
 
164
- - [ ] **Task 3.2**: Frontend `AdminConfigResponse` types
165
- - Description: Mirror the backend's extended `AdminConfigResponse` in `admin-frontend/src/types.ts`. Include `capabilities` so screens can fail soft against older backends.
166
- - Files: `admin-frontend/src/types.ts`
124
+ - [x] **Task 3.2**: Extend frontend types for `widgetIds`, `capabilities`, `autocompleteFields`, `excludeFields`
125
+ - Files: `admin-frontend/src/types.ts`
167
126
  - Depends on: 2.4
168
- - Acceptance: Types compile; `useAdminConfig` returns the extended shape.
169
127
 
170
- - [ ] **Task 3.3 (revised)**: `AdminScreenRouter`
171
- - Description: Create `admin-frontend/src/AdminScreenRouter.tsx`. Takes a `name` prop (the URL segment). Reads `/admin/config`, looks up: (a) if `name` matches a `customScreen`, render the registered widget from `useScreenWidget(name)`; (b) else if it matches a model `name`, render `AdminModelTable`. Falls back to a NotFound state with a helpful message ("No model or custom screen named X — did you forget to register the widget component in AdminProvider.widgets.screens?").
172
- - Files: `admin-frontend/src/AdminScreenRouter.tsx`, `admin-frontend/src/AdminScreenRouter.test.tsx`
128
+ - [x] **Task 3.3**: `AdminScreenRouter`
129
+ - Files: `admin-frontend/src/AdminScreenRouter.tsx`, tests
173
130
  - Depends on: 3.1, 3.2, 2.3
174
- - Acceptance: Render with a model name → table appears. Render with a customScreen name + registered widget → widget appears. Unknown name → helpful error.
175
131
 
176
- - [ ] **Task 3.4**: `AdminHome` widget-driven home page
177
- - Description: Create `admin-frontend/src/AdminHome.tsx`. Reads `home.widgets` from `/admin/config`. For each entry: (a) `{type: "..."}` → built-in widget by type; (b) `"plugin-widget-id"` → lookup in `AdminProvider.widgets`; (c) React component → render directly. Layout: grid by default. Fall back to a single `modelsGrid` widget (the legacy layout) when `home` is absent.
178
- - Files: `admin-frontend/src/AdminHome.tsx`, `admin-frontend/src/AdminHome.test.tsx`
132
+ - [x] **Task 3.4**: Refactor `AdminHome` — slots + registry (built-ins + plugin ids)
133
+ - Extract widget renderers to `widgets/`; keep slot layout from v2
134
+ - Files: `admin-frontend/src/AdminHome.tsx`, `widgets/*`, tests
179
135
  - Depends on: 3.1, 3.2
180
- - Acceptance: With no `home` config → legacy grid renders. With `home.widgets: [{type:"modelStats", models:["User"]}, "feature-flags-overrides"]` → both render in order. Unknown widget id → console warning, skipped silently.
181
136
 
182
- - [ ] **Task 3.5**: Built-in `ModelsGridWidget` (legacy grid)
183
- - Description: Lift the existing `AdminModelList` grid logic into `admin-frontend/src/widgets/ModelsGridWidget.tsx`. Renders one card per model from `/admin/config`. Click → navigate to `/admin/{name}`. Optionally filter by `group` prop.
184
- - Files: `admin-frontend/src/widgets/ModelsGridWidget.tsx`, `admin-frontend/src/widgets/ModelsGridWidget.test.tsx`
185
- - Depends on: 3.2
186
- - Acceptance: Renders all models with field counts. `group="Identity"` shows only Users in the example app.
187
-
188
- - [ ] **Task 3.6**: Built-in `ModelStatsWidget`
189
- - Description: For each model in `models` prop, run `useListQuery({pageSize: 1, page: 1})` and display the total count plus model display name + link. Loading shimmer state. Error fallback shows the model name with `—`.
190
- - Files: `admin-frontend/src/widgets/ModelStatsWidget.tsx`, `admin-frontend/src/widgets/ModelStatsWidget.test.tsx`
191
- - Depends on: 3.1, 3.2
192
- - Acceptance: Renders counts for the configured models; empty models prop → renders nothing.
137
+ - [x] **Task 3.5**: Extract built-in widgets to `widgets/`
138
+ - `ModelsGridWidget`, `ModelStatsWidget`, `RecentActivityWidget`, `ScriptRunnerWidget`, `CustomScreensListWidget`, `VersionConfigWidget`
139
+ - Files: `admin-frontend/src/widgets/*.tsx`, tests
140
+ - Depends on: 3.4
193
141
 
194
- - [ ] **Task 3.7**: Built-in `RecentActivityWidget`
195
- - Description: For each model in `models` (default: all), fetch `?sort=-updated&pageSize=limit/N&page=1` and interleave the results sorted by `updated`. Show a short row per item: model display name + a chosen `listDisplay` field + relative time (Luxon).
196
- - Files: `admin-frontend/src/widgets/RecentActivityWidget.tsx`, `admin-frontend/src/widgets/RecentActivityWidget.test.tsx`
197
- - Depends on: 3.1, 3.2
198
- - Acceptance: With `limit: 5` and three models, shows 5 most-recently-updated items across the three.
199
-
200
- - [ ] **Task 3.8**: Built-in `ScriptRunnerWidget`
201
- - Description: Compact replacement for `AdminScriptList` on the home page: just the script name + a Run button that opens the existing `AdminScriptRunModal`.
202
- - Files: `admin-frontend/src/widgets/ScriptRunnerWidget.tsx`, `admin-frontend/src/widgets/ScriptRunnerWidget.test.tsx`
142
+ - [x] **Task 3.6**: `AdminFieldRenderer` + form widget lookup from context
143
+ - Files: `AdminFieldRenderer.tsx`, `AdminModelForm.tsx`, tests
203
144
  - Depends on: 3.1
204
- - Acceptance: Renders the scripts from `/admin/config`; clicking Run opens the existing modal and the script executes.
205
-
206
- - [ ] **Task 3.9**: Built-in `CustomScreensListWidget`
207
- - Description: Renders one card per `customScreen` from `/admin/config`, navigating to `/admin/{name}`.
208
- - Files: `admin-frontend/src/widgets/CustomScreensListWidget.tsx`, `admin-frontend/src/widgets/CustomScreensListWidget.test.tsx`
209
- - Depends on: 3.2
210
- - Acceptance: Renders DocumentStorage and any plugin-contributed custom screens.
211
145
 
212
- - [ ] **Task 3.10**: `AdminFieldRenderer` reads widgets from context
213
- - Description: Replace the hardcoded widget switch in `AdminFieldRenderer.tsx`. Read the widget map from `useAdminContext().widgets.fields`. If `fieldConfig.widget` is set and present in the map → render that. Else fall back to the existing type-based rendering. Same change in `AdminModelForm.tsx` for form-side widget lookup.
214
- - Files: `admin-frontend/src/AdminFieldRenderer.tsx`, `admin-frontend/src/AdminModelForm.tsx`, `admin-frontend/src/AdminFieldRenderer.test.tsx`
215
- - Depends on: 3.1
216
- - Acceptance: Registering `widgets={{fields: {markdown: MyMarkdownEditor}}}` causes a field with `widget: "markdown"` to use `MyMarkdownEditor`. Unregistered widget → falls back to default with a one-time `console.warn`.
146
+ - [x] **Task 3.7**: `AdminModelList` thin wrapper → `AdminHome` + deprecation for `customScreens` prop
147
+ - Files: `AdminModelList.tsx`
148
+ - Depends on: 3.4
217
149
 
218
- - [ ] **Task 3.11**: Keep `AdminModelList` working as a thin compatibility wrapper
219
- - Description: Reduce `AdminModelList.tsx` to render `<AdminHome widgets={[{type:"modelsGrid"}, {type:"scriptRunner"}, {type:"customScreensList"}]} />` (or a minimal equivalent). Preserve the existing `customScreens` prop by logging a deprecation warning when present + `AdminProvider` widgets aren't used. Document removal target as next major.
220
- - Files: `admin-frontend/src/AdminModelList.tsx`
221
- - Depends on: 3.4, 3.5, 3.8, 3.9
222
- - Acceptance: Existing consumers (example-frontend's `app/admin/index.tsx`) render unchanged. Deprecation warning fires once if `customScreens` prop is set.
150
+ - [x] **Task 3.8**: Package exports (`AdminProvider`, `AdminScreenRouter`, `BUILT_IN_*_WIDGETS`)
151
+ - Files: `admin-frontend/src/index.tsx`
152
+ - Depends on: 3.1–3.7
223
153
 
224
- - [ ] **Task 3.12**: Frontend package exports
225
- - Description: Update `admin-frontend/src/index.tsx` to export: `AdminProvider`, `AdminScreenRouter`, `AdminHome`, all built-in widgets, the widget registry types, and a `BUILT_IN_WIDGETS` object so consumers can spread it: `widgets={{fields: {...BUILT_IN_FIELD_WIDGETS, ...mine}, home: {...BUILT_IN_HOME_WIDGETS, ...mine}}}`.
226
- - Files: `admin-frontend/src/index.tsx`
227
- - Depends on: 3.1, 3.3, 3.4, 3.5, 3.6, 3.7, 3.8, 3.9, 3.10
228
- - Acceptance: `import {AdminProvider, AdminHome, AdminScreenRouter, BUILT_IN_FIELD_WIDGETS} from "@terreno/admin-frontend"` works.
154
+ ---
229
155
 
230
156
  ## Phase 4 — Admin frontend feature lift
231
157
 
232
- - [ ] **Task 4.1 (revised)**: Search box in `AdminModelTable`
233
- - Description: Add a debounced search input at the top of the table when `modelMeta.searchFields.length > 0`. Surfaces as `?q=` query param. Show "Searching X, Y, Z" helper text. Reset to page 1 on change. Add `testID="admin-table-search"` to the input. Debounce is 250ms (constant `ADMIN_SEARCH_DEBOUNCE_MS` in admin-frontend/src/Constants.ts).
234
- - Files: `admin-frontend/src/AdminModelTable.tsx`, `admin-frontend/src/Constants.ts`, `admin-frontend/src/AdminModelTable.test.tsx`
235
- - Depends on: 2.7, 3.2
236
- - Acceptance: Typing in the search box waits 250ms, fires one request with `?q=`. Empty box hides search hits. Clearing → resets to unsearched list. `getByTestId("admin-table-search")` works.
237
-
238
- - [ ] **Task 4.2 (revised)**: `AdminFilterDrawer`
239
- - Description: Right-side collapsible drawer rendered when `modelMeta.filters.length > 0`. Renders one input per filter type:
240
- - `boolean` → BooleanField (toggle) — `testID="admin-filter-{field}"`
241
- - `text` → TextField — `testID="admin-filter-{field}"`
242
- - `choice` → SelectField — `testID="admin-filter-{field}"`
243
- - `ref` → AdminRefField in autocomplete mode — `testID="admin-filter-{field}"`
244
- - `date` → DateTimeField (single) — `testID="admin-filter-{field}"`
245
- - `dateRange` → two DateTimeFields — `testID="admin-filter-{field}-gte"` / `"admin-filter-{field}-lte"`
246
- State serialized as `filter[k]=v` (range as `filter[k][gte]=` / `[lte]=`). Apply button (`testID="admin-filter-apply"`) updates the list query. On mobile (`<md`) → bottom-sheet via existing `ActionSheet` pattern (`testID="admin-filter-sheet"`). Drawer root `testID="admin-filter-drawer"`.
247
- - Files: `admin-frontend/src/AdminFilterDrawer.tsx`, `admin-frontend/src/AdminFilterDrawer.test.tsx`, `admin-frontend/src/AdminModelTable.tsx` (mount)
158
+ - [x] **Task 4.1**: `AdminFilterDrawer` — replace inline filters in `AdminModelTable`
159
+ - Mobile sheet; testIDs per original spec
160
+ - Files: `AdminFilterDrawer.tsx`, `AdminModelTable.tsx`, tests
248
161
  - Depends on: 3.2
249
- - Acceptance: Selecting "admin: true" in the drawer narrows the list. Combining filters AND-narrows server-side. Drawer collapses/expands via header toggle. Mobile viewport → sheet. All testIDs accessible.
250
162
 
251
- - [ ] **Task 4.3 (revised)**: Bulk-select column + select-all
252
- - Description: Add a leading checkbox column to `AdminModelTable`. Header checkbox (`testID="admin-table-select-all"`) toggles all rows on the current page. Row checkboxes have `testID="admin-table-row-checkbox-{id}"`. Selected row count shown above the table (`testID="admin-table-selection-count"`). Maintain selected ids across pagination, BUT clear selection on filter change, search change, OR sort change.
253
- - Files: `admin-frontend/src/AdminModelTable.tsx`, `admin-frontend/src/AdminModelTable.test.tsx`
254
- - Depends on: none
255
- - Acceptance: Selecting 3 rows shows "3 selected". Header checkbox toggles current page. Filter / search / sort change clears selection. testIDs queryable.
163
+ - [x] **Task 4.2**: Extract `AdminActionMenu` — still uses `bulk-patch` / `background-tasks`
164
+ - Files: `AdminActionMenu.tsx`, `AdminModelTable.tsx`, tests
165
+ - Depends on: none (v2 logic move)
256
166
 
257
- - [ ] **Task 4.4 (revised)**: `AdminActionMenu` + action invocation
258
- - Description: Dropdown (`testID="admin-action-menu"`) above the table next to the selection count when `modelMeta.actions.length > 0`. Disabled when no rows selected. On select: show confirmation modal (`testID="admin-action-confirm-{name}"`) with `action.confirm` if set. POST `{ids}` to `/admin/{routePath}/actions/:name`. For sync actions: toast success + refetch list. For `background: true`: open `AdminScriptRunModal` with the returned `taskId`. Hide actions where `modelMeta.actions[i].allowed === false`.
259
- - Files: `admin-frontend/src/AdminActionMenu.tsx`, `admin-frontend/src/AdminActionMenu.test.tsx`, `admin-frontend/src/AdminModelTable.tsx` (mount)
260
- - Depends on: 2.8, 4.3
261
- - Acceptance: Selecting 2 rows and running "Activate" → POST fires with both ids, success toast, list refetches. Selecting a background action → modal opens with progress. Confirmation modal renders `action.confirm` text. `allowed: false` action does not render.
167
+ - [x] **Task 4.3**: Bulk selection polish — clear on filter/search/sort change (verify v2)
168
+ - Files: `AdminModelTable.tsx`, tests
169
+ - Depends on: 4.1
262
170
 
263
- - [ ] **Task 4.5**: `sortableFields` and `pageSize` enforcement in the table
264
- - Description: Column headers are only sortable for fields in `modelMeta.sortableFields`. Use `modelMeta.pageSize` as the default if no override is in URL state.
265
- - Files: `admin-frontend/src/AdminModelTable.tsx`, `admin-frontend/src/AdminModelTable.test.tsx`
266
- - Depends on: 3.2
267
- - Acceptance: Click on a non-sortable column header is a no-op (cursor: default). Page size defaults to `modelMeta.pageSize`.
171
+ - [x] **Task 4.4**: `sortableFields` + `pageSize` UI enforcement (verify/complete v2)
172
+ - Files: `AdminModelTable.tsx`, tests
173
+ - Depends on: 2.7
268
174
 
269
- - [ ] **Task 4.6**: `fieldsets` in `AdminModelForm`
270
- - Description: When `modelMeta.fieldsets` is set, render the form as one collapsible Accordion section per fieldset (using existing `Accordion` from `@terreno/ui`). Honor `collapsed` (default expanded) and `description` (rendered under the fieldset title). `fieldOrder` continues to work when `fieldsets` is absent.
271
- - Files: `admin-frontend/src/AdminModelForm.tsx`, `admin-frontend/src/AdminModelForm.test.tsx`
175
+ - [x] **Task 4.5**: `autocompleteFields` async ref picker
176
+ - Files: `AdminRefField.tsx`, `AdminModelForm.tsx`, tests
272
177
  - Depends on: 3.2
273
- - Acceptance: A model with 3 fieldsets renders 3 collapsible groups in the configured order. Fields outside any fieldset render in an "Other" section at the bottom.
274
178
 
275
- - [ ] **Task 4.7**: `readonlyFields` in `AdminModelForm`
276
- - Description: Fields in `modelMeta.readonlyFields` are rendered as `disabled` form fields (display only). Show a small "read-only" badge next to the label. Already enforced server-side (Task 1.7).
277
- - Files: `admin-frontend/src/AdminModelForm.tsx`, `admin-frontend/src/AdminModelForm.test.tsx`
278
- - Depends on: 3.2
279
- - Acceptance: `readonlyFields: ["created"]` → the `created` field is visible but disabled with the badge.
280
-
281
- - [ ] **Task 4.8**: Autocomplete refs (`autocompleteFields`)
282
- - Description: When a ref field is listed in `modelMeta.autocompleteFields`, render `AdminRefField` in async-search mode (debounced query against `/search?q=&fields=...` on the referenced model). Default behavior (fetch-all-and-dropdown) is preserved for refs not in the list.
283
- - Files: `admin-frontend/src/AdminRefField.tsx`, `admin-frontend/src/AdminRefField.test.tsx`
284
- - Depends on: 2.7, 3.2
285
- - Acceptance: With `autocompleteFields: ["organizationId"]`, typing fires `/search` requests; results render in the dropdown. Without it, the field uses a full prefetch dropdown as today.
179
+ - [x] **Task 4.6**: Static permission UI — hide create/delete/actions when flags false
180
+ - Files: `AdminModelTable.tsx`, `AdminModelForm.tsx`, `AdminActionMenu.tsx`
181
+ - Depends on: 2.5, 4.2
286
182
 
287
- - [ ] **Task 4.9**: Per-action permission hiding in the UI
288
- - Description: Honor `modelMeta.permissions.{create,update,delete}` booleans from `/admin/config`. Hide "Create New" if `permissions.create === false`. Hide actions whose `allowed === false`. Hide row delete if `permissions.delete === false`.
289
- - Files: `admin-frontend/src/AdminModelTable.tsx`, `admin-frontend/src/AdminModelForm.tsx`, `admin-frontend/src/AdminActionMenu.tsx`
290
- - Depends on: 2.5, 4.4
291
- - Acceptance: A user without create permission sees no "Create New" button in the table.
183
+ ---
292
184
 
293
185
  ## Phase 5 — Pre-built admin migrations
294
186
 
295
- - [ ] **Task 5.0 (NEW)**: Decide FeatureFlagOverridesWidget (and other plugin frontend widgets) shipping location
296
- - Description: `feature-flags/` is backend-only today. The plugin's frontend admin widget needs a home. Decide between (a) ship a separate `@terreno/feature-flags-frontend` package that exports React components — adds a build target and a new package, but the dependency arrow points correctly (admin-frontend ← feature-flags-frontend); (b) host plugin-frontend widgets inside `@terreno/admin-frontend/src/widgets/` and have admin-frontend depend on feature-flags' types — admin-frontend grows knowledge of every plugin, but it's one package. Document the choice in `docs/explanation/admin-plugin-frontend.md`. For v1 we pick option (b) to avoid the build-config churn; revisit if a third-party plugin appears.
297
- - Files: `docs/explanation/admin-plugin-frontend.md` (NEW)
187
+ - [x] **Task 5.1**: Document plugin widget location (`docs/explanation/admin-plugin-frontend.md`)
188
+ - Decision: widgets live in `admin-frontend/src/widgets/`
298
189
  - Depends on: none
299
- - Acceptance: Decision recorded; Tasks 5.2 / 5.4 / 5.5 reference it.
300
-
301
- - [ ] **Task 5.1 (revised)**: `FeatureFlagsApp.adminContribution()` + safe legacy-export deprecation
302
- - Description: Implement `adminContribution()` on `FeatureFlagsApp` returning the FeatureFlag model admin. Include `homeWidgets: [{id: "feature-flags-overrides", displayName: "Flag Overrides", icon: "flag"}]`. Keep `register()` unchanged. For the bare `featureFlagAdminConfig` export (`feature-flags/src/featureFlagModel.ts:157`, re-exported from `feature-flags/src/index.ts:2`): wrap with a `Proxy` that logs `[feature-flags] featureFlagAdminConfig is deprecated. Use new FeatureFlagsApp() and register it with TerrenoApp — see docs/how-to/admin-import-prebuilt.md` on first property access (once per process via WeakSet). Plan removal in the next major.
303
- - Files: `feature-flags/src/featureFlagsApp.ts`, `feature-flags/src/featureFlagModel.ts` (Proxy wrap), `feature-flags/src/featureFlagsApp.test.ts`
304
- - Depends on: 1.4, 2.2, 5.0
305
- - Acceptance: `new FeatureFlagsApp(...)` registered on `TerrenoApp` causes `/admin/config` to include the FeatureFlag model + the home widget id, with no manual host wiring. Reading `featureFlagAdminConfig.listFields` logs the deprecation warning exactly once across a process.
306
-
307
- - [ ] **Task 5.2**: `FeatureFlagOverridesWidget` (frontend)
308
- - Description: A home-page widget that lists current feature-flag overrides for the logged-in user with a quick toggle. Lives in a frontend-shipping location (either `feature-flags/src/frontend.tsx` if the package goes isomorphic, or a sibling `feature-flags-frontend` package — pick to match existing project convention; if unclear, put it in `admin-frontend/src/widgets/FeatureFlagOverridesWidget.tsx` for v1). Register under the id `"feature-flags-overrides"` in a `FEATURE_FLAGS_ADMIN_WIDGETS` export.
309
- - Files: `admin-frontend/src/widgets/FeatureFlagOverridesWidget.tsx`, `admin-frontend/src/widgets/FeatureFlagOverridesWidget.test.tsx`, `admin-frontend/src/index.tsx`
310
- - Depends on: 3.1, 5.1
311
- - Acceptance: Adding `"feature-flags-overrides"` to `home.widgets` renders the widget. Spreading `FEATURE_FLAGS_ADMIN_WIDGETS` into the provider's `widgets.home` map makes it resolvable.
312
-
313
- - [ ] **Task 5.3**: `ConsentApp.adminContribution()`
314
- - Description: Implement `adminContribution()` on `ConsentApp` to return ConsentForm and ConsentResponse admins (currently configured in `example-backend/src/server.ts`). Include `widgetComponents` field-widgets: `locale-content`, `locale-default`, `checkbox-list` (the components already exist in `admin-frontend/src` — re-export them under known keys).
315
- - Files: wherever `ConsentApp` lives in `api/src/` (likely `api/src/consent/consentApp.ts`), `admin-frontend/src/widgets/consentWidgets.ts` (NEW, re-export existing editors), `admin-frontend/src/index.tsx`, plus tests
316
- - Depends on: 1.4, 2.2, 3.10
317
- - Acceptance: Registering `ConsentApp` causes both consent admins to appear in `/admin/config`. Form-side widgets resolve via the registry without consumer changes.
318
-
319
- - [ ] **Task 5.4**: `DocumentStorageApp.adminContribution()`
320
- - Description: Convert `DocumentStorageApp` from "parallel admin" to a proper plugin contribution: keep its `register()` (mounts the documents endpoints), but additionally return `customScreens: [{name: "documents", displayName: "Documents", icon: "folder"}]` and a frontend widget keyed `"documents"` rendering the existing `DocumentStorageBrowser`. Remove the consumer's hand-roll of the route in `example-frontend`.
321
- - Files: `admin-backend/src/documentStorageApp.ts`, `admin-frontend/src/widgets/DocumentsScreenWidget.tsx` (NEW, wraps existing `DocumentStorageBrowser`), tests
190
+
191
+ - [x] **Task 5.2**: `FeatureFlagsApp.adminContribution()` + deprecate `featureFlagAdminConfig`
192
+ - Files: `feature-flags/src/featureFlagsApp.ts`, tests
193
+ - Depends on: 1.4, 2.2
194
+
195
+ - [x] **Task 5.3**: `FeatureFlagOverridesWidget` in `admin-frontend/src/widgets/`
196
+ - Files: `widgets/FeatureFlagOverridesWidget.tsx`, tests, exports
197
+ - Depends on: 3.1, 5.2
198
+
199
+ - [x] **Task 5.4**: `ConsentApp.adminContribution()` + consent field widgets registry
200
+ - Files: consent app, `widgets/consentWidgets.ts`, tests
201
+ - Depends on: 1.4, 2.2, 3.6
202
+
203
+ - [x] **Task 5.5**: `DocumentStorageApp.adminContribution()` + `DocumentsScreenWidget`
204
+ - Files: `documentStorageApp.ts`, `widgets/DocumentsScreenWidget.tsx`, tests
322
205
  - Depends on: 1.4, 2.3, 3.3
323
- - Acceptance: `new DocumentStorageApp(...)` registered → `/admin/documents` route works in the new screen router with no host wiring.
324
206
 
325
- - [ ] **Task 5.5**: `AIAdminApp` plugin
326
- - Description: Wrap the existing `addAiRequestsExplorerRoutes` into a small `AIAdminApp` plugin in `ai/src/` that implements `adminContribution()` returning `customScreens: [{name: "ai-requests", displayName: "AI Requests"}]`. Frontend widget wraps the existing custom AIAdminScreen.
327
- - Files: `ai/src/aiAdminApp.ts` (NEW), `admin-frontend/src/widgets/AIRequestsScreenWidget.tsx` (NEW; uses existing aggregation endpoint), tests
207
+ - [x] **Task 5.6**: `AIAdminApp` plugin + `AIRequestsScreenWidget`
208
+ - Files: `ai/src/aiAdminApp.ts`, `widgets/AIRequestsScreenWidget.tsx`, tests
328
209
  - Depends on: 1.4, 2.3
329
- - Acceptance: Consumer drops the bespoke `customScreens` prop on `AdminModelList`, and the AI screen still appears via `new AIAdminApp()` registration.
330
-
331
- ## Phase 6 — Examples and docs
332
-
333
- - [ ] **Task 6.0 (NEW)**: Audit `example-frontend/app/admin/consent-forms/` and `consent-responses/` custom screens
334
- - Description: List every file under those directories. Identify behaviors the generic `AdminScreenRouter` + `AdminModelForm` doesn't subsume (e.g., custom signing UI in `ConsentResponseViewer`). For each, decide: (a) becomes a screen widget contributed by `ConsentApp` (preferred), (b) stays as a hand-rolled route in example-frontend with a documented exception, (c) is unnecessary and can be deleted. Write findings into `docs/explanation/admin-consent-migration.md`.
335
- - Files: `docs/explanation/admin-consent-migration.md` (NEW)
336
- - Depends on: 5.3
337
- - Acceptance: Doc enumerates each file with an explicit disposition.
338
-
339
- - [ ] **Task 6.1 (revised)**: Migrate `example-backend` to the new shape — single atomic change
340
- - Description: In `example-backend/src/server.ts`, move `Todos` and `Users` admin config from `AdminApp.models` onto each model's `modelRouter({admin: {...}})` call (`example-backend/src/api/todos.ts`, `example-backend/src/api/users.ts`). Remove the manually-passed `featureFlagAdminConfig` / `ConsentForm` / `ConsentResponse` entries — those come from plugin contributions now. Leave one legacy entry in `AdminApp.models` to demonstrate back-compat. Add `home: {widgets: [...]}` to AdminApp showing several widget types. This MUST land as a single commit/PR — don't split because Tasks 5.1/5.3/5.4/5.5 changing one at a time breaks the example app between phases.
341
- - Files: `example-backend/src/server.ts`, `example-backend/src/api/todos.ts`, `example-backend/src/api/users.ts`
342
- - Depends on: 5.1, 5.3, 5.4, 5.5
343
- - Acceptance: `bun run backend:dev` boots. `GET /admin/config` returns the same models as before. Legacy entry logs exactly one deprecation warning. CI does not have a window where example-backend is broken between phases.
344
-
345
- - [ ] **Task 6.2**: Migrate `example-frontend` admin layout
346
- - Description: Wrap `app/admin/_layout.tsx` in `<AdminProvider api={api} baseUrl="/admin" widgets={{home: {...BUILT_IN_HOME_WIDGETS, "feature-flags-overrides": FeatureFlagOverridesWidget, ...}, fields: {...BUILT_IN_FIELD_WIDGETS, ...}}}>`. Replace `app/admin/index.tsx` with `<AdminHome />`. Replace `app/admin/[model]/index.tsx`'s string-switch with `<AdminScreenRouter name={modelName as string} />`. Verify form/edit routes still work.
347
- - Files: `example-frontend/app/admin/_layout.tsx`, `example-frontend/app/admin/index.tsx`, `example-frontend/app/admin/[model]/index.tsx`
348
- - Depends on: 3.12, 5.2, 5.4
349
- - Acceptance: `bun run frontend:web` runs. Admin home renders configured widgets. Navigating to `/admin/users`, `/admin/feature-flags`, `/admin/documents`, `/admin/ai-requests` all work via the screen router.
350
-
351
- - [ ] **Task 6.3**: How-to doc — Add an admin to your model
352
- - Description: Cookbook walking through adding `admin: {...}` to a `modelRouter` call, listing the most-used fields with one-line each, then showing the final `/admin/users` UI. Link from the admin-backend README.
353
- - Files: `docs/how-to/admin-add-model.md`
354
- - Depends on: 6.1
355
- - Acceptance: Doc walks a reader through adding admin to a new model in under 5 minutes.
356
-
357
- - [ ] **Task 6.4**: How-to doc — Custom field widget
358
- - Description: Cookbook for building a custom field widget: declare the widget name in `fieldOverrides`, write the component implementing the `AdminWidgetComponent` interface, register it in the provider. Use a `RichTextEditor` example.
359
- - Files: `docs/how-to/admin-custom-widget.md`
360
- - Depends on: 6.2
361
- - Acceptance: Doc shows a working example.
362
-
363
- - [ ] **Task 6.5**: How-to doc — Customize the admin home page
364
- - Description: Walk through `home.widgets` with each built-in widget type, plugin-contributed widgets, and an ad-hoc component.
365
- - Files: `docs/how-to/admin-custom-home.md`
366
- - Depends on: 6.2
367
- - Acceptance: Doc demonstrates every widget type.
368
-
369
- - [ ] **Task 6.6**: How-to doc — Import pre-built admins
370
- - Description: Show how to register `ConsentApp`, `FeatureFlagsApp`, `DocumentStorageApp`, `AIAdminApp` and surface their admins. Each section is ~5 lines of code.
371
- - Files: `docs/how-to/admin-import-prebuilt.md`
372
- - Depends on: 6.1
373
- - Acceptance: Doc covers all four plugins.
374
-
375
- - [ ] **Task 6.7**: Reference doc — `AdminConfig`
376
- - Description: Full table of `AdminConfig` fields with Django equivalents in a sidebar column. One-line description per field. Include a small "migration from legacy shape" section.
377
- - Files: `docs/reference/admin-config.md`
210
+
211
+ ---
212
+
213
+ ## Phase 6 — Examples, docs, E2E (single atomic PR)
214
+
215
+ - [x] **Task 6.1**: Audit consent custom screens — `docs/explanation/admin-consent-migration.md`
216
+ - Depends on: 5.4
217
+
218
+ - [x] **Task 6.2**: **Atomic** migrate `example-backend` — `admin:` on modelRouters, plugins only, one legacy `models[]` entry, `home.slots`
219
+ - Files: `example-backend/src/server.ts`, `api/todos.ts`, `api/users.ts`
220
+ - Depends on: 5.2, 5.4, 5.5, 5.6
221
+
222
+ - [x] **Task 6.3**: **Atomic** migrate `example-frontend` + `admin-spa` — `AdminProvider`, `AdminHome`, `AdminScreenRouter`
223
+ - Files: `example-frontend/app/admin/**`, `admin-spa/app/**`
224
+ - Depends on: 3.8, 5.3, 5.5, 5.6
225
+
226
+ - [x] **Task 6.4**: How-to: `admin-add-model.md`, `admin-custom-widget.md`, `admin-custom-home.md`, `admin-import-prebuilt.md`
227
+ - Depends on: 6.2, 6.3
228
+
229
+ - [x] **Task 6.5**: Reference: `docs/reference/admin-config.md`
378
230
  - Depends on: 1.1
379
- - Acceptance: Reference covers every field on `AdminConfig`, `AdminFilter`, `AdminFieldset`, `AdminAction`, `AdminContribution`.
231
+
232
+ - [x] **Task 6.6**: Playwright E2E (split by feature file)
233
+ - `admin-home.spec.ts`, `admin-table-search-filter.spec.ts`, `admin-table-bulk-actions.spec.ts`, `admin-form.spec.ts`, `admin-custom-screens.spec.ts`
234
+ - Depends on: 6.3
235
+
236
+ ---
380
237
 
381
238
  ## Phase 7 — Deprecations & polish
382
239
 
383
- - [ ] **Task 7.1**: Update `.claude/rules/admin-backend/00-admin-backend.md` and `admin-frontend/00-admin-frontend.md`
384
- - Description: Reflect the new API in the per-package rule files so future Claude sessions and the MCP server get the updated patterns.
385
- - Files: `.claude/rules/admin-backend/00-admin-backend.md`, `.claude/rules/admin-frontend/00-admin-frontend.md`
386
- - Depends on: all
387
- - Acceptance: Rule files show `admin:` on modelRouter, `AdminProvider`, `AdminScreenRouter`, widget registry, and the legacy shape as deprecated.
240
+ - [ ] **Task 7.1**: Update admin-backend + admin-frontend rule files
241
+ - Files: `.cursor/rules/admin-*/`, `.claude/rules/admin-*/`
388
242
 
389
- - [ ] **Task 7.2**: MCP server updates
390
- - Description: Update `@terreno/mcp-server` `generate_route` tool to emit the new `admin:` config shape by default. Add a new prompt or tool: `generate_admin_widget` that scaffolds a custom widget.
391
- - Files: `mcp-server/src/tools.ts`, `mcp-server/src/prompts.ts`
392
- - Depends on: 6.7
393
- - Acceptance: `generate_route` output includes an `admin: {...}` block.
243
+ - [ ] **Task 7.2**: MCP `generate_route` emits `admin:` block; optional `generate_admin_widget`
244
+ - Files: `mcp-server/src/tools.ts`, `prompts.ts`
394
245
 
395
- - [ ] **Task 7.3**: CHANGELOG + version bump
396
- - Description: Add a "Admin overhaul (Django-admin parity)" section to the changelogs of `@terreno/api`, `@terreno/admin-backend`, `@terreno/admin-frontend`, `@terreno/feature-flags`, `@terreno/ai`. Plan a minor version bump (e.g. 0.13.0 → 0.14.0). Note the deprecations and the codemod recipe.
397
- - Files: `api/CHANGELOG.md`, `admin-backend/CHANGELOG.md`, `admin-frontend/CHANGELOG.md`, `feature-flags/CHANGELOG.md`, `ai/CHANGELOG.md`, root `package.json` if catalog-versioned
398
- - Depends on: all
399
- - Acceptance: Changelogs read well; version bumps consistent across packages.
400
-
401
- - [ ] **Task 7.4 (revised)**: End-to-end integration tests, split by feature
402
- - Description: Write Playwright specs in `example-frontend/e2e/`, one feature per file (per CLAUDE.local.md guidelines):
403
- - `admin-home.spec.ts` — login as admin, verify widgets render, click each widget link
404
- - `admin-table-search-filter.spec.ts` — search, filter drawer apply, combined filter+search, mobile sheet variant
405
- - `admin-table-bulk-actions.spec.ts` — multi-select, sync action, background action via run modal, confirmation modal
406
- - `admin-form.spec.ts` — fieldsets collapse/expand, readonly field disabled, autocomplete ref picker, validation error
407
- - `admin-custom-screens.spec.ts` — Documents browser navigation + upload, AI Requests explorer
408
- - All testIDs already added in Phase 4 tasks (4.1-4.4) and Phase 5. `loginAs(page)` used in `beforeEach`.
409
- - Files: 5 new specs under `example-frontend/e2e/`
410
- - Depends on: 6.2
411
- - Acceptance: All five specs pass locally; CI runs them in parallel.
412
-
413
- - [ ] **Task 7.5**: Cross-package compile + lint pass
414
- - Description: Run `bun run compile && bun run lint` across the monorepo, fix anything that breaks from the new types or renamed exports. Re-run `bun run test`.
415
- - Files: monorepo
246
+ - [ ] **Task 7.3**: CHANGELOG + minor version bump note
247
+ - Files: package CHANGELOGs
248
+
249
+ - [ ] **Task 7.4**: `docs/how-to/admin-migrate.md` for downstream consumers
250
+ - Depends on: 6.3
251
+
252
+ - [ ] **Task 7.5**: Structured admin telemetry logs (config size, filter errors, deprecation warnings)
253
+ - Files: `admin-backend/src/adminApp.ts`, `filterParser.ts`, `legacy.ts`
254
+
255
+ - [ ] **Task 7.6**: Monorepo compile + lint + test green
416
256
  - Depends on: all
417
- - Acceptance: Green CI.
418
-
419
- - [ ] **Task 7.6 (NEW)**: Migration helper script for downstream consumers
420
- - Description: Write a short sed-based migration recipe in `docs/how-to/admin-migrate.md`. Idempotent. Walks a consuming repo through: (1) bumping `@terreno/*` versions, (2) detecting `AdminApp({models: [...]})` and either keeping (legacy ok) or migrating to `admin:` on modelRouter, (3) replacing `<AdminModelList .../>` with `<AdminProvider><AdminHome/></AdminProvider>`, (4) replacing the consumer's `[model]/index.tsx` string-switch with `<AdminScreenRouter name={...}/>`. Include before/after diffs. Test the recipe by running it against `example-frontend` and verifying it produces the same result as Task 6.2.
421
- - Files: `docs/how-to/admin-migrate.md`, `scripts/admin-migrate.sh` (optional)
422
- - Depends on: 6.2
423
- - Acceptance: Doc walks a reader through the migration in under 15 minutes; sed recipe (if shipped) is idempotent.
424
-
425
- - [ ] **Task 7.7 (NEW)**: Telemetry and observability
426
- - Description: Add structured log lines (via `logger.info` with stable shape) for: (1) `/admin/config` response time + size, (2) action invocation: `{actionName, modelName, idCount, userId, durationMs, outcome: "ok"|"error"|"forbidden"|"rejected"}`, (3) filter parse errors: `{modelName, filterName, error}`, (4) deprecation warnings (legacy shape, legacy export, legacy customScreens prop): emit each once-per-process. No new dashboards; downstream consumers can scrape from logs.
427
- - Files: `admin-backend/src/adminApp.ts`, `admin-backend/src/actionRunner.ts`, `admin-backend/src/filterParser.ts`, `admin-backend/src/legacy.ts`
428
- - Depends on: 2.4, 2.6, 2.8
429
- - Acceptance: A `grep "level":"info","event":"admin.action"` over the log stream during the E2E run shows entries for every action invocation with the documented fields.
257
+
258
+ ---
259
+
260
+ ## Removed from scope (per decisions)
261
+
262
+ - ~~`actionRunner.ts` / `POST .../actions/:name`~~ → use v2 `bulk-patch`
263
+ - ~~`AdminActionContext` / frozen action context~~ → not needed without custom action runners
264
+ - ~~Per-user `/admin/config` permission booleans~~ → static flags; see RBAC IP
265
+ - ~~`home.widgets` typed layout~~ → `home.slots` only