spine-framework 0.3.144 → 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.
- package/.env.example +76 -0
- package/.framework/.spine-manifest.json +19 -0
- package/.framework/cli/commands/agents.ts +61 -33
- package/.framework/cli/commands/auth.ts +60 -28
- package/.framework/cli/commands/create-app.ts +212 -80
- package/.framework/cli/commands/dev.ts +184 -68
- package/.framework/cli/commands/doctor.ts +324 -152
- package/.framework/cli/commands/generate.ts +119 -68
- package/.framework/cli/commands/init.ts +141 -242
- package/.framework/cli/commands/install-app.ts +1336 -314
- package/.framework/cli/commands/items.ts +31 -29
- package/.framework/cli/commands/migrate.ts +299 -156
- package/.framework/cli/commands/migrations.ts +62 -30
- package/.framework/cli/commands/pipelines.ts +31 -29
- package/.framework/cli/commands/status.ts +105 -45
- package/.framework/cli/commands/system.ts +47 -42
- package/.framework/cli/commands/test.ts +161 -102
- package/.framework/cli/commands/upgrade.ts +260 -0
- package/.framework/cli/commands/validate-app.ts +517 -161
- package/.framework/cli/context.ts +117 -85
- package/.framework/cli/env-loader.ts +74 -43
- package/.framework/cli/index.ts +70 -46
- package/.framework/cli/lib/project-files.ts +404 -0
- package/.framework/docs/guides/testing-guide.md +1 -1
- package/.framework/docs/runtime-types.md +72 -72
- package/.framework/functions/_shared/access.ts +84 -0
- package/.framework/functions/_shared/agent-runner.ts +5051 -1026
- package/.framework/functions/_shared/app-manifest.ts +214 -94
- package/.framework/functions/_shared/ast.ts +214 -0
- package/.framework/functions/_shared/audit.ts +91 -55
- package/.framework/functions/_shared/cil.ts +368 -127
- package/.framework/functions/_shared/db.ts +337 -68
- package/.framework/functions/_shared/funnel.ts +228 -0
- package/.framework/functions/_shared/geo.ts +105 -0
- package/.framework/functions/_shared/hash-api-key.ts +94 -17
- package/.framework/functions/_shared/index.ts +22 -224
- package/.framework/functions/_shared/kb.ts +832 -58
- package/.framework/functions/_shared/llm.ts +118 -18
- package/.framework/functions/_shared/logging.ts +317 -19
- package/.framework/functions/_shared/middleware.ts +701 -131
- package/.framework/functions/_shared/notify.ts +70 -0
- package/.framework/functions/_shared/outcomes.ts +34 -19
- package/.framework/functions/_shared/permissions.ts +501 -315
- package/.framework/functions/_shared/pipeline-runner.ts +704 -155
- package/.framework/functions/_shared/principal.ts +436 -263
- package/.framework/functions/_shared/rate-limit.ts +233 -0
- package/.framework/functions/_shared/resolve-credentials.ts +86 -19
- package/.framework/functions/_shared/resolve-ids.ts +203 -139
- package/.framework/functions/_shared/retry-deliveries.ts +51 -16
- package/.framework/functions/_shared/schema-utils.ts +602 -521
- package/.framework/functions/_shared/security.ts +345 -0
- package/.framework/functions/_shared/testing.ts +197 -172
- package/.framework/functions/_shared/tracing.ts +351 -21
- package/.framework/functions/_shared/trigger-engine.ts +425 -136
- package/.framework/functions/_shared/webhook-registration.ts +71 -70
- package/.framework/functions/_shared/webhook-registry.ts +126 -83
- package/.framework/functions/account-nodes.ts +30 -30
- package/.framework/functions/adapters/generic-webhook-inbound.ts +49 -16
- package/.framework/functions/adapters/generic-webhook-outbound.ts +17 -17
- package/.framework/functions/adapters/openai-outbound.ts +15 -16
- package/.framework/functions/admin-data.ts +596 -173
- package/.framework/functions/admin-funnel.ts +64 -60
- package/.framework/functions/admin-install-funnel.ts +84 -66
- package/.framework/functions/admin-signals.ts +69 -51
- package/.framework/functions/ai-agents.ts +431 -106
- package/.framework/functions/ai-interactions.ts +186 -112
- package/.framework/functions/api-keys.ts +107 -107
- package/.framework/functions/apps.ts +154 -136
- package/.framework/functions/auth.ts +83 -65
- package/.framework/functions/cil-agent-ingest.ts +189 -232
- package/.framework/functions/cil-agent.ts +433 -177
- package/.framework/functions/cil-callmap.ts +642 -430
- package/.framework/functions/cil-config.ts +139 -135
- package/.framework/functions/cil-context.ts +214 -127
- package/.framework/functions/cil-doc.ts +587 -458
- package/.framework/functions/cil-embeddings.ts +453 -364
- package/.framework/functions/cil-graph.ts +427 -142
- package/.framework/functions/cil-ingest.ts +1418 -1154
- package/.framework/functions/cil-pipeline.ts +276 -21
- package/.framework/functions/cil-project.ts +659 -132
- package/.framework/functions/cil-validate.ts +198 -164
- package/.framework/functions/debug-auth.ts +57 -55
- package/.framework/functions/deliveries.ts +80 -21
- package/.framework/functions/embeddings.ts +1023 -318
- package/.framework/functions/eval-harness.ts +146 -108
- package/.framework/functions/integration-outbound.ts +205 -75
- package/.framework/functions/integration-routes.ts +128 -23
- package/.framework/functions/integrations.ts +125 -100
- package/.framework/functions/invites.ts +1095 -460
- package/.framework/functions/item-progress.ts +158 -102
- package/.framework/functions/kb.ts +34 -10
- package/.framework/functions/logs.ts +133 -133
- package/.framework/functions/observability.ts +152 -122
- package/.framework/functions/pipeline-executions.ts +147 -132
- package/.framework/functions/pipelines.ts +134 -119
- package/.framework/functions/prompt-configs.ts +96 -80
- package/.framework/functions/roles.ts +112 -82
- package/.framework/functions/security.ts +450 -0
- package/.framework/functions/system-cron.ts +117 -80
- package/.framework/functions/system.ts +158 -98
- package/.framework/functions/tests.ts +29 -29
- package/.framework/functions/timers.ts +94 -95
- package/.framework/functions/trigger-scheduler.ts +10 -0
- package/.framework/functions/triggers.ts +179 -149
- package/.framework/functions/types.ts +137 -125
- package/.framework/mcp/index.ts +133 -0
- package/.framework/mcp/load-env.ts +60 -0
- package/.framework/mcp/shared/app-manifest.ts +275 -0
- package/.framework/mcp/shared/db.ts +477 -0
- package/.framework/mcp/shared/kb.ts +118 -0
- package/.framework/mcp/tools/architect.ts +431 -0
- package/.framework/mcp/tools/cil/context.ts +345 -0
- package/.framework/mcp/tools/cil/graph.ts +242 -0
- package/.framework/mcp/tools/cil/ingest.ts +1217 -0
- package/.framework/mcp/tools/cil/pipeline-orchestrator.ts +693 -0
- package/.framework/mcp/tools/cil/pipeline.ts +767 -0
- package/.framework/mcp/tools/cil/project.ts +791 -0
- package/.framework/mcp/tools/cil/retrieval.ts +336 -0
- package/.framework/mcp/tools/cil/search-core.ts +454 -0
- package/.framework/mcp/tools/cil/search.ts +97 -0
- package/.framework/mcp/tools/cil/suggest.ts +186 -0
- package/.framework/mcp/tools/cil/validate.ts +1369 -0
- package/.framework/mcp/tools/kb/delete.ts +139 -0
- package/.framework/mcp/tools/kb/index.ts +52 -0
- package/.framework/mcp/tools/kb/ingest.ts +84 -0
- package/.framework/mcp/tools/kb/list-types.ts +102 -0
- package/.framework/mcp/tools/kb/publish.ts +100 -0
- package/.framework/mcp/tools/kb/reindex.ts +86 -0
- package/.framework/mcp/tools/kb/revoke.ts +85 -0
- package/.framework/mcp/tools/kb/search.ts +101 -0
- package/.framework/mcp/tools/kb/upsert.ts +125 -0
- package/.framework/mcp/tools/observability.ts +510 -0
- package/.framework/migrations/001_core_schema.sql +15 -0
- package/.framework/migrations/002_core_rls_and_functions.sql +15 -0
- package/.framework/migrations/003_core_system_data.sql +16 -1
- package/.framework/migrations/004_core_rpc_and_broker.sql +17 -1
- package/.framework/migrations/005_core_observability.sql +17 -1
- package/.framework/migrations/006_security_hardening_rls.sql +15 -0
- package/.framework/migrations/007_kb_article_list.sql +17 -1
- package/.framework/migrations/008_items_target.sql +15 -0
- package/.framework/migrations/009_roles_slug_unique.sql +15 -0
- package/.framework/migrations/010_auth_user_trigger.sql +15 -0
- package/.framework/migrations/011_spine_system_account.sql +16 -0
- package/.framework/migrations/012_service_role_grants.sql +16 -0
- package/.framework/migrations/013_get_account_apps_role_filter.sql +15 -0
- package/.framework/migrations/014_apps_required_roles.sql +16 -0
- package/.framework/migrations/015_types_kind_slug_unique.sql +20 -0
- package/.framework/migrations/016_cil_types.sql +281 -0
- package/.framework/migrations/017_cil_stable_ids.sql +37 -0
- package/.framework/migrations/018_runtime_tables.sql +129 -0
- package/.framework/migrations/019_agent_prompt_columns.sql +56 -0
- package/.framework/migrations/020_schema_reconciliation.sql +67 -0
- package/.framework/migrations/021_kb_embedding_lifecycle.sql +31 -0
- package/.framework/migrations/032_cil_status_migration.sql +73 -0
- package/.framework/migrations/033_cil_versioning.sql +73 -0
- package/.framework/migrations/034_match_embeddings_knn.sql +115 -0
- package/.framework/migrations/035_rate_limits.sql +55 -0
- package/.framework/migrations/036_demo_record_flags.sql +72 -0
- package/.framework/migrations/037_kb_types_canonical.sql +244 -0
- package/.framework/migrations/038_drop_demo_record_flags.sql +38 -0
- package/.framework/migrations/042_registration_hierarchy.sql +309 -0
- package/.framework/migrations/043_policy_actor_cleanup.sql +39 -0
- package/.framework/migrations/044_match_embeddings_filtered_knn.sql +151 -0
- package/.framework/migrations/045_security_critical_rls.sql +139 -0
- package/.framework/migrations/046_security_missing_rls.sql +79 -0
- package/.framework/migrations/047_people_updated_by.sql +27 -0
- package/.framework/migrations/048_watcher_last_seen_at.sql +24 -0
- package/.framework/migrations/049_security_blocklist.sql +148 -0
- package/.framework/migrations/050_notifications.sql +49 -0
- package/.framework/migrations/051_message_agent_fields.sql +74 -0
- package/.framework/migrations/052_registration_security.sql +421 -0
- package/.framework/migrations/053_builtin_action_seeds.sql +66 -0
- package/.framework/migrations/054_kb_ingest_lease.sql +157 -0
- package/.framework/migrations/055_type_id_drift_reconciliation.sql +33 -0
- package/.framework/migrations/056_slug_column_reconciliation.sql +38 -0
- package/.framework/migrations/057_pipelines_slug_identity.sql +14 -0
- package/.framework/migrations/058_account_data_field_declarations.sql +53 -0
- package/.framework/migrations/059_registration_placement_fixes.sql +269 -0
- package/.framework/migrations/060_account_person_record_permissions.sql +38 -0
- package/.framework/migrations/061_fk_dedup.sql +32 -0
- package/.framework/public/spine-logo.svg +10 -3
- package/.framework/scripts/app-install-cli.ts +190 -93
- package/.framework/scripts/assemble-frontend.sh +127 -44
- package/.framework/scripts/assemble-functions.sh +128 -30
- package/.framework/scripts/boundary-check.sh +6 -2
- package/.framework/scripts/build-manifest.sh +24 -68
- package/.framework/scripts/check-core-integrity.sh +15 -76
- package/.framework/scripts/checksum-manifest.mts +343 -0
- package/.framework/scripts/cil-migrate-markers.ts +403 -0
- package/.framework/scripts/fix-cil-markers.mjs +176 -0
- package/.framework/scripts/verify-integrity.sh +21 -57
- package/.framework/scripts/verify-manifest.mts +305 -0
- package/.framework/seeds/types/funnel_signal_use.json +74 -21
- package/.framework/seeds/types/spine_instance.json +73 -21
- package/.framework/src/App.tsx +118 -72
- package/.framework/src/apps/admin/index.tsx +855 -58
- package/.framework/src/components/AppWrapper.tsx +43 -45
- package/.framework/src/components/CustomAppLoader.tsx +160 -116
- package/.framework/src/components/admin/AdminListPage.tsx +46 -44
- package/.framework/src/components/admin/AdminSidebar.tsx +57 -47
- package/.framework/src/components/admin/AdminStatsCard.tsx +46 -44
- package/.framework/src/components/admin/PageInstructions.tsx +98 -2
- package/.framework/src/components/admin/SortableTableHeader.tsx +44 -45
- package/.framework/src/components/app-shell/GenericAppShell.tsx +64 -65
- package/.framework/src/components/app-shell/GenericDetailPage.tsx +44 -47
- package/.framework/src/components/app-shell/GenericListPage.tsx +45 -49
- package/.framework/src/components/app-sidebar.tsx +42 -45
- package/.framework/src/components/auth/ProtectedRoute.tsx +48 -41
- package/.framework/src/components/auth/TurnstileWidget.tsx +27 -0
- package/.framework/src/components/kb/KBArticleEditor.tsx +231 -22
- package/.framework/src/components/kb/KBArticleList.tsx +251 -10
- package/.framework/src/components/layout/AppShell.tsx +51 -45
- package/.framework/src/components/layout/Header.tsx +42 -43
- package/.framework/src/components/layout/Layout.tsx +46 -46
- package/.framework/src/components/layout/Sidebar.tsx +196 -96
- package/.framework/src/components/runtime/DataDetailHeader.tsx +55 -30
- package/.framework/src/components/runtime/DataDetailPage.tsx +31 -269
- package/.framework/src/components/runtime/DataFilters.tsx +49 -74
- package/.framework/src/components/runtime/DataHeader.tsx +53 -32
- package/.framework/src/components/runtime/DataListPage.tsx +51 -155
- package/.framework/src/components/runtime/DataStats.tsx +66 -44
- package/.framework/src/components/runtime/DataTable.tsx +49 -149
- package/.framework/src/components/runtime/SchemaDetailForm.tsx +45 -75
- package/.framework/src/components/runtime/index.ts +119 -16
- package/.framework/src/components/search-form.tsx +46 -29
- package/.framework/src/components/shared/AgentView.tsx +65 -48
- package/.framework/src/components/shared/FieldRenderer.tsx +144 -50
- package/.framework/src/components/shared/InviteManager.tsx +207 -97
- package/.framework/src/components/shared/MemberList.tsx +97 -0
- package/.framework/src/components/shared/SchemaFields.tsx +107 -86
- package/.framework/src/components/ui/DataTable.tsx +80 -68
- package/.framework/src/components/ui/Form.tsx +157 -146
- package/.framework/src/components/ui/ItemCard.tsx +74 -67
- package/.framework/src/components/ui/ItemListView.tsx +73 -68
- package/.framework/src/components/ui/LoadingSpinner.tsx +43 -48
- package/.framework/src/components/ui/Modal.tsx +78 -68
- package/.framework/src/components/ui/RichTextEditor.tsx +89 -69
- package/.framework/src/components/ui/accordion.tsx +85 -86
- package/.framework/src/components/ui/alert-dialog.tsx +197 -184
- package/.framework/src/components/ui/alert.tsx +105 -94
- package/.framework/src/components/ui/aspect-ratio.tsx +57 -43
- package/.framework/src/components/ui/avatar.tsx +122 -107
- package/.framework/src/components/ui/badge.tsx +60 -55
- package/.framework/src/components/ui/breadcrumb.tsx +127 -128
- package/.framework/src/components/ui/button-group.tsx +86 -72
- package/.framework/src/components/ui/button.tsx +71 -50
- package/.framework/src/components/ui/calendar.tsx +72 -57
- package/.framework/src/components/ui/card.tsx +136 -129
- package/.framework/src/components/ui/carousel.tsx +226 -107
- package/.framework/src/components/ui/chart.tsx +252 -147
- package/.framework/src/components/ui/checkbox.tsx +43 -44
- package/.framework/src/components/ui/collapsible.tsx +86 -71
- package/.framework/src/components/ui/combobox.tsx +282 -249
- package/.framework/src/components/ui/command.tsx +158 -146
- package/.framework/src/components/ui/context-menu.tsx +239 -240
- package/.framework/src/components/ui/dialog.tsx +174 -157
- package/.framework/src/components/ui/direction.tsx +72 -56
- package/.framework/src/components/ui/drawer.tsx +168 -169
- package/.framework/src/components/ui/dropdown-menu.tsx +254 -223
- package/.framework/src/components/ui/empty.tsx +131 -120
- package/.framework/src/components/ui/field.tsx +205 -173
- package/.framework/src/components/ui/hover-card.tsx +71 -72
- package/.framework/src/components/ui/input-group.tsx +148 -133
- package/.framework/src/components/ui/input-otp.tsx +99 -86
- package/.framework/src/components/ui/input.tsx +43 -41
- package/.framework/src/components/ui/item.tsx +207 -185
- package/.framework/src/components/ui/kbd.tsx +57 -56
- package/.framework/src/components/ui/label.tsx +45 -41
- package/.framework/src/components/ui/menubar.tsx +268 -236
- package/.framework/src/components/ui/native-select.tsx +85 -86
- package/.framework/src/components/ui/navigation-menu.tsx +166 -156
- package/.framework/src/components/ui/pagination.tsx +131 -141
- package/.framework/src/components/ui/popover.tsx +125 -128
- package/.framework/src/components/ui/progress.tsx +47 -44
- package/.framework/src/components/ui/radio-group.tsx +60 -55
- package/.framework/src/components/ui/resizable.tsx +85 -72
- package/.framework/src/components/ui/scroll-area.tsx +57 -58
- package/.framework/src/components/ui/select.tsx +182 -169
- package/.framework/src/components/ui/separator.tsx +42 -43
- package/.framework/src/components/ui/sheet.tsx +170 -159
- package/.framework/src/components/ui/sidebar.tsx +395 -398
- package/.framework/src/components/ui/skeleton.tsx +42 -43
- package/.framework/src/components/ui/slider.tsx +61 -44
- package/.framework/src/components/ui/sonner.tsx +44 -44
- package/.framework/src/components/ui/spinner.tsx +47 -42
- package/.framework/src/components/ui/switch.tsx +56 -42
- package/.framework/src/components/ui/table-primitives.tsx +272 -132
- package/.framework/src/components/ui/table.tsx +140 -142
- package/.framework/src/components/ui/tabs.tsx +110 -93
- package/.framework/src/components/ui/textarea.tsx +43 -44
- package/.framework/src/components/ui/toggle-group.tsx +87 -71
- package/.framework/src/components/ui/toggle.tsx +56 -56
- package/.framework/src/components/ui/tooltip.tsx +99 -85
- package/.framework/src/contexts/AppContext.tsx +166 -114
- package/.framework/src/contexts/AuthContext.tsx +232 -137
- package/.framework/src/hooks/use-mobile.ts +43 -44
- package/.framework/src/hooks/useApi.ts +185 -172
- package/.framework/src/hooks/useAppPath.ts +31 -29
- package/.framework/src/hooks/useApps.ts +77 -42
- package/.framework/src/hooks/useEntityList.ts +66 -29
- package/.framework/src/hooks/useEntityRecord.ts +76 -32
- package/.framework/src/hooks/useForm.ts +62 -30
- package/.framework/src/hooks/useListSchema.ts +60 -43
- package/.framework/src/hooks/useSchemaRecord.ts +115 -95
- package/.framework/src/lib/api.ts +258 -134
- package/.framework/src/lib/registration.ts +181 -0
- package/.framework/src/lib/supabase.ts +118 -81
- package/.framework/src/lib/utils.ts +266 -266
- package/.framework/src/main.tsx +43 -44
- package/.framework/src/pages/DashboardPage.tsx +44 -43
- package/.framework/src/pages/NoAppPage.tsx +50 -0
- package/.framework/src/pages/NotFoundPage.tsx +32 -29
- package/.framework/src/pages/SetupPage.tsx +34 -33
- package/.framework/src/pages/admin/AIAgentDetailPage.tsx +69 -58
- package/.framework/src/pages/admin/AIAgentsPage.tsx +43 -40
- package/.framework/src/pages/admin/APIKeyDetailPage.tsx +63 -55
- package/.framework/src/pages/admin/APIKeysPage.tsx +44 -42
- package/.framework/src/pages/admin/AlertsConfigPage.tsx +117 -94
- package/.framework/src/pages/admin/AppDetailPage.tsx +48 -42
- package/.framework/src/pages/admin/AppsPage.tsx +45 -42
- package/.framework/src/pages/admin/DeliveriesPage.tsx +49 -17
- package/.framework/src/pages/admin/DesignedPage.tsx +31 -30
- package/.framework/src/pages/admin/EmbeddingDetailPage.tsx +58 -0
- package/.framework/src/pages/admin/EmbeddingsPage.tsx +49 -0
- package/.framework/src/pages/admin/ExtendedShadcnTestPage.tsx +31 -29
- package/.framework/src/pages/admin/IncrementalShadcnTestPage.tsx +46 -29
- package/.framework/src/pages/admin/IntegratedDashboard.tsx +32 -29
- package/.framework/src/pages/admin/IntegrationDetailPage.tsx +68 -55
- package/.framework/src/pages/admin/IntegrationsPage.tsx +45 -42
- package/.framework/src/pages/admin/InvitesPage.tsx +60 -44
- package/.framework/src/pages/admin/LogsPage.tsx +87 -137
- package/.framework/src/pages/admin/MinimalShadcnTestPage.tsx +31 -29
- package/.framework/src/pages/admin/ObservabilityDashboard.tsx +150 -89
- package/.framework/src/pages/admin/PipelineDetailPage.tsx +77 -104
- package/.framework/src/pages/admin/PipelineExecutionsPage.tsx +44 -43
- package/.framework/src/pages/admin/PipelinesPage.tsx +59 -145
- package/.framework/src/pages/admin/PromptConfigDetailPage.tsx +53 -42
- package/.framework/src/pages/admin/PromptConfigsPage.tsx +47 -117
- package/.framework/src/pages/admin/ProperlyDesignedPage.tsx +31 -44
- package/.framework/src/pages/admin/RoleDetailPage.tsx +49 -92
- package/.framework/src/pages/admin/RolesPage.tsx +47 -164
- package/.framework/src/pages/admin/SecurityPage.tsx +310 -0
- package/.framework/src/pages/admin/SelectTestPage.tsx +30 -32
- package/.framework/src/pages/admin/ShadcnTestPage.tsx +37 -44
- package/.framework/src/pages/admin/SimpleDashboard.tsx +32 -44
- package/.framework/src/pages/admin/TestRunDetailPage.tsx +97 -44
- package/.framework/src/pages/admin/TestingDashboard.tsx +126 -71
- package/.framework/src/pages/admin/TimerDetailPage.tsx +67 -55
- package/.framework/src/pages/admin/TimersPage.tsx +44 -42
- package/.framework/src/pages/admin/TriggerDetailPage.tsx +47 -42
- package/.framework/src/pages/admin/TriggersPage.tsx +58 -57
- package/.framework/src/pages/admin/TypeDetailPage.tsx +91 -55
- package/.framework/src/pages/admin/TypesPage.tsx +45 -42
- package/.framework/src/pages/admin/cil/CILAgentPage.tsx +258 -128
- package/.framework/src/pages/admin/cil/CILChunkDetailPage.tsx +127 -123
- package/.framework/src/pages/admin/cil/CILDashboardPage.tsx +308 -235
- package/.framework/src/pages/admin/cil/CILFileListPage.tsx +159 -110
- package/.framework/src/pages/admin/cil/CILFileSelectPage.tsx +432 -0
- package/.framework/src/pages/admin/cil/CILGraphPage.tsx +200 -178
- package/.framework/src/pages/admin/cil/CILIngestionPage.tsx +286 -239
- package/.framework/src/pages/admin/cil/CILProjectsPage.tsx +211 -90
- package/.framework/src/pages/admin/cil/CILSearchPage.tsx +156 -143
- package/.framework/src/pages/admin/cil/CILSuggestionsPage.tsx +379 -0
- package/.framework/src/pages/admin/cil/CILVersionsPage.tsx +337 -0
- package/.framework/src/pages/admin/cil/FlexiblePipelinePage.tsx +219 -214
- package/.framework/src/pages/admin/kb/KBAdminPage.tsx +64 -0
- package/.framework/src/pages/auth/AuthConfirmPage.tsx +135 -65
- package/.framework/src/pages/auth/ForgotPasswordPage.tsx +39 -31
- package/.framework/src/pages/auth/LoginPage.tsx +38 -32
- package/.framework/src/pages/auth/PendingApprovalPage.tsx +31 -28
- package/.framework/src/pages/auth/RegisterPage.tsx +429 -155
- package/.framework/src/pages/spine-framework/APIPage.tsx +33 -30
- package/.framework/src/pages/spine-framework/CLIPage.tsx +32 -30
- package/.framework/src/types/auth.ts +101 -93
- package/.framework/src/types/types.ts +283 -262
- package/.framework/templates/project.json +87 -0
- package/.framework/templates/spine.config.neutral.json +7 -0
- package/.framework/tests/api/api-surface.test.ts +381 -0
- package/.framework/tests/fixtures/seed.ts +427 -0
- package/.framework/tests/fixtures/teardown.ts +228 -0
- package/.framework/tests/reporter.ts +351 -0
- package/.framework/tests/setup.ts +135 -0
- package/.framework/tests/ui/ui-sweep.ts +278 -0
- package/.framework/tests/unit/admin-data-logger.test.ts +310 -0
- package/.framework/tests/unit/agent-runner.test.ts +2389 -0
- package/.framework/tests/unit/ai-interactions.test.ts +81 -0
- package/.framework/tests/unit/app-manifest.test.ts +359 -0
- package/.framework/tests/unit/assembly-scripts.test.ts +172 -0
- package/.framework/tests/unit/cil-determinism.test.ts +85 -0
- package/.framework/tests/unit/core-isolation.test.ts +153 -0
- package/.framework/tests/unit/funnel-signals.test.ts +135 -0
- package/.framework/tests/unit/install-app.test.ts +1759 -0
- package/.framework/tests/unit/invites-security.test.ts +572 -0
- package/.framework/tests/unit/kb-ingest-lease.test.ts +287 -0
- package/.framework/tests/unit/kb-search.test.ts +119 -0
- package/.framework/tests/unit/migrate.test.ts +509 -0
- package/.framework/tests/unit/notify.test.ts +136 -0
- package/.framework/tests/unit/permissions.test.ts +269 -0
- package/.framework/tests/unit/pipeline-runner.test.ts +755 -0
- package/.framework/tests/unit/principal-cache.test.ts +138 -0
- package/.framework/tests/unit/retrieval-golden.test.ts +520 -0
- package/.framework/tests/unit/runtime-isolation.test.ts +642 -0
- package/.framework/tests/unit/schema-utils.test.ts +225 -0
- package/.framework/tests/unit/trigger-engine.test.ts +513 -0
- package/.framework/tests/unit/validate-app.test.ts +527 -0
- package/README.md +34 -3
- package/bin/spine-framework.cjs +1 -0
- package/config/tailwind.config.cjs +7 -0
- package/config/tsconfig.functions.json +42 -0
- package/config/tsconfig.json +37 -9
- package/config/vite.config.ts +31 -2
- package/netlify.toml +31 -3
- package/package.json +46 -29
- package/scripts/assemble-frontend.sh +127 -44
- package/scripts/assemble-functions.sh +127 -29
- package/scripts/boundary-check.sh +6 -2
- package/scripts/build-manifest.sh +24 -68
- package/scripts/checksum-manifest.mts +345 -0
- package/scripts/dev-clean.sh +86 -0
- package/scripts/dev-netlify.sh +49 -0
- package/scripts/netlify-dev-wrapper.sh +2 -0
- package/scripts/verify-install.ts +358 -0
- package/scripts/verify-integrity.sh +21 -57
- package/scripts/verify-manifest.mts +305 -0
- package/templates/project/gitignore +45 -0
- package/{config → templates/project}/package-lock.json +22335 -9366
- package/vitest.config.ts +46 -0
- package/.framework/public/spine.config.json +0 -91
- package/config/deno.lock +0 -108
- package/config/tsconfig.node.tsbuildinfo +0 -1
- package/config/tsconfig.tsbuildinfo +0 -1
- package/config/vite.config.d.ts +0 -2
- package/custom/.devin/AGENT.md +0 -357
- package/custom/.devin/skills/install-app/SKILL.md +0 -39
- package/custom/.devin/skills/spine-workbench-discovery/SKILL.md +0 -39
- package/custom/.devin/skills/spine-workbench-idea/SKILL.md +0 -36
- package/custom/.devin/skills/spine-workbench-implement/SKILL.md +0 -46
- package/custom/.devin/skills/spine-workbench-initiative/SKILL.md +0 -28
- package/custom/.devin/skills/spine-workbench-plan/SKILL.md +0 -38
- package/custom/.devin/workflows/app-install.yaml +0 -83
- package/custom/README.md +0 -79
- package/docs/README.md +0 -15
- package/scripts/app-install-cli.ts +0 -286
- package/scripts/check-core-integrity.sh +0 -82
- package/scripts/ingest-chunks.cjs +0 -202
- package/scripts/kb-chunk-parser.cjs +0 -312
- package/scripts/kb-chunk-parser.ts +0 -330
- package/scripts/kb-ingest-docs.ts +0 -203
- package/scripts/load-test-app-install.ts +0 -484
- package/scripts/test-login.js +0 -33
- package/spine.config.json +0 -91
|
@@ -1,41 +1,77 @@
|
|
|
1
|
-
// CIL[BEGIN
|
|
2
|
-
// CIL[DOC-MACRO purpose="
|
|
3
|
-
// CIL[DOC-RESPONSIBILITY responsibility="Provide
|
|
4
|
-
// CIL[DOC-CONTEXT module="
|
|
5
|
-
// CIL[DOC-CONTRACT inputs="none" outputs="
|
|
6
|
-
// CIL[DOC-MICRO logic_summary="
|
|
7
|
-
// CIL[DOC-CONFIDENCE pass="second" score="0.
|
|
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="
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
// CIL[DOC-
|
|
32
|
-
// CIL[
|
|
33
|
-
// CIL[
|
|
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
|
|
62
|
-
// CIL[
|
|
63
|
-
// CIL[DOC-
|
|
64
|
-
// CIL[DOC-
|
|
65
|
-
// CIL[DOC-
|
|
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
|
|
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
|
|
91
|
-
// CIL[
|
|
92
|
-
// CIL[DOC-
|
|
93
|
-
// CIL[DOC-
|
|
94
|
-
// CIL[DOC-
|
|
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
|
|
116
|
-
// CIL[
|
|
117
|
-
// CIL[DOC-
|
|
118
|
-
// CIL[DOC-
|
|
119
|
-
// CIL[DOC-
|
|
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
|
|
139
|
-
// CIL[
|
|
140
|
-
// CIL[DOC-
|
|
141
|
-
// CIL[DOC-
|
|
142
|
-
// CIL[DOC-
|
|
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
|
|
186
|
-
// CIL[
|
|
187
|
-
// CIL[DOC-
|
|
188
|
-
// CIL[DOC-
|
|
189
|
-
// CIL[DOC-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
role_slug: effectiveRole,
|
|
329
|
-
}
|
|
519
|
+
try {
|
|
520
|
+
const result = await finalizeRegistration(pendingIntent)
|
|
521
|
+
clearPendingRegistration()
|
|
330
522
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
}
|
|
523
|
+
if (result.status === 'pending') {
|
|
524
|
+
navigate('/auth/pending-approval', { replace: true })
|
|
525
|
+
return
|
|
526
|
+
}
|
|
336
527
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
|
|
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
|
|
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
|
-
|
|
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 (
|
|
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
|
|
389
|
-
<
|
|
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
|
|
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"]
|