spine-framework 0.3.16 → 0.3.18

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 (269) hide show
  1. package/.framework/cli/commands/create-app.ts +1 -1
  2. package/.framework/cli/commands/dev.ts +215 -33
  3. package/.framework/cli/index.ts +37 -75
  4. package/.framework/functions/_shared/app-manifest.ts +8 -15
  5. package/.framework/functions/_shared/db.ts +11 -15
  6. package/.framework/functions/_shared/index.ts +7 -16
  7. package/.framework/functions/_shared/principal.ts +4 -62
  8. package/.framework/functions/_shared/resolve-ids.ts +6 -6
  9. package/.framework/functions/_shared/webhook-registry.ts +43 -1
  10. package/.framework/functions/admin-funnel.ts +104 -0
  11. package/.framework/functions/admin-install-funnel.ts +225 -0
  12. package/.framework/functions/admin-signals.ts +241 -0
  13. package/.framework/functions/integration-routes.ts +3 -7
  14. package/.framework/migrations/001_schema.sql +406 -0
  15. package/.framework/migrations/002_rls_and_functions.sql +710 -0
  16. package/.framework/migrations/003_system_data.sql +520 -0
  17. package/.framework/migrations/004_enhancements.sql +43 -0
  18. package/.framework/public/_redirects +3 -0
  19. package/.framework/public/spine-logo.jpg +0 -0
  20. package/.framework/public/spine-logo.svg +3 -0
  21. package/{scripts → .framework/scripts}/assemble-frontend.sh +8 -5
  22. package/{scripts → .framework/scripts}/assemble-functions.sh +4 -47
  23. package/{scripts → .framework/scripts}/assemble.sh +2 -8
  24. package/{scripts → .framework/scripts}/boundary-check.sh +5 -9
  25. package/{scripts → .framework/scripts}/check-core-integrity.sh +0 -0
  26. package/.framework/seeds/link_types/account_cli_instances.json +9 -0
  27. package/.framework/seeds/types/funnel_signal_use.json +98 -0
  28. package/.framework/seeds/types/spine_instance.json +109 -0
  29. package/.framework/src/App.tsx +11 -21
  30. package/.framework/src/components/CustomAppLoader.tsx +2 -19
  31. package/.framework/src/components/ui/accordion.tsx +1 -1
  32. package/.framework/src/components/ui/alert-dialog.tsx +1 -1
  33. package/.framework/src/components/ui/aspect-ratio.tsx +1 -1
  34. package/.framework/src/components/ui/avatar.tsx +1 -1
  35. package/.framework/src/components/ui/badge.tsx +2 -2
  36. package/.framework/src/components/ui/breadcrumb.tsx +2 -2
  37. package/.framework/src/components/ui/button-group.tsx +2 -2
  38. package/.framework/src/components/ui/button.tsx +2 -2
  39. package/.framework/src/components/ui/chart.tsx +6 -1
  40. package/.framework/src/components/ui/checkbox.tsx +1 -1
  41. package/.framework/src/components/ui/collapsible.tsx +1 -1
  42. package/.framework/src/components/ui/context-menu.tsx +1 -1
  43. package/.framework/src/components/ui/dialog.tsx +1 -1
  44. package/.framework/src/components/ui/direction.tsx +17 -1
  45. package/.framework/src/components/ui/dropdown-menu.tsx +1 -1
  46. package/.framework/src/components/ui/hover-card.tsx +1 -1
  47. package/.framework/src/components/ui/item.tsx +2 -2
  48. package/.framework/src/components/ui/label.tsx +1 -1
  49. package/.framework/src/components/ui/menubar.tsx +1 -1
  50. package/.framework/src/components/ui/navigation-menu.tsx +1 -1
  51. package/.framework/src/components/ui/popover.tsx +1 -1
  52. package/.framework/src/components/ui/progress.tsx +1 -1
  53. package/.framework/src/components/ui/radio-group.tsx +1 -1
  54. package/.framework/src/components/ui/scroll-area.tsx +1 -1
  55. package/.framework/src/components/ui/select.tsx +1 -1
  56. package/.framework/src/components/ui/separator.tsx +1 -1
  57. package/.framework/src/components/ui/sheet.tsx +1 -1
  58. package/.framework/src/components/ui/sidebar.tsx +6 -6
  59. package/.framework/src/components/ui/slider.tsx +1 -1
  60. package/.framework/src/components/ui/switch.tsx +1 -1
  61. package/.framework/src/components/ui/tabs.tsx +1 -1
  62. package/.framework/src/components/ui/toggle-group.tsx +1 -1
  63. package/.framework/src/components/ui/toggle.tsx +1 -1
  64. package/.framework/src/components/ui/tooltip.tsx +1 -1
  65. package/.framework/src/pages/auth/LoginPage.tsx +10 -9
  66. package/LICENSE.md +63 -93
  67. package/README.md +254 -129
  68. package/custom/.devin/skills/install-app/SKILL.md +39 -0
  69. package/custom/.devin/workflows/app-install.yaml +83 -0
  70. package/custom/README.md +79 -0
  71. package/docs/APPS.md +232 -0
  72. package/docs/ARCHITECTURE.md +246 -0
  73. package/docs/INSTALL.md +155 -0
  74. package/docs/README.md +170 -0
  75. package/docs/design-schema-spec.md +1837 -0
  76. package/docs/dev-guides/01-create-an-app.md +183 -0
  77. package/docs/dev-guides/02-create-a-function.md +178 -0
  78. package/docs/dev-guides/03-create-a-component.md +178 -0
  79. package/docs/dev-guides/04-webhook-handlers.md +129 -0
  80. package/docs/dev-guides/05-testing.md +168 -0
  81. package/docs/dev-guides/README.md +40 -0
  82. package/docs/npm-packages-plan.md +288 -0
  83. package/docs/spine-core--api-reference.md +822 -0
  84. package/package.json +78 -150
  85. package/.framework/cli/bin.cjs +0 -14
  86. package/.framework/cli/commands/claim.ts +0 -430
  87. package/.framework/cli/commands/init.ts +0 -219
  88. package/.framework/cli/commands/install-app.ts +0 -685
  89. package/.framework/cli/commands/migrate.ts +0 -178
  90. package/.framework/cli/commands/publish.ts +0 -191
  91. package/.framework/cli/commands/status.ts +0 -268
  92. package/.framework/cli/commands/uninstall-app.ts +0 -166
  93. package/.framework/cli/commands/update-app.ts +0 -397
  94. package/.framework/cli/welcome.cjs +0 -45
  95. package/.framework/config/signal-client.ts +0 -147
  96. package/.framework/config/signals.ts +0 -70
  97. package/.framework/docs/STABILITY.md +0 -52
  98. package/.framework/docs/examples/custom-app/README.md +0 -77
  99. package/.framework/docs/examples/custom-function/README.md +0 -27
  100. package/.framework/docs/examples/custom-function/handler.ts +0 -48
  101. package/.framework/docs/examples/custom-webhook/README.md +0 -68
  102. package/.framework/functions/handler-registry.ts +0 -22
  103. package/.framework/functions/signal.ts +0 -168
  104. package/.framework/migrations/000_foundation.sql +0 -1302
  105. package/.framework/migrations/001_seed.sql +0 -163
  106. package/.framework/migrations/002_seed_constraints.sql +0 -19
  107. package/.framework/migrations/003_auth_user_trigger.sql +0 -67
  108. package/.framework/migrations/004_app_version_tracking.sql +0 -8
  109. package/.framework/migrations/005_pipelines_slug_stages.sql +0 -21
  110. package/.framework/src/pages/auth/RegisterPage.tsx +0 -163
  111. package/CHANGELOG.md +0 -68
  112. package/STRUCTURE.md +0 -150
  113. package/bin/spine-framework.cjs +0 -62
  114. package/bin/welcome.cjs +0 -58
  115. package/bin/ws-shim.ts +0 -10
  116. package/config/components.json +0 -25
  117. package/config/deno.lock +0 -108
  118. package/config/package-lock.json +0 -17183
  119. package/config/postcss.config.cjs +0 -10
  120. package/config/tailwind.config.cjs +0 -78
  121. package/config/tsconfig.build.json +0 -32
  122. package/config/tsconfig.cli.json +0 -18
  123. package/config/tsconfig.json +0 -41
  124. package/config/tsconfig.node.json +0 -17
  125. package/config/tsconfig.node.tsbuildinfo +0 -1
  126. package/config/tsconfig.tsbuildinfo +0 -1
  127. package/config/typedoc.json +0 -16
  128. package/config/vite.config.d.ts +0 -2
  129. package/config/vite.config.ts +0 -71
  130. package/dist/cli/commands/agents.d.ts +0 -39
  131. package/dist/cli/commands/agents.d.ts.map +0 -1
  132. package/dist/cli/commands/auth.d.ts +0 -36
  133. package/dist/cli/commands/auth.d.ts.map +0 -1
  134. package/dist/cli/commands/claim.d.ts +0 -38
  135. package/dist/cli/commands/claim.d.ts.map +0 -1
  136. package/dist/cli/commands/create-app.d.ts +0 -23
  137. package/dist/cli/commands/create-app.d.ts.map +0 -1
  138. package/dist/cli/commands/dev.d.ts +0 -24
  139. package/dist/cli/commands/dev.d.ts.map +0 -1
  140. package/dist/cli/commands/doctor.d.ts +0 -42
  141. package/dist/cli/commands/doctor.d.ts.map +0 -1
  142. package/dist/cli/commands/generate.d.ts +0 -36
  143. package/dist/cli/commands/generate.d.ts.map +0 -1
  144. package/dist/cli/commands/init.d.ts +0 -20
  145. package/dist/cli/commands/init.d.ts.map +0 -1
  146. package/dist/cli/commands/install-app.d.ts +0 -30
  147. package/dist/cli/commands/install-app.d.ts.map +0 -1
  148. package/dist/cli/commands/items.d.ts +0 -45
  149. package/dist/cli/commands/items.d.ts.map +0 -1
  150. package/dist/cli/commands/migrate.d.ts +0 -21
  151. package/dist/cli/commands/migrate.d.ts.map +0 -1
  152. package/dist/cli/commands/migrations.d.ts +0 -41
  153. package/dist/cli/commands/migrations.d.ts.map +0 -1
  154. package/dist/cli/commands/pipelines.d.ts +0 -40
  155. package/dist/cli/commands/pipelines.d.ts.map +0 -1
  156. package/dist/cli/commands/publish.d.ts +0 -23
  157. package/dist/cli/commands/publish.d.ts.map +0 -1
  158. package/dist/cli/commands/status.d.ts +0 -23
  159. package/dist/cli/commands/status.d.ts.map +0 -1
  160. package/dist/cli/commands/system.d.ts +0 -29
  161. package/dist/cli/commands/system.d.ts.map +0 -1
  162. package/dist/cli/commands/test.d.ts +0 -46
  163. package/dist/cli/commands/test.d.ts.map +0 -1
  164. package/dist/cli/commands/uninstall-app.d.ts +0 -23
  165. package/dist/cli/commands/uninstall-app.d.ts.map +0 -1
  166. package/dist/cli/commands/update-app.d.ts +0 -24
  167. package/dist/cli/commands/update-app.d.ts.map +0 -1
  168. package/dist/cli/context.d.ts +0 -88
  169. package/dist/cli/context.d.ts.map +0 -1
  170. package/dist/cli/env-loader.d.ts +0 -14
  171. package/dist/cli/env-loader.d.ts.map +0 -1
  172. package/dist/cli/index.d.ts +0 -44
  173. package/dist/cli/index.d.ts.map +0 -1
  174. package/dist/config/signals.d.ts +0 -82
  175. package/dist/config/signals.d.ts.map +0 -1
  176. package/dist/functions/_shared/agent-runner.d.ts +0 -156
  177. package/dist/functions/_shared/agent-runner.d.ts.map +0 -1
  178. package/dist/functions/_shared/app-manifest.d.ts +0 -68
  179. package/dist/functions/_shared/app-manifest.d.ts.map +0 -1
  180. package/dist/functions/_shared/audit.d.ts +0 -91
  181. package/dist/functions/_shared/audit.d.ts.map +0 -1
  182. package/dist/functions/_shared/db.d.ts +0 -125
  183. package/dist/functions/_shared/db.d.ts.map +0 -1
  184. package/dist/functions/_shared/index.d.ts +0 -299
  185. package/dist/functions/_shared/index.d.ts.map +0 -1
  186. package/dist/functions/_shared/middleware.d.ts +0 -315
  187. package/dist/functions/_shared/middleware.d.ts.map +0 -1
  188. package/dist/functions/_shared/permissions.d.ts +0 -626
  189. package/dist/functions/_shared/permissions.d.ts.map +0 -1
  190. package/dist/functions/_shared/pipeline-runner.d.ts +0 -124
  191. package/dist/functions/_shared/pipeline-runner.d.ts.map +0 -1
  192. package/dist/functions/_shared/principal.d.ts +0 -284
  193. package/dist/functions/_shared/principal.d.ts.map +0 -1
  194. package/dist/functions/_shared/resolve-ids.d.ts +0 -10
  195. package/dist/functions/_shared/resolve-ids.d.ts.map +0 -1
  196. package/dist/functions/_shared/schema-utils.d.ts +0 -181
  197. package/dist/functions/_shared/schema-utils.d.ts.map +0 -1
  198. package/dist/functions/_shared/testing.d.ts +0 -172
  199. package/dist/functions/_shared/testing.d.ts.map +0 -1
  200. package/dist/functions/_shared/trigger-engine.d.ts +0 -140
  201. package/dist/functions/_shared/trigger-engine.d.ts.map +0 -1
  202. package/dist/functions/_shared/webhook-registration.d.ts +0 -81
  203. package/dist/functions/_shared/webhook-registration.d.ts.map +0 -1
  204. package/dist/functions/_shared/webhook-registry.d.ts +0 -41
  205. package/dist/functions/_shared/webhook-registry.d.ts.map +0 -1
  206. package/dist/functions/account-nodes.d.ts +0 -48
  207. package/dist/functions/account-nodes.d.ts.map +0 -1
  208. package/dist/functions/admin-data.d.ts +0 -178
  209. package/dist/functions/admin-data.d.ts.map +0 -1
  210. package/dist/functions/ai-agents.d.ts +0 -125
  211. package/dist/functions/ai-agents.d.ts.map +0 -1
  212. package/dist/functions/api-keys.d.ts +0 -140
  213. package/dist/functions/api-keys.d.ts.map +0 -1
  214. package/dist/functions/apps.d.ts +0 -163
  215. package/dist/functions/apps.d.ts.map +0 -1
  216. package/dist/functions/auth.d.ts +0 -74
  217. package/dist/functions/auth.d.ts.map +0 -1
  218. package/dist/functions/debug-auth.d.ts +0 -33
  219. package/dist/functions/debug-auth.d.ts.map +0 -1
  220. package/dist/functions/embeddings.d.ts +0 -205
  221. package/dist/functions/embeddings.d.ts.map +0 -1
  222. package/dist/functions/handler-registry.d.ts +0 -22
  223. package/dist/functions/handler-registry.d.ts.map +0 -1
  224. package/dist/functions/integration-routes.d.ts +0 -45
  225. package/dist/functions/integration-routes.d.ts.map +0 -1
  226. package/dist/functions/integrations.d.ts +0 -124
  227. package/dist/functions/integrations.d.ts.map +0 -1
  228. package/dist/functions/item-progress.d.ts +0 -41
  229. package/dist/functions/item-progress.d.ts.map +0 -1
  230. package/dist/functions/logs.d.ts +0 -162
  231. package/dist/functions/logs.d.ts.map +0 -1
  232. package/dist/functions/observability.d.ts +0 -123
  233. package/dist/functions/observability.d.ts.map +0 -1
  234. package/dist/functions/pipeline-executions.d.ts +0 -190
  235. package/dist/functions/pipeline-executions.d.ts.map +0 -1
  236. package/dist/functions/pipelines.d.ts +0 -171
  237. package/dist/functions/pipelines.d.ts.map +0 -1
  238. package/dist/functions/prompt-configs.d.ts +0 -125
  239. package/dist/functions/prompt-configs.d.ts.map +0 -1
  240. package/dist/functions/roles.d.ts +0 -118
  241. package/dist/functions/roles.d.ts.map +0 -1
  242. package/dist/functions/signal.d.ts +0 -73
  243. package/dist/functions/signal.d.ts.map +0 -1
  244. package/dist/functions/system-cron.d.ts +0 -65
  245. package/dist/functions/system-cron.d.ts.map +0 -1
  246. package/dist/functions/system.d.ts +0 -29
  247. package/dist/functions/system.d.ts.map +0 -1
  248. package/dist/functions/tests.d.ts +0 -28
  249. package/dist/functions/tests.d.ts.map +0 -1
  250. package/dist/functions/timers.d.ts +0 -139
  251. package/dist/functions/timers.d.ts.map +0 -1
  252. package/dist/functions/triggers.d.ts +0 -203
  253. package/dist/functions/triggers.d.ts.map +0 -1
  254. package/dist/functions/types.d.ts +0 -151
  255. package/dist/functions/types.d.ts.map +0 -1
  256. package/dist/src/types/types.d.ts +0 -364
  257. package/dist/src/types/types.d.ts.map +0 -1
  258. package/index.html +0 -13
  259. package/netlify.toml +0 -36
  260. package/package-project.json +0 -71
  261. package/scripts/ingest-chunks.cjs +0 -202
  262. package/scripts/kb-chunk-parser.cjs +0 -312
  263. package/scripts/kb-chunk-parser.ts +0 -330
  264. package/scripts/load-test-app-install.ts +0 -484
  265. package/scripts/netlify-dev-wrapper.sh +0 -22
  266. package/vitest.config.ts +0 -45
  267. /package/{scripts → .framework/scripts}/app-install-cli.ts +0 -0
  268. /package/{scripts → .framework/scripts}/build-manifest.sh +0 -0
  269. /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