spine-framework 0.3.124 → 0.3.126

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 (319) hide show
  1. package/.framework/cli/commands/agents.ts +35 -37
  2. package/.framework/cli/commands/auth.ts +35 -34
  3. package/.framework/cli/commands/create-app.ts +104 -40
  4. package/.framework/cli/commands/dev.ts +104 -69
  5. package/.framework/cli/commands/doctor.ts +179 -69
  6. package/.framework/cli/commands/generate.ts +83 -143
  7. package/.framework/cli/commands/init.ts +72 -34
  8. package/.framework/cli/commands/install-app.ts +260 -125
  9. package/.framework/cli/commands/items.ts +33 -44
  10. package/.framework/cli/commands/migrate.ts +122 -40
  11. package/.framework/cli/commands/migrations.ts +33 -40
  12. package/.framework/cli/commands/pipelines.ts +31 -39
  13. package/.framework/cli/commands/status.ts +47 -23
  14. package/.framework/cli/commands/system.ts +47 -37
  15. package/.framework/cli/commands/test.ts +112 -68
  16. package/.framework/cli/commands/validate-app.ts +215 -121
  17. package/.framework/cli/context.ts +112 -130
  18. package/.framework/cli/env-loader.ts +47 -18
  19. package/.framework/cli/index.ts +81 -42
  20. package/.framework/docs/runtime-types.md +1024 -0
  21. package/.framework/functions/_shared/agent-runner.ts +1296 -961
  22. package/.framework/functions/_shared/app-manifest.ts +82 -68
  23. package/.framework/functions/_shared/audit.ts +135 -83
  24. package/.framework/functions/_shared/{cil-state.ts → cil.ts} +158 -56
  25. package/.framework/functions/_shared/db.ts +80 -126
  26. package/.framework/functions/_shared/hash-api-key.ts +63 -0
  27. package/.framework/functions/_shared/index.ts +224 -335
  28. package/.framework/functions/_shared/kb.ts +319 -0
  29. package/.framework/functions/_shared/llm.ts +92 -0
  30. package/.framework/functions/_shared/logging.ts +288 -0
  31. package/.framework/functions/_shared/middleware.ts +329 -365
  32. package/.framework/functions/_shared/outcomes.ts +36 -0
  33. package/.framework/functions/_shared/permissions.ts +359 -643
  34. package/.framework/functions/_shared/pipeline-runner.ts +243 -316
  35. package/.framework/functions/_shared/principal.ts +376 -468
  36. package/.framework/functions/_shared/resolve-credentials.ts +102 -0
  37. package/.framework/functions/_shared/resolve-ids.ts +188 -16
  38. package/.framework/functions/_shared/retry-deliveries.ts +104 -0
  39. package/.framework/functions/_shared/schema-utils.ts +611 -373
  40. package/.framework/functions/_shared/testing.ts +207 -137
  41. package/.framework/functions/_shared/tracing.ts +276 -0
  42. package/.framework/functions/_shared/trigger-engine.ts +159 -211
  43. package/.framework/functions/_shared/webhook-registration.ts +95 -81
  44. package/.framework/functions/_shared/webhook-registry.ts +153 -75
  45. package/.framework/functions/account-nodes.ts +31 -53
  46. package/.framework/functions/adapters/generic-webhook-inbound.ts +56 -0
  47. package/.framework/functions/adapters/generic-webhook-outbound.ts +43 -0
  48. package/.framework/functions/adapters/openai-outbound.ts +40 -0
  49. package/.framework/functions/admin-data.ts +685 -537
  50. package/.framework/functions/admin-funnel.ts +31 -20
  51. package/.framework/functions/admin-install-funnel.ts +47 -28
  52. package/.framework/functions/admin-signals.ts +47 -29
  53. package/.framework/functions/ai-agents.ts +122 -147
  54. package/.framework/functions/ai-interactions.ts +180 -90
  55. package/.framework/functions/api-keys.ts +250 -192
  56. package/.framework/functions/apps.ts +203 -197
  57. package/.framework/functions/auth.ts +98 -91
  58. package/.framework/functions/cil-agent-ingest.ts +773 -0
  59. package/.framework/functions/cil-agent.ts +592 -0
  60. package/.framework/functions/cil-callmap.ts +1227 -0
  61. package/.framework/functions/cil-config.ts +583 -0
  62. package/.framework/functions/cil-context.ts +445 -0
  63. package/.framework/functions/cil-doc.ts +1752 -0
  64. package/.framework/functions/cil-embeddings.ts +1202 -0
  65. package/.framework/functions/cil-graph.ts +585 -0
  66. package/.framework/functions/cil-ingest.ts +2416 -0
  67. package/.framework/functions/cil-pipeline.ts +318 -0
  68. package/.framework/functions/cil-project.ts +499 -0
  69. package/.framework/functions/cil-validate.ts +469 -0
  70. package/.framework/functions/debug-auth.ts +73 -53
  71. package/.framework/functions/deliveries.ts +117 -0
  72. package/.framework/functions/embeddings.ts +528 -516
  73. package/.framework/functions/eval-harness.ts +158 -106
  74. package/.framework/functions/integration-outbound.ts +311 -0
  75. package/.framework/functions/integration-routes.ts +240 -466
  76. package/.framework/functions/integrations.ts +132 -140
  77. package/.framework/functions/invites.ts +401 -201
  78. package/.framework/functions/item-progress.ts +113 -53
  79. package/.framework/functions/kb.ts +28 -0
  80. package/.framework/functions/logs.ts +239 -222
  81. package/.framework/functions/observability.ts +308 -259
  82. package/.framework/functions/pipeline-executions.ts +179 -235
  83. package/.framework/functions/pipelines.ts +163 -205
  84. package/.framework/functions/prompt-configs.ts +109 -156
  85. package/.framework/functions/roles.ts +97 -147
  86. package/.framework/functions/system-cron.ts +132 -218
  87. package/.framework/functions/system.ts +112 -52
  88. package/.framework/functions/tests.ts +32 -28
  89. package/.framework/functions/timers.ts +162 -210
  90. package/.framework/functions/triggers.ts +282 -346
  91. package/.framework/functions/types.ts +283 -400
  92. package/.framework/migrations/001_core_schema.sql +630 -0
  93. package/.framework/migrations/002_core_rls_and_functions.sql +532 -0
  94. package/.framework/migrations/{003_system_data.sql → 003_core_system_data.sql} +26 -153
  95. package/.framework/migrations/004_core_rpc_and_broker.sql +421 -0
  96. package/.framework/migrations/005_core_observability.sql +598 -0
  97. package/.framework/migrations/006_security_hardening_rls.sql +97 -0
  98. package/.framework/migrations/007_kb_article_list.sql +237 -0
  99. package/.framework/migrations/008_items_target.sql +8 -0
  100. package/.framework/migrations/009_roles_slug_unique.sql +11 -0
  101. package/.framework/public/spine.config.json +91 -0
  102. package/.framework/scripts/app-install-cli.ts +112 -98
  103. package/.framework/src/App.tsx +67 -20
  104. package/.framework/src/apps/admin/index.tsx +97 -27
  105. package/.framework/src/components/AppWrapper.tsx +50 -11
  106. package/.framework/src/components/CustomAppLoader.tsx +131 -22
  107. package/.framework/src/components/admin/AdminListPage.tsx +75 -8
  108. package/.framework/src/components/admin/AdminSidebar.tsx +148 -36
  109. package/.framework/src/components/admin/AdminStatsCard.tsx +48 -29
  110. package/.framework/src/components/admin/PageInstructions.tsx +49 -0
  111. package/.framework/src/components/admin/SortableTableHeader.tsx +49 -2
  112. package/.framework/src/components/app-shell/GenericAppShell.tsx +69 -11
  113. package/.framework/src/components/app-shell/GenericDetailPage.tsx +55 -10
  114. package/.framework/src/components/app-shell/GenericListPage.tsx +76 -9
  115. package/.framework/src/components/app-sidebar.tsx +50 -3
  116. package/.framework/src/components/auth/ProtectedRoute.tsx +53 -45
  117. package/.framework/src/components/kb/KBArticleEditor.tsx +409 -0
  118. package/.framework/src/components/kb/KBArticleList.tsx +557 -0
  119. package/.framework/src/components/layout/AppShell.tsx +54 -16
  120. package/.framework/src/components/layout/Header.tsx +50 -38
  121. package/.framework/src/components/layout/Layout.tsx +48 -32
  122. package/.framework/src/components/layout/Sidebar.tsx +167 -16
  123. package/.framework/src/components/runtime/DataDetailHeader.tsx +33 -1
  124. package/.framework/src/components/runtime/DataDetailPage.tsx +289 -4
  125. package/.framework/src/components/runtime/DataFilters.tsx +78 -1
  126. package/.framework/src/components/runtime/DataHeader.tsx +32 -29
  127. package/.framework/src/components/runtime/DataListPage.tsx +161 -2
  128. package/.framework/src/components/runtime/DataStats.tsx +48 -1
  129. package/.framework/src/components/runtime/DataTable.tsx +153 -1
  130. package/.framework/src/components/runtime/SchemaDetailForm.tsx +77 -46
  131. package/.framework/src/components/runtime/index.ts +16 -9
  132. package/.framework/src/components/search-form.tsx +33 -1
  133. package/.framework/src/components/shared/AgentView.tsx +48 -51
  134. package/.framework/src/components/shared/FieldRenderer.tsx +84 -67
  135. package/.framework/src/components/shared/InviteManager.tsx +113 -52
  136. package/.framework/src/components/shared/MemberList.tsx +182 -0
  137. package/.framework/src/components/shared/SchemaFields.tsx +96 -78
  138. package/.framework/src/components/ui/DataTable.tsx +86 -56
  139. package/.framework/src/components/ui/Form.tsx +188 -109
  140. package/.framework/src/components/ui/ItemCard.tsx +86 -58
  141. package/.framework/src/components/ui/ItemListView.tsx +88 -61
  142. package/.framework/src/components/ui/LoadingSpinner.tsx +52 -27
  143. package/.framework/src/components/ui/Modal.tsx +81 -4
  144. package/.framework/src/components/ui/RichTextEditor.tsx +101 -9
  145. package/.framework/src/components/ui/accordion.tsx +97 -5
  146. package/.framework/src/components/ui/alert-dialog.tsx +225 -13
  147. package/.framework/src/components/ui/alert.tsx +113 -6
  148. package/.framework/src/components/ui/aspect-ratio.tsx +49 -2
  149. package/.framework/src/components/ui/avatar.tsx +129 -7
  150. package/.framework/src/components/ui/badge.tsx +65 -3
  151. package/.framework/src/components/ui/breadcrumb.tsx +145 -8
  152. package/.framework/src/components/ui/button-group.tsx +81 -4
  153. package/.framework/src/components/ui/button.tsx +49 -2
  154. package/.framework/src/components/ui/calendar.tsx +65 -3
  155. package/.framework/src/components/ui/card.tsx +145 -8
  156. package/.framework/src/components/ui/carousel.tsx +129 -7
  157. package/.framework/src/components/ui/chart.tsx +161 -10
  158. package/.framework/src/components/ui/checkbox.tsx +49 -2
  159. package/.framework/src/components/ui/collapsible.tsx +81 -5
  160. package/.framework/src/components/ui/combobox.tsx +305 -20
  161. package/.framework/src/components/ui/command.tsx +177 -11
  162. package/.framework/src/components/ui/context-menu.tsx +273 -17
  163. package/.framework/src/components/ui/dialog.tsx +209 -27
  164. package/.framework/src/components/ui/direction.tsx +64 -4
  165. package/.framework/src/components/ui/drawer.tsx +193 -12
  166. package/.framework/src/components/ui/dropdown-menu.tsx +273 -18
  167. package/.framework/src/components/ui/empty.tsx +145 -9
  168. package/.framework/src/components/ui/field.tsx +209 -14
  169. package/.framework/src/components/ui/hover-card.tsx +81 -4
  170. package/.framework/src/components/ui/input-group.tsx +162 -10
  171. package/.framework/src/components/ui/input-otp.tsx +98 -5
  172. package/.framework/src/components/ui/input.tsx +49 -2
  173. package/.framework/src/components/ui/item.tsx +225 -13
  174. package/.framework/src/components/ui/kbd.tsx +65 -3
  175. package/.framework/src/components/ui/label.tsx +49 -2
  176. package/.framework/src/components/ui/menubar.tsx +289 -17
  177. package/.framework/src/components/ui/native-select.tsx +97 -5
  178. package/.framework/src/components/ui/navigation-menu.tsx +177 -10
  179. package/.framework/src/components/ui/pagination.tsx +161 -9
  180. package/.framework/src/components/ui/popover.tsx +145 -8
  181. package/.framework/src/components/ui/progress.tsx +49 -2
  182. package/.framework/src/components/ui/radio-group.tsx +65 -3
  183. package/.framework/src/components/ui/resizable.tsx +82 -4
  184. package/.framework/src/components/ui/scroll-area.tsx +65 -3
  185. package/.framework/src/components/ui/select.tsx +193 -11
  186. package/.framework/src/components/ui/separator.tsx +49 -2
  187. package/.framework/src/components/ui/sheet.tsx +193 -11
  188. package/.framework/src/components/ui/sidebar.tsx +481 -38
  189. package/.framework/src/components/ui/skeleton.tsx +49 -2
  190. package/.framework/src/components/ui/slider.tsx +49 -2
  191. package/.framework/src/components/ui/sonner.tsx +49 -2
  192. package/.framework/src/components/ui/spinner.tsx +49 -2
  193. package/.framework/src/components/ui/switch.tsx +49 -2
  194. package/.framework/src/components/ui/table-primitives.tsx +160 -33
  195. package/.framework/src/components/ui/table.tsx +161 -9
  196. package/.framework/src/components/ui/tabs.tsx +113 -6
  197. package/.framework/src/components/ui/textarea.tsx +49 -2
  198. package/.framework/src/components/ui/toggle-group.tsx +81 -4
  199. package/.framework/src/components/ui/toggle.tsx +65 -3
  200. package/.framework/src/components/ui/tooltip.tsx +97 -5
  201. package/.framework/src/contexts/AppContext.tsx +129 -45
  202. package/.framework/src/contexts/AuthContext.tsx +187 -144
  203. package/.framework/src/hooks/use-mobile.ts +48 -2
  204. package/.framework/src/hooks/useApi.ts +208 -159
  205. package/.framework/src/hooks/useAppPath.ts +32 -23
  206. package/.framework/src/hooks/useApps.ts +51 -20
  207. package/.framework/src/hooks/useEntityList.ts +32 -78
  208. package/.framework/src/hooks/useEntityRecord.ts +35 -96
  209. package/.framework/src/hooks/useForm.ts +32 -92
  210. package/.framework/src/hooks/useListSchema.ts +49 -78
  211. package/.framework/src/hooks/useSchemaRecord.ts +114 -105
  212. package/.framework/src/lib/api.ts +195 -116
  213. package/.framework/src/lib/supabase.ts +98 -64
  214. package/.framework/src/lib/utils.ts +287 -151
  215. package/.framework/src/main.tsx +49 -3
  216. package/.framework/src/pages/DashboardPage.tsx +52 -29
  217. package/.framework/src/pages/NotFoundPage.tsx +32 -10
  218. package/.framework/src/pages/SetupPage.tsx +32 -12
  219. package/.framework/src/pages/admin/AIAgentDetailPage.tsx +119 -10
  220. package/.framework/src/pages/admin/AIAgentsPage.tsx +48 -19
  221. package/.framework/src/pages/admin/APIKeyDetailPage.tsx +187 -17
  222. package/.framework/src/pages/admin/APIKeysPage.tsx +98 -22
  223. package/.framework/src/pages/admin/AlertsConfigPage.tsx +113 -26
  224. package/.framework/src/pages/admin/AppDetailPage.tsx +125 -8
  225. package/.framework/src/pages/admin/AppsPage.tsx +49 -2
  226. package/.framework/src/pages/admin/DeliveriesPage.tsx +196 -0
  227. package/.framework/src/pages/admin/DesignedPage.tsx +32 -18
  228. package/.framework/src/pages/admin/EmbeddingDetailPage.tsx +215 -484
  229. package/.framework/src/pages/admin/EmbeddingsPage.tsx +116 -319
  230. package/.framework/src/pages/admin/ExtendedShadcnTestPage.tsx +34 -9
  231. package/.framework/src/pages/admin/IncrementalShadcnTestPage.tsx +34 -9
  232. package/.framework/src/pages/admin/IntegratedDashboard.tsx +32 -16
  233. package/.framework/src/pages/admin/IntegrationDetailPage.tsx +183 -26
  234. package/.framework/src/pages/admin/IntegrationsPage.tsx +48 -19
  235. package/.framework/src/pages/admin/InvitesPage.tsx +48 -14
  236. package/.framework/src/pages/admin/LogsPage.tsx +422 -75
  237. package/.framework/src/pages/admin/MinimalShadcnTestPage.tsx +34 -9
  238. package/.framework/src/pages/admin/ObservabilityDashboard.tsx +113 -24
  239. package/.framework/src/pages/admin/PipelineDetailPage.tsx +206 -34
  240. package/.framework/src/pages/admin/PipelineExecutionsPage.tsx +50 -17
  241. package/.framework/src/pages/admin/PipelinesPage.tsx +160 -21
  242. package/.framework/src/pages/admin/PromptConfigDetailPage.tsx +378 -88
  243. package/.framework/src/pages/admin/PromptConfigsPage.tsx +125 -16
  244. package/.framework/src/pages/admin/ProperlyDesignedPage.tsx +49 -10
  245. package/.framework/src/pages/admin/RoleDetailPage.tsx +130 -10
  246. package/.framework/src/pages/admin/RolesPage.tsx +171 -18
  247. package/.framework/src/pages/admin/SelectTestPage.tsx +34 -9
  248. package/.framework/src/pages/admin/ShadcnTestPage.tsx +49 -11
  249. package/.framework/src/pages/admin/SimpleDashboard.tsx +49 -10
  250. package/.framework/src/pages/admin/TestRunDetailPage.tsx +49 -4
  251. package/.framework/src/pages/admin/TestingDashboard.tsx +98 -28
  252. package/.framework/src/pages/admin/TimerDetailPage.tsx +143 -33
  253. package/.framework/src/pages/admin/TimersPage.tsx +48 -20
  254. package/.framework/src/pages/admin/TriggerDetailPage.tsx +118 -30
  255. package/.framework/src/pages/admin/TriggersPage.tsx +64 -19
  256. package/.framework/src/pages/admin/TypeDetailPage.tsx +138 -15
  257. package/.framework/src/pages/admin/TypesPage.tsx +48 -20
  258. package/.framework/src/pages/admin/cil/CILAgentPage.tsx +465 -0
  259. package/.framework/src/pages/admin/cil/CILChunkDetailPage.tsx +787 -0
  260. package/.framework/src/pages/admin/cil/CILDashboardPage.tsx +699 -0
  261. package/.framework/src/pages/admin/cil/CILFileListPage.tsx +647 -0
  262. package/.framework/src/pages/admin/cil/CILGraphPage.tsx +616 -0
  263. package/.framework/src/pages/admin/cil/CILIngestionPage.tsx +1402 -0
  264. package/.framework/src/pages/admin/cil/CILProjectsPage.tsx +614 -0
  265. package/.framework/src/pages/admin/cil/CILSearchPage.tsx +571 -0
  266. package/.framework/src/pages/admin/cil/FlexiblePipelinePage.tsx +731 -0
  267. package/.framework/src/pages/admin/kb/KBAdminPage.tsx +64 -0
  268. package/.framework/src/pages/auth/AuthConfirmPage.tsx +52 -51
  269. package/.framework/src/pages/auth/ForgotPasswordPage.tsx +31 -22
  270. package/.framework/src/pages/auth/LoginPage.tsx +50 -23
  271. package/.framework/src/pages/auth/PendingApprovalPage.tsx +31 -15
  272. package/.framework/src/pages/auth/RegisterPage.tsx +281 -317
  273. package/.framework/src/pages/spine-framework/APIPage.tsx +33 -0
  274. package/.framework/src/pages/spine-framework/CLIPage.tsx +33 -0
  275. package/.framework/src/types/auth.ts +128 -71
  276. package/.framework/src/types/types.ts +373 -157
  277. package/custom/.devin/skills/spine-workbench-discovery/SKILL.md +39 -0
  278. package/custom/.devin/skills/spine-workbench-idea/SKILL.md +36 -0
  279. package/custom/.devin/skills/spine-workbench-implement/SKILL.md +46 -0
  280. package/custom/.devin/skills/spine-workbench-initiative/SKILL.md +28 -0
  281. package/custom/.devin/skills/spine-workbench-plan/SKILL.md +38 -0
  282. package/docs/README.md +9 -130
  283. package/netlify.toml +5 -2
  284. package/package.json +13 -25
  285. package/scripts/kb-ingest-docs.ts +203 -0
  286. package/spine.config.json +65 -4
  287. package/.framework/migrations/001_schema.sql +0 -425
  288. package/.framework/migrations/002_rls_and_functions.sql +0 -763
  289. package/.framework/migrations/004_enhancements.sql +0 -43
  290. package/.framework/migrations/005_auth_user_trigger.sql +0 -105
  291. package/.framework/migrations/006_fix_current_actor_id.sql +0 -21
  292. package/.framework/migrations/016_invites.sql +0 -27
  293. package/.framework/migrations/017_fix_get_account_apps.sql +0 -78
  294. package/.framework/migrations/018_apps_required_roles.sql +0 -17
  295. package/.framework/migrations/019_triggers_unique_constraint.sql +0 -13
  296. package/.framework/migrations/020_get_account_apps_role_filter.sql +0 -117
  297. package/.framework/migrations/021_app_domains.sql +0 -112
  298. package/.framework/migrations/022_route_prefix_domain_unique.sql +0 -21
  299. package/.framework/migrations/023_match_embeddings_rpc.sql +0 -78
  300. package/.framework/migrations/024_messages_account_id.sql +0 -9
  301. package/.framework/migrations/025_retype_threads_messages.sql +0 -240
  302. package/.framework/migrations/026_fix_rls_policies.sql +0 -47
  303. package/.framework/migrations/027_embedding_lifecycle.sql +0 -176
  304. package/.framework/migrations/028_fix_message_data_permissions.sql +0 -49
  305. package/.framework/migrations/029_tighten_support_triage_prompt.sql +0 -41
  306. package/.framework/migrations/030_messages_updated_by.sql +0 -6
  307. package/.framework/migrations/031_seed_upsert_constraints.sql +0 -35
  308. package/docs/APPS.md +0 -232
  309. package/docs/ARCHITECTURE.md +0 -246
  310. package/docs/INSTALL.md +0 -155
  311. package/docs/design-schema-spec.md +0 -1837
  312. package/docs/dev-guides/01-create-an-app.md +0 -183
  313. package/docs/dev-guides/02-create-a-function.md +0 -178
  314. package/docs/dev-guides/03-create-a-component.md +0 -178
  315. package/docs/dev-guides/04-webhook-handlers.md +0 -129
  316. package/docs/dev-guides/05-testing.md +0 -168
  317. package/docs/dev-guides/README.md +0 -40
  318. package/docs/npm-packages-plan.md +0 -288
  319. package/docs/spine-core--api-reference.md +0 -822
