spine-framework 0.3.145 → 0.4.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 (451) hide show
  1. package/.env.example +76 -0
  2. package/.framework/.spine-manifest.json +19 -0
  3. package/.framework/cli/commands/agents.ts +61 -33
  4. package/.framework/cli/commands/auth.ts +60 -28
  5. package/.framework/cli/commands/create-app.ts +212 -80
  6. package/.framework/cli/commands/dev.ts +184 -68
  7. package/.framework/cli/commands/doctor.ts +324 -152
  8. package/.framework/cli/commands/generate.ts +119 -68
  9. package/.framework/cli/commands/init.ts +141 -242
  10. package/.framework/cli/commands/install-app.ts +1336 -314
  11. package/.framework/cli/commands/items.ts +31 -29
  12. package/.framework/cli/commands/migrate.ts +299 -156
  13. package/.framework/cli/commands/migrations.ts +62 -30
  14. package/.framework/cli/commands/pipelines.ts +31 -29
  15. package/.framework/cli/commands/status.ts +105 -45
  16. package/.framework/cli/commands/system.ts +47 -42
  17. package/.framework/cli/commands/test.ts +161 -102
  18. package/.framework/cli/commands/upgrade.ts +260 -0
  19. package/.framework/cli/commands/validate-app.ts +517 -161
  20. package/.framework/cli/context.ts +117 -85
  21. package/.framework/cli/env-loader.ts +74 -43
  22. package/.framework/cli/index.ts +70 -46
  23. package/.framework/cli/lib/project-files.ts +404 -0
  24. package/.framework/docs/guides/testing-guide.md +1 -1
  25. package/.framework/docs/runtime-types.md +72 -72
  26. package/.framework/functions/_shared/access.ts +84 -0
  27. package/.framework/functions/_shared/agent-runner.ts +5051 -1026
  28. package/.framework/functions/_shared/app-manifest.ts +214 -94
  29. package/.framework/functions/_shared/ast.ts +214 -0
  30. package/.framework/functions/_shared/audit.ts +91 -55
  31. package/.framework/functions/_shared/cil.ts +368 -127
  32. package/.framework/functions/_shared/db.ts +337 -68
  33. package/.framework/functions/_shared/funnel.ts +228 -0
  34. package/.framework/functions/_shared/geo.ts +105 -0
  35. package/.framework/functions/_shared/hash-api-key.ts +94 -17
  36. package/.framework/functions/_shared/index.ts +22 -224
  37. package/.framework/functions/_shared/kb.ts +832 -58
  38. package/.framework/functions/_shared/llm.ts +118 -18
  39. package/.framework/functions/_shared/logging.ts +317 -19
  40. package/.framework/functions/_shared/middleware.ts +701 -131
  41. package/.framework/functions/_shared/notify.ts +70 -0
  42. package/.framework/functions/_shared/outcomes.ts +34 -19
  43. package/.framework/functions/_shared/permissions.ts +501 -315
  44. package/.framework/functions/_shared/pipeline-runner.ts +704 -155
  45. package/.framework/functions/_shared/principal.ts +436 -263
  46. package/.framework/functions/_shared/rate-limit.ts +233 -0
  47. package/.framework/functions/_shared/resolve-credentials.ts +86 -19
  48. package/.framework/functions/_shared/resolve-ids.ts +203 -139
  49. package/.framework/functions/_shared/retry-deliveries.ts +51 -16
  50. package/.framework/functions/_shared/schema-utils.ts +602 -521
  51. package/.framework/functions/_shared/security.ts +345 -0
  52. package/.framework/functions/_shared/testing.ts +197 -172
  53. package/.framework/functions/_shared/tracing.ts +351 -21
  54. package/.framework/functions/_shared/trigger-engine.ts +425 -136
  55. package/.framework/functions/_shared/webhook-registration.ts +71 -70
  56. package/.framework/functions/_shared/webhook-registry.ts +126 -83
  57. package/.framework/functions/account-nodes.ts +30 -30
  58. package/.framework/functions/adapters/generic-webhook-inbound.ts +49 -16
  59. package/.framework/functions/adapters/generic-webhook-outbound.ts +17 -17
  60. package/.framework/functions/adapters/openai-outbound.ts +15 -16
  61. package/.framework/functions/admin-data.ts +596 -173
  62. package/.framework/functions/admin-funnel.ts +64 -60
  63. package/.framework/functions/admin-install-funnel.ts +84 -66
  64. package/.framework/functions/admin-signals.ts +69 -51
  65. package/.framework/functions/ai-agents.ts +431 -106
  66. package/.framework/functions/ai-interactions.ts +186 -112
  67. package/.framework/functions/api-keys.ts +107 -107
  68. package/.framework/functions/apps.ts +154 -136
  69. package/.framework/functions/auth.ts +83 -65
  70. package/.framework/functions/cil-agent-ingest.ts +189 -232
  71. package/.framework/functions/cil-agent.ts +433 -177
  72. package/.framework/functions/cil-callmap.ts +642 -430
  73. package/.framework/functions/cil-config.ts +139 -135
  74. package/.framework/functions/cil-context.ts +214 -127
  75. package/.framework/functions/cil-doc.ts +587 -458
  76. package/.framework/functions/cil-embeddings.ts +453 -364
  77. package/.framework/functions/cil-graph.ts +427 -142
  78. package/.framework/functions/cil-ingest.ts +1418 -1154
  79. package/.framework/functions/cil-pipeline.ts +276 -21
  80. package/.framework/functions/cil-project.ts +659 -132
  81. package/.framework/functions/cil-validate.ts +198 -164
  82. package/.framework/functions/debug-auth.ts +57 -55
  83. package/.framework/functions/deliveries.ts +80 -21
  84. package/.framework/functions/embeddings.ts +1023 -318
  85. package/.framework/functions/eval-harness.ts +146 -108
  86. package/.framework/functions/integration-outbound.ts +205 -75
  87. package/.framework/functions/integration-routes.ts +128 -23
  88. package/.framework/functions/integrations.ts +125 -100
  89. package/.framework/functions/invites.ts +1095 -460
  90. package/.framework/functions/item-progress.ts +158 -102
  91. package/.framework/functions/kb.ts +34 -10
  92. package/.framework/functions/logs.ts +133 -133
  93. package/.framework/functions/observability.ts +152 -122
  94. package/.framework/functions/pipeline-executions.ts +147 -132
  95. package/.framework/functions/pipelines.ts +134 -119
  96. package/.framework/functions/prompt-configs.ts +96 -80
  97. package/.framework/functions/roles.ts +112 -82
  98. package/.framework/functions/security.ts +450 -0
  99. package/.framework/functions/system-cron.ts +117 -80
  100. package/.framework/functions/system.ts +158 -98
  101. package/.framework/functions/tests.ts +29 -29
  102. package/.framework/functions/timers.ts +94 -95
  103. package/.framework/functions/trigger-scheduler.ts +10 -0
  104. package/.framework/functions/triggers.ts +179 -149
  105. package/.framework/functions/types.ts +137 -125
  106. package/.framework/mcp/index.ts +133 -0
  107. package/.framework/mcp/load-env.ts +60 -0
  108. package/.framework/mcp/shared/app-manifest.ts +275 -0
  109. package/.framework/mcp/shared/db.ts +477 -0
  110. package/.framework/mcp/shared/kb.ts +118 -0
  111. package/.framework/mcp/tools/architect.ts +431 -0
  112. package/.framework/mcp/tools/cil/context.ts +345 -0
  113. package/.framework/mcp/tools/cil/graph.ts +242 -0
  114. package/.framework/mcp/tools/cil/ingest.ts +1217 -0
  115. package/.framework/mcp/tools/cil/pipeline-orchestrator.ts +693 -0
  116. package/.framework/mcp/tools/cil/pipeline.ts +767 -0
  117. package/.framework/mcp/tools/cil/project.ts +791 -0
  118. package/.framework/mcp/tools/cil/retrieval.ts +336 -0
  119. package/.framework/mcp/tools/cil/search-core.ts +454 -0
  120. package/.framework/mcp/tools/cil/search.ts +97 -0
  121. package/.framework/mcp/tools/cil/suggest.ts +186 -0
  122. package/.framework/mcp/tools/cil/validate.ts +1369 -0
  123. package/.framework/mcp/tools/kb/delete.ts +139 -0
  124. package/.framework/mcp/tools/kb/index.ts +52 -0
  125. package/.framework/mcp/tools/kb/ingest.ts +84 -0
  126. package/.framework/mcp/tools/kb/list-types.ts +102 -0
  127. package/.framework/mcp/tools/kb/publish.ts +100 -0
  128. package/.framework/mcp/tools/kb/reindex.ts +86 -0
  129. package/.framework/mcp/tools/kb/revoke.ts +85 -0
  130. package/.framework/mcp/tools/kb/search.ts +101 -0
  131. package/.framework/mcp/tools/kb/upsert.ts +125 -0
  132. package/.framework/mcp/tools/observability.ts +510 -0
  133. package/.framework/migrations/001_core_schema.sql +15 -0
  134. package/.framework/migrations/002_core_rls_and_functions.sql +15 -0
  135. package/.framework/migrations/003_core_system_data.sql +16 -1
  136. package/.framework/migrations/004_core_rpc_and_broker.sql +17 -1
  137. package/.framework/migrations/005_core_observability.sql +17 -1
  138. package/.framework/migrations/006_security_hardening_rls.sql +15 -0
  139. package/.framework/migrations/007_kb_article_list.sql +17 -1
  140. package/.framework/migrations/008_items_target.sql +15 -0
  141. package/.framework/migrations/009_roles_slug_unique.sql +15 -0
  142. package/.framework/migrations/010_auth_user_trigger.sql +15 -0
  143. package/.framework/migrations/011_spine_system_account.sql +16 -0
  144. package/.framework/migrations/012_service_role_grants.sql +16 -0
  145. package/.framework/migrations/013_get_account_apps_role_filter.sql +15 -0
  146. package/.framework/migrations/014_apps_required_roles.sql +16 -0
  147. package/.framework/migrations/015_types_kind_slug_unique.sql +15 -0
  148. package/.framework/migrations/016_cil_types.sql +281 -0
  149. package/.framework/migrations/017_cil_stable_ids.sql +37 -0
  150. package/.framework/migrations/018_runtime_tables.sql +129 -0
  151. package/.framework/migrations/019_agent_prompt_columns.sql +56 -0
  152. package/.framework/migrations/020_schema_reconciliation.sql +67 -0
  153. package/.framework/migrations/021_kb_embedding_lifecycle.sql +31 -0
  154. package/.framework/migrations/032_cil_status_migration.sql +73 -0
  155. package/.framework/migrations/033_cil_versioning.sql +73 -0
  156. package/.framework/migrations/034_match_embeddings_knn.sql +115 -0
  157. package/.framework/migrations/035_rate_limits.sql +55 -0
  158. package/.framework/migrations/036_demo_record_flags.sql +72 -0
  159. package/.framework/migrations/037_kb_types_canonical.sql +244 -0
  160. package/.framework/migrations/038_drop_demo_record_flags.sql +38 -0
  161. package/.framework/migrations/042_registration_hierarchy.sql +309 -0
  162. package/.framework/migrations/043_policy_actor_cleanup.sql +39 -0
  163. package/.framework/migrations/044_match_embeddings_filtered_knn.sql +151 -0
  164. package/.framework/migrations/045_security_critical_rls.sql +139 -0
  165. package/.framework/migrations/046_security_missing_rls.sql +79 -0
  166. package/.framework/migrations/047_people_updated_by.sql +27 -0
  167. package/.framework/migrations/048_watcher_last_seen_at.sql +24 -0
  168. package/.framework/migrations/049_security_blocklist.sql +148 -0
  169. package/.framework/migrations/050_notifications.sql +49 -0
  170. package/.framework/migrations/051_message_agent_fields.sql +74 -0
  171. package/.framework/migrations/052_registration_security.sql +421 -0
  172. package/.framework/migrations/053_builtin_action_seeds.sql +66 -0
  173. package/.framework/migrations/054_kb_ingest_lease.sql +157 -0
  174. package/.framework/migrations/055_type_id_drift_reconciliation.sql +33 -0
  175. package/.framework/migrations/056_slug_column_reconciliation.sql +38 -0
  176. package/.framework/migrations/057_pipelines_slug_identity.sql +14 -0
  177. package/.framework/migrations/058_account_data_field_declarations.sql +53 -0
  178. package/.framework/migrations/059_registration_placement_fixes.sql +269 -0
  179. package/.framework/migrations/060_account_person_record_permissions.sql +38 -0
  180. package/.framework/migrations/061_fk_dedup.sql +32 -0
  181. package/.framework/public/spine-logo.svg +10 -3
  182. package/.framework/scripts/app-install-cli.ts +190 -93
  183. package/.framework/scripts/assemble-frontend.sh +127 -44
  184. package/.framework/scripts/assemble-functions.sh +128 -30
  185. package/.framework/scripts/boundary-check.sh +6 -2
  186. package/.framework/scripts/build-manifest.sh +24 -68
  187. package/.framework/scripts/check-core-integrity.sh +15 -76
  188. package/.framework/scripts/checksum-manifest.mts +343 -0
  189. package/.framework/scripts/cil-migrate-markers.ts +403 -0
  190. package/.framework/scripts/fix-cil-markers.mjs +176 -0
  191. package/.framework/scripts/verify-integrity.sh +21 -57
  192. package/.framework/scripts/verify-manifest.mts +305 -0
  193. package/.framework/seeds/types/funnel_signal_use.json +74 -21
  194. package/.framework/seeds/types/spine_instance.json +73 -21
  195. package/.framework/src/App.tsx +118 -72
  196. package/.framework/src/apps/admin/index.tsx +855 -58
  197. package/.framework/src/components/AppWrapper.tsx +43 -45
  198. package/.framework/src/components/CustomAppLoader.tsx +160 -116
  199. package/.framework/src/components/admin/AdminListPage.tsx +46 -44
  200. package/.framework/src/components/admin/AdminSidebar.tsx +57 -47
  201. package/.framework/src/components/admin/AdminStatsCard.tsx +46 -44
  202. package/.framework/src/components/admin/PageInstructions.tsx +98 -2
  203. package/.framework/src/components/admin/SortableTableHeader.tsx +44 -45
  204. package/.framework/src/components/app-shell/GenericAppShell.tsx +64 -65
  205. package/.framework/src/components/app-shell/GenericDetailPage.tsx +44 -47
  206. package/.framework/src/components/app-shell/GenericListPage.tsx +45 -49
  207. package/.framework/src/components/app-sidebar.tsx +42 -45
  208. package/.framework/src/components/auth/ProtectedRoute.tsx +48 -41
  209. package/.framework/src/components/auth/TurnstileWidget.tsx +27 -0
  210. package/.framework/src/components/kb/KBArticleEditor.tsx +231 -22
  211. package/.framework/src/components/kb/KBArticleList.tsx +251 -10
  212. package/.framework/src/components/layout/AppShell.tsx +51 -45
  213. package/.framework/src/components/layout/Header.tsx +42 -43
  214. package/.framework/src/components/layout/Layout.tsx +46 -46
  215. package/.framework/src/components/layout/Sidebar.tsx +196 -96
  216. package/.framework/src/components/runtime/DataDetailHeader.tsx +55 -30
  217. package/.framework/src/components/runtime/DataDetailPage.tsx +31 -269
  218. package/.framework/src/components/runtime/DataFilters.tsx +49 -74
  219. package/.framework/src/components/runtime/DataHeader.tsx +53 -32
  220. package/.framework/src/components/runtime/DataListPage.tsx +51 -155
  221. package/.framework/src/components/runtime/DataStats.tsx +66 -44
  222. package/.framework/src/components/runtime/DataTable.tsx +49 -149
  223. package/.framework/src/components/runtime/SchemaDetailForm.tsx +45 -75
  224. package/.framework/src/components/runtime/index.ts +119 -16
  225. package/.framework/src/components/search-form.tsx +46 -29
  226. package/.framework/src/components/shared/AgentView.tsx +65 -48
  227. package/.framework/src/components/shared/FieldRenderer.tsx +144 -50
  228. package/.framework/src/components/shared/InviteManager.tsx +207 -97
  229. package/.framework/src/components/shared/MemberList.tsx +97 -0
  230. package/.framework/src/components/shared/SchemaFields.tsx +107 -86
  231. package/.framework/src/components/ui/DataTable.tsx +80 -68
  232. package/.framework/src/components/ui/Form.tsx +157 -146
  233. package/.framework/src/components/ui/ItemCard.tsx +74 -67
  234. package/.framework/src/components/ui/ItemListView.tsx +73 -68
  235. package/.framework/src/components/ui/LoadingSpinner.tsx +43 -48
  236. package/.framework/src/components/ui/Modal.tsx +78 -68
  237. package/.framework/src/components/ui/RichTextEditor.tsx +89 -69
  238. package/.framework/src/components/ui/accordion.tsx +85 -86
  239. package/.framework/src/components/ui/alert-dialog.tsx +197 -184
  240. package/.framework/src/components/ui/alert.tsx +105 -94
  241. package/.framework/src/components/ui/aspect-ratio.tsx +57 -43
  242. package/.framework/src/components/ui/avatar.tsx +122 -107
  243. package/.framework/src/components/ui/badge.tsx +60 -55
  244. package/.framework/src/components/ui/breadcrumb.tsx +127 -128
  245. package/.framework/src/components/ui/button-group.tsx +86 -72
  246. package/.framework/src/components/ui/button.tsx +71 -50
  247. package/.framework/src/components/ui/calendar.tsx +72 -57
  248. package/.framework/src/components/ui/card.tsx +136 -129
  249. package/.framework/src/components/ui/carousel.tsx +226 -107
  250. package/.framework/src/components/ui/chart.tsx +252 -147
  251. package/.framework/src/components/ui/checkbox.tsx +43 -44
  252. package/.framework/src/components/ui/collapsible.tsx +86 -71
  253. package/.framework/src/components/ui/combobox.tsx +282 -249
  254. package/.framework/src/components/ui/command.tsx +158 -146
  255. package/.framework/src/components/ui/context-menu.tsx +239 -240
  256. package/.framework/src/components/ui/dialog.tsx +174 -157
  257. package/.framework/src/components/ui/direction.tsx +72 -56
  258. package/.framework/src/components/ui/drawer.tsx +168 -169
  259. package/.framework/src/components/ui/dropdown-menu.tsx +254 -223
  260. package/.framework/src/components/ui/empty.tsx +131 -120
  261. package/.framework/src/components/ui/field.tsx +205 -173
  262. package/.framework/src/components/ui/hover-card.tsx +71 -72
  263. package/.framework/src/components/ui/input-group.tsx +148 -133
  264. package/.framework/src/components/ui/input-otp.tsx +99 -86
  265. package/.framework/src/components/ui/input.tsx +43 -41
  266. package/.framework/src/components/ui/item.tsx +207 -185
  267. package/.framework/src/components/ui/kbd.tsx +57 -56
  268. package/.framework/src/components/ui/label.tsx +45 -41
  269. package/.framework/src/components/ui/menubar.tsx +268 -236
  270. package/.framework/src/components/ui/native-select.tsx +85 -86
  271. package/.framework/src/components/ui/navigation-menu.tsx +166 -156
  272. package/.framework/src/components/ui/pagination.tsx +131 -141
  273. package/.framework/src/components/ui/popover.tsx +125 -128
  274. package/.framework/src/components/ui/progress.tsx +47 -44
  275. package/.framework/src/components/ui/radio-group.tsx +60 -55
  276. package/.framework/src/components/ui/resizable.tsx +85 -72
  277. package/.framework/src/components/ui/scroll-area.tsx +57 -58
  278. package/.framework/src/components/ui/select.tsx +182 -169
  279. package/.framework/src/components/ui/separator.tsx +42 -43
  280. package/.framework/src/components/ui/sheet.tsx +170 -159
  281. package/.framework/src/components/ui/sidebar.tsx +395 -398
  282. package/.framework/src/components/ui/skeleton.tsx +42 -43
  283. package/.framework/src/components/ui/slider.tsx +61 -44
  284. package/.framework/src/components/ui/sonner.tsx +44 -44
  285. package/.framework/src/components/ui/spinner.tsx +47 -42
  286. package/.framework/src/components/ui/switch.tsx +56 -42
  287. package/.framework/src/components/ui/table-primitives.tsx +272 -132
  288. package/.framework/src/components/ui/table.tsx +140 -142
  289. package/.framework/src/components/ui/tabs.tsx +110 -93
  290. package/.framework/src/components/ui/textarea.tsx +43 -44
  291. package/.framework/src/components/ui/toggle-group.tsx +87 -71
  292. package/.framework/src/components/ui/toggle.tsx +56 -56
  293. package/.framework/src/components/ui/tooltip.tsx +99 -85
  294. package/.framework/src/contexts/AppContext.tsx +166 -114
  295. package/.framework/src/contexts/AuthContext.tsx +232 -137
  296. package/.framework/src/hooks/use-mobile.ts +43 -44
  297. package/.framework/src/hooks/useApi.ts +185 -172
  298. package/.framework/src/hooks/useAppPath.ts +31 -29
  299. package/.framework/src/hooks/useApps.ts +77 -42
  300. package/.framework/src/hooks/useEntityList.ts +66 -29
  301. package/.framework/src/hooks/useEntityRecord.ts +76 -32
  302. package/.framework/src/hooks/useForm.ts +62 -30
  303. package/.framework/src/hooks/useListSchema.ts +60 -43
  304. package/.framework/src/hooks/useSchemaRecord.ts +115 -95
  305. package/.framework/src/lib/api.ts +258 -134
  306. package/.framework/src/lib/registration.ts +181 -0
  307. package/.framework/src/lib/supabase.ts +118 -81
  308. package/.framework/src/lib/utils.ts +266 -266
  309. package/.framework/src/main.tsx +43 -44
  310. package/.framework/src/pages/DashboardPage.tsx +44 -43
  311. package/.framework/src/pages/NoAppPage.tsx +50 -0
  312. package/.framework/src/pages/NotFoundPage.tsx +32 -29
  313. package/.framework/src/pages/SetupPage.tsx +34 -33
  314. package/.framework/src/pages/admin/AIAgentDetailPage.tsx +69 -58
  315. package/.framework/src/pages/admin/AIAgentsPage.tsx +43 -40
  316. package/.framework/src/pages/admin/APIKeyDetailPage.tsx +63 -55
  317. package/.framework/src/pages/admin/APIKeysPage.tsx +44 -42
  318. package/.framework/src/pages/admin/AlertsConfigPage.tsx +117 -94
  319. package/.framework/src/pages/admin/AppDetailPage.tsx +48 -42
  320. package/.framework/src/pages/admin/AppsPage.tsx +45 -42
  321. package/.framework/src/pages/admin/DeliveriesPage.tsx +49 -17
  322. package/.framework/src/pages/admin/DesignedPage.tsx +31 -30
  323. package/.framework/src/pages/admin/EmbeddingDetailPage.tsx +58 -0
  324. package/.framework/src/pages/admin/EmbeddingsPage.tsx +49 -0
  325. package/.framework/src/pages/admin/ExtendedShadcnTestPage.tsx +31 -29
  326. package/.framework/src/pages/admin/IncrementalShadcnTestPage.tsx +46 -29
  327. package/.framework/src/pages/admin/IntegratedDashboard.tsx +32 -29
  328. package/.framework/src/pages/admin/IntegrationDetailPage.tsx +68 -55
  329. package/.framework/src/pages/admin/IntegrationsPage.tsx +45 -42
  330. package/.framework/src/pages/admin/InvitesPage.tsx +60 -44
  331. package/.framework/src/pages/admin/LogsPage.tsx +87 -137
  332. package/.framework/src/pages/admin/MinimalShadcnTestPage.tsx +31 -29
  333. package/.framework/src/pages/admin/ObservabilityDashboard.tsx +150 -89
  334. package/.framework/src/pages/admin/PipelineDetailPage.tsx +77 -104
  335. package/.framework/src/pages/admin/PipelineExecutionsPage.tsx +44 -43
  336. package/.framework/src/pages/admin/PipelinesPage.tsx +59 -145
  337. package/.framework/src/pages/admin/PromptConfigDetailPage.tsx +53 -42
  338. package/.framework/src/pages/admin/PromptConfigsPage.tsx +47 -117
  339. package/.framework/src/pages/admin/ProperlyDesignedPage.tsx +31 -44
  340. package/.framework/src/pages/admin/RoleDetailPage.tsx +49 -92
  341. package/.framework/src/pages/admin/RolesPage.tsx +47 -164
  342. package/.framework/src/pages/admin/SecurityPage.tsx +310 -0
  343. package/.framework/src/pages/admin/SelectTestPage.tsx +30 -32
  344. package/.framework/src/pages/admin/ShadcnTestPage.tsx +37 -44
  345. package/.framework/src/pages/admin/SimpleDashboard.tsx +32 -44
  346. package/.framework/src/pages/admin/TestRunDetailPage.tsx +97 -44
  347. package/.framework/src/pages/admin/TestingDashboard.tsx +126 -71
  348. package/.framework/src/pages/admin/TimerDetailPage.tsx +67 -55
  349. package/.framework/src/pages/admin/TimersPage.tsx +44 -42
  350. package/.framework/src/pages/admin/TriggerDetailPage.tsx +47 -42
  351. package/.framework/src/pages/admin/TriggersPage.tsx +58 -57
  352. package/.framework/src/pages/admin/TypeDetailPage.tsx +91 -55
  353. package/.framework/src/pages/admin/TypesPage.tsx +45 -42
  354. package/.framework/src/pages/admin/cil/CILAgentPage.tsx +258 -128
  355. package/.framework/src/pages/admin/cil/CILChunkDetailPage.tsx +127 -123
  356. package/.framework/src/pages/admin/cil/CILDashboardPage.tsx +308 -235
  357. package/.framework/src/pages/admin/cil/CILFileListPage.tsx +159 -110
  358. package/.framework/src/pages/admin/cil/CILFileSelectPage.tsx +432 -0
  359. package/.framework/src/pages/admin/cil/CILGraphPage.tsx +200 -178
  360. package/.framework/src/pages/admin/cil/CILIngestionPage.tsx +286 -239
  361. package/.framework/src/pages/admin/cil/CILProjectsPage.tsx +211 -90
  362. package/.framework/src/pages/admin/cil/CILSearchPage.tsx +156 -143
  363. package/.framework/src/pages/admin/cil/CILSuggestionsPage.tsx +379 -0
  364. package/.framework/src/pages/admin/cil/CILVersionsPage.tsx +337 -0
  365. package/.framework/src/pages/admin/cil/FlexiblePipelinePage.tsx +219 -214
  366. package/.framework/src/pages/admin/kb/KBAdminPage.tsx +64 -0
  367. package/.framework/src/pages/auth/AuthConfirmPage.tsx +135 -65
  368. package/.framework/src/pages/auth/ForgotPasswordPage.tsx +39 -31
  369. package/.framework/src/pages/auth/LoginPage.tsx +38 -32
  370. package/.framework/src/pages/auth/PendingApprovalPage.tsx +31 -28
  371. package/.framework/src/pages/auth/RegisterPage.tsx +429 -155
  372. package/.framework/src/pages/spine-framework/APIPage.tsx +33 -30
  373. package/.framework/src/pages/spine-framework/CLIPage.tsx +32 -30
  374. package/.framework/src/types/auth.ts +101 -93
  375. package/.framework/src/types/types.ts +283 -262
  376. package/.framework/templates/project.json +87 -0
  377. package/.framework/templates/spine.config.neutral.json +7 -0
  378. package/.framework/tests/api/api-surface.test.ts +381 -0
  379. package/.framework/tests/fixtures/seed.ts +427 -0
  380. package/.framework/tests/fixtures/teardown.ts +228 -0
  381. package/.framework/tests/reporter.ts +351 -0
  382. package/.framework/tests/setup.ts +135 -0
  383. package/.framework/tests/ui/ui-sweep.ts +278 -0
  384. package/.framework/tests/unit/admin-data-logger.test.ts +310 -0
  385. package/.framework/tests/unit/agent-runner.test.ts +2389 -0
  386. package/.framework/tests/unit/ai-interactions.test.ts +81 -0
  387. package/.framework/tests/unit/app-manifest.test.ts +359 -0
  388. package/.framework/tests/unit/assembly-scripts.test.ts +172 -0
  389. package/.framework/tests/unit/cil-determinism.test.ts +85 -0
  390. package/.framework/tests/unit/core-isolation.test.ts +153 -0
  391. package/.framework/tests/unit/funnel-signals.test.ts +135 -0
  392. package/.framework/tests/unit/install-app.test.ts +1759 -0
  393. package/.framework/tests/unit/invites-security.test.ts +572 -0
  394. package/.framework/tests/unit/kb-ingest-lease.test.ts +287 -0
  395. package/.framework/tests/unit/kb-search.test.ts +119 -0
  396. package/.framework/tests/unit/migrate.test.ts +509 -0
  397. package/.framework/tests/unit/notify.test.ts +136 -0
  398. package/.framework/tests/unit/permissions.test.ts +269 -0
  399. package/.framework/tests/unit/pipeline-runner.test.ts +755 -0
  400. package/.framework/tests/unit/principal-cache.test.ts +138 -0
  401. package/.framework/tests/unit/retrieval-golden.test.ts +520 -0
  402. package/.framework/tests/unit/runtime-isolation.test.ts +642 -0
  403. package/.framework/tests/unit/schema-utils.test.ts +225 -0
  404. package/.framework/tests/unit/trigger-engine.test.ts +513 -0
  405. package/.framework/tests/unit/validate-app.test.ts +527 -0
  406. package/README.md +34 -3
  407. package/bin/spine-framework.cjs +1 -0
  408. package/config/tailwind.config.cjs +7 -0
  409. package/config/tsconfig.functions.json +42 -0
  410. package/config/tsconfig.json +37 -9
  411. package/config/vite.config.ts +31 -2
  412. package/netlify.toml +31 -3
  413. package/package.json +46 -29
  414. package/scripts/assemble-frontend.sh +127 -44
  415. package/scripts/assemble-functions.sh +127 -29
  416. package/scripts/boundary-check.sh +6 -2
  417. package/scripts/build-manifest.sh +24 -68
  418. package/scripts/checksum-manifest.mts +345 -0
  419. package/scripts/dev-clean.sh +86 -0
  420. package/scripts/dev-netlify.sh +49 -0
  421. package/scripts/netlify-dev-wrapper.sh +2 -0
  422. package/scripts/verify-install.ts +358 -0
  423. package/scripts/verify-integrity.sh +21 -57
  424. package/scripts/verify-manifest.mts +305 -0
  425. package/templates/project/gitignore +45 -0
  426. package/{config → templates/project}/package-lock.json +22335 -9366
  427. package/vitest.config.ts +46 -0
  428. package/.framework/public/spine.config.json +0 -91
  429. package/config/deno.lock +0 -108
  430. package/config/tsconfig.node.tsbuildinfo +0 -1
  431. package/config/tsconfig.tsbuildinfo +0 -1
  432. package/config/vite.config.d.ts +0 -2
  433. package/custom/.devin/AGENT.md +0 -357
  434. package/custom/.devin/skills/install-app/SKILL.md +0 -39
  435. package/custom/.devin/skills/spine-workbench-discovery/SKILL.md +0 -39
  436. package/custom/.devin/skills/spine-workbench-idea/SKILL.md +0 -36
  437. package/custom/.devin/skills/spine-workbench-implement/SKILL.md +0 -46
  438. package/custom/.devin/skills/spine-workbench-initiative/SKILL.md +0 -28
  439. package/custom/.devin/skills/spine-workbench-plan/SKILL.md +0 -38
  440. package/custom/.devin/workflows/app-install.yaml +0 -83
  441. package/custom/README.md +0 -79
  442. package/docs/README.md +0 -15
  443. package/scripts/app-install-cli.ts +0 -286
  444. package/scripts/check-core-integrity.sh +0 -82
  445. package/scripts/ingest-chunks.cjs +0 -202
  446. package/scripts/kb-chunk-parser.cjs +0 -312
  447. package/scripts/kb-chunk-parser.ts +0 -330
  448. package/scripts/kb-ingest-docs.ts +0 -203
  449. package/scripts/load-test-app-install.ts +0 -484
  450. package/scripts/test-login.js +0 -33
  451. package/spine.config.json +0 -91
