spine-framework 0.1.61 → 0.2.0

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