@@ -0,0 +1,557 @@
1
+ import { Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react'
2
+ import { apiFetch } from '../../lib/api'
3
+ import { formatDateTime } from '../../lib/utils'
4
+ import { cn } from '../../lib/utils'
5
+ import { Button } from '../ui/button'
6
+ import { Input } from '../ui/input'
7
+ import { Badge } from '../ui/badge'
8
+ import { Skeleton } from '../ui/skeleton'
9
+ import { Plus, Search, Edit, BookOpen, GripVertical, X } from 'lucide-react'
10
+
11
+ export interface KBArticleListProps {
12
+ typeSlugs: string[]
13
+ typeName?: string
14
+ onSelect?: (record: KBRecord) => void
15
+ onNew?: () => void
16
+ onEdit?: (record: KBRecord) => void
17
+ }
18
+
19
+ interface KBRecord {
20
+ id: string
21
+ title: string
22
+ description?: string
23
+ status: string
24
+ type_id: string
25
+ type_slug?: string
26
+ created_at: string
27
+ data: Record<string, any>
28
+ [key: string]: any
29
+ }
30
+
31
+ interface TreeNode extends KBRecord {
32
+ children: TreeNode[]
33
+ }
34
+
35
+ function isNullId(a: string | null | undefined): a is null | undefined {
36
+ return a === null || a === undefined || a === ''
37
+ }
38
+
39
+ function sameId(a: string | null | undefined, b: string | null | undefined): boolean {
40
+ return a === b || (isNullId(a) && isNullId(b))
41
+ }
42
+
43
+ function sameParent(record: KBRecord, parentId: string | undefined): boolean {
44
+ return sameId(record.data?.parent_id as string | undefined, parentId)
45
+ }
46
+
47
+ function sortByOrder(a: KBRecord, b: KBRecord): number {
48
+ return (a.data?.order ?? Number.MAX_SAFE_INTEGER) - (b.data?.order ?? Number.MAX_SAFE_INTEGER)
49
+ }
50
+
51
+ export function KBArticleList({ typeSlugs, typeName, onSelect, onNew, onEdit }: KBArticleListProps) {
52
+ const [typeRecords, setTypeRecords] = useState<any[]>([])
53
+ const [articles, setArticles] = useState<KBRecord[]>([])
54
+ const [loading, setLoading] = useState(true)
55
+ const [error, setError] = useState<string | null>(null)
56
+ const [searchQuery, setSearchQuery] = useState('')
57
+ const [searchResultIds, setSearchResultIds] = useState<Set<string> | null>(null)
58
+ const [searching, setSearching] = useState(false)
59
+ const [statusFilter, setStatusFilter] = useState<string>('all')
60
+ const [typeFilter, setTypeFilter] = useState<string>('all')
61
+ const [dragging, setDragging] = useState<string | null>(null)
62
+
63
+ const typeById = useMemo(() => {
64
+ const map: Record<string, any> = {}
65
+ typeRecords.forEach((t) => { map[t.id] = t })
66
+ return map
67
+ }, [typeRecords])
68
+
69
+ const typeSlugsSet = useMemo(() => new Set(typeSlugs), [typeSlugs])
70
+
71
+ const statusOptions = useMemo(() => {
72
+ const opts = new Set<string>()
73
+ typeRecords.forEach((t) => {
74
+ const options = t.design_schema?.fields?.status?.options || ['draft', 'published', 'archived']
75
+ options.forEach((o: string) => opts.add(o))
76
+ })
77
+ return ['all', ...Array.from(opts)]
78
+ }, [typeRecords])
79
+
80
+ const statusLabel = (s: string) => s === 'all' ? 'All' : (s?.charAt(0).toUpperCase() + s?.slice(1))
81
+
82
+ useEffect(() => {
83
+ let cancelled = false
84
+ setLoading(true)
85
+ apiFetch('/api/types?action=list')
86
+ .then((r) => r.json())
87
+ .then((j) => {
88
+ if (cancelled) return
89
+ const found = (j.data || []).filter((t: any) => typeSlugsSet.has(t.slug))
90
+ setTypeRecords(found)
91
+ })
92
+ .catch(() => setTypeRecords([]))
93
+ return () => { cancelled = true }
94
+ }, [typeSlugsSet])
95
+
96
+ const loadTree = useCallback(async () => {
97
+ if (typeRecords.length === 0) return
98
+ setLoading(true)
99
+ setError(null)
100
+ try {
101
+ const loadCache = async (title: string): Promise<Record<string, any[]>> => {
102
+ const res = await apiFetch(
103
+ `/api/admin-data?action=list&entity=items&type_slug=kb-article-list&title=${encodeURIComponent(title)}&limit=1&offset=0`
104
+ )
105
+ const json = await res.json()
106
+ const records = Array.isArray(json) ? json : (json.data || [])
107
+ const record = records[0]
108
+ return record?.data?.trees || {}
109
+ }
110
+ const [globalTrees, accountTrees] = await Promise.all([
111
+ loadCache('KB Article List - Global'),
112
+ loadCache('KB Article List - Account'),
113
+ ])
114
+ const allTrees = { ...globalTrees, ...accountTrees }
115
+ const flat = typeSlugs.flatMap((slug) => allTrees[slug] || [])
116
+ setArticles(flat.map((r: any) => ({
117
+ ...r,
118
+ type_slug: r.type_slug || typeById[r.type_id]?.slug,
119
+ })))
120
+ } catch (e: any) {
121
+ setError(e.message || 'Failed to load articles')
122
+ setArticles([])
123
+ } finally {
124
+ setLoading(false)
125
+ }
126
+ }, [typeRecords, typeSlugs, typeById])
127
+
128
+ const handleSearch = useCallback(async (query: string) => {
129
+ if (typeRecords.length === 0 || query.trim().length < 2) {
130
+ setSearchResultIds(null)
131
+ return
132
+ }
133
+ setSearching(true)
134
+ setError(null)
135
+ try {
136
+ const res = await apiFetch('/api/kb?action=search', {
137
+ method: 'POST',
138
+ headers: { 'Content-Type': 'application/json' },
139
+ body: JSON.stringify({
140
+ query: query.trim(),
141
+ type_slugs: typeSlugs,
142
+ sources: ['kb'],
143
+ limit: 100,
144
+ similarity_threshold: 0.4,
145
+ }),
146
+ })
147
+ const json = await res.json()
148
+ console.log('[KBArticleList] search json:', json)
149
+ const results = json.data?.results || json.results || []
150
+ const ids = new Set<string>(results.map((r: any) => r.id).filter(Boolean))
151
+ console.log('[KBArticleList] search results:', results.length, 'ids:', ids.size)
152
+ setSearchResultIds(ids.size > 0 ? ids : new Set())
153
+ } catch (e: any) {
154
+ setError(e.message || 'Search failed')
155
+ setSearchResultIds(null)
156
+ } finally {
157
+ setSearching(false)
158
+ }
159
+ }, [typeRecords, typeSlugs])
160
+
161
+ useEffect(() => {
162
+ loadTree()
163
+ }, [loadTree])
164
+
165
+ const hasActiveSearch = searchResultIds !== null
166
+
167
+ const { visibleArticles, roots, byId } = useMemo(() => {
168
+ let out = articles
169
+ if (statusFilter !== 'all') out = out.filter((a) => a.status === statusFilter)
170
+ if (typeFilter !== 'all') out = out.filter((a) => a.type_slug === typeFilter || a.data?.kb_type === typeFilter)
171
+
172
+ const fullById = new Map<string, KBRecord>()
173
+ for (const a of articles) fullById.set(a.id, a)
174
+
175
+ if (searchResultIds) {
176
+ const ancestorSet = new Set<string>(searchResultIds)
177
+ for (const id of searchResultIds) {
178
+ let current = fullById.get(id)
179
+ while (current?.data?.parent_id) {
180
+ const parentId = current.data.parent_id as string
181
+ if (ancestorSet.has(parentId)) break
182
+ ancestorSet.add(parentId)
183
+ current = fullById.get(parentId)
184
+ }
185
+ }
186
+ out = out.filter((a) => ancestorSet.has(a.id))
187
+ }
188
+
189
+ const nodes: TreeNode[] = out.map((a) => ({ ...a, children: [] }))
190
+ const byId = new Map<string, TreeNode>()
191
+ const idSet = new Set<string>()
192
+ for (const n of nodes) {
193
+ byId.set(n.id, n)
194
+ idSet.add(n.id)
195
+ }
196
+ const roots: TreeNode[] = []
197
+ for (const n of nodes) {
198
+ const parentId = n.data?.parent_id as string | null | undefined
199
+ if (parentId && idSet.has(parentId)) {
200
+ const parent = byId.get(parentId)
201
+ if (parent) parent.children.push(n)
202
+ } else {
203
+ roots.push(n)
204
+ }
205
+ }
206
+ for (const node of byId.values()) {
207
+ node.children = node.children.sort(sortByOrder)
208
+ }
209
+ return { visibleArticles: out, roots: roots.sort(sortByOrder), byId }
210
+ }, [articles, statusFilter, typeFilter, searchResultIds])
211
+
212
+ const isTreeMode = useMemo(
213
+ () => statusFilter === 'all' && typeFilter === 'all',
214
+ [statusFilter, typeFilter]
215
+ )
216
+
217
+ const handleDrop = async (itemId: string, targetParentId: string | undefined, insertIndex: number) => {
218
+ const item = articles.find((a) => a.id === itemId)
219
+ if (!item) return
220
+ setDragging(null)
221
+
222
+ const oldParentId = item.data?.parent_id as string | undefined
223
+ const isSameGroup = sameId(oldParentId, targetParentId)
224
+ let effectiveIndex = insertIndex
225
+
226
+ if (isSameGroup) {
227
+ const fullTargetGroup = [...articles]
228
+ .filter((a) => sameParent(a, targetParentId))
229
+ .sort(sortByOrder)
230
+ const originalIndex = fullTargetGroup.findIndex((a) => a.id === itemId)
231
+ if (originalIndex !== -1 && insertIndex > originalIndex) {
232
+ effectiveIndex = insertIndex - 1
233
+ }
234
+ }
235
+
236
+ const targetChildren = articles
237
+ .filter((a) => sameParent(a, targetParentId) && a.id !== itemId)
238
+ .sort(sortByOrder)
239
+ const newGroup = [...targetChildren]
240
+ newGroup.splice(effectiveIndex, 0, item)
241
+
242
+ const updates: { id: string; type_slug: string; data: any }[] = []
243
+
244
+ newGroup.forEach((a, order) => {
245
+ const nextData = { ...a.data, order }
246
+ if (a.id === itemId) {
247
+ nextData.parent_id = targetParentId ?? null
248
+ }
249
+ const parentChanged = a.id === itemId && !sameId(a.data?.parent_id as string | undefined, targetParentId)
250
+ if (a.data?.order !== order || parentChanged) {
251
+ updates.push({
252
+ id: a.id,
253
+ type_slug: a.type_slug || typeById[a.type_id]?.slug || 'kb-spine-public',
254
+ data: nextData,
255
+ })
256
+ }
257
+ })
258
+
259
+ if (!isSameGroup) {
260
+ const sourceChildren = articles
261
+ .filter((a) => sameParent(a, oldParentId) && a.id !== itemId)
262
+ .sort(sortByOrder)
263
+ sourceChildren.forEach((a, order) => {
264
+ if (a.data?.order !== order) {
265
+ updates.push({
266
+ id: a.id,
267
+ type_slug: a.type_slug || typeById[a.type_id]?.slug || 'kb-spine-public',
268
+ data: { ...a.data, order },
269
+ })
270
+ }
271
+ })
272
+ }
273
+
274
+ if (updates.length === 0) return
275
+
276
+ try {
277
+ await Promise.all(
278
+ updates.map((u) =>
279
+ apiFetch(`/api/admin-data?entity=items&id=${u.id}`, {
280
+ method: 'PATCH',
281
+ headers: { 'Content-Type': 'application/json' },
282
+ body: JSON.stringify({ type_slug: u.type_slug, data: u.data }),
283
+ })
284
+ )
285
+ )
286
+ await loadTree()
287
+ } catch (e: any) {
288
+ setError(e.message || 'Failed to update order')
289
+ }
290
+ }
291
+
292
+ const DropIndicator = () => (
293
+ <div className="h-0.5 bg-primary rounded-full my-1" />
294
+ )
295
+
296
+ function getDropIndex(e: React.DragEvent, container: HTMLElement | null): number {
297
+ if (!container) return 0
298
+ const itemEls = Array.from(container.children).filter((c) => c.getAttribute('data-item') === 'true')
299
+ if (itemEls.length === 0) return 0
300
+ const containerRect = container.getBoundingClientRect()
301
+ const mouseY = e.clientY - containerRect.top
302
+ for (let i = 0; i < itemEls.length; i++) {
303
+ const rect = itemEls[i].getBoundingClientRect()
304
+ const centerY = rect.top + rect.height / 2 - containerRect.top
305
+ if (mouseY < centerY) return i
306
+ }
307
+ return itemEls.length
308
+ }
309
+
310
+ function SortableDropZone({
311
+ parentId,
312
+ nodes,
313
+ depth,
314
+ }: {
315
+ parentId: string | undefined
316
+ nodes: TreeNode[]
317
+ depth: number
318
+ }): JSX.Element {
319
+ const [over, setOver] = useState(false)
320
+ const [insertIndex, setInsertIndex] = useState<number | null>(null)
321
+ const listRef = useRef<HTMLDivElement>(null)
322
+
323
+ const computeIndex = useCallback(
324
+ (e: React.DragEvent) => getDropIndex(e, listRef.current),
325
+ []
326
+ )
327
+
328
+ return (
329
+ <div
330
+ ref={listRef}
331
+ className={cn(
332
+ 'space-y-0.5',
333
+ depth > 0 ? 'pl-4 border-l border-border/50' : '',
334
+ over && 'rounded-md bg-primary/5',
335
+ depth > 0 && nodes.length === 0 ? 'min-h-[1.5rem]' : ''
336
+ )}
337
+ onDragEnter={(e) => {
338
+ e.preventDefault()
339
+ e.stopPropagation()
340
+ if (!e.relatedTarget || !listRef.current?.contains(e.relatedTarget as Node)) {
341
+ setOver(true)
342
+ }
343
+ }}
344
+ onDragLeave={(e) => {
345
+ e.preventDefault()
346
+ e.stopPropagation()
347
+ if (!e.relatedTarget || !listRef.current?.contains(e.relatedTarget as Node)) {
348
+ setOver(false)
349
+ setInsertIndex(null)
350
+ }
351
+ }}
352
+ onDragOver={(e) => {
353
+ e.preventDefault()
354
+ e.stopPropagation()
355
+ e.dataTransfer.dropEffect = 'move'
356
+ setInsertIndex(computeIndex(e))
357
+ }}
358
+ onDrop={(e) => {
359
+ e.preventDefault()
360
+ e.stopPropagation()
361
+ setOver(false)
362
+ const id = e.dataTransfer.getData('text/plain')
363
+ const index = computeIndex(e)
364
+ setInsertIndex(null)
365
+ if (id) handleDrop(id, parentId, index)
366
+ }}
367
+ >
368
+ {nodes.length === 0 && over && (
369
+ <div className="text-xs text-muted-foreground py-2 px-3 border border-dashed border-border/60 rounded-md text-center">
370
+ Drop here as child
371
+ </div>
372
+ )}
373
+ {nodes.map((node, i) => (
374
+ <Fragment key={node.id}>
375
+ {insertIndex === i && <DropIndicator />}
376
+ <div
377
+ data-item="true"
378
+ draggable={isTreeMode && !hasActiveSearch}
379
+ onDragStart={(e) => {
380
+ setDragging(node.id)
381
+ e.dataTransfer.setData('text/plain', node.id)
382
+ e.dataTransfer.effectAllowed = 'move'
383
+ }}
384
+ onDragEnd={() => setDragging(null)}
385
+ onDragOver={(e) => {
386
+ e.preventDefault()
387
+ e.dataTransfer.dropEffect = 'move'
388
+ }}
389
+ onDrop={(e) => {
390
+ e.preventDefault()
391
+ e.stopPropagation()
392
+ const id = e.dataTransfer.getData('text/plain')
393
+ if (id) handleDrop(id, node.id, node.children.length)
394
+ }}
395
+ className={cn(
396
+ 'group flex items-center gap-2 rounded-md py-1.5 px-2 transition-colors select-none',
397
+ dragging === node.id ? 'opacity-50' : 'hover:bg-accent/50',
398
+ isTreeMode ? 'cursor-grab active:cursor-grabbing' : ''
399
+ )}
400
+ style={{ paddingLeft: `${depth * 16 + 8}px` }}
401
+ >
402
+ {isTreeMode && (
403
+ <GripVertical size={14} className="shrink-0 text-muted-foreground opacity-0 group-hover:opacity-100" />
404
+ )}
405
+ <span className="flex-1 text-left text-sm truncate">
406
+ {node.title}
407
+ </span>
408
+ <Badge variant="outline" className="text-[0.625rem] shrink-0">
409
+ {node.type_slug || '—'}
410
+ </Badge>
411
+ {onEdit && (
412
+ <Button
413
+ variant="ghost"
414
+ size="sm"
415
+ className="h-6 w-6 p-0 shrink-0"
416
+ onClick={() => onEdit(node)}
417
+ >
418
+ <Edit size={14} />
419
+ </Button>
420
+ )}
421
+ </div>
422
+ <SortableDropZone parentId={node.id} nodes={node.children} depth={depth + 1} />
423
+ </Fragment>
424
+ ))}
425
+ {insertIndex === nodes.length && <DropIndicator />}
426
+ </div>
427
+ )
428
+ }
429
+
430
+ const renderFlatRow = (record: KBRecord) => (
431
+ <div
432
+ key={record.id}
433
+ className="flex items-center gap-3 py-2 px-2 hover:bg-accent/50 rounded-md"
434
+ >
435
+ <button onClick={() => onSelect?.(record)} className="flex-1 text-left text-sm truncate">
436
+ {record.title}
437
+ </button>
438
+ <Badge variant={record.status === 'published' ? 'default' : 'secondary'} className="text-[0.625rem]">
439
+ {record.status}
440
+ </Badge>
441
+ <Badge variant="outline" className="text-[0.625rem]">
442
+ {record.type_slug || '—'}
443
+ </Badge>
444
+ <span className="text-xs text-muted-foreground hidden sm:inline">
445
+ {formatDateTime(record.created_at)}
446
+ </span>
447
+ {onEdit && (
448
+ <Button variant="ghost" size="sm" className="h-6 w-6 p-0" onClick={() => onEdit(record)}>
449
+ <Edit size={14} />
450
+ </Button>
451
+ )}
452
+ </div>
453
+ )
454
+
455
+ return (
456
+ <div className="p-6 h-full overflow-y-auto space-y-4">
457
+ <div className="flex items-center justify-between">
458
+ <h1 className="text-2xl font-semibold flex items-center gap-2">
459
+ <BookOpen className="w-6 h-6" />
460
+ {typeName || 'Knowledge Base'}
461
+ </h1>
462
+ {onNew && (
463
+ <Button onClick={onNew}>
464
+ <Plus className="w-4 h-4 mr-2" />
465
+ New Article
466
+ </Button>
467
+ )}
468
+ </div>
469
+
470
+ <div className="flex flex-wrap gap-2 items-center">
471
+ <div className="relative flex-1 max-w-md flex items-center gap-2">
472
+ <div className="relative flex-1">
473
+ <Search className="absolute left-2.5 top-2 h-4 w-4 text-muted-foreground" />
474
+ <Input
475
+ placeholder="Search articles…"
476
+ value={searchQuery}
477
+ onChange={(e) => { setSearchQuery(e.target.value) }}
478
+ onKeyDown={(e) => { if (e.key === 'Enter') handleSearch(searchQuery) }}
479
+ className="pl-8"
480
+ />
481
+ {searchQuery && (
482
+ <button
483
+ onClick={() => { setSearchQuery(''); setSearchResultIds(null) }}
484
+ className="absolute right-2.5 top-2 text-muted-foreground hover:text-foreground"
485
+ >
486
+ <X size={14} />
487
+ </button>
488
+ )}
489
+ </div>
490
+ <Button
491
+ size="sm"
492
+ onClick={() => { handleSearch(searchQuery) }}
493
+ disabled={searching || searchQuery.trim().length < 2}
494
+ >
495
+ {searching ? 'Searching…' : 'Search'}
496
+ </Button>
497
+ </div>
498
+ {statusOptions.map((s: string) => (
499
+ <Button
500
+ key={s}
501
+ variant={statusFilter === s ? 'default' : 'outline'}
502
+ size="sm"
503
+ onClick={() => { setStatusFilter(s) }}
504
+ >
505
+ {statusLabel(s)}
506
+ </Button>
507
+ ))}
508
+ </div>
509
+
510
+ <div className="flex flex-wrap gap-2 items-center">
511
+ <span className="text-sm text-muted-foreground">Type:</span>
512
+ <Button
513
+ variant={typeFilter === 'all' ? 'default' : 'outline'}
514
+ size="sm"
515
+ onClick={() => { setTypeFilter('all') }}
516
+ >
517
+ All
518
+ </Button>
519
+ {typeRecords.map((t) => (
520
+ <Button
521
+ key={t.slug}
522
+ variant={typeFilter === t.slug ? 'default' : 'outline'}
523
+ size="sm"
524
+ onClick={() => { setTypeFilter(t.slug) }}
525
+ >
526
+ {t.name || t.slug}
527
+ </Button>
528
+ ))}
529
+ </div>
530
+
531
+ {error && (
532
+ <div className="p-3 text-sm text-destructive bg-destructive/10 border border-destructive/20 rounded-md">
533
+ {error}
534
+ </div>
535
+ )}
536
+
537
+ {loading ? (
538
+ <div className="p-4 space-y-2">
539
+ {Array.from({ length: 5 }).map((_, i) => <Skeleton key={i} className="h-8 w-full" />)}
540
+ </div>
541
+ ) : visibleArticles.length === 0 ? (
542
+ <div className="p-8 text-center text-muted-foreground border border-dashed rounded-md">
543
+ <BookOpen className="h-10 w-10 mx-auto mb-3 opacity-30" />
544
+ <p className="text-sm">No articles found.</p>
545
+ </div>
546
+ ) : isTreeMode ? (
547
+ <div className="space-y-1">
548
+ <SortableDropZone parentId={undefined} nodes={roots} depth={0} />
549
+ </div>
550
+ ) : (
551
+ <div className="space-y-1 border-t border-border/50 pt-2">
552
+ {visibleArticles.map(renderFlatRow)}
553
+ </div>
554
+ )}
555
+ </div>
556
+ )
557
+ }
@@ -1,13 +1,21 @@
1
- /**
2
- * @module src/components/layout/AppShell
3
- * @audience installer
4
- * @layer frontend-component
5
- * @stability stable
6
- *
7
- * Application shell with Sidebar-05 layout for professional SaaS look.
8
- * Wraps page content with collapsible sidebar navigation.
9
- */
10
1
 
2
+
3
+
4
+ // CIL[BEGIN chunk_id="appshell_moduleheader_1783929861" type="module_header" identity="imports" parent="none"]
5
+ // CIL[DOC-MACRO purpose="Import React, router, and UI components for app shell layout"]
6
+ // CIL[DOC-RESPONSIBILITY responsibility="Provide dependencies for AppShell component"]
7
+ // CIL[DOC-CONTEXT module="src/components/layout/AppShell" layer="frontend-component" related_files="[\"custom/apps/cortex/pages/operations/OperationsDashboard.tsx\",\"custom/apps/portal/pages/ContentPage.tsx\",\".framework/src/components/ui/dialog.tsx\",\".framework/src/pages/admin/TypeDetailPage.tsx\",\"custom/apps/cortex/functions/custom_cil-embeddings.ts\",\"\\\"]"]
8
+ // CIL[DOC-CONTRACT inputs="none" outputs="React, Link, Sidebar components, Separator, Breadcrumb components" errors="none" side_effects="none"]
9
+ // CIL[DOC-MICRO logic_summary="Imports React, router Link, shadcn sidebar, separator and breadcrumb components" notable_decisions="[]"]
10
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
11
+ // CIL[DOC-STABILITY stability="stable"]
12
+ // CIL[DOC-AUDIENCE audience="developer"]
13
+ // CIL[DOC-VECTOR type="context" content="Layer: frontend-component. Direct callers: 0. Direct calls: 0. Related files: custom/apps/cortex/pages/operations/OperationsDashboard.tsx, custom/apps/portal/pages/ContentPage.tsx, .framework/src/components/ui/dialog.tsx, .framework/src/pages/admin/TypeDetailPage.tsx, custom/apps/cortex/functions/custom_cil-embeddings.ts, \."]
14
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
15
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
16
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
17
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
18
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
11
19
  import * as React from "react"
12
20
  import { Link } from 'react-router-dom'
13
21
  import { SidebarProvider, SidebarInset, SidebarTrigger } from "../ui/sidebar"
@@ -20,26 +28,56 @@ import {
20
28
  BreadcrumbPage,
21
29
  BreadcrumbSeparator,
22
30
  } from "../ui/breadcrumb"
23
-
31
+ // CIL[END chunk_id="appshell_moduleheader_1783929861"]
32
+ // CIL[BEGIN chunk_id="appshell_appshellprops_1783929861" type="interface" identity="AppShellProps" parent="none"]
33
+ // CIL[DOC-MACRO purpose="Define props interface for AppShell component with sidebar and navigation"]
34
+ // CIL[DOC-RESPONSIBILITY responsibility="Specify AppShell component contract for professional layout"]
35
+ // CIL[DOC-CONTEXT module="src/components/layout/AppShell" layer="frontend-component" related_files="[\"custom/apps/portal/pages/ContentPage.tsx\",\".framework/functions/_shared/schema-utils.ts\",\".framework/src/components/ui/dialog.tsx\",\".framework/functions/_shared/principal.ts\",\"custom/apps/cortex/pages/operations/OperationsDashboard.tsx\",\"\\\"]"]
36
+ // CIL[DOC-CONTRACT inputs="none" outputs="AppShellProps interface" errors="none" side_effects="none"]
37
+ // CIL[DOC-MICRO logic_summary="Interface with sidebar, children, breadcrumbs, and optional header actions" notable_decisions="[]"]
38
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
39
+ // CIL[DOC-STABILITY stability="stable"]
40
+ // CIL[DOC-VECTOR type="context" content="Layer: frontend-component. Direct callers: 0. Direct calls: 0. Related files: custom/apps/portal/pages/ContentPage.tsx, .framework/functions/_shared/schema-utils.ts, .framework/src/components/ui/dialog.tsx, .framework/functions/_shared/principal.ts, custom/apps/cortex/pages/operations/OperationsDashboard.tsx, \."]
41
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
42
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
43
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
44
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
45
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
24
46
  interface AppShellProps {
25
47
  sidebar: React.ReactNode
26
48
  children: React.ReactNode
27
49
  breadcrumbs?: { title: string; url?: string }[]
28
50
  headerActions?: React.ReactNode
51
+ banner?: React.ReactNode
29
52
  }
30
-
53
+ // CIL[END chunk_id="appshell_appshellprops_1783929861"]
54
+ // CIL[BEGIN chunk_id="appshell_appshell_1783929861" type="function" identity="AppShell" parent="none"]
55
+ // CIL[DOC-MACRO purpose="Render professional app shell with sidebar, header breadcrumbs and main content"]
56
+ // CIL[DOC-RESPONSIBILITY responsibility="Provide complete app layout with navigation and content areas"]
57
+ // CIL[DOC-CONTEXT module="src/components/layout/AppShell" layer="frontend-component" related_files="[\"custom/apps/portal/pages/ContentPage.tsx\",\"custom/apps/cortex/pages/operations/OperationsDashboard.tsx\",\"custom/apps/cortex/functions/custom_cil-embeddings.ts\",\".framework/src/pages/admin/RolesPage.tsx\",\"\\\"]"]
58
+ // CIL[DOC-CONTRACT inputs="none" outputs="JSX app shell layout" errors="none" side_effects="Manages sidebar state and provides navigation structure"]
59
+ // CIL[DOC-MICRO logic_summary="SidebarProvider with collapsible sidebar, header with breadcrumbs, and main content area" notable_decisions="[]"]
60
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
61
+ // CIL[DOC-STABILITY stability="stable"]
62
+ // CIL[DOC-VECTOR type="context" content="Layer: frontend-component. Direct callers: 0. Direct calls: 0. Related files: custom/apps/portal/pages/ContentPage.tsx, custom/apps/cortex/pages/operations/OperationsDashboard.tsx, custom/apps/cortex/functions/custom_cil-embeddings.ts, .framework/src/pages/admin/RolesPage.tsx, \."]
63
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
64
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
65
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
66
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
67
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
31
68
  export function AppShell({
32
69
  sidebar,
33
70
  children,
34
71
  breadcrumbs,
35
72
  headerActions,
73
+ banner,
36
74
  }: AppShellProps) {
37
75
  return (
38
76
  <SidebarProvider>
39
77
  {sidebar}
40
- <SidebarInset>
41
- {/* Header */}
42
- <header className="flex h-16 shrink-0 items-center gap-2 transition-[width,height] ease-linear group-has-[[data-collapsible=icon]]/sidebar-wrapper:h-12">
78
+ <SidebarInset className="h-svh min-h-0 overflow-hidden">
79
+ {banner}
80
+ <header className="flex h-12 shrink-0 items-center gap-2 transition-[width,height] ease-linear group-has-[[data-collapsible=icon]]/sidebar-wrapper:h-12">
43
81
  <div className="flex items-center gap-2 px-4">
44
82
  <SidebarTrigger className="-ml-1" />
45
83
  <Separator orientation="vertical" className="mr-2 h-4" />
@@ -81,11 +119,11 @@ export function AppShell({
81
119
  )}
82
120
  </header>
83
121
 
84
- {/* Main Content */}
85
- <main className="flex-1 flex flex-col min-h-0 overflow-hidden p-4 md:p-6 lg:p-8">
122
+ <main className="flex-1 flex flex-col min-h-0 overflow-auto p-4 md:p-6 lg:p-8">
86
123
  {children}
87
124
  </main>
88
125
  </SidebarInset>
89
126
  </SidebarProvider>
90
127
  )
91
128
  }
129
+ // CIL[END chunk_id="appshell_appshell_1783929861"]