@@ -1,41 +1,77 @@
1
- // CIL[BEGIN chunk_id="RegisterPage_module_header_1783662155" type="module_header" identity="module_header" parent="none"]
2
- // CIL[DOC-MACRO purpose="Import React hooks, routing, auth, and Supabase for registration functionality"]
3
- // CIL[DOC-RESPONSIBILITY responsibility="Provide necessary imports for complex registration component with multiple flows"]
4
- // CIL[DOC-CONTEXT module="src/pages/auth/RegisterPage" layer="frontend-page" related_files="[\"custom/apps/portal/pages/ContentPage.tsx\",\"custom/apps/cortex/pages/operations/OperationsDashboard.tsx\",\".framework/src/pages/admin/TypeDetailPage.tsx\",\".framework/functions/_shared/principal.ts\",\".framework/functions/_shared/schema-utils.ts\",\"\\\"]"]
5
- // CIL[DOC-CONTRACT inputs="none" outputs="React hooks, routing, auth, Supabase client" errors="none" side_effects="none"]
6
- // CIL[DOC-MICRO logic_summary="Imports for registration page with invite handling and domain checking" notable_decisions="[]"]
7
- // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
1
+ // CIL[BEGIN stable_id="~/.framework/src/pages/auth/RegisterPage.tsx:module_header:module_header" type="module_header" identity="module_header" parent="~/.framework/src/pages/auth/RegisterPage.tsx"]
2
+ // CIL[DOC-MACRO purpose="User registration page for the auth flow"]
3
+ // CIL[DOC-RESPONSIBILITY responsibility="Provide the React component and helpers for registering new accounts, handling invites, and redirecting after signup"]
4
+ // CIL[DOC-CONTEXT module="auth" layer="page" related_files=".framework/src/components/ui/LoadingSpinner.tsx,.framework/src/contexts/AuthContext.tsx,.framework/src/lib/supabase.ts"]
5
+ // CIL[DOC-CONTRACT inputs="none" outputs="none" errors="none" side_effects="none"]
6
+ // CIL[DOC-MICRO logic_summary="Declares imports and shared types for the registration page" notable_decisions="[]"]
7
+ // CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
8
8
  // CIL[DOC-STABILITY stability="stable"]
