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,183 @@
1
+ # Guide 01: Create a Custom App
2
+
3
+ ## What this guide covers
4
+
5
+ How to scaffold, configure, and run a new custom app on Spine.
6
+
7
+ ---
8
+
9
+ ## Step 1: Scaffold with the CLI
10
+
11
+ ```bash
12
+ npm run spine-framework create-app my-app -- --name "My App" --role member
13
+ ```
14
+
15
+ This creates:
16
+ ```
17
+ custom/apps/my-app/
18
+ manifest.json ← app metadata, routes, nav, roles
19
+ index.tsx ← React entry point
20
+ components/ ← your components go here
21
+ ```
22
+
23
+ And inserts a row into the `apps` table in the database.
24
+
25
+ **Options:**
26
+ ```bash
27
+ --name "My App" # Display name (defaults to title-cased slug)
28
+ --role member # Required role: member | support | system_admin (default: member)
29
+ --force # Overwrite if directory already exists
30
+ ```
31
+
32
+ ---
33
+
34
+ ## Step 2: Configure manifest.json
35
+
36
+ `custom/apps/my-app/manifest.json` controls everything about the app.
37
+
38
+ ```json
39
+ {
40
+ "name": "My App",
41
+ "slug": "my-app",
42
+ "description": "What this app does",
43
+ "version": "1.0.0",
44
+ "required_roles": ["member"],
45
+ "routes": [
46
+ "/my-app",
47
+ "/my-app/dashboard",
48
+ "/my-app/settings"
49
+ ],
50
+ "nav_items": [
51
+ {
52
+ "title": "Dashboard",
53
+ "path": "/my-app/dashboard",
54
+ "icon": "LayoutDashboard",
55
+ "order": 1
56
+ },
57
+ {
58
+ "title": "Settings",
59
+ "path": "/my-app/settings",
60
+ "icon": "Settings",
61
+ "order": 2
62
+ }
63
+ ],
64
+ "features": [],
65
+ "entry_point": "./index.tsx"
66
+ }
67
+ ```
68
+
69
+ **Key fields:**
70
+ - `required_roles` — array of roles that can access this app. Any match grants access.
71
+ - `routes` — informational; actual routing is handled by `index.tsx`
72
+ - `nav_items` — used by generic app shells; custom apps render their own sidebar
73
+
74
+ ---
75
+
76
+ ## Step 3: Build index.tsx
77
+
78
+ `index.tsx` is the React entry point. Core mounts it at `/{slug}/*`.
79
+
80
+ ```tsx
81
+ import * as React from 'react'
82
+ import { Routes, Route, Navigate } from 'react-router-dom'
83
+ import { AppShell } from '@core/components/layout/AppShell'
84
+ import { useAuth } from '@core/contexts/AuthContext'
85
+ import MyAppSidebar from './components/MyAppSidebar'
86
+
87
+ function Dashboard() {
88
+ const { user } = useAuth()
89
+ return (
90
+ <div className="p-6">
91
+ <h1 className="text-2xl font-bold">Dashboard</h1>
92
+ <p className="text-slate-500">Welcome, {user?.full_name}</p>
93
+ </div>
94
+ )
95
+ }
96
+
97
+ export default function MyApp() {
98
+ return (
99
+ <AppShell sidebar={<MyAppSidebar />}>
100
+ <Routes>
101
+ <Route index element={<Navigate to="dashboard" replace />} />
102
+ <Route path="dashboard" element={<Dashboard />} />
103
+ </Routes>
104
+ </AppShell>
105
+ )
106
+ }
107
+ ```
108
+
109
+ **Rules for index.tsx:**
110
+ - Always `export default` the root component
111
+ - Use `@core/...` imports for anything from core — never relative paths to `.framework/`
112
+ - Routes are relative — `/my-app/dashboard` is just `path="dashboard"` here
113
+
114
+ ---
115
+
116
+ ## Step 4: Assemble and run
117
+
118
+ ```bash
119
+ npm run assemble
120
+ netlify dev
121
+ ```
122
+
123
+ Navigate to `http://localhost:8888/my-app`.
124
+
125
+ ---
126
+
127
+ ## Step 5: Verify
128
+
129
+ ```bash
130
+ # App files exist
131
+ ls custom/apps/my-app/
132
+
133
+ # App is in the database
134
+ # Check: GET /api/apps?action=list should include your slug
135
+ ```
136
+
137
+ ---
138
+
139
+ ## File structure reference
140
+
141
+ ```
142
+ custom/apps/my-app/
143
+ manifest.json ← required
144
+ index.tsx ← required: default export = root component
145
+ components/
146
+ MyAppSidebar.tsx ← optional: custom sidebar
147
+ MyFeatureCard.tsx ← optional: any component
148
+ pages/
149
+ DashboardPage.tsx ← optional: split pages out for readability
150
+ ```
151
+
152
+ ---
153
+
154
+ ## Core imports available in custom apps
155
+
156
+ ```tsx
157
+ // Auth
158
+ import { useAuth } from '@core/contexts/AuthContext'
159
+
160
+ // Layout
161
+ import { AppShell } from '@core/components/layout/AppShell'
162
+
163
+ // UI primitives
164
+ import { Button } from '@core/components/ui/button'
165
+ import { LoadingSpinner } from '@core/components/ui/LoadingSpinner'
166
+ import { Sidebar, SidebarMenu, SidebarMenuItem } from '@core/components/ui/sidebar'
167
+
168
+ // Data fetching
169
+ import { apiFetch } from '@core/lib/api'
170
+
171
+ // Router (re-exported from react-router-dom)
172
+ import { Routes, Route, Navigate, useNavigate, useParams } from 'react-router-dom'
173
+ ```
174
+
175
+ > **Rule:** Always use `@core/...` for core imports. Never use relative paths like `../../../../.framework/src/...`.
176
+ > `@core` resolves to `.assembled/src` in both dev and build.
177
+
178
+ ---
179
+
180
+ ## Next steps
181
+
182
+ - [02-create-a-function.md](./02-create-a-function.md) — Add a backend API to your app
183
+ - [03-create-a-component.md](./03-create-a-component.md) — Build reusable components
@@ -0,0 +1,178 @@
1
+ # Guide 02: Create a Custom Function
2
+
3
+ ## What this guide covers
4
+
5
+ How to write a custom Netlify function that uses core authentication, database access, and middleware.
6
+
7
+ ---
8
+
9
+ ## Naming convention
10
+
11
+ Custom functions live in `custom/functions/` and must be prefixed `custom_`:
12
+
13
+ ```
14
+ custom/functions/custom_my-feature.ts
15
+ ```
16
+
17
+ After assembly, this becomes `.netlify/functions/custom_my-feature` and is callable at:
18
+ ```
19
+ GET /api/custom_my-feature?action=list
20
+ ```
21
+
22
+ ---
23
+
24
+ ## Step 1: Create the function file
25
+
26
+ ```typescript
27
+ // custom/functions/custom_my-feature.ts
28
+ import { createHandler } from '@core/_shared'
29
+
30
+ export const handler = createHandler(async (event, ctx) => {
31
+ const action = ctx.query?.action || 'list'
32
+
33
+ switch (action) {
34
+ case 'list': {
35
+ const { data, error } = await ctx.db
36
+ .from('items')
37
+ .select('*')
38
+ .eq('type_slug', 'my_type')
39
+
40
+ if (error) throw error
41
+ return { data }
42
+ }
43
+
44
+ case 'get': {
45
+ const id = ctx.query?.id
46
+ if (!id) throw new Error('id is required')
47
+
48
+ const { data, error } = await ctx.db
49
+ .from('items')
50
+ .select('*')
51
+ .eq('id', id)
52
+ .single()
53
+
54
+ if (error) throw error
55
+ return { data }
56
+ }
57
+
58
+ default:
59
+ throw new Error(`Unknown action: ${action}`)
60
+ }
61
+ })
62
+ ```
63
+
64
+ ---
65
+
66
+ ## What createHandler gives you
67
+
68
+ `createHandler` wraps your function with:
69
+ - **Authentication** — rejects unauthenticated requests with 401
70
+ - **Authorization** — `ctx.principal` has the user's roles and permissions
71
+ - **Database** — `ctx.db` is a Supabase client scoped to the user's account via RLS
72
+ - **Logging** — `ctx.logger` for structured logs
73
+ - **Error handling** — uncaught errors become 500 responses with safe messages
74
+
75
+ ```typescript
76
+ interface CoreContext {
77
+ db: SupabaseClient // RLS-scoped to authenticated user's account
78
+ principal: Principal // { id, account_id, roles, permissions }
79
+ query: Record<string, string> // parsed query string
80
+ body: any // parsed request body
81
+ logger: Logger // structured logging
82
+ }
83
+ ```
84
+
85
+ ---
86
+
87
+ ## Accessing the authenticated user
88
+
89
+ ```typescript
90
+ export const handler = createHandler(async (event, ctx) => {
91
+ const { principal } = ctx
92
+
93
+ console.log(principal.id) // user UUID
94
+ console.log(principal.account_id) // tenant account UUID
95
+ console.log(principal.roles) // e.g. ['member', 'support']
96
+
97
+ // Check role
98
+ if (!principal.roles.includes('support')) {
99
+ throw new Error('Support role required')
100
+ }
101
+
102
+ return { data: 'ok' }
103
+ })
104
+ ```
105
+
106
+ ---
107
+
108
+ ## Admin database access
109
+
110
+ `ctx.db` is RLS-scoped (safe for user-facing data). For admin operations use `adminDb`:
111
+
112
+ ```typescript
113
+ import { createHandler, adminDb } from '@core/_shared'
114
+
115
+ export const handler = createHandler(async (event, ctx) => {
116
+ // adminDb bypasses RLS — use with care
117
+ const { data } = await adminDb
118
+ .from('accounts')
119
+ .select('*')
120
+
121
+ return { data }
122
+ })
123
+ ```
124
+
125
+ ---
126
+
127
+ ## Calling your function from the frontend
128
+
129
+ ```typescript
130
+ import { apiFetch } from '@core/lib/api'
131
+
132
+ // apiFetch handles auth headers and the /api → /.netlify/functions proxy
133
+ const response = await apiFetch('/api/custom_my-feature?action=list')
134
+ const { data } = await response.json()
135
+ ```
136
+
137
+ ---
138
+
139
+ ## Step 2: Assemble and test
140
+
141
+ ```bash
142
+ npm run assemble:functions
143
+ # Functions reload automatically in netlify dev
144
+
145
+ # Test directly
146
+ curl http://localhost:8888/api/custom_my-feature?action=list \
147
+ -H "Authorization: Bearer <your-jwt>"
148
+ ```
149
+
150
+ ---
151
+
152
+ ## POST requests
153
+
154
+ ```typescript
155
+ export const handler = createHandler(async (event, ctx) => {
156
+ if (event.httpMethod === 'POST') {
157
+ const payload = ctx.body // already parsed JSON
158
+
159
+ const { data, error } = await ctx.db
160
+ .from('items')
161
+ .insert({ ...payload, account_id: ctx.principal.account_id })
162
+ .select()
163
+ .single()
164
+
165
+ if (error) throw error
166
+ return { data }
167
+ }
168
+
169
+ throw new Error('Method not allowed')
170
+ })
171
+ ```
172
+
173
+ ---
174
+
175
+ ## Next steps
176
+
177
+ - [03-create-a-component.md](./03-create-a-component.md) — Build UI components for your app
178
+ - [04-webhook-handlers.md](./04-webhook-handlers.md) — React to system events
@@ -0,0 +1,178 @@
1
+ # Guide 03: Create a Custom Component
2
+
3
+ ## What this guide covers
4
+
5
+ How to build React components inside a custom app, using core hooks, UI primitives, and layout components.
6
+
7
+ ---
8
+
9
+ ## Where components live
10
+
11
+ ```
12
+ custom/apps/my-app/
13
+ components/
14
+ MyAppSidebar.tsx ← navigation sidebar
15
+ MyFeatureCard.tsx ← any reusable UI piece
16
+ MyDataTable.tsx ← data display
17
+ ```
18
+
19
+ Components are local to your app. They are never imported by core.
20
+
21
+ ---
22
+
23
+ ## Import rules
24
+
25
+ ```tsx
26
+ // ✅ Correct — use @core alias for anything from core
27
+ import { useAuth } from '@core/contexts/AuthContext'
28
+ import { Button } from '@core/components/ui/button'
29
+ import { AppShell } from '@core/components/layout/AppShell'
30
+
31
+ // ✅ Correct — relative imports within your own app
32
+ import MyFeatureCard from './MyFeatureCard'
33
+ import MyFeatureCard from '../components/MyFeatureCard'
34
+
35
+ // ❌ Wrong — never use relative paths to .framework
36
+ import { useAuth } from '../../../../.framework/src/contexts/AuthContext'
37
+ ```
38
+
39
+ ---
40
+
41
+ ## Building a sidebar
42
+
43
+ Sidebars use the core `Sidebar` primitives from shadcn/ui.
44
+
45
+ ```tsx
46
+ // custom/apps/my-app/components/MyAppSidebar.tsx
47
+ import * as React from 'react'
48
+ import { useNavigate, useLocation } from 'react-router-dom'
49
+ import { useAuth } from '@core/contexts/AuthContext'
50
+ import {
51
+ Sidebar,
52
+ SidebarContent,
53
+ SidebarGroup,
54
+ SidebarGroupContent,
55
+ SidebarGroupLabel,
56
+ SidebarHeader,
57
+ SidebarMenu,
58
+ SidebarMenuButton,
59
+ SidebarMenuItem,
60
+ SidebarRail,
61
+ } from '@core/components/ui/sidebar'
62
+ import { LayoutDashboard, Settings } from 'lucide-react'
63
+
64
+ const navItems = [
65
+ { title: 'Dashboard', url: '/my-app/dashboard', icon: LayoutDashboard },
66
+ { title: 'Settings', url: '/my-app/settings', icon: Settings },
67
+ ]
68
+
69
+ export function MyAppSidebar() {
70
+ const navigate = useNavigate()
71
+ const location = useLocation()
72
+
73
+ return (
74
+ <Sidebar>
75
+ <SidebarHeader>
76
+ <span className="font-semibold px-2">My App</span>
77
+ </SidebarHeader>
78
+ <SidebarContent>
79
+ <SidebarGroup>
80
+ <SidebarGroupLabel>Navigation</SidebarGroupLabel>
81
+ <SidebarGroupContent>
82
+ <SidebarMenu>
83
+ {navItems.map(item => (
84
+ <SidebarMenuItem key={item.url}>
85
+ <SidebarMenuButton
86
+ isActive={location.pathname.startsWith(item.url)}
87
+ onClick={() => navigate(item.url)}
88
+ >
89
+ <item.icon className="w-4 h-4" />
90
+ <span>{item.title}</span>
91
+ </SidebarMenuButton>
92
+ </SidebarMenuItem>
93
+ ))}
94
+ </SidebarMenu>
95
+ </SidebarGroupContent>
96
+ </SidebarGroup>
97
+ </SidebarContent>
98
+ <SidebarRail />
99
+ </Sidebar>
100
+ )
101
+ }
102
+ ```
103
+
104
+ ---
105
+
106
+ ## Fetching data in a component
107
+
108
+ ```tsx
109
+ import * as React from 'react'
110
+ import { apiFetch } from '@core/lib/api'
111
+ import { LoadingSpinner } from '@core/components/ui/LoadingSpinner'
112
+
113
+ interface Item {
114
+ id: string
115
+ title: string
116
+ }
117
+
118
+ export function ItemList() {
119
+ const [items, setItems] = React.useState<Item[]>([])
120
+ const [loading, setLoading] = React.useState(true)
121
+
122
+ React.useEffect(() => {
123
+ apiFetch('/api/custom_my-feature?action=list')
124
+ .then(r => r.json())
125
+ .then(({ data }) => setItems(data || []))
126
+ .finally(() => setLoading(false))
127
+ }, [])
128
+
129
+ if (loading) return <LoadingSpinner />
130
+
131
+ return (
132
+ <ul className="space-y-2">
133
+ {items.map(item => (
134
+ <li key={item.id} className="p-3 border rounded">
135
+ {item.title}
136
+ </li>
137
+ ))}
138
+ </ul>
139
+ )
140
+ }
141
+ ```
142
+
143
+ ---
144
+
145
+ ## Available core UI components
146
+
147
+ All from `@core/components/ui/...` (shadcn/ui):
148
+
149
+ | Import | Component |
150
+ |--------|-----------|
151
+ | `@core/components/ui/button` | `Button` |
152
+ | `@core/components/ui/badge` | `Badge` |
153
+ | `@core/components/ui/card` | `Card`, `CardHeader`, `CardContent` |
154
+ | `@core/components/ui/dialog` | `Dialog`, `DialogContent`, `DialogHeader` |
155
+ | `@core/components/ui/input` | `Input` |
156
+ | `@core/components/ui/label` | `Label` |
157
+ | `@core/components/ui/select` | `Select`, `SelectContent`, `SelectItem` |
158
+ | `@core/components/ui/sidebar` | `Sidebar`, `SidebarMenu`, `SidebarMenuItem`, etc. |
159
+ | `@core/components/ui/tabs` | `Tabs`, `TabsList`, `TabsTrigger`, `TabsContent` |
160
+ | `@core/components/ui/tooltip` | `Tooltip`, `TooltipProvider` |
161
+ | `@core/components/ui/LoadingSpinner` | `LoadingSpinner` |
162
+
163
+ ---
164
+
165
+ ## Available core hooks
166
+
167
+ | Import | Hook | Purpose |
168
+ |--------|------|---------|
169
+ | `@core/contexts/AuthContext` | `useAuth()` | Current user, login/logout |
170
+ | `@core/contexts/AppContext` | `useCurrentApp()` | Current app record |
171
+ | `@core/lib/api` | `apiFetch()` | Auth-aware fetch wrapper |
172
+
173
+ ---
174
+
175
+ ## Next steps
176
+
177
+ - [04-webhook-handlers.md](./04-webhook-handlers.md) — React to system events
178
+ - [05-testing.md](./05-testing.md) — Test your components and functions
@@ -0,0 +1,129 @@
1
+ # Guide 04: Webhook Handlers
2
+
3
+ ## What this guide covers
4
+
5
+ How to register a custom webhook handler that reacts to integration events without modifying core.
6
+
7
+ ---
8
+
9
+ ## How it works
10
+
11
+ Core's `integration-routes.ts` resolves webhook handlers dynamically from the `webhook_handlers` database table at runtime. Custom handlers self-register into that table — core never imports them directly.
12
+
13
+ ```
14
+ Integration event arrives
15
+ → core looks up handler name in webhook_handlers table
16
+ → calls the registered Netlify function
17
+ → your custom function runs
18
+ ```
19
+
20
+ ---
21
+
22
+ ## Step 1: Create the handler function
23
+
24
+ ```typescript
25
+ // custom/functions/custom_my-handler.ts
26
+ import { createHandler, registerWebhookHandler } from '@core/_shared'
27
+
28
+ // Self-register on cold start
29
+ registerWebhookHandler({
30
+ name: 'my-handler',
31
+ functionName: 'custom_my-handler',
32
+ description: 'Handles item.created events from my integration',
33
+ events: ['item.created', 'item.updated'],
34
+ })
35
+
36
+ export const handler = createHandler(async (event, ctx) => {
37
+ const payload = ctx.body
38
+
39
+ // payload.event — event name e.g. 'item.created'
40
+ // payload.data — the item/record that triggered the event
41
+ // ctx.principal — the system or user that triggered it
42
+ // ctx.db — RLS-scoped database client
43
+
44
+ console.log('Received event:', payload.event)
45
+
46
+ // Do your work
47
+ const { error } = await ctx.db
48
+ .from('items')
49
+ .insert({
50
+ type_slug: 'my_log',
51
+ data: { source_event: payload.event, source_id: payload.data?.id },
52
+ account_id: ctx.principal.account_id,
53
+ })
54
+
55
+ if (error) throw error
56
+
57
+ return { received: true }
58
+ })
59
+ ```
60
+
61
+ ---
62
+
63
+ ## registerWebhookHandler options
64
+
65
+ ```typescript
66
+ registerWebhookHandler({
67
+ name: string // Unique handler name — used to look up this handler
68
+ functionName: string // Netlify function name (must match the file prefix)
69
+ description?: string // Human-readable description
70
+ events?: string[] // Event names this handler responds to (informational)
71
+ })
72
+ ```
73
+
74
+ Registration is idempotent — calling it multiple times (e.g. on every cold start) is safe. It upserts by `name`.
75
+
76
+ ---
77
+
78
+ ## Step 2: Register the handler in the database
79
+
80
+ The `registerWebhookHandler` call runs on cold start and writes to `webhook_handlers`. You can also seed it manually:
81
+
82
+ ```sql
83
+ INSERT INTO webhook_handlers (name, function_name, description, events, is_active)
84
+ VALUES (
85
+ 'my-handler',
86
+ 'custom_my-handler',
87
+ 'Handles item.created events',
88
+ '["item.created", "item.updated"]',
89
+ true
90
+ )
91
+ ON CONFLICT (name) DO UPDATE
92
+ SET function_name = EXCLUDED.function_name,
93
+ is_active = EXCLUDED.is_active;
94
+ ```
95
+
96
+ ---
97
+
98
+ ## Step 3: Route events to your handler
99
+
100
+ Integration routes look up the handler by name. To route an incoming webhook to your handler, the integration config should reference `"my-handler"` as the handler name. This is typically set in the `integrations` table or the integration's config payload.
101
+
102
+ ---
103
+
104
+ ## Step 4: Assemble and test
105
+
106
+ ```bash
107
+ npm run assemble:functions
108
+
109
+ # Trigger manually (replace with your webhook payload)
110
+ curl -X POST http://localhost:8888/api/integration-routes \
111
+ -H "Content-Type: application/json" \
112
+ -d '{"handler": "my-handler", "event": "item.created", "data": {"id": "123"}}'
113
+ ```
114
+
115
+ ---
116
+
117
+ ## Verify registration
118
+
119
+ ```sql
120
+ SELECT name, function_name, events, is_active
121
+ FROM webhook_handlers
122
+ WHERE name = 'my-handler';
123
+ ```
124
+
125
+ ---
126
+
127
+ ## Next steps
128
+
129
+ - [05-testing.md](./05-testing.md) — Test your handler with the core test harness