spine-framework 0.3.17 → 0.3.19

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 (270) hide show
  1. package/.framework/cli/bin.mjs +16 -0
  2. package/.framework/cli/commands/create-app.ts +1 -1
  3. package/.framework/cli/commands/dev.ts +215 -33
  4. package/.framework/cli/index.ts +37 -75
  5. package/.framework/functions/_shared/app-manifest.ts +8 -15
  6. package/.framework/functions/_shared/db.ts +11 -15
  7. package/.framework/functions/_shared/index.ts +7 -16
  8. package/.framework/functions/_shared/principal.ts +4 -62
  9. package/.framework/functions/_shared/resolve-ids.ts +6 -6
  10. package/.framework/functions/_shared/webhook-registry.ts +43 -1
  11. package/.framework/functions/admin-funnel.ts +104 -0
  12. package/.framework/functions/admin-install-funnel.ts +225 -0
  13. package/.framework/functions/admin-signals.ts +241 -0
  14. package/.framework/functions/integration-routes.ts +4 -8
  15. package/.framework/migrations/001_schema.sql +406 -0
  16. package/.framework/migrations/002_rls_and_functions.sql +710 -0
  17. package/.framework/migrations/003_system_data.sql +520 -0
  18. package/.framework/migrations/004_enhancements.sql +43 -0
  19. package/.framework/public/_redirects +3 -0
  20. package/.framework/public/spine-logo.jpg +0 -0
  21. package/.framework/public/spine-logo.svg +3 -0
  22. package/{scripts → .framework/scripts}/assemble-frontend.sh +8 -5
  23. package/{scripts → .framework/scripts}/assemble-functions.sh +4 -47
  24. package/{scripts → .framework/scripts}/assemble.sh +2 -8
  25. package/{scripts → .framework/scripts}/boundary-check.sh +5 -9
  26. package/{scripts → .framework/scripts}/check-core-integrity.sh +0 -0
  27. package/.framework/seeds/link_types/account_cli_instances.json +9 -0
  28. package/.framework/seeds/types/funnel_signal_use.json +98 -0
  29. package/.framework/seeds/types/spine_instance.json +109 -0
  30. package/.framework/src/App.tsx +11 -21
  31. package/.framework/src/components/CustomAppLoader.tsx +2 -19
  32. package/.framework/src/components/ui/accordion.tsx +1 -1
  33. package/.framework/src/components/ui/alert-dialog.tsx +1 -1
  34. package/.framework/src/components/ui/aspect-ratio.tsx +1 -1
  35. package/.framework/src/components/ui/avatar.tsx +1 -1
  36. package/.framework/src/components/ui/badge.tsx +2 -2
  37. package/.framework/src/components/ui/breadcrumb.tsx +2 -2
  38. package/.framework/src/components/ui/button-group.tsx +2 -2
  39. package/.framework/src/components/ui/button.tsx +2 -2
  40. package/.framework/src/components/ui/chart.tsx +6 -1
  41. package/.framework/src/components/ui/checkbox.tsx +1 -1
  42. package/.framework/src/components/ui/collapsible.tsx +1 -1
  43. package/.framework/src/components/ui/context-menu.tsx +1 -1
  44. package/.framework/src/components/ui/dialog.tsx +1 -1
  45. package/.framework/src/components/ui/direction.tsx +17 -1
  46. package/.framework/src/components/ui/dropdown-menu.tsx +1 -1
  47. package/.framework/src/components/ui/hover-card.tsx +1 -1
  48. package/.framework/src/components/ui/item.tsx +2 -2
  49. package/.framework/src/components/ui/label.tsx +1 -1
  50. package/.framework/src/components/ui/menubar.tsx +1 -1
  51. package/.framework/src/components/ui/navigation-menu.tsx +1 -1
  52. package/.framework/src/components/ui/popover.tsx +1 -1
  53. package/.framework/src/components/ui/progress.tsx +1 -1
  54. package/.framework/src/components/ui/radio-group.tsx +1 -1
  55. package/.framework/src/components/ui/scroll-area.tsx +1 -1
  56. package/.framework/src/components/ui/select.tsx +1 -1
  57. package/.framework/src/components/ui/separator.tsx +1 -1
  58. package/.framework/src/components/ui/sheet.tsx +1 -1
  59. package/.framework/src/components/ui/sidebar.tsx +6 -6
  60. package/.framework/src/components/ui/slider.tsx +1 -1
  61. package/.framework/src/components/ui/switch.tsx +1 -1
  62. package/.framework/src/components/ui/tabs.tsx +1 -1
  63. package/.framework/src/components/ui/toggle-group.tsx +1 -1
  64. package/.framework/src/components/ui/toggle.tsx +1 -1
  65. package/.framework/src/components/ui/tooltip.tsx +1 -1
  66. package/.framework/src/pages/auth/LoginPage.tsx +10 -9
  67. package/LICENSE.md +63 -93
  68. package/README.md +254 -129
  69. package/custom/.devin/skills/install-app/SKILL.md +39 -0
  70. package/custom/.devin/workflows/app-install.yaml +83 -0
  71. package/custom/README.md +79 -0
  72. package/docs/APPS.md +232 -0
  73. package/docs/ARCHITECTURE.md +246 -0
  74. package/docs/INSTALL.md +155 -0
  75. package/docs/README.md +170 -0
  76. package/docs/design-schema-spec.md +1837 -0
  77. package/docs/dev-guides/01-create-an-app.md +183 -0
  78. package/docs/dev-guides/02-create-a-function.md +178 -0
  79. package/docs/dev-guides/03-create-a-component.md +178 -0
  80. package/docs/dev-guides/04-webhook-handlers.md +129 -0
  81. package/docs/dev-guides/05-testing.md +168 -0
  82. package/docs/dev-guides/README.md +40 -0
  83. package/docs/npm-packages-plan.md +288 -0
  84. package/docs/spine-core--api-reference.md +822 -0
  85. package/package.json +79 -150
  86. package/.framework/cli/bin.cjs +0 -14
  87. package/.framework/cli/commands/claim.ts +0 -430
  88. package/.framework/cli/commands/init.ts +0 -219
  89. package/.framework/cli/commands/install-app.ts +0 -685
  90. package/.framework/cli/commands/migrate.ts +0 -178
  91. package/.framework/cli/commands/publish.ts +0 -191
  92. package/.framework/cli/commands/status.ts +0 -268
  93. package/.framework/cli/commands/uninstall-app.ts +0 -166
  94. package/.framework/cli/commands/update-app.ts +0 -397
  95. package/.framework/cli/welcome.cjs +0 -45
  96. package/.framework/config/signal-client.ts +0 -147
  97. package/.framework/config/signals.ts +0 -70
  98. package/.framework/docs/STABILITY.md +0 -52
  99. package/.framework/docs/examples/custom-app/README.md +0 -77
  100. package/.framework/docs/examples/custom-function/README.md +0 -27
  101. package/.framework/docs/examples/custom-function/handler.ts +0 -48
  102. package/.framework/docs/examples/custom-webhook/README.md +0 -68
  103. package/.framework/functions/handler-registry.ts +0 -22
  104. package/.framework/functions/signal.ts +0 -168
  105. package/.framework/migrations/000_foundation.sql +0 -1302
  106. package/.framework/migrations/001_seed.sql +0 -163
  107. package/.framework/migrations/002_seed_constraints.sql +0 -19
  108. package/.framework/migrations/003_auth_user_trigger.sql +0 -67
  109. package/.framework/migrations/004_app_version_tracking.sql +0 -8
  110. package/.framework/migrations/005_pipelines_slug_stages.sql +0 -21
  111. package/.framework/src/pages/auth/RegisterPage.tsx +0 -163
  112. package/CHANGELOG.md +0 -71
  113. package/STRUCTURE.md +0 -150
  114. package/bin/spine-framework.cjs +0 -62
  115. package/bin/welcome.cjs +0 -58
  116. package/bin/ws-shim.ts +0 -10
  117. package/config/components.json +0 -25
  118. package/config/deno.lock +0 -108
  119. package/config/package-lock.json +0 -17183
  120. package/config/postcss.config.cjs +0 -10
  121. package/config/tailwind.config.cjs +0 -78
  122. package/config/tsconfig.build.json +0 -32
  123. package/config/tsconfig.cli.json +0 -18
  124. package/config/tsconfig.json +0 -41
  125. package/config/tsconfig.node.json +0 -17
  126. package/config/tsconfig.node.tsbuildinfo +0 -1
  127. package/config/tsconfig.tsbuildinfo +0 -1
  128. package/config/typedoc.json +0 -16
  129. package/config/vite.config.d.ts +0 -2
  130. package/config/vite.config.ts +0 -71
  131. package/dist/cli/commands/agents.d.ts +0 -39
  132. package/dist/cli/commands/agents.d.ts.map +0 -1
  133. package/dist/cli/commands/auth.d.ts +0 -36
  134. package/dist/cli/commands/auth.d.ts.map +0 -1
  135. package/dist/cli/commands/claim.d.ts +0 -38
  136. package/dist/cli/commands/claim.d.ts.map +0 -1
  137. package/dist/cli/commands/create-app.d.ts +0 -23
  138. package/dist/cli/commands/create-app.d.ts.map +0 -1
  139. package/dist/cli/commands/dev.d.ts +0 -24
  140. package/dist/cli/commands/dev.d.ts.map +0 -1
  141. package/dist/cli/commands/doctor.d.ts +0 -42
  142. package/dist/cli/commands/doctor.d.ts.map +0 -1
  143. package/dist/cli/commands/generate.d.ts +0 -36
  144. package/dist/cli/commands/generate.d.ts.map +0 -1
  145. package/dist/cli/commands/init.d.ts +0 -20
  146. package/dist/cli/commands/init.d.ts.map +0 -1
  147. package/dist/cli/commands/install-app.d.ts +0 -30
  148. package/dist/cli/commands/install-app.d.ts.map +0 -1
  149. package/dist/cli/commands/items.d.ts +0 -45
  150. package/dist/cli/commands/items.d.ts.map +0 -1
  151. package/dist/cli/commands/migrate.d.ts +0 -21
  152. package/dist/cli/commands/migrate.d.ts.map +0 -1
  153. package/dist/cli/commands/migrations.d.ts +0 -41
  154. package/dist/cli/commands/migrations.d.ts.map +0 -1
  155. package/dist/cli/commands/pipelines.d.ts +0 -40
  156. package/dist/cli/commands/pipelines.d.ts.map +0 -1
  157. package/dist/cli/commands/publish.d.ts +0 -23
  158. package/dist/cli/commands/publish.d.ts.map +0 -1
  159. package/dist/cli/commands/status.d.ts +0 -23
  160. package/dist/cli/commands/status.d.ts.map +0 -1
  161. package/dist/cli/commands/system.d.ts +0 -29
  162. package/dist/cli/commands/system.d.ts.map +0 -1
  163. package/dist/cli/commands/test.d.ts +0 -46
  164. package/dist/cli/commands/test.d.ts.map +0 -1
  165. package/dist/cli/commands/uninstall-app.d.ts +0 -23
  166. package/dist/cli/commands/uninstall-app.d.ts.map +0 -1
  167. package/dist/cli/commands/update-app.d.ts +0 -24
  168. package/dist/cli/commands/update-app.d.ts.map +0 -1
  169. package/dist/cli/context.d.ts +0 -88
  170. package/dist/cli/context.d.ts.map +0 -1
  171. package/dist/cli/env-loader.d.ts +0 -14
  172. package/dist/cli/env-loader.d.ts.map +0 -1
  173. package/dist/cli/index.d.ts +0 -44
  174. package/dist/cli/index.d.ts.map +0 -1
  175. package/dist/config/signals.d.ts +0 -82
  176. package/dist/config/signals.d.ts.map +0 -1
  177. package/dist/functions/_shared/agent-runner.d.ts +0 -156
  178. package/dist/functions/_shared/agent-runner.d.ts.map +0 -1
  179. package/dist/functions/_shared/app-manifest.d.ts +0 -68
  180. package/dist/functions/_shared/app-manifest.d.ts.map +0 -1
  181. package/dist/functions/_shared/audit.d.ts +0 -91
  182. package/dist/functions/_shared/audit.d.ts.map +0 -1
  183. package/dist/functions/_shared/db.d.ts +0 -125
  184. package/dist/functions/_shared/db.d.ts.map +0 -1
  185. package/dist/functions/_shared/index.d.ts +0 -299
  186. package/dist/functions/_shared/index.d.ts.map +0 -1
  187. package/dist/functions/_shared/middleware.d.ts +0 -315
  188. package/dist/functions/_shared/middleware.d.ts.map +0 -1
  189. package/dist/functions/_shared/permissions.d.ts +0 -626
  190. package/dist/functions/_shared/permissions.d.ts.map +0 -1
  191. package/dist/functions/_shared/pipeline-runner.d.ts +0 -124
  192. package/dist/functions/_shared/pipeline-runner.d.ts.map +0 -1
  193. package/dist/functions/_shared/principal.d.ts +0 -284
  194. package/dist/functions/_shared/principal.d.ts.map +0 -1
  195. package/dist/functions/_shared/resolve-ids.d.ts +0 -10
  196. package/dist/functions/_shared/resolve-ids.d.ts.map +0 -1
  197. package/dist/functions/_shared/schema-utils.d.ts +0 -181
  198. package/dist/functions/_shared/schema-utils.d.ts.map +0 -1
  199. package/dist/functions/_shared/testing.d.ts +0 -172
  200. package/dist/functions/_shared/testing.d.ts.map +0 -1
  201. package/dist/functions/_shared/trigger-engine.d.ts +0 -140
  202. package/dist/functions/_shared/trigger-engine.d.ts.map +0 -1
  203. package/dist/functions/_shared/webhook-registration.d.ts +0 -81
  204. package/dist/functions/_shared/webhook-registration.d.ts.map +0 -1
  205. package/dist/functions/_shared/webhook-registry.d.ts +0 -41
  206. package/dist/functions/_shared/webhook-registry.d.ts.map +0 -1
  207. package/dist/functions/account-nodes.d.ts +0 -48
  208. package/dist/functions/account-nodes.d.ts.map +0 -1
  209. package/dist/functions/admin-data.d.ts +0 -178
  210. package/dist/functions/admin-data.d.ts.map +0 -1
  211. package/dist/functions/ai-agents.d.ts +0 -125
  212. package/dist/functions/ai-agents.d.ts.map +0 -1
  213. package/dist/functions/api-keys.d.ts +0 -140
  214. package/dist/functions/api-keys.d.ts.map +0 -1
  215. package/dist/functions/apps.d.ts +0 -163
  216. package/dist/functions/apps.d.ts.map +0 -1
  217. package/dist/functions/auth.d.ts +0 -74
  218. package/dist/functions/auth.d.ts.map +0 -1
  219. package/dist/functions/debug-auth.d.ts +0 -33
  220. package/dist/functions/debug-auth.d.ts.map +0 -1
  221. package/dist/functions/embeddings.d.ts +0 -205
  222. package/dist/functions/embeddings.d.ts.map +0 -1
  223. package/dist/functions/handler-registry.d.ts +0 -22
  224. package/dist/functions/handler-registry.d.ts.map +0 -1
  225. package/dist/functions/integration-routes.d.ts +0 -45
  226. package/dist/functions/integration-routes.d.ts.map +0 -1
  227. package/dist/functions/integrations.d.ts +0 -124
  228. package/dist/functions/integrations.d.ts.map +0 -1
  229. package/dist/functions/item-progress.d.ts +0 -41
  230. package/dist/functions/item-progress.d.ts.map +0 -1
  231. package/dist/functions/logs.d.ts +0 -162
  232. package/dist/functions/logs.d.ts.map +0 -1
  233. package/dist/functions/observability.d.ts +0 -123
  234. package/dist/functions/observability.d.ts.map +0 -1
  235. package/dist/functions/pipeline-executions.d.ts +0 -190
  236. package/dist/functions/pipeline-executions.d.ts.map +0 -1
  237. package/dist/functions/pipelines.d.ts +0 -171
  238. package/dist/functions/pipelines.d.ts.map +0 -1
  239. package/dist/functions/prompt-configs.d.ts +0 -125
  240. package/dist/functions/prompt-configs.d.ts.map +0 -1
  241. package/dist/functions/roles.d.ts +0 -118
  242. package/dist/functions/roles.d.ts.map +0 -1
  243. package/dist/functions/signal.d.ts +0 -73
  244. package/dist/functions/signal.d.ts.map +0 -1
  245. package/dist/functions/system-cron.d.ts +0 -65
  246. package/dist/functions/system-cron.d.ts.map +0 -1
  247. package/dist/functions/system.d.ts +0 -29
  248. package/dist/functions/system.d.ts.map +0 -1
  249. package/dist/functions/tests.d.ts +0 -28
  250. package/dist/functions/tests.d.ts.map +0 -1
  251. package/dist/functions/timers.d.ts +0 -139
  252. package/dist/functions/timers.d.ts.map +0 -1
  253. package/dist/functions/triggers.d.ts +0 -203
  254. package/dist/functions/triggers.d.ts.map +0 -1
  255. package/dist/functions/types.d.ts +0 -151
  256. package/dist/functions/types.d.ts.map +0 -1
  257. package/dist/src/types/types.d.ts +0 -364
  258. package/dist/src/types/types.d.ts.map +0 -1
  259. package/index.html +0 -13
  260. package/netlify.toml +0 -36
  261. package/package-project.json +0 -71
  262. package/scripts/ingest-chunks.cjs +0 -202
  263. package/scripts/kb-chunk-parser.cjs +0 -312
  264. package/scripts/kb-chunk-parser.ts +0 -330
  265. package/scripts/load-test-app-install.ts +0 -484
  266. package/scripts/netlify-dev-wrapper.sh +0 -22
  267. package/vitest.config.ts +0 -45
  268. /package/{scripts → .framework/scripts}/app-install-cli.ts +0 -0
  269. /package/{scripts → .framework/scripts}/build-manifest.sh +0 -0
  270. /package/{scripts → .framework/scripts}/verify-integrity.sh +0 -0