9
- // CIL[DOC-AUDIENCE audience="developer"]
10
- // CIL[DOC-VECTOR type="context" content="Layer: frontend-page. Direct callers: 0. Direct calls: 0. Related files: custom/apps/portal/pages/ContentPage.tsx, custom/apps/cortex/pages/operations/OperationsDashboard.tsx, .framework/src/pages/admin/TypeDetailPage.tsx, .framework/functions/_shared/principal.ts, .framework/functions/_shared/schema-utils.ts, \."]
11
- // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
12
- // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
13
- // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
14
- // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
15
- // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
16
- import React, { useState, useEffect, useCallback } from 'react'
9
+ // CIL[DOC-AUDIENCE audience="developers"]
10
+
11
+ import React, { useState, useEffect, useCallback, useRef } from 'react'
17
12
  import { Link, useNavigate, useSearchParams } from 'react-router-dom'
18
13
  import { LoadingSpinner } from '../../components/ui/LoadingSpinner'
19
14
  import { useAuth } from '../../contexts/AuthContext'
20
15
  import { supabase } from '../../lib/supabase'
21
- // CIL[END chunk_id="RegisterPage_module_header_1783662155"]
22
- // CIL[BEGIN chunk_id="RegisterPage_types_1783662155" type="type" identity="types" parent="none"]
23
- // CIL[DOC-MACRO purpose="Define TypeScript types for registration flows and configuration"]
24
- // CIL[DOC-RESPONSIBILITY responsibility="Provide type definitions for account types, invite data, and registration config"]
25
- // CIL[DOC-CONTEXT module="src/pages/auth/RegisterPage" layer="frontend-page" related_files="[\"custom/apps/portal/pages/ContentPage.tsx\",\".framework/functions/_shared/principal.ts\",\"custom/apps/cortex/pages/operations/OperationsDashboard.tsx\",\".framework/functions/_shared/schema-utils.ts\",\".framework/src/pages/admin/TypeDetailPage.tsx\",\"\\\"]"]
26
- // CIL[DOC-CONTRACT inputs="none" outputs="UseType, DomainMatchChoice, InvitePreview, RegistrationConfig" errors="none" side_effects="none"]
27
- // CIL[DOC-MICRO logic_summary="Type definitions for personal/company account types and invite handling" notable_decisions="[]"]
28
- // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
29
- // CIL[DOC-STABILITY stability="stable"]
30
- // CIL[DOC-VECTOR type="context" content="Layer: frontend-page. Direct callers: 0. Direct calls: 0. Related files: custom/apps/portal/pages/ContentPage.tsx, .framework/functions/_shared/principal.ts, custom/apps/cortex/pages/operations/OperationsDashboard.tsx, .framework/functions/_shared/schema-utils.ts, .framework/src/pages/admin/TypeDetailPage.tsx, \."]
31
- // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
32
- // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
33
- // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
16
+ import { apiFetch } from '../../lib/api'
17
+ import {
18
+ clearPendingRegistration,
19
+ finalizeRegistration,
20
+ readPendingRegistration,
21
+ savePendingRegistration,
22
+ RegistrationError,
23
+ PendingRegistrationIntent,
24
+ } from '../../lib/registration'
25
+ import { TurnstileWidget, turnstileEnabled } from '../../components/auth/TurnstileWidget'
26
+ // CIL[DOC-NOTE type="change" content="Batch4b: POST invites calls go through apiFetch (authenticated); pending-registration intent helpers imported for confirm-email resume"]
27
+ // CIL[END stable_id="~/.framework/src/pages/auth/RegisterPage.tsx:module_header:module_header"]
28
+ // CIL[BEGIN stable_id="~/.framework/src/pages/auth/RegisterPage.tsx:usetype:type" type="type" identity="UseType" parent="~/.framework/src/pages/auth/RegisterPage.tsx"]
29
+ // CIL[DOC-VECTOR type="context" content="purpose='User registration page for the auth flow'; No verified external callers; imports: .framework/src/components/ui/LoadingSpinner.tsx,.framework/src/contexts/AuthContext.tsx,.framework/src/lib/supabase.ts"]
30
+ // CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
31
+ // CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
32
+ // CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
34
33
  // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
35
34
  // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
35
+ // CIL[DOC-MACRO purpose="Discriminate personal versus company registration paths"]
36
+ // CIL[DOC-RESPONSIBILITY responsibility="Define the allowed values for registration use type"]
37
+ // CIL[DOC-CONTEXT module="auth" layer="type" related_files=".framework/src/components/ui/LoadingSpinner.tsx,.framework/src/contexts/AuthContext.tsx,.framework/src/lib/supabase.ts,.framework/src/pages/auth/RegisterPage.tsx"]
38
+ // CIL[DOC-CONTRACT inputs="none" outputs="Union type literal 'personal' | 'company'" errors="none" side_effects="none"]
39
+ // CIL[DOC-MICRO logic_summary="Type alias for 'personal' | 'company'" notable_decisions="[]"]
40
+ // CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
41
+ // CIL[DOC-STABILITY stability="stable"]
36
42
  type UseType = 'personal' | 'company'
43
+ // CIL[END stable_id="~/.framework/src/pages/auth/RegisterPage.tsx:usetype:type"]
44
+ // CIL[BEGIN stable_id="~/.framework/src/pages/auth/RegisterPage.tsx:domainmatchchoice:type" type="type" identity="DomainMatchChoice" parent="~/.framework/src/pages/auth/RegisterPage.tsx"]
45
+ // CIL[DOC-VECTOR type="context" content="purpose='Discriminate personal versus company registration paths'; No verified external callers; imports: .framework/src/components/ui/LoadingSpinner.tsx,.framework/src/contexts/AuthContext.tsx,.framework/src/lib/supabase.ts"]
46
+ // CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
47
+ // CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
48
+ // CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
49
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
50
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
51
+ // CIL[DOC-MACRO purpose="Represent the user choice when an existing account matches the email domain"]
52
+ // CIL[DOC-RESPONSIBILITY responsibility="Define domain match join or create options"]
53
+ // CIL[DOC-CONTEXT module="auth" layer="type" related_files=".framework/src/components/ui/LoadingSpinner.tsx,.framework/src/contexts/AuthContext.tsx,.framework/src/lib/supabase.ts,.framework/src/pages/auth/RegisterPage.tsx"]
54
+ // CIL[DOC-CONTRACT inputs="none" outputs="Union type literal 'join' | 'create_new' | null" errors="none" side_effects="none"]
55
+ // CIL[DOC-MICRO logic_summary="Type alias for 'join' | 'create_new' | null" notable_decisions="[]"]
56
+ // CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
57
+ // CIL[DOC-STABILITY stability="stable"]
37
58
  type DomainMatchChoice = 'join' | 'create_new' | null
59
+ // CIL[END stable_id="~/.framework/src/pages/auth/RegisterPage.tsx:domainmatchchoice:type"]
60
+ // CIL[DOC-VECTOR type="context" content="purpose='Represent the user choice when an existing account matches the email domain'; No verified external callers; imports: .framework/src/components/ui/LoadingSpinner.tsx,.framework/src/contexts/AuthContext.tsx,.framework/src/lib/supabase.ts"]
61
+ // CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
62
+ // CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
63
+ // CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
64
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
65
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
38
66
 
67
+ // CIL[BEGIN stable_id="~/.framework/src/pages/auth/RegisterPage.tsx:invitepreview:interface" type="interface" identity="InvitePreview" parent="~/.framework/src/pages/auth/RegisterPage.tsx"]
68
+ // CIL[DOC-MACRO purpose="Describe the payload returned by invite token validation"]
69
+ // CIL[DOC-RESPONSIBILITY responsibility="Interface for invitation metadata"]
70
+ // CIL[DOC-CONTEXT module="auth" layer="interface" related_files=".framework/src/components/ui/LoadingSpinner.tsx,.framework/src/contexts/AuthContext.tsx,.framework/src/lib/supabase.ts,.framework/src/pages/auth/RegisterPage.tsx"]
71
+ // CIL[DOC-CONTRACT inputs="none" outputs="structured invite preview object" errors="none" side_effects="none"]
72
+ // CIL[DOC-MICRO logic_summary="Defines email, full_name, account_name, role_name, and kind fields" notable_decisions="[]"]
73
+ // CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
74
+ // CIL[DOC-STABILITY stability="stable"]
39
75
  interface InvitePreview {
40
76
  email: string
41
77
  full_name: string | null
@@ -43,7 +79,23 @@ interface InvitePreview {
43
79
  role_name: string | null
44
80
  kind: string
45
81
  }
82
+ // CIL[END stable_id="~/.framework/src/pages/auth/RegisterPage.tsx:invitepreview:interface"]
83
+ // CIL[DOC-VECTOR type="context" content="purpose='Describe the payload returned by invite token validation'; No verified external callers; imports: .framework/src/components/ui/LoadingSpinner.tsx,.framework/src/contexts/AuthContext.tsx,.framework/src/lib/supabase.ts"]
84
+ // CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
85
+ // CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
86
+ // CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
87
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
88
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
46
89
 
90
+
91
+ // CIL[BEGIN stable_id="~/.framework/src/pages/auth/RegisterPage.tsx:registrationconfig:interface" type="interface" identity="RegistrationConfig" parent="~/.framework/src/pages/auth/RegisterPage.tsx"]
92
+ // CIL[DOC-MACRO purpose="Describe server-side registration routing configuration"]
93
+ // CIL[DOC-RESPONSIBILITY responsibility="Interface for per-app role and account strategy mapping"]
94
+ // CIL[DOC-CONTEXT module="auth" layer="interface" related_files=".framework/src/components/ui/LoadingSpinner.tsx,.framework/src/contexts/AuthContext.tsx,.framework/src/lib/supabase.ts,.framework/src/pages/auth/RegisterPage.tsx"]
95
+ // CIL[DOC-CONTRACT inputs="none" outputs="RegistrationConfig object" errors="none" side_effects="none"]
96
+ // CIL[DOC-MICRO logic_summary="Defines default_app, default_role, allow_url_override, apps map, account_strategy, and target_account" notable_decisions="[]"]
97
+ // CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
98
+ // CIL[DOC-STABILITY stability="stable"]
47
99
  interface RegistrationConfig {
48
100
  default_app: string
49
101
  default_role: string
@@ -56,23 +108,35 @@ interface RegistrationConfig {
56
108
  enabled: boolean
57
109
  account_strategy: 'existing' | 'new' | 'choice'
58
110
  target_account?: string
111
+ /** When true, company-domain matches may be offered a join_request.
112
+ Server-side, join_request only succeeds when this flag is set — the
113
+ client must hide the join choice otherwise. */
114
+ allow_domain_join?: boolean
115
+ /** Account slug that parents accounts created via the 'new' strategy.
116
+ Resolved server-side; shown here only for completeness. */
117
+ parent_account?: string
118
+ /** When true, skip the personal/company choice and company-name field —
119
+ the workspace is auto-named from the user's name/email. */
120
+ default_workspace_name?: boolean
59
121
  }>
60
122
  }
61
- // CIL[END chunk_id="RegisterPage_types_1783662155"]
62
- // CIL[BEGIN chunk_id="RegisterPage_callinvitesapi_1783662155" type="function" identity="callInvitesApi" parent="none"]
63
- // CIL[DOC-MACRO purpose="Call invites API endpoint for registration and domain validation"]
64
- // CIL[DOC-RESPONSIBILITY responsibility="Handle HTTP requests to invites API with GET/POST methods"]
65
- // CIL[DOC-CONTEXT module="src/pages/auth/RegisterPage" layer="frontend-page" related_files="[\".framework/functions/_shared/schema-utils.ts\",\"custom/apps/portal/pages/ContentPage.tsx\",\".framework/src/pages/admin/TypeDetailPage.tsx\",\"custom/apps/cortex/pages/operations/OperationsDashboard.tsx\",\".framework/functions/_shared/principal.ts\",\"\\\"]"]
66
- // CIL[DOC-CONTRACT inputs="none" outputs="API response JSON" errors="network errors, API errors" side_effects="HTTP requests to backend"]
67
- // CIL[DOC-MICRO logic_summary="Generic API wrapper for invites functionality with method handling" notable_decisions="[]"]
68
- // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
69
- // CIL[DOC-STABILITY stability="stable"]
70
- // CIL[DOC-VECTOR type="context" content="Layer: frontend-page. Direct callers: 2. Direct calls: 0. Related files: .framework/functions/_shared/schema-utils.ts, custom/apps/portal/pages/ContentPage.tsx, .framework/src/pages/admin/TypeDetailPage.tsx, custom/apps/cortex/pages/operations/OperationsDashboard.tsx, .framework/functions/_shared/principal.ts, \."]
71
- // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 2. Total affected: 2."]
72
- // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
73
- // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
123
+ // CIL[END stable_id="~/.framework/src/pages/auth/RegisterPage.tsx:registrationconfig:interface"]
124
+ // CIL[DOC-VECTOR type="context" content="purpose='Describe server-side registration routing configuration'; No verified external callers; imports: .framework/src/components/ui/LoadingSpinner.tsx,.framework/src/contexts/AuthContext.tsx,.framework/src/lib/supabase.ts"]
125
+ // CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
126
+ // CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
127
+ // CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
74
128
  // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
75
129
  // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
130
+
131
+ // CIL[BEGIN stable_id="~/.framework/src/pages/auth/RegisterPage.tsx:callinvitesapi:function" type="function" identity="callInvitesApi" parent="~/.framework/src/pages/auth/RegisterPage.tsx"]
132
+ // CIL[DOC-MACRO purpose="Call the Netlify invites edge function"]
133
+ // CIL[DOC-RESPONSIBILITY responsibility="Abstract GET and POST calls to the /invites endpoint"]
134
+ // CIL[DOC-CONTEXT module="auth" layer="function" related_files=".framework/src/components/ui/LoadingSpinner.tsx,.framework/src/contexts/AuthContext.tsx,.framework/src/lib/supabase.ts,.framework/src/pages/auth/RegisterPage.tsx"]
135
+ // CIL[DOC-CONTRACT inputs="action, params, method" outputs="parsed JSON response" errors="network or malformed response" side_effects="HTTP request to /.netlify/functions/invites"]
136
+ // CIL[DOC-MICRO logic_summary="Builds query string for GET or POST body for the invites endpoint" notable_decisions="POST calls go through apiFetch so the session Bearer token is attached — complete-registration is authenticated server-side; public GET actions (check-first-user, check-domain, validate-token) stay on raw fetch"]
137
+ // CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
138
+ // CIL[DOC-STABILITY stability="stable"]
139
+ // CIL[DOC-NOTE type="contract" content="complete-registration rejects anonymous callers; only GET actions remain unauthenticated"]
76
140
  async function callInvitesApi(action: string, params: Record<string, any> = {}, method: 'GET' | 'POST' = 'GET') {
77
141
  const base = '/.netlify/functions/invites'
78
142
  if (method === 'GET') {
@@ -80,28 +144,29 @@ async function callInvitesApi(action: string, params: Record<string, any> = {},
80
144
  const res = await fetch(`${base}?${qs}`)
81
145
  return res.json()
82
146
  }
83
- const res = await fetch(`${base}?action=${action}`, {
147
+ const res = await apiFetch(`${base}?action=${action}`, {
84
148
  method: 'POST',
85
149
  headers: { 'Content-Type': 'application/json' },
86
150
  body: JSON.stringify(params),
87
151
  })
88
152
  return res.json()
89
153
  }
90
- // CIL[END chunk_id="RegisterPage_callinvitesapi_1783662155"]
91
- // CIL[BEGIN chunk_id="RegisterPage_loadregistrationconfig_1783662155" type="function" identity="loadRegistrationConfig" parent="none"]
92
- // CIL[DOC-MACRO purpose="Load registration configuration from spine.config.json file"]
93
- // CIL[DOC-RESPONSIBILITY responsibility="Fetch and parse registration config with error handling"]
94
- // CIL[DOC-CONTEXT module="src/pages/auth/RegisterPage" layer="frontend-page" related_files="[\"custom/apps/cortex/functions/custom_cil-embeddings.ts\",\"\\\"]"]
95
- // CIL[DOC-CONTRACT inputs="none" outputs="RegistrationConfig or null" errors="fetch errors, parse errors" side_effects="HTTP request to config file"]
96
- // CIL[DOC-MICRO logic_summary="Fetch config file and extract registration section with fallback" notable_decisions="[]"]
97
- // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
98
- // CIL[DOC-STABILITY stability="stable"]
99
- // CIL[DOC-VECTOR type="context" content="Layer: frontend-page. Direct callers: 1. Direct calls: 0. Related files: custom/apps/cortex/functions/custom_cil-embeddings.ts, \."]
100
- // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 1. Total affected: 1."]
101
- // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
102
- // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
154
+ // CIL[END stable_id="~/.framework/src/pages/auth/RegisterPage.tsx:callinvitesapi:function"]
155
+ // CIL[DOC-VECTOR type="context" content="purpose='Call the Netlify invites edge function'; No verified external callers; imports: .framework/src/components/ui/LoadingSpinner.tsx,.framework/src/contexts/AuthContext.tsx,.framework/src/lib/supabase.ts"]
156
+ // CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
157
+ // CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
158
+ // CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
103
159
  // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
104
160
  // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
161
+
162
+ // CIL[BEGIN stable_id="~/.framework/src/pages/auth/RegisterPage.tsx:loadregistrationconfig:function" type="function" identity="loadRegistrationConfig" parent="~/.framework/src/pages/auth/RegisterPage.tsx"]
163
+ // CIL[DOC-MACRO purpose="Fetch the runtime registration configuration from spine.config.json"]
164
+ // CIL[DOC-RESPONSIBILITY responsibility="Load and parse public registration config"]
165
+ // CIL[DOC-CONTEXT module="auth" layer="function" related_files=".framework/src/components/ui/LoadingSpinner.tsx,.framework/src/contexts/AuthContext.tsx,.framework/src/lib/supabase.ts,.framework/src/pages/auth/RegisterPage.tsx"]
166
+ // CIL[DOC-CONTRACT inputs="none" outputs="RegistrationConfig or null" errors="fetch or parse failure" side_effects="HTTP GET /spine.config.json"]
167
+ // CIL[DOC-MICRO logic_summary="Fetches JSON and returns config.registration or null on any error" notable_decisions="[]"]
168
+ // CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
169
+ // CIL[DOC-STABILITY stability="stable"]
105
170
  async function loadRegistrationConfig(): Promise<RegistrationConfig | null> {
106
171
  try {
107
172
  const res = await fetch('/spine.config.json')
@@ -112,21 +177,22 @@ async function loadRegistrationConfig(): Promise<RegistrationConfig | null> {
112
177
  return null
113
178
  }
114
179
  }
115
- // CIL[END chunk_id="RegisterPage_loadregistrationconfig_1783662155"]
116
- // CIL[BEGIN chunk_id="RegisterPage_checkfirstuser_1783662155" type="function" identity="checkFirstUser" parent="none"]
117
- // CIL[DOC-MACRO purpose="Check if current user is the first user in the system"]
118
- // CIL[DOC-RESPONSIBILITY responsibility="Determine if registration should use simplified first-user flow"]
119
- // CIL[DOC-CONTEXT module="src/pages/auth/RegisterPage" layer="frontend-page" related_files="[\".framework/functions/_shared/principal.ts\",\".framework/migrations/025_retype_threads_messages.sql\",\"custom/apps/cortex/pages/operations/OperationsDashboard.tsx\",\".framework/src/pages/admin/RolesPage.tsx\"]"]
120
- // CIL[DOC-CONTRACT inputs="none" outputs="boolean indicating if first user" errors="API errors" side_effects="API call to check-first-user"]
121
- // CIL[DOC-MICRO logic_summary="Call invites API to check first user status with fallback" notable_decisions="[]"]
122
- // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
123
- // CIL[DOC-STABILITY stability="stable"]
124
- // CIL[DOC-VECTOR type="context" content="Layer: frontend-page. Direct callers: 1. Direct calls: 0. Related files: .framework/functions/_shared/principal.ts, .framework/migrations/025_retype_threads_messages.sql, custom/apps/cortex/pages/operations/OperationsDashboard.tsx, .framework/src/pages/admin/RolesPage.tsx."]
125
- // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 1. Total affected: 1."]
126
- // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
127
- // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
180
+ // CIL[END stable_id="~/.framework/src/pages/auth/RegisterPage.tsx:loadregistrationconfig:function"]
181
+ // CIL[DOC-VECTOR type="context" content="purpose='Fetch the runtime registration configuration from spine.config.json'; No verified external callers; imports: .framework/src/components/ui/LoadingSpinner.tsx,.framework/src/contexts/AuthContext.tsx,.framework/src/lib/supabase.ts"]
182
+ // CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
183
+ // CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
184
+ // CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
128
185
  // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
129
186
  // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
187
+
188
+ // CIL[BEGIN stable_id="~/.framework/src/pages/auth/RegisterPage.tsx:checkfirstuser:function" type="function" identity="checkFirstUser" parent="~/.framework/src/pages/auth/RegisterPage.tsx"]
189
+ // CIL[DOC-MACRO purpose="Determine whether this platform has no users yet"]
190
+ // CIL[DOC-RESPONSIBILITY responsibility="Query the invites API for the first-user flag"]
191
+ // CIL[DOC-CONTEXT module="auth" layer="function" related_files=".framework/src/components/ui/LoadingSpinner.tsx,.framework/src/contexts/AuthContext.tsx,.framework/src/lib/supabase.ts,.framework/src/pages/auth/RegisterPage.tsx"]
192
+ // CIL[DOC-CONTRACT inputs="none" outputs="boolean" errors="network or error treated as false" side_effects="HTTP GET to the invites API"]
193
+ // CIL[DOC-MICRO logic_summary="Calls callInvitesApi('check-first-user') and returns data.is_first_user or false" notable_decisions="[]"]
194
+ // CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
195
+ // CIL[DOC-STABILITY stability="stable"]
130
196
  async function checkFirstUser(): Promise<boolean> {
131
197
  try {
132
198
  const result = await callInvitesApi('check-first-user')
@@ -135,21 +201,22 @@ async function checkFirstUser(): Promise<boolean> {
135
201
  return false
136
202
  }
137
203
  }
138
- // CIL[END chunk_id="RegisterPage_checkfirstuser_1783662155"]
139
- // CIL[BEGIN chunk_id="RegisterPage_getpostregistrationredirect_1783662155" type="function" identity="getPostRegistrationRedirect" parent="none"]
140
- // CIL[DOC-MACRO purpose="Determine redirect path after successful registration"]
141
- // CIL[DOC-RESPONSIBILITY responsibility="Calculate post-registration redirect based on config, URL params, and invite status"]
142
- // CIL[DOC-CONTEXT module="src/pages/auth/RegisterPage" layer="frontend-page" related_files="[]"]
143
- // CIL[DOC-CONTRACT inputs="none" outputs="redirect path string" errors="none" side_effects="none"]
144
- // CIL[DOC-MICRO logic_summary="Redirect logic with URL override support and config fallbacks" notable_decisions="[]"]
145
- // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
146
- // CIL[DOC-STABILITY stability="stable"]
147
- // CIL[DOC-VECTOR type="context" content="Layer: frontend-page. Direct callers: 0. Direct calls: 0. Related files: none."]
148
- // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
149
- // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
150
- // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
204
+ // CIL[END stable_id="~/.framework/src/pages/auth/RegisterPage.tsx:checkfirstuser:function"]
205
+ // CIL[DOC-VECTOR type="context" content="purpose='Determine whether this platform has no users yet'; No verified external callers; imports: .framework/src/components/ui/LoadingSpinner.tsx,.framework/src/contexts/AuthContext.tsx,.framework/src/lib/supabase.ts"]
206
+ // CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
207
+ // CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
208
+ // CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
151
209
  // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
152
210
  // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
211
+
212
+ // CIL[BEGIN stable_id="~/.framework/src/pages/auth/RegisterPage.tsx:getpostregistrationredirect:function" type="function" identity="getPostRegistrationRedirect" parent="~/.framework/src/pages/auth/RegisterPage.tsx"]
213
+ // CIL[DOC-MACRO purpose="Resolve the redirect path after successful registration"]
214
+ // CIL[DOC-RESPONSIBILITY responsibility="Map role, app URL overrides, and config to a destination route"]
215
+ // CIL[DOC-CONTEXT module="auth" layer="function" related_files=".framework/src/components/ui/LoadingSpinner.tsx,.framework/src/contexts/AuthContext.tsx,.framework/src/lib/supabase.ts,.framework/src/pages/auth/RegisterPage.tsx"]
216
+ // CIL[DOC-CONTRACT inputs="config, searchParams, isInvite, effectiveRole, inviteAccountId" outputs="string path" errors="none" side_effects="none"]
217
+ // CIL[DOC-MICRO logic_summary="Chooses target app/role path with fallbacks to /" notable_decisions="[]"]
218
+ // CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
219
+ // CIL[DOC-STABILITY stability="stable"]
153
220
  function getPostRegistrationRedirect(
154
221
  config: RegistrationConfig | null,
155
222
  searchParams: URLSearchParams,
@@ -182,26 +249,29 @@ function getPostRegistrationRedirect(
182
249
 
183
250
  return '/'
184
251
  }
185
- // CIL[END chunk_id="RegisterPage_getpostregistrationredirect_1783662155"]
186
- // CIL[BEGIN chunk_id="RegisterPage_registerpage_1783662155" type="function" identity="RegisterPage" parent="none"]
187
- // CIL[DOC-MACRO purpose="Handle complex registration flow with invite support, domain checking, and multiple account strategies"]
188
- // CIL[DOC-RESPONSIBILITY responsibility="Manage registration state, form validation, invite processing, and user creation flows"]
189
- // CIL[DOC-CONTEXT module="src/pages/auth/RegisterPage" layer="frontend-page" related_files="[]"]
190
- // CIL[DOC-CONTRACT inputs="none" outputs="registered user session or error states" errors="validation errors, API errors, network errors" side_effects="creates user account, updates auth state"]
191
- // CIL[DOC-MICRO logic_summary="Complex registration component with multiple flows and validation" notable_decisions="[]"]
192
- // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
193
- // CIL[DOC-STABILITY stability="stable"]
194
- // CIL[DOC-VECTOR type="context" content="Layer: frontend-page. Direct callers: 0. Direct calls: 0. Related files: none."]
195
- // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
196
- // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
197
- // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
252
+ // CIL[END stable_id="~/.framework/src/pages/auth/RegisterPage.tsx:getpostregistrationredirect:function"]
253
+ // CIL[DOC-VECTOR type="context" content="purpose='Resolve the redirect path after successful registration'; No verified external callers; imports: .framework/src/components/ui/LoadingSpinner.tsx,.framework/src/contexts/AuthContext.tsx,.framework/src/lib/supabase.ts"]
254
+ // CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
255
+ // CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
256
+ // CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
198
257
  // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
199
258
  // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
259
+
260
+ // CIL[BEGIN stable_id="~/.framework/src/pages/auth/RegisterPage.tsx:registerpage:function" type="function" identity="RegisterPage" parent="~/.framework/src/pages/auth/RegisterPage.tsx"]
261
+ // CIL[DOC-MACRO purpose="Render the registration form and handle account creation"]
262
+ // CIL[DOC-RESPONSIBILITY responsibility="Provide the main registration page UI, validate input, complete registration, and navigate"]
263
+ // CIL[DOC-CONTEXT module="auth" layer="function" related_files=".framework/src/components/ui/LoadingSpinner.tsx,.framework/src/contexts/AuthContext.tsx,.framework/src/lib/supabase.ts,.framework/src/pages/auth/RegisterPage.tsx"]
264
+ // CIL[DOC-CONTRACT inputs="none (reads URL search params and auth context)" outputs="JSX registration page" errors="supabase errors, invite token errors, and registration errors surfaced in UI" side_effects="Updates React state, calls supabase auth, calls the invites API, and navigates"]
265
+ // CIL[DOC-MICRO logic_summary="Handles token validation, domain checking, form submission, account creation, and post-registration redirect" notable_decisions="['invite_token passed in signUp metadata','complete-registration via apiFetch with {app,path,account_name} only','server role_slug drives redirect','confirmation-pending state + sessionStorage spine.pending_registration','resume mode for authenticated unplaced users','domain-join gated by app allow_domain_join/choice policy']"]
266
+ // CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
267
+ // CIL[DOC-STABILITY stability="stable"]
268
+ // CIL[DOC-NOTE type="change" content="Batch4b: signUp sends invite_token; POST complete-registration is authenticated and server binds email/role; pending intent persisted before placement call and resumed at /register?resume=1 or after /auth/confirm"]
200
269
  export function RegisterPage() {
201
270
  const [searchParams] = useSearchParams()
202
271
  const navigate = useNavigate()
203
- const { refreshUser } = useAuth()
272
+ const { user, refreshUser, logout } = useAuth()
204
273
  const inviteToken = searchParams.get('t')
274
+ const resumeRequested = searchParams.get('resume') === '1'
205
275
 
206
276
  const [regConfig, setRegConfig] = useState<RegistrationConfig | null>(null)
207
277
  const [isFirstUser, setIsFirstUser] = useState<boolean | null>(null)
@@ -224,6 +294,31 @@ export function RegisterPage() {
224
294
 
225
295
  const [error, setError] = useState('')
226
296
  const [isLoading, setIsLoading] = useState(false)
297
+ const [captchaToken, setCaptchaToken] = useState<string | null>(null)
298
+
299
+ // Batch4b: confirmation-pending and resume-mode state
300
+ const [confirmationPending, setConfirmationPending] = useState(false)
301
+ const [resuming, setResuming] = useState(false)
302
+ const [resumeError, setResumeError] = useState('')
303
+ const [resumeAttempt, setResumeAttempt] = useState(0)
304
+ const resumeRanRef = useRef(false)
305
+
306
+ // Resolve which app/policy governs this registration (shared by submit and
307
+ // the domain-join gate). Role is a client hint only — the server assigns.
308
+ const resolveRegistrationContext = useCallback(() => {
309
+ const urlRole = searchParams.get('role')
310
+ const urlApp = searchParams.get('app')
311
+ const effectiveRole = urlRole || regConfig?.default_role || 'member'
312
+ const effectiveApp =
313
+ urlApp || regConfig?.default_app_by_role?.[effectiveRole] || regConfig?.default_app
314
+ const appConfig = effectiveApp ? regConfig?.apps[effectiveApp] : undefined
315
+ const accountStrategy = appConfig?.account_strategy ?? 'new'
316
+ // join_request only succeeds server-side when allow_domain_join is set;
317
+ // 'choice' strategy implies the same choice UI is permitted.
318
+ const domainJoinAllowed =
319
+ accountStrategy === 'choice' || appConfig?.allow_domain_join === true
320
+ return { effectiveRole, effectiveApp, appConfig, accountStrategy, domainJoinAllowed }
321
+ }, [regConfig, searchParams])
227
322
 
228
323
  useEffect(() => {
229
324
  ;(async () => {
@@ -236,6 +331,71 @@ export function RegisterPage() {
236
331
  })()
237
332
  }, [])
238
333
 
334
+ // Batch4b resume mode: an authenticated-but-unplaced user (no account_id)
335
+ // is sent here via /register?resume=1 (see App.tsx). If a pending intent was
336
+ // stored before the confirm-email round-trip, re-run complete-registration
337
+ // with it; otherwise finish with a minimal default intent so the user is
338
+ // never stranded on a dead page.
339
+ const isUnplacedUser = !!user && !user.account_id
340
+ useEffect(() => {
341
+ if (!isUnplacedUser || resumeRanRef.current || isLoading || resuming) return
342
+ if (isFirstUser === null) return // wait for config + first-user probe
343
+ if (error || resumeError) return // a submit/resume error is already on screen
344
+
345
+ // Unplaced users can't use the signup form (their auth user already
346
+ // exists), so resume always fires — with the stored intent, or a minimal
347
+ // derived default when none was saved (e.g. confirmed in another browser).
348
+ const intent = readPendingRegistration()
349
+
350
+ if (intent?.invite) {
351
+ // Invite stub was already claimed by the auth.users trigger at signUp —
352
+ // confirmation only activated the session. Rebind context and land.
353
+ resumeRanRef.current = true
354
+ setResuming(true)
355
+ ;(async () => {
356
+ clearPendingRegistration()
357
+ await refreshUser()
358
+ navigate('/', { replace: true })
359
+ })()
360
+ return
361
+ }
362
+
363
+ const effectiveIntent: PendingRegistrationIntent = intent ?? {
364
+ app: regConfig?.default_app,
365
+ path: 'new_account',
366
+ account_name: user?.full_name || user?.email?.split('@')[0],
367
+ invite: false,
368
+ }
369
+ if (!effectiveIntent.path) {
370
+ resumeRanRef.current = true
371
+ setResumeError('Saved registration details are incomplete. Please contact support or sign out and register again.')
372
+ return
373
+ }
374
+
375
+ resumeRanRef.current = true
376
+ setResuming(true)
377
+ ;(async () => {
378
+ try {
379
+ const result = await finalizeRegistration(effectiveIntent)
380
+ clearPendingRegistration()
381
+ if (result.status === 'pending') {
382
+ navigate('/auth/pending-approval', { replace: true })
383
+ return
384
+ }
385
+ await refreshUser()
386
+ navigate(getPostRegistrationRedirect(regConfig, searchParams, false, result.role_slug), { replace: true })
387
+ } catch (err: any) {
388
+ // Definitive server rejections clear the intent (retrying would fail
389
+ // identically); transient network errors keep it for the next attempt.
390
+ if (!(err instanceof RegistrationError) || err.serverResponded) {
391
+ clearPendingRegistration()
392
+ }
393
+ setResuming(false)
394
+ setResumeError(err?.message || 'Could not finish your registration.')
395
+ }
396
+ })()
397
+ }, [isUnplacedUser, isFirstUser, isLoading, resuming, resumeRequested, resumeAttempt, error, resumeError, regConfig, user, refreshUser, navigate, searchParams])
398
+
239
399
  useEffect(() => {
240
400
  if (!inviteToken) return
241
401
  ;(async () => {
@@ -253,7 +413,10 @@ export function RegisterPage() {
253
413
  }, [inviteToken])
254
414
 
255
415
  const handleEmailBlur = useCallback(async () => {
416
+ // Batch4b: domain-join probing only runs when the resolved app policy
417
+ // allows it (account_strategy 'choice' or allow_domain_join).
256
418
  if (!email || !email.includes('@') || inviteToken || useType === 'personal' || isFirstUser) return
419
+ if (!resolveRegistrationContext().domainJoinAllowed) return
257
420
 
258
421
  setDomainCheckLoading(true)
259
422
  setDomainMatchAccountId(null)
@@ -265,7 +428,7 @@ export function RegisterPage() {
265
428
  if (result.data?.match && result.data?.account_id) {
266
429
  setDomainMatchAccountId(result.data.account_id)
267
430
  }
268
- }, [email, inviteToken, useType, isFirstUser])
431
+ }, [email, inviteToken, useType, isFirstUser, resolveRegistrationContext])
269
432
 
270
433
  const handleSubmit = async (e: React.FormEvent) => {
271
434
  e.preventDefault()
@@ -279,12 +442,38 @@ export function RegisterPage() {
279
442
  setIsLoading(true)
280
443
 
281
444
  try {
445
+ // Resolve the registration intent up-front so it can be persisted BEFORE
446
+ // any placement call — a lost response must be resumable. The server
447
+ // binds email/role/account from ctx.principal + installed app config;
448
+ // the client sends only {app, path, account_name}.
449
+ const { effectiveApp, accountStrategy, domainJoinAllowed } = resolveRegistrationContext()
450
+
451
+ const accountName = useType === 'company'
452
+ ? (companyName || fullName || email.split('@')[0])
453
+ : (fullName || email.split('@')[0])
454
+
455
+ let registrationPath: PendingRegistrationIntent['path'] = 'new_account'
456
+ if (accountStrategy === 'existing') {
457
+ registrationPath = 'existing_account'
458
+ } else if (domainJoinAllowed && domainMatchAccountId && domainMatchChoice === 'join') {
459
+ registrationPath = 'join_request'
460
+ }
461
+
462
+ const pendingIntent: PendingRegistrationIntent = inviteToken
463
+ ? { invite: true } // claim happens in the auth.users trigger at signUp
464
+ : { app: effectiveApp, path: registrationPath, account_name: accountName, invite: false }
465
+
282
466
  const { data: signUpData, error: signUpError } = await supabase.auth.signUp({
283
467
  email: invitePreview?.email ?? email,
284
468
  password,
285
469
  options: {
286
- data: { full_name: fullName },
470
+ // invite_token is required server-side to claim the invite stub
471
+ data: {
472
+ full_name: fullName,
473
+ ...(inviteToken ? { invite_token: inviteToken } : {}),
474
+ },
287
475
  emailRedirectTo: `${window.location.origin}/auth/confirm`,
476
+ captchaToken: captchaToken ?? undefined,
288
477
  },
289
478
  })
290
479
 
@@ -293,71 +482,59 @@ export function RegisterPage() {
293
482
  return
294
483
  }
295
484
 
485
+ // Email-confirmation flow: no session yet. Persist the non-secret
486
+ // intent and show a check-your-email state; AuthConfirmPage resumes it.
487
+ if (!signUpData.session) {
488
+ savePendingRegistration(pendingIntent)
489
+ setConfirmationPending(true)
490
+ return
491
+ }
492
+
296
493
  if (isFirstUser) {
297
- await refreshUser()
298
- navigate('/spine-framework/admin', { replace: true })
494
+ const freshUser = await refreshUser()
495
+ // Only trust the server-confirmed role — another browser may have won
496
+ // the bootstrap race, in which case land them in their resolved app.
497
+ const confirmedAdmin =
498
+ freshUser?.is_system_admin === true ||
499
+ (freshUser?.roles ?? []).includes('system_admin')
500
+ navigate(
501
+ confirmedAdmin
502
+ ? '/spine-framework/admin'
503
+ : getPostRegistrationRedirect(regConfig, searchParams, false, freshUser?.roles?.[0]),
504
+ { replace: true }
505
+ )
299
506
  return
300
507
  }
301
508
 
302
509
  if (inviteToken) {
510
+ // Claim already happened at signUp; just rebind context and land.
511
+ clearPendingRegistration()
512
+ await refreshUser()
303
513
  navigate('/', { replace: true })
304
514
  return
305
515
  }
306
516
 
307
- const resolvedEmail = email
308
-
309
- const urlRole = searchParams.get('role')
310
- const urlApp = searchParams.get('app')
311
-
312
- let effectiveRole = urlRole || regConfig?.default_role || 'member'
313
- const effectiveApp = urlApp || regConfig?.default_app_by_role?.[effectiveRole] || regConfig?.default_app
314
- let effectiveAccountStrategy: 'existing' | 'new' | 'choice' = 'new'
315
- let targetAccount: string | undefined
316
-
317
- if (effectiveApp && regConfig?.apps[effectiveApp]) {
318
- const appConfig = regConfig.apps[effectiveApp]
319
-
320
- effectiveRole = urlRole || appConfig.default_role
321
- effectiveAccountStrategy = appConfig.account_strategy
322
- targetAccount = appConfig.target_account
323
- }
517
+ savePendingRegistration(pendingIntent)
324
518
 
325
- const completeRegistrationParams: any = {
326
- email: resolvedEmail,
327
- path: 'new_account',
328
- role_slug: effectiveRole,
329
- }
519
+ try {
520
+ const result = await finalizeRegistration(pendingIntent)
521
+ clearPendingRegistration()
330
522
 
331
- if (useType === 'company') {
332
- completeRegistrationParams.account_name = companyName || fullName || email.split('@')[0]
333
- } else {
334
- completeRegistrationParams.account_name = fullName || email.split('@')[0]
335
- }
523
+ if (result.status === 'pending') {
524
+ navigate('/auth/pending-approval', { replace: true })
525
+ return
526
+ }
336
527
 
337
- if (effectiveAccountStrategy === 'existing' && targetAccount) {
338
- completeRegistrationParams.path = 'existing_account'
339
- completeRegistrationParams.target_account = targetAccount
340
- } else if (domainMatchAccountId && domainMatchChoice === 'join') {
341
- completeRegistrationParams.path = 'join_request'
342
- completeRegistrationParams.target_account_id = domainMatchAccountId
343
- }
344
-
345
- const result = await callInvitesApi('complete-registration', completeRegistrationParams, 'POST')
346
-
347
- if (result.error) {
348
- setError(result.error)
349
- return
350
- }
351
-
352
- if (result.data?.next === 'pending_approval') {
353
- navigate('/auth/pending-approval', { replace: true })
354
- return
528
+ await refreshUser()
529
+ // Server-truth role decides the landing path — never the client guess.
530
+ navigate(getPostRegistrationRedirect(regConfig, searchParams, false, result.role_slug), { replace: true })
531
+ } catch (err: any) {
532
+ if (!(err instanceof RegistrationError) || err.serverResponded) {
533
+ clearPendingRegistration()
534
+ }
535
+ setError(err?.message || 'Registration failed')
355
536
  }
356
537
 
357
- await refreshUser()
358
- const redirectPath = getPostRegistrationRedirect(regConfig, searchParams, false, effectiveRole)
359
- navigate(redirectPath, { replace: true })
360
-
361
538
  } catch (err: any) {
362
539
  setError(err.message || 'Registration failed')
363
540
  } finally {
@@ -366,11 +543,100 @@ export function RegisterPage() {
366
543
  }
367
544
 
368
545
  const isInvite = !!inviteToken && !tokenError
369
- const showPersonalCompanyToggle = !isInvite && !isFirstUser
546
+ const requestedApp = searchParams.get('app') || regConfig?.default_app
547
+ const hideWorkspaceStep = !isInvite && !isFirstUser &&
548
+ !!requestedApp && regConfig?.apps[requestedApp]?.default_workspace_name === true
549
+ const showPersonalCompanyToggle = !isInvite && !isFirstUser && !hideWorkspaceStep
370
550
  const showCompanyName = showPersonalCompanyToggle && useType === 'company'
371
- const showDomainMatchChoice = !!domainMatchAccountId && !isInvite && !isFirstUser
551
+ // Batch4b: domain-join UI only when the resolved app policy allows it —
552
+ // server rejects join_request without allow_domain_join.
553
+ const domainJoinAllowed = resolveRegistrationContext().domainJoinAllowed
554
+ const showDomainMatchChoice = !!domainMatchAccountId && !isInvite && !isFirstUser && domainJoinAllowed
555
+
556
+ // Resume-in-progress: authenticated unplaced user finishing placement.
557
+ if (resuming) {
558
+ return (
559
+ <div className="min-h-screen flex items-center justify-center bg-slate-50">
560
+ <div className="text-center">
561
+ <LoadingSpinner size="lg" />
562
+ <p className="mt-4 text-slate-600">Finishing your registration...</p>
563
+ </div>
564
+ </div>
565
+ )
566
+ }
372
567
 
373
- if (tokenLoading || isFirstUser === null || (!isFirstUser && !isInvite && !regConfig)) {
568
+ if (resumeError) {
569
+ return (
570
+ <div className="min-h-screen flex items-center justify-center bg-slate-50 px-4">
571
+ <div className="max-w-md w-full text-center space-y-6">
572
+ <div className="space-y-2">
573
+ <h1 className="text-2xl font-semibold text-slate-900">Registration problem</h1>
574
+ <p className="text-slate-600">{resumeError}</p>
575
+ </div>
576
+ <div className="space-y-3">
577
+ <button
578
+ type="button"
579
+ onClick={() => {
580
+ resumeRanRef.current = false
581
+ setResumeError('')
582
+ setResumeAttempt(n => n + 1)
583
+ }}
584
+ className="block w-full py-2 px-4 rounded-[5px] bg-slate-900 text-white text-sm font-medium hover:bg-slate-800"
585
+ >
586
+ Try again
587
+ </button>
588
+ {/* Authenticated unplaced users can't reach /login — App routes
589
+ them back here — so offer a real sign-out escape hatch. */}
590
+ {isUnplacedUser ? (
591
+ <button
592
+ type="button"
593
+ onClick={() => logout()}
594
+ className="block w-full text-sm text-accent-blue hover:text-accent-blue-light"
595
+ >
596
+ Sign out
597
+ </button>
598
+ ) : (
599
+ <Link to="/login" className="block text-sm text-accent-blue hover:text-accent-blue-light">
600
+ Back to sign in
601
+ </Link>
602
+ )}
603
+ </div>
604
+ </div>
605
+ </div>
606
+ )
607
+ }
608
+
609
+ // Confirmation-enabled signup: no session was returned, so the user must
610
+ // confirm their email. The non-secret pending intent is already in
611
+ // sessionStorage — AuthConfirmPage resumes it after verification.
612
+ if (confirmationPending) {
613
+ return (
614
+ <div className="min-h-screen flex items-center justify-center bg-slate-50 px-4">
615
+ <div className="max-w-md w-full text-center space-y-6">
616
+ <div className="mx-auto h-16 w-16 flex items-center justify-center rounded-full bg-green-100">
617
+ <svg className="h-8 w-8 text-green-600" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor">
618
+ <path strokeLinecap="round" strokeLinejoin="round" d="M21.75 6.75v10.5a2.25 2.25 0 0 1-2.25 2.25h-15a2.25 2.25 0 0 1-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0 0 19.5 4.5h-15a2.25 2.25 0 0 0-2.25 2.25m19.5 0v.243a2.25 2.25 0 0 1-1.07 1.916l-7.5 4.615a2.25 2.25 0 0 1-2.36 0L3.32 8.91a2.25 2.25 0 0 1-1.07-1.916V6.75" />
619
+ </svg>
620
+ </div>
621
+ <div className="space-y-2">
622
+ <h1 className="text-2xl font-semibold text-slate-900">Check your email</h1>
623
+ <p className="text-slate-600">
624
+ We sent a confirmation link to{' '}
625
+ <span className="font-medium">{invitePreview?.email ?? email}</span>.
626
+ Click it to activate your account — we'll finish setting things up
627
+ automatically.
628
+ </p>
629
+ </div>
630
+ <Link to="/login" className="block text-sm text-accent-blue hover:text-accent-blue-light">
631
+ Back to sign in
632
+ </Link>
633
+ </div>
634
+ </div>
635
+ )
636
+ }
637
+
638
+ // Never swap to the loading state while a submit is in flight.
639
+ if (!isLoading && (tokenLoading || isFirstUser === null || (!isFirstUser && !isInvite && !regConfig))) {
374
640
  return (
375
641
  <div className="min-h-screen flex items-center justify-center bg-slate-50">
376
642
  <div className="text-center">
@@ -385,8 +651,8 @@ export function RegisterPage() {
385
651
  <div className="min-h-screen flex items-center justify-center bg-slate-50 py-12 px-4 sm:px-6 lg:px-8">
386
652
  <div className="max-w-md w-full space-y-8">
387
653
  <div>
388
- <div className="mx-auto h-12 w-12 flex items-center justify-center rounded-full bg-slate-900">
389
- <span className="text-white font-bold text-xl">S</span>
654
+ <div className="mx-auto flex items-center justify-center">
655
+ <img src="/spine-logo.svg" alt="Spine Framework" className="h-14 w-auto" />
390
656
  </div>
391
657
  <h2 className="mt-6 text-center text-3xl font-extrabold text-slate-900">
392
658
  {isInvite ? 'Complete your registration' : 'Create your account'}
@@ -559,10 +825,12 @@ export function RegisterPage() {
559
825
  </div>
560
826
  )}
561
827
 
828
+ <TurnstileWidget onToken={setCaptchaToken} />
829
+
562
830
  <div>
563
831
  <button
564
832
  type="submit"
565
- disabled={isLoading || (showDomainMatchChoice && !domainMatchChoice)}
833
+ disabled={isLoading || (showDomainMatchChoice && !domainMatchChoice) || (turnstileEnabled && !captchaToken)}
566
834
  className="group relative w-full flex justify-center py-2 px-4 border border-transparent rounded-[5px] shadow-sm text-sm font-medium text-white bg-slate-900 hover:bg-slate-800 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-accent-blue disabled:opacity-50 disabled:cursor-not-allowed"
567
835
  >
568
836
  {isLoading ? (
@@ -589,4 +857,10 @@ export function RegisterPage() {
589
857
  </div>
590
858
  )
591
859
  }
592
- // CIL[END chunk_id="RegisterPage_registerpage_1783662155"]
860
+ // CIL[END stable_id="~/.framework/src/pages/auth/RegisterPage.tsx:registerpage:function"]
861
+ // CIL[DOC-VECTOR type="context" content="purpose='Render the registration form and handle account creation'; No verified external callers; imports: .framework/src/components/ui/LoadingSpinner.tsx,.framework/src/contexts/AuthContext.tsx,.framework/src/lib/supabase.ts"]
862
+ // CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
863
+ // CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
864
+ // CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
865
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
866
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]