@@ -0,0 +1,520 @@
1
+ -- Migration 003: System Data
2
+ -- Seeds for day-zero operation including system role, system account, spine-core app,
3
+ -- runtime types, admin app, and RPC functions.
4
+
5
+ -- ============================================
6
+ -- 1. SYSTEM ROLE (system_admin)
7
+ -- ============================================
8
+
9
+ INSERT INTO public.roles (id, slug, name, description, permissions, is_system, is_active, is_protected, app_id, account_id)
10
+ VALUES (
11
+ gen_random_uuid(),
12
+ 'system_admin',
13
+ 'System Admin',
14
+ 'Full system access - can manage all accounts, types, and system configuration',
15
+ '["*"]'::jsonb,
16
+ true,
17
+ true,
18
+ true,
19
+ NULL,
20
+ NULL
21
+ );
22
+
23
+ -- ============================================
24
+ -- 2. MASTER SYSTEM ACCOUNT
25
+ -- ============================================
26
+
27
+ INSERT INTO public.accounts (id, slug, display_name, description, data, is_active, design_schema, validation_schema, type_id)
28
+ VALUES (
29
+ gen_random_uuid(),
30
+ 'spine-system',
31
+ 'Spine System',
32
+ 'Master system account for internal Spine operations',
33
+ '{}'::jsonb,
34
+ true,
35
+ '{}'::jsonb,
36
+ '{}'::jsonb,
37
+ gen_random_uuid() -- placeholder, will update after types seeded
38
+ );
39
+
40
+ -- ============================================
41
+ -- 3. SYSTEM APP (spine-core)
42
+ -- ============================================
43
+
44
+ INSERT INTO public.apps (id, slug, name, description, version, app_type, source, is_active, is_system, renderer, route_prefix)
45
+ VALUES (
46
+ gen_random_uuid(),
47
+ 'spine-core',
48
+ 'Spine Core',
49
+ 'Core Spine runtime - provides accounts, people, items, threads, messages, links, attachments, watchers',
50
+ '1.0.0',
51
+ 'system',
52
+ 'builtin',
53
+ true,
54
+ true,
55
+ 'none',
56
+ NULL
57
+ );
58
+
59
+ -- ============================================
60
+ -- 4. ADMIN APP
61
+ -- ============================================
62
+
63
+ INSERT INTO public.apps (
64
+ id, slug, name, description, icon, color, version,
65
+ app_type, source, config, nav_items, min_role,
66
+ is_active, is_system, route_prefix, renderer
67
+ )
68
+ VALUES (
69
+ gen_random_uuid(),
70
+ 'admin',
71
+ 'Admin',
72
+ 'Spine system administration interface',
73
+ 'settings',
74
+ 'slate',
75
+ '1.0.0',
76
+ 'system',
77
+ 'builtin',
78
+ '{}'::jsonb,
79
+ '[]'::jsonb,
80
+ 'system_admin',
81
+ true,
82
+ true,
83
+ '/admin',
84
+ 'custom'
85
+ )
86
+ ON CONFLICT (slug) DO UPDATE SET
87
+ route_prefix = EXCLUDED.route_prefix,
88
+ renderer = EXCLUDED.renderer,
89
+ min_role = EXCLUDED.min_role,
90
+ is_system = EXCLUDED.is_system,
91
+ app_type = EXCLUDED.app_type;
92
+
93
+ -- ============================================
94
+ -- 5. RUNTIME ENTITY TYPES (8 core types)
95
+ -- ============================================
96
+
97
+ DO $$
98
+ DECLARE
99
+ v_spine_core_app_id uuid;
100
+ v_system_account_id uuid;
101
+ BEGIN
102
+ SELECT id INTO v_spine_core_app_id FROM public.apps WHERE slug = 'spine-core' LIMIT 1;
103
+ SELECT id INTO v_system_account_id FROM public.accounts WHERE slug = 'spine-system' LIMIT 1;
104
+
105
+ -- 5.1 accounts type
106
+ INSERT INTO public.types (id, app_id, kind, slug, name, description, design_schema, validation_schema, ownership, is_active)
107
+ VALUES (
108
+ gen_random_uuid(),
109
+ v_spine_core_app_id,
110
+ 'account',
111
+ 'account',
112
+ 'Account',
113
+ 'Organization or tenant account',
114
+ jsonb_build_object(
115
+ 'scope', 'account',
116
+ 'record_permissions', jsonb_build_object('system_admin', ARRAY['create', 'read', 'update', 'delete']),
117
+ 'fields', jsonb_build_object(
118
+ 'slug', jsonb_build_object('data_type', 'text', 'label', 'Slug', 'required', true, 'system', true, 'validation', null),
119
+ 'display_name', jsonb_build_object('data_type', 'text', 'label', 'Display Name', 'required', true, 'system', true, 'validation', null),
120
+ 'description', jsonb_build_object('data_type', 'textarea', 'label', 'Description', 'required', false, 'system', true, 'validation', null),
121
+ 'is_active', jsonb_build_object('data_type', 'boolean', 'label', 'Active', 'required', true, 'system', true, 'validation', null),
122
+ 'created_at', jsonb_build_object('data_type', 'datetime', 'label', 'Created', 'required', false, 'system', true, 'readonly', true, 'validation', null),
123
+ 'updated_at', jsonb_build_object('data_type', 'datetime', 'label', 'Updated', 'required', false, 'system', true, 'readonly', true, 'validation', null)
124
+ ),
125
+ 'views', jsonb_build_object(
126
+ 'default_list', jsonb_build_object('type', 'list', 'display', 'table', 'label', 'Accounts',
127
+ 'fields', jsonb_build_object(
128
+ 'display_name', jsonb_build_object('sortable', true, 'display_type', 'text'),
129
+ 'slug', jsonb_build_object('sortable', true, 'display_type', 'text'),
130
+ 'is_active', jsonb_build_object('sortable', true, 'display_type', 'badge')
131
+ )
132
+ ),
133
+ 'default_detail', jsonb_build_object('type', 'detail', 'label', 'Account',
134
+ 'sections', jsonb_build_array(
135
+ jsonb_build_object('title', 'Overview', 'fields', jsonb_build_array('slug', 'display_name', 'description', 'is_active')),
136
+ jsonb_build_object('title', 'Metadata', 'fields', jsonb_build_array('created_at', 'updated_at'))
137
+ )
138
+ )
139
+ )
140
+ ),
141
+ '{}'::jsonb,
142
+ 'pack',
143
+ true
144
+ );
145
+
146
+ -- 5.2 people type
147
+ INSERT INTO public.types (id, app_id, kind, slug, name, description, design_schema, validation_schema, ownership, is_active)
148
+ VALUES (
149
+ gen_random_uuid(),
150
+ v_spine_core_app_id,
151
+ 'person',
152
+ 'person',
153
+ 'Person',
154
+ 'User or contact person',
155
+ jsonb_build_object(
156
+ 'scope', 'account',
157
+ 'record_permissions', jsonb_build_object('system_admin', ARRAY['create', 'read', 'update', 'delete']),
158
+ 'fields', jsonb_build_object(
159
+ 'full_name', jsonb_build_object('data_type', 'text', 'label', 'Full Name', 'required', true, 'system', true, 'validation', null),
160
+ 'email', jsonb_build_object('data_type', 'email', 'label', 'Email', 'required', true, 'system', true, 'validation', null),
161
+ 'phone', jsonb_build_object('data_type', 'phone', 'label', 'Phone', 'required', false, 'system', true, 'validation', null),
162
+ 'avatar_url', jsonb_build_object('data_type', 'url', 'label', 'Avatar URL', 'required', false, 'system', true, 'validation', null),
163
+ 'status', jsonb_build_object('data_type', 'text', 'label', 'Status', 'required', true, 'system', true, 'validation', null),
164
+ 'is_active', jsonb_build_object('data_type', 'boolean', 'label', 'Active', 'required', true, 'system', true, 'validation', null),
165
+ 'created_at', jsonb_build_object('data_type', 'datetime', 'label', 'Created', 'required', false, 'system', true, 'readonly', true, 'validation', null),
166
+ 'updated_at', jsonb_build_object('data_type', 'datetime', 'label', 'Updated', 'required', false, 'system', true, 'readonly', true, 'validation', null)
167
+ ),
168
+ 'views', jsonb_build_object(
169
+ 'default_list', jsonb_build_object('type', 'list', 'display', 'table', 'label', 'People',
170
+ 'fields', jsonb_build_object(
171
+ 'full_name', jsonb_build_object('sortable', true, 'display_type', 'text'),
172
+ 'email', jsonb_build_object('sortable', true, 'display_type', 'text'),
173
+ 'status', jsonb_build_object('sortable', true, 'display_type', 'badge'),
174
+ 'is_active', jsonb_build_object('sortable', true, 'display_type', 'badge')
175
+ )
176
+ ),
177
+ 'default_detail', jsonb_build_object('type', 'detail', 'label', 'Person',
178
+ 'sections', jsonb_build_array(
179
+ jsonb_build_object('title', 'Profile', 'fields', jsonb_build_array('full_name', 'email', 'phone', 'avatar_url')),
180
+ jsonb_build_object('title', 'Status', 'fields', jsonb_build_array('status', 'is_active')),
181
+ jsonb_build_object('title', 'Metadata', 'fields', jsonb_build_array('created_at', 'updated_at'))
182
+ )
183
+ )
184
+ )
185
+ ),
186
+ '{}'::jsonb,
187
+ 'pack',
188
+ true
189
+ );
190
+
191
+ -- 5.3 items type
192
+ INSERT INTO public.types (id, app_id, kind, slug, name, description, design_schema, validation_schema, ownership, is_active)
193
+ VALUES (
194
+ gen_random_uuid(),
195
+ v_spine_core_app_id,
196
+ 'item',
197
+ 'item',
198
+ 'Item',
199
+ 'Generic item - tickets, tasks, records',
200
+ jsonb_build_object(
201
+ 'scope', 'account',
202
+ 'record_permissions', jsonb_build_object('system_admin', ARRAY['create', 'read', 'update', 'delete']),
203
+ 'fields', jsonb_build_object(
204
+ 'title', jsonb_build_object('data_type', 'text', 'label', 'Title', 'required', true, 'system', true, 'validation', null),
205
+ 'description', jsonb_build_object('data_type', 'textarea', 'label', 'Description', 'required', false, 'system', true, 'validation', null),
206
+ 'status', jsonb_build_object('data_type', 'text', 'label', 'Status', 'required', true, 'system', true, 'validation', null),
207
+ 'is_active', jsonb_build_object('data_type', 'boolean', 'label', 'Active', 'required', true, 'system', true, 'validation', null),
208
+ 'created_at', jsonb_build_object('data_type', 'datetime', 'label', 'Created', 'required', false, 'system', true, 'readonly', true, 'validation', null),
209
+ 'updated_at', jsonb_build_object('data_type', 'datetime', 'label', 'Updated', 'required', false, 'system', true, 'readonly', true, 'validation', null)
210
+ ),
211
+ 'views', jsonb_build_object(
212
+ 'default_list', jsonb_build_object('type', 'list', 'display', 'table', 'label', 'Items',
213
+ 'fields', jsonb_build_object(
214
+ 'title', jsonb_build_object('sortable', true, 'display_type', 'text'),
215
+ 'status', jsonb_build_object('sortable', true, 'display_type', 'badge'),
216
+ 'is_active', jsonb_build_object('sortable', true, 'display_type', 'badge'),
217
+ 'created_at', jsonb_build_object('sortable', true, 'display_type', 'datetime')
218
+ )
219
+ ),
220
+ 'default_detail', jsonb_build_object('type', 'detail', 'label', 'Item',
221
+ 'sections', jsonb_build_array(
222
+ jsonb_build_object('title', 'Overview', 'fields', jsonb_build_array('title', 'description', 'status', 'is_active')),
223
+ jsonb_build_object('title', 'Metadata', 'fields', jsonb_build_array('created_at', 'updated_at'))
224
+ )
225
+ )
226
+ )
227
+ ),
228
+ '{}'::jsonb,
229
+ 'pack',
230
+ true
231
+ );
232
+
233
+ -- 5.4 threads type
234
+ INSERT INTO public.types (id, app_id, kind, slug, name, description, design_schema, validation_schema, ownership, is_active)
235
+ VALUES (
236
+ gen_random_uuid(),
237
+ v_spine_core_app_id,
238
+ 'thread',
239
+ 'thread',
240
+ 'Thread',
241
+ 'Discussion thread for items or accounts',
242
+ jsonb_build_object(
243
+ 'scope', 'account',
244
+ 'record_permissions', jsonb_build_object('system_admin', ARRAY['create', 'read', 'update', 'delete']),
245
+ 'fields', jsonb_build_object(
246
+ 'title', jsonb_build_object('data_type', 'text', 'label', 'Title', 'required', false, 'system', true, 'validation', null),
247
+ 'target_type', jsonb_build_object('data_type', 'text', 'label', 'Target Type', 'required', true, 'system', true, 'validation', null),
248
+ 'target_id', jsonb_build_object('data_type', 'uuid', 'label', 'Target ID', 'required', true, 'system', true, 'validation', null),
249
+ 'visibility', jsonb_build_object('data_type', 'text', 'label', 'Visibility', 'required', true, 'system', true, 'validation', null),
250
+ 'status', jsonb_build_object('data_type', 'text', 'label', 'Status', 'required', true, 'system', true, 'validation', null),
251
+ 'is_active', jsonb_build_object('data_type', 'boolean', 'label', 'Active', 'required', true, 'system', true, 'validation', null),
252
+ 'created_at', jsonb_build_object('data_type', 'datetime', 'label', 'Created', 'required', false, 'system', true, 'readonly', true, 'validation', null),
253
+ 'updated_at', jsonb_build_object('data_type', 'datetime', 'label', 'Updated', 'required', false, 'system', true, 'readonly', true, 'validation', null)
254
+ ),
255
+ 'views', jsonb_build_object(
256
+ 'default_list', jsonb_build_object('type', 'list', 'display', 'table', 'label', 'Threads',
257
+ 'fields', jsonb_build_object(
258
+ 'title', jsonb_build_object('sortable', true, 'display_type', 'text'),
259
+ 'target_type', jsonb_build_object('sortable', true, 'display_type', 'text'),
260
+ 'visibility', jsonb_build_object('sortable', true, 'display_type', 'badge'),
261
+ 'status', jsonb_build_object('sortable', true, 'display_type', 'badge')
262
+ )
263
+ ),
264
+ 'default_detail', jsonb_build_object('type', 'detail', 'label', 'Thread',
265
+ 'sections', jsonb_build_array(
266
+ jsonb_build_object('title', 'Overview', 'fields', jsonb_build_array('title', 'target_type', 'target_id', 'visibility', 'status')),
267
+ jsonb_build_object('title', 'Metadata', 'fields', jsonb_build_array('created_at', 'updated_at'))
268
+ )
269
+ )
270
+ )
271
+ ),
272
+ '{}'::jsonb,
273
+ 'pack',
274
+ true
275
+ );
276
+
277
+ -- 5.5 messages type
278
+ INSERT INTO public.types (id, app_id, kind, slug, name, description, design_schema, validation_schema, ownership, is_active)
279
+ VALUES (
280
+ gen_random_uuid(),
281
+ v_spine_core_app_id,
282
+ 'message',
283
+ 'message',
284
+ 'Message',
285
+ 'Individual message within a thread',
286
+ jsonb_build_object(
287
+ 'scope', 'account',
288
+ 'record_permissions', jsonb_build_object('system_admin', ARRAY['create', 'read', 'update', 'delete']),
289
+ 'fields', jsonb_build_object(
290
+ 'content', jsonb_build_object('data_type', 'textarea', 'label', 'Content', 'required', true, 'system', true, 'validation', null),
291
+ 'direction', jsonb_build_object('data_type', 'text', 'label', 'Direction', 'required', true, 'system', true, 'validation', null),
292
+ 'sequence', jsonb_build_object('data_type', 'number', 'label', 'Sequence', 'required', true, 'system', true, 'validation', null),
293
+ 'visibility', jsonb_build_object('data_type', 'text', 'label', 'Visibility', 'required', true, 'system', true, 'validation', null),
294
+ 'is_active', jsonb_build_object('data_type', 'boolean', 'label', 'Active', 'required', true, 'system', true, 'validation', null),
295
+ 'created_at', jsonb_build_object('data_type', 'datetime', 'label', 'Created', 'required', false, 'system', true, 'readonly', true, 'validation', null),
296
+ 'updated_at', jsonb_build_object('data_type', 'datetime', 'label', 'Updated', 'required', false, 'system', true, 'readonly', true, 'validation', null)
297
+ ),
298
+ 'views', jsonb_build_object(
299
+ 'default_list', jsonb_build_object('type', 'list', 'display', 'table', 'label', 'Messages',
300
+ 'fields', jsonb_build_object(
301
+ 'content', jsonb_build_object('sortable', false, 'display_type', 'text'),
302
+ 'direction', jsonb_build_object('sortable', true, 'display_type', 'badge'),
303
+ 'visibility', jsonb_build_object('sortable', true, 'display_type', 'badge'),
304
+ 'created_at', jsonb_build_object('sortable', true, 'display_type', 'datetime')
305
+ )
306
+ ),
307
+ 'default_detail', jsonb_build_object('type', 'detail', 'label', 'Message',
308
+ 'sections', jsonb_build_array(
309
+ jsonb_build_object('title', 'Content', 'fields', jsonb_build_array('content', 'direction', 'visibility')),
310
+ jsonb_build_object('title', 'Metadata', 'fields', jsonb_build_array('sequence', 'created_at', 'updated_at'))
311
+ )
312
+ )
313
+ )
314
+ ),
315
+ '{}'::jsonb,
316
+ 'pack',
317
+ true
318
+ );
319
+
320
+ -- 5.6 links type
321
+ INSERT INTO public.types (id, app_id, kind, slug, name, description, design_schema, validation_schema, ownership, is_active)
322
+ VALUES (
323
+ gen_random_uuid(),
324
+ v_spine_core_app_id,
325
+ 'link',
326
+ 'link',
327
+ 'Link',
328
+ 'Relationship between two entities',
329
+ jsonb_build_object(
330
+ 'scope', 'account',
331
+ 'record_permissions', jsonb_build_object('system_admin', ARRAY['create', 'read', 'update', 'delete']),
332
+ 'fields', jsonb_build_object(
333
+ 'source_type', jsonb_build_object('data_type', 'text', 'label', 'Source Type', 'required', true, 'system', true, 'validation', null),
334
+ 'source_id', jsonb_build_object('data_type', 'uuid', 'label', 'Source ID', 'required', true, 'system', true, 'validation', null),
335
+ 'target_type', jsonb_build_object('data_type', 'text', 'label', 'Target Type', 'required', true, 'system', true, 'validation', null),
336
+ 'target_id', jsonb_build_object('data_type', 'uuid', 'label', 'Target ID', 'required', true, 'system', true, 'validation', null),
337
+ 'link_type', jsonb_build_object('data_type', 'text', 'label', 'Link Type', 'required', false, 'system', true, 'validation', null),
338
+ 'created_at', jsonb_build_object('data_type', 'datetime', 'label', 'Created', 'required', false, 'system', true, 'readonly', true, 'validation', null),
339
+ 'updated_at', jsonb_build_object('data_type', 'datetime', 'label', 'Updated', 'required', false, 'system', true, 'readonly', true, 'validation', null)
340
+ ),
341
+ 'views', jsonb_build_object(
342
+ 'default_list', jsonb_build_object('type', 'list', 'display', 'table', 'label', 'Links',
343
+ 'fields', jsonb_build_object(
344
+ 'source_type', jsonb_build_object('sortable', true, 'display_type', 'text'),
345
+ 'target_type', jsonb_build_object('sortable', true, 'display_type', 'text'),
346
+ 'link_type', jsonb_build_object('sortable', true, 'display_type', 'text'),
347
+ 'created_at', jsonb_build_object('sortable', true, 'display_type', 'datetime')
348
+ )
349
+ ),
350
+ 'default_detail', jsonb_build_object('type', 'detail', 'label', 'Link',
351
+ 'sections', jsonb_build_array(
352
+ jsonb_build_object('title', 'Connection', 'fields', jsonb_build_array('source_type', 'source_id', 'target_type', 'target_id', 'link_type')),
353
+ jsonb_build_object('title', 'Metadata', 'fields', jsonb_build_array('created_at', 'updated_at'))
354
+ )
355
+ )
356
+ )
357
+ ),
358
+ '{}'::jsonb,
359
+ 'pack',
360
+ true
361
+ );
362
+
363
+ -- 5.7 attachments type
364
+ INSERT INTO public.types (id, app_id, kind, slug, name, description, design_schema, validation_schema, ownership, is_active)
365
+ VALUES (
366
+ gen_random_uuid(),
367
+ v_spine_core_app_id,
368
+ 'attachment',
369
+ 'attachment',
370
+ 'Attachment',
371
+ 'File attachment with storage reference',
372
+ jsonb_build_object(
373
+ 'scope', 'account',
374
+ 'record_permissions', jsonb_build_object('system_admin', ARRAY['create', 'read', 'update', 'delete']),
375
+ 'fields', jsonb_build_object(
376
+ 'filename', jsonb_build_object('data_type', 'text', 'label', 'Filename', 'required', true, 'system', true, 'validation', null),
377
+ 'file_size', jsonb_build_object('data_type', 'number', 'label', 'File Size', 'required', false, 'system', true, 'validation', null),
378
+ 'mime_type', jsonb_build_object('data_type', 'text', 'label', 'MIME Type', 'required', false, 'system', true, 'validation', null),
379
+ 'storage_path', jsonb_build_object('data_type', 'text', 'label', 'Storage Path', 'required', false, 'system', true, 'validation', null),
380
+ 'storage_provider', jsonb_build_object('data_type', 'text', 'label', 'Storage Provider', 'required', true, 'system', true, 'validation', null),
381
+ 'created_at', jsonb_build_object('data_type', 'datetime', 'label', 'Created', 'required', false, 'system', true, 'readonly', true, 'validation', null),
382
+ 'updated_at', jsonb_build_object('data_type', 'datetime', 'label', 'Updated', 'required', false, 'system', true, 'readonly', true, 'validation', null)
383
+ ),
384
+ 'views', jsonb_build_object(
385
+ 'default_list', jsonb_build_object('type', 'list', 'display', 'table', 'label', 'Attachments',
386
+ 'fields', jsonb_build_object(
387
+ 'filename', jsonb_build_object('sortable', true, 'display_type', 'text'),
388
+ 'file_size', jsonb_build_object('sortable', true, 'display_type', 'number'),
389
+ 'mime_type', jsonb_build_object('sortable', true, 'display_type', 'text'),
390
+ 'created_at', jsonb_build_object('sortable', true, 'display_type', 'datetime')
391
+ )
392
+ ),
393
+ 'default_detail', jsonb_build_object('type', 'detail', 'label', 'Attachment',
394
+ 'sections', jsonb_build_array(
395
+ jsonb_build_object('title', 'File', 'fields', jsonb_build_array('filename', 'file_size', 'mime_type', 'storage_provider')),
396
+ jsonb_build_object('title', 'Storage', 'fields', jsonb_build_array('storage_path')),
397
+ jsonb_build_object('title', 'Metadata', 'fields', jsonb_build_array('created_at', 'updated_at'))
398
+ )
399
+ )
400
+ )
401
+ ),
402
+ '{}'::jsonb,
403
+ 'pack',
404
+ true
405
+ );
406
+
407
+ -- 5.8 watchers type
408
+ INSERT INTO public.types (id, app_id, kind, slug, name, description, design_schema, validation_schema, ownership, is_active)
409
+ VALUES (
410
+ gen_random_uuid(),
411
+ v_spine_core_app_id,
412
+ 'watcher',
413
+ 'watcher',
414
+ 'Watcher',
415
+ 'Person watching an entity for notifications',
416
+ jsonb_build_object(
417
+ 'scope', 'account',
418
+ 'record_permissions', jsonb_build_object('system_admin', ARRAY['create', 'read', 'update', 'delete']),
419
+ 'fields', jsonb_build_object(
420
+ 'target_type', jsonb_build_object('data_type', 'text', 'label', 'Target Type', 'required', true, 'system', true, 'validation', null),
421
+ 'target_id', jsonb_build_object('data_type', 'uuid', 'label', 'Target ID', 'required', true, 'system', true, 'validation', null),
422
+ 'person_id', jsonb_build_object('data_type', 'uuid', 'label', 'Person', 'required', true, 'system', true, 'validation', null),
423
+ 'watch_type', jsonb_build_object('data_type', 'text', 'label', 'Watch Type', 'required', true, 'system', true, 'validation', null),
424
+ 'notification_level', jsonb_build_object('data_type', 'text', 'label', 'Notification Level', 'required', true, 'system', true, 'validation', null),
425
+ 'created_at', jsonb_build_object('data_type', 'datetime', 'label', 'Created', 'required', false, 'system', true, 'readonly', true, 'validation', null),
426
+ 'updated_at', jsonb_build_object('data_type', 'datetime', 'label', 'Updated', 'required', false, 'system', true, 'readonly', true, 'validation', null)
427
+ ),
428
+ 'views', jsonb_build_object(
429
+ 'default_list', jsonb_build_object('type', 'list', 'display', 'table', 'label', 'Watchers',
430
+ 'fields', jsonb_build_object(
431
+ 'target_type', jsonb_build_object('sortable', true, 'display_type', 'text'),
432
+ 'watch_type', jsonb_build_object('sortable', true, 'display_type', 'badge'),
433
+ 'notification_level', jsonb_build_object('sortable', true, 'display_type', 'badge'),
434
+ 'created_at', jsonb_build_object('sortable', true, 'display_type', 'datetime')
435
+ )
436
+ ),
437
+ 'default_detail', jsonb_build_object('type', 'detail', 'label', 'Watcher',
438
+ 'sections', jsonb_build_array(
439
+ jsonb_build_object('title', 'Target', 'fields', jsonb_build_array('target_type', 'target_id')),
440
+ jsonb_build_object('title', 'Settings', 'fields', jsonb_build_array('person_id', 'watch_type', 'notification_level')),
441
+ jsonb_build_object('title', 'Metadata', 'fields', jsonb_build_array('created_at', 'updated_at'))
442
+ )
443
+ )
444
+ )
445
+ ),
446
+ '{}'::jsonb,
447
+ 'pack',
448
+ true
449
+ );
450
+
451
+ -- Update the spine-system account with the actual accounts type_id
452
+ UPDATE public.accounts
453
+ SET type_id = (SELECT id FROM public.types WHERE slug = 'account' AND app_id = v_spine_core_app_id LIMIT 1)
454
+ WHERE slug = 'spine-system';
455
+
456
+ END $$;
457
+
458
+ -- ============================================
459
+ -- 6. RPC FUNCTION: get_account_apps
460
+ -- ============================================
461
+
462
+ CREATE OR REPLACE FUNCTION public.get_account_apps(
463
+ account_id uuid,
464
+ include_system boolean DEFAULT true,
465
+ include_inactive boolean DEFAULT false
466
+ )
467
+ RETURNS TABLE(
468
+ id uuid,
469
+ slug text,
470
+ name text,
471
+ description text,
472
+ icon text,
473
+ color text,
474
+ version text,
475
+ app_type text,
476
+ source text,
477
+ owner_account_id uuid,
478
+ is_active boolean,
479
+ is_system boolean,
480
+ min_role text,
481
+ config jsonb,
482
+ nav_items jsonb,
483
+ route_prefix text,
484
+ renderer text,
485
+ created_at timestamptz
486
+ )
487
+ LANGUAGE plpgsql
488
+ AS $function$
489
+ BEGIN
490
+ RETURN QUERY
491
+ SELECT
492
+ a.id,
493
+ a.slug,
494
+ a.name,
495
+ a.description,
496
+ a.icon,
497
+ a.color,
498
+ a.version,
499
+ a.app_type,
500
+ a.source,
501
+ a.owner_account_id,
502
+ a.is_active,
503
+ a.is_system,
504
+ a.min_role,
505
+ a.config,
506
+ a.nav_items,
507
+ a.route_prefix,
508
+ a.renderer,
509
+ a.created_at
510
+ FROM public.apps a
511
+ WHERE
512
+ (include_system OR a.is_system = false)
513
+ AND (include_inactive OR a.is_active = true)
514
+ AND (a.is_system OR a.owner_account_id = get_account_apps.account_id)
515
+ ORDER BY
516
+ a.is_system DESC,
517
+ a.app_type,
518
+ a.name;
519
+ END;
520
+ $function$;
@@ -0,0 +1,43 @@
1
+ -- Migration 004: Enhancements
2
+ -- Additional enhancements and refinements to the Spine schema
3
+
4
+ -- ============================================
5
+ -- TYPE SCOPE STAMPING (from 012_type_scope.sql)
6
+ -- ============================================
7
+ -- Platform-scoped types: owned by spine-system, any authenticated user can read
8
+ UPDATE public.types
9
+ SET design_schema = design_schema || '{"scope":"platform"}'::jsonb
10
+ WHERE slug IN ('kb_article', 'course_lesson', 'community_post', 'community_question');
11
+
12
+ -- Customer-scoped types: owned by the customer's account
13
+ UPDATE public.types
14
+ SET design_schema = design_schema || '{"scope":"customer"}'::jsonb
15
+ WHERE slug IN ('support_ticket');
16
+
17
+ -- Account-scoped types: owned by the calling account (default, explicit for clarity)
18
+ UPDATE public.types
19
+ SET design_schema = design_schema || '{"scope":"account"}'::jsonb
20
+ WHERE design_schema->>'scope' IS NULL;
21
+
22
+ -- ============================================
23
+ -- TABLE COMMENTS
24
+ -- ============================================
25
+
26
+ COMMENT ON TABLE public.account_paths IS
27
+ 'Stores ancestor-descendant relationships for account hierarchy traversal';
28
+
29
+ COMMENT ON TABLE public.webhook_handlers IS
30
+ 'Dynamic registry for webhook handlers. Custom functions self-register here to be discoverable by the integration-routes handler without static imports.';
31
+
32
+ -- ============================================
33
+ -- ENHANCEMENT NOTES
34
+ -- ============================================
35
+ -- This file contains miscellaneous enhancements that refine the core schema:
36
+ --
37
+ -- 1. Type scope stamping: Ensures all types have an explicit scope field
38
+ -- in their design_schema for proper RLS and visibility control.
39
+ --
40
+ -- 2. Table comments: Documentation for key infrastructure tables.
41
+ --
42
+ -- Future enhancements should be added here as ALTER statements or
43
+ -- configuration updates that don't require new table creation.
@@ -0,0 +1,3 @@
1
+ /api/* /.netlify/functions/:splat 200
2
+ /v2/* /.netlify/v2/functions/:splat 200
3
+ /* /index.html 200
Binary file
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="none">
2
+ <rect width="32" height="32" rx="5" fill="#1e2a4a"/>
3
+ </svg>
@@ -17,7 +17,7 @@
17
17
  set -euo pipefail
18
18
 
19
19
  SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
20
- PROJECT_ROOT="${PROJECT_ROOT:-$(dirname "$SCRIPT_DIR")}"
20
+ PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
21
21
 
22
22
  CORE_SRC_DIR="$PROJECT_ROOT/.framework/src"
23
23
  CUSTOM_SRC_DIR="$PROJECT_ROOT/custom/apps"
@@ -61,11 +61,14 @@ if [ -f "$PROJECT_ROOT/.framework/public/_redirects" ]; then
61
61
  echo " ✓ Core: _redirects copied to Vite-served directory for Netlify dev"
62
62
  fi
63
63
 
64
- # 6. Copy custom/apps/ → .assembled/custom/apps/ (preserves path for CustomAppLoader glob)
65
- ASSEMBLED_CUSTOM_DIR="$PROJECT_ROOT/.assembled/custom/apps"
64
+ # 6. Overlay custom src (overrides + additions)
66
65
  if [ -d "$CUSTOM_SRC_DIR" ]; then
67
- mkdir -p "$ASSEMBLED_CUSTOM_DIR"
68
- cp -r "$CUSTOM_SRC_DIR"/* "$ASSEMBLED_CUSTOM_DIR"/ 2>/dev/null || true
66
+ # Copy files and subdirectories
67
+ cp -r "$CUSTOM_SRC_DIR"/* "$TARGET_SRC_DIR"/ 2>/dev/null || true
68
+
69
+ # Handle subdirectories properly
70
+ find "$CUSTOM_SRC_DIR" -mindepth 1 -type d -exec sh -c 'dir="$1"; base=$(basename "$dir"); mkdir -p "'"$TARGET_SRC_DIR"'/$base" && cp -r "$dir"/* "'"$TARGET_SRC_DIR"'/$base/"' _ {} \; 2>/dev/null || true
71
+
69
72
  CUSTOM_COUNT=$(find "$CUSTOM_SRC_DIR" -name '*.tsx' -o -name '*.ts' -o -name '*.css' 2>/dev/null | wc -l | tr -d ' ')
70
73
  echo " ✓ Custom: $CUSTOM_COUNT files"
71
74
  else