@pilotiq/pilotiq 0.24.1 → 0.24.2

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 (480) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/boost/guidelines.md +566 -0
  3. package/boost/skills/pilotiq-fields/SKILL.md +47 -0
  4. package/boost/skills/pilotiq-fields/rules/field-catalog.md +288 -0
  5. package/boost/skills/pilotiq-fields/rules/reactive-fields.md +199 -0
  6. package/boost/skills/pilotiq-fields/rules/validation.md +198 -0
  7. package/boost/skills/pilotiq-relations/SKILL.md +47 -0
  8. package/boost/skills/pilotiq-relations/rules/relation-managers.md +256 -0
  9. package/boost/skills/pilotiq-relations/rules/repeater-relationship.md +177 -0
  10. package/boost/skills/pilotiq-resource/SKILL.md +61 -0
  11. package/boost/skills/pilotiq-resource/rules/authorization.md +242 -0
  12. package/boost/skills/pilotiq-resource/rules/defining-resources.md +228 -0
  13. package/boost/skills/pilotiq-resource/rules/page-overrides.md +296 -0
  14. package/package.json +6 -1
  15. package/.turbo/turbo-build.log +0 -8
  16. package/CLAUDE.md +0 -265
  17. package/src/Cluster.test.ts +0 -283
  18. package/src/Cluster.ts +0 -83
  19. package/src/Column.test.ts +0 -199
  20. package/src/Column.ts +0 -710
  21. package/src/Global.test.ts +0 -367
  22. package/src/Global.ts +0 -169
  23. package/src/Page.test.ts +0 -114
  24. package/src/Page.ts +0 -208
  25. package/src/Pilotiq.perf.test.ts +0 -252
  26. package/src/Pilotiq.test.ts +0 -129
  27. package/src/Pilotiq.ts +0 -1158
  28. package/src/PilotiqRegistry.ts +0 -36
  29. package/src/PilotiqServiceProvider.ts +0 -121
  30. package/src/RelationManager.test.ts +0 -400
  31. package/src/RelationManager.ts +0 -527
  32. package/src/RenderHook.test.ts +0 -252
  33. package/src/RenderHook.ts +0 -242
  34. package/src/Resource.test.ts +0 -284
  35. package/src/Resource.ts +0 -526
  36. package/src/RightPanel.test.ts +0 -202
  37. package/src/RightPanel.ts +0 -132
  38. package/src/Tab.test.ts +0 -91
  39. package/src/Tab.ts +0 -156
  40. package/src/UserMenuItem.ts +0 -145
  41. package/src/actions/Action.test.ts +0 -2526
  42. package/src/actions/Action.ts +0 -1515
  43. package/src/actions/ActionGroup.test.ts +0 -112
  44. package/src/actions/ActionGroup.ts +0 -173
  45. package/src/actions/attachFactory.ts +0 -172
  46. package/src/actions/bulkFactories.ts +0 -168
  47. package/src/actions/crudFactories.ts +0 -220
  48. package/src/actions/exportFactory.ts +0 -225
  49. package/src/actions/factoryHelpers.ts +0 -177
  50. package/src/actions/importFactory.ts +0 -243
  51. package/src/actions/index.ts +0 -17
  52. package/src/actions/m2mFactories.ts +0 -193
  53. package/src/actions/relationFactories.ts +0 -372
  54. package/src/applyPageHooks.test.ts +0 -463
  55. package/src/applyPageHooks.ts +0 -330
  56. package/src/authorization.test.ts +0 -483
  57. package/src/breadcrumbs.test.ts +0 -238
  58. package/src/cells/coerce.test.ts +0 -85
  59. package/src/cells/coerce.ts +0 -84
  60. package/src/clusterPaths.ts +0 -35
  61. package/src/columns/BadgeColumn.test.ts +0 -54
  62. package/src/columns/BadgeColumn.ts +0 -32
  63. package/src/columns/BooleanColumn.test.ts +0 -41
  64. package/src/columns/BooleanColumn.ts +0 -18
  65. package/src/columns/ColorColumn.test.ts +0 -37
  66. package/src/columns/ColorColumn.ts +0 -38
  67. package/src/columns/IconColumn.test.ts +0 -54
  68. package/src/columns/IconColumn.ts +0 -37
  69. package/src/columns/ImageColumn.test.ts +0 -41
  70. package/src/columns/ImageColumn.ts +0 -28
  71. package/src/columns/SelectColumn.ts +0 -98
  72. package/src/columns/TextColumn.test.ts +0 -190
  73. package/src/columns/TextColumn.ts +0 -20
  74. package/src/columns/TextInputColumn.ts +0 -68
  75. package/src/columns/ToggleColumn.ts +0 -46
  76. package/src/columns/editableColumns.test.ts +0 -238
  77. package/src/columns/index.ts +0 -9
  78. package/src/defaultGlobalPages.ts +0 -95
  79. package/src/defaultPages.test.ts +0 -634
  80. package/src/defaultPages.ts +0 -617
  81. package/src/defaultViewPage.test.ts +0 -147
  82. package/src/elements/Form.test.ts +0 -223
  83. package/src/elements/Form.ts +0 -416
  84. package/src/elements/ListTabs.ts +0 -28
  85. package/src/elements/Table.test.ts +0 -422
  86. package/src/elements/Table.ts +0 -850
  87. package/src/elements/TableGroup.test.ts +0 -260
  88. package/src/elements/TableGroup.ts +0 -334
  89. package/src/elements/dispatchAction.test.ts +0 -463
  90. package/src/elements/dispatchAction.ts +0 -355
  91. package/src/elements/dispatchForm.test.ts +0 -477
  92. package/src/elements/dispatchForm.ts +0 -1993
  93. package/src/elements/dispatchTable.test.ts +0 -1514
  94. package/src/elements/dispatchTable.ts +0 -745
  95. package/src/elements/index.ts +0 -21
  96. package/src/entries/BadgeEntry.ts +0 -39
  97. package/src/entries/CodeEntry.test.ts +0 -40
  98. package/src/entries/CodeEntry.ts +0 -52
  99. package/src/entries/ColorEntry.ts +0 -63
  100. package/src/entries/ComponentEntry.test.ts +0 -173
  101. package/src/entries/ComponentEntry.ts +0 -95
  102. package/src/entries/Entry.ts +0 -304
  103. package/src/entries/IconEntry.ts +0 -49
  104. package/src/entries/ImageEntry.ts +0 -61
  105. package/src/entries/KeyValueEntry.ts +0 -47
  106. package/src/entries/RepeatableEntry.test.ts +0 -239
  107. package/src/entries/RepeatableEntry.ts +0 -173
  108. package/src/entries/TextEntry.test.ts +0 -394
  109. package/src/entries/TextEntry.ts +0 -60
  110. package/src/entries/index.ts +0 -12
  111. package/src/entries/leaves.test.ts +0 -306
  112. package/src/entries/registry.ts +0 -54
  113. package/src/fields/BuilderField.test.ts +0 -1188
  114. package/src/fields/BuilderField.ts +0 -605
  115. package/src/fields/BuilderRelationship.test.ts +0 -811
  116. package/src/fields/CheckboxField.test.ts +0 -44
  117. package/src/fields/CheckboxField.ts +0 -27
  118. package/src/fields/CheckboxListField.test.ts +0 -99
  119. package/src/fields/CheckboxListField.ts +0 -66
  120. package/src/fields/ColorPickerField.test.ts +0 -33
  121. package/src/fields/ColorPickerField.ts +0 -25
  122. package/src/fields/DateField.ts +0 -54
  123. package/src/fields/DateTimeField.test.ts +0 -55
  124. package/src/fields/EmailField.ts +0 -16
  125. package/src/fields/Field.test.ts +0 -654
  126. package/src/fields/Field.ts +0 -817
  127. package/src/fields/FileUploadField.test.ts +0 -143
  128. package/src/fields/FileUploadField.ts +0 -159
  129. package/src/fields/HiddenField.test.ts +0 -27
  130. package/src/fields/HiddenField.ts +0 -28
  131. package/src/fields/KeyValueField.test.ts +0 -105
  132. package/src/fields/KeyValueField.ts +0 -55
  133. package/src/fields/MarkdownField.test.ts +0 -167
  134. package/src/fields/MarkdownField.ts +0 -162
  135. package/src/fields/NumberField.ts +0 -33
  136. package/src/fields/RadioField.test.ts +0 -94
  137. package/src/fields/RadioField.ts +0 -67
  138. package/src/fields/RepeaterField.test.ts +0 -1806
  139. package/src/fields/RepeaterField.ts +0 -939
  140. package/src/fields/RepeaterRelationship.test.ts +0 -1923
  141. package/src/fields/RepeaterSimple.test.ts +0 -248
  142. package/src/fields/RowButton.test.ts +0 -219
  143. package/src/fields/RowButton.ts +0 -135
  144. package/src/fields/SelectField.test.ts +0 -192
  145. package/src/fields/SelectField.ts +0 -235
  146. package/src/fields/SliderField.test.ts +0 -50
  147. package/src/fields/SliderField.ts +0 -53
  148. package/src/fields/SlugField.ts +0 -24
  149. package/src/fields/TagsInputField.test.ts +0 -154
  150. package/src/fields/TagsInputField.ts +0 -133
  151. package/src/fields/TextField.test.ts +0 -213
  152. package/src/fields/TextField.ts +0 -177
  153. package/src/fields/TextareaField.test.ts +0 -58
  154. package/src/fields/TextareaField.ts +0 -59
  155. package/src/fields/ToggleButtonsField.test.ts +0 -106
  156. package/src/fields/ToggleButtonsField.ts +0 -59
  157. package/src/fields/ToggleField.ts +0 -16
  158. package/src/fields/disableOptionsWhenSelectedInSiblingRepeaterItems.test.ts +0 -319
  159. package/src/fields/optionsResolver.ts +0 -95
  160. package/src/fields/resolveField.ts +0 -28
  161. package/src/filters/BooleanFilter.ts +0 -35
  162. package/src/filters/DateRangeFilter.test.ts +0 -194
  163. package/src/filters/DateRangeFilter.ts +0 -148
  164. package/src/filters/Filter.test.ts +0 -268
  165. package/src/filters/Filter.ts +0 -184
  166. package/src/filters/FormFilter.test.ts +0 -238
  167. package/src/filters/FormFilter.ts +0 -215
  168. package/src/filters/MultiSelectFilter.test.ts +0 -119
  169. package/src/filters/MultiSelectFilter.ts +0 -78
  170. package/src/filters/QueryBuilderFilter.test.ts +0 -662
  171. package/src/filters/QueryBuilderFilter.ts +0 -398
  172. package/src/filters/SelectFilter.ts +0 -46
  173. package/src/filters/TernaryFilter.test.ts +0 -160
  174. package/src/filters/TernaryFilter.ts +0 -72
  175. package/src/filters/TrashedFilter.test.ts +0 -149
  176. package/src/filters/TrashedFilter.ts +0 -55
  177. package/src/filters/queryBuilder/BooleanConstraint.ts +0 -31
  178. package/src/filters/queryBuilder/Constraint.ts +0 -115
  179. package/src/filters/queryBuilder/DateConstraint.ts +0 -69
  180. package/src/filters/queryBuilder/NumberConstraint.ts +0 -66
  181. package/src/filters/queryBuilder/SelectConstraint.ts +0 -72
  182. package/src/filters/queryBuilder/TextConstraint.ts +0 -64
  183. package/src/filters/queryBuilder/index.ts +0 -12
  184. package/src/icons/index.ts +0 -2
  185. package/src/icons/lucide.ts +0 -204
  186. package/src/icons/registry.test.ts +0 -56
  187. package/src/icons/registry.ts +0 -41
  188. package/src/icons/types.ts +0 -47
  189. package/src/index.ts +0 -525
  190. package/src/io/csv.test.ts +0 -142
  191. package/src/io/csv.ts +0 -170
  192. package/src/nestedRelationManagerData.test.ts +0 -547
  193. package/src/notifications/Notification.test.ts +0 -210
  194. package/src/notifications/Notification.ts +0 -354
  195. package/src/notifications/broadcast.test.ts +0 -110
  196. package/src/notifications/broadcast.ts +0 -95
  197. package/src/notifications/database.test.ts +0 -383
  198. package/src/notifications/database.ts +0 -398
  199. package/src/notifications/databaseNotifications.test.ts +0 -187
  200. package/src/notifications/dispatchNotificationAction.test.ts +0 -341
  201. package/src/notifications/dispatchNotificationAction.ts +0 -142
  202. package/src/notifications/flash.test.ts +0 -89
  203. package/src/notifications/flash.ts +0 -71
  204. package/src/notifications/index.ts +0 -45
  205. package/src/notifications/registerBroadcastAuth.test.ts +0 -134
  206. package/src/notifications/registerBroadcastAuth.ts +0 -100
  207. package/src/notifications/resolveSavedNotification.test.ts +0 -82
  208. package/src/notifications/resolveSavedNotification.ts +0 -59
  209. package/src/notifications/types.ts +0 -93
  210. package/src/orm/m2mAccessor.ts +0 -66
  211. package/src/orm/modelDefaults.test.ts +0 -633
  212. package/src/orm/modelDefaults.ts +0 -666
  213. package/src/pageData/breadcrumbs.ts +0 -288
  214. package/src/pageData/forms.ts +0 -578
  215. package/src/pageData/helpers.ts +0 -857
  216. package/src/pageData/misc.ts +0 -347
  217. package/src/pageData/navigation.ts +0 -842
  218. package/src/pageData/relationPages.ts +0 -1248
  219. package/src/pageData/relationTabs.ts +0 -286
  220. package/src/pageData/resourcePages.ts +0 -609
  221. package/src/pageData.test.ts +0 -1545
  222. package/src/pageData.ts +0 -341
  223. package/src/plugins/index.ts +0 -8
  224. package/src/plugins/themeEditor.test.ts +0 -36
  225. package/src/plugins/themeEditor.ts +0 -45
  226. package/src/react/AppShell.tsx +0 -251
  227. package/src/react/CollabExtensionFactoryRegistry.ts +0 -55
  228. package/src/react/CollabRoomContext.ts +0 -98
  229. package/src/react/CollabTextRendererRegistry.ts +0 -102
  230. package/src/react/CommandPalette.tsx +0 -375
  231. package/src/react/CurrentUserContext.tsx +0 -50
  232. package/src/react/CustomPageWrapperGate.tsx +0 -69
  233. package/src/react/CustomPageWrapperRegistry.ts +0 -45
  234. package/src/react/FieldFocusReporterRegistry.ts +0 -37
  235. package/src/react/FieldLabelSlotRegistry.ts +0 -30
  236. package/src/react/FieldPresenceRegistry.ts +0 -46
  237. package/src/react/FormCollabBindingRegistry.ts +0 -242
  238. package/src/react/FormStateContext.tsx +0 -591
  239. package/src/react/HeadHooks.tsx +0 -126
  240. package/src/react/MarkdownEditorRegistry.test.ts +0 -38
  241. package/src/react/MarkdownEditorRegistry.ts +0 -107
  242. package/src/react/NotificationActionStrip.tsx +0 -263
  243. package/src/react/NotificationBell.tsx +0 -426
  244. package/src/react/PendingSuggestionApplierRegistry.test.ts +0 -97
  245. package/src/react/PendingSuggestionApplierRegistry.ts +0 -98
  246. package/src/react/PendingSuggestionOverlayRegistry.ts +0 -54
  247. package/src/react/PendingSuggestionsContext.tsx +0 -172
  248. package/src/react/RecordWrapperGate.tsx +0 -58
  249. package/src/react/RecordWrapperRegistry.ts +0 -39
  250. package/src/react/RenderHookSlot.tsx +0 -32
  251. package/src/react/RightSidebar.tsx +0 -257
  252. package/src/react/RightSidebarContext.tsx +0 -234
  253. package/src/react/RightSidebarTrigger.tsx +0 -53
  254. package/src/react/RowCoordsContext.tsx +0 -23
  255. package/src/react/SchemaRenderer.tsx +0 -549
  256. package/src/react/SearchTrigger.tsx +0 -46
  257. package/src/react/ThemeProvider.tsx +0 -93
  258. package/src/react/ThemeSettingsPage.tsx +0 -579
  259. package/src/react/ThemeToggle.tsx +0 -20
  260. package/src/react/Toaster.tsx +0 -158
  261. package/src/react/UserMenu.tsx +0 -196
  262. package/src/react/WidgetDataContext.tsx +0 -157
  263. package/src/react/cells/EditableCell.tsx +0 -389
  264. package/src/react/component-slots.test.ts +0 -103
  265. package/src/react/component-slots.ts +0 -116
  266. package/src/react/fieldJsHandler.test.ts +0 -166
  267. package/src/react/fieldJsHandler.ts +0 -79
  268. package/src/react/fields/BuilderInput.tsx +0 -1078
  269. package/src/react/fields/CheckboxInput.tsx +0 -39
  270. package/src/react/fields/CheckboxListInput.tsx +0 -102
  271. package/src/react/fields/ColorInput.tsx +0 -71
  272. package/src/react/fields/DateFieldInput.tsx +0 -70
  273. package/src/react/fields/DateTimeInput.tsx +0 -62
  274. package/src/react/fields/FieldShell.tsx +0 -348
  275. package/src/react/fields/FileUploadInput.tsx +0 -639
  276. package/src/react/fields/HiddenInput.tsx +0 -17
  277. package/src/react/fields/KeyValueInput.tsx +0 -230
  278. package/src/react/fields/MarkdownInput.tsx +0 -560
  279. package/src/react/fields/RadioInput.tsx +0 -81
  280. package/src/react/fields/RepeaterInput.test.ts +0 -116
  281. package/src/react/fields/RepeaterInput.tsx +0 -1420
  282. package/src/react/fields/SelectFieldInput.tsx +0 -280
  283. package/src/react/fields/SliderInput.tsx +0 -81
  284. package/src/react/fields/TagsInput.tsx +0 -283
  285. package/src/react/fields/TextLikeInput.tsx +0 -256
  286. package/src/react/fields/ToggleButtonsInput.tsx +0 -60
  287. package/src/react/fields/ToggleFieldInput.tsx +0 -56
  288. package/src/react/fields/relationshipRenameDispatch.test.ts +0 -106
  289. package/src/react/fields/relationshipRenameDispatch.ts +0 -97
  290. package/src/react/fields/repeaterReconcile.test.ts +0 -114
  291. package/src/react/fields/repeaterReconcile.ts +0 -104
  292. package/src/react/fields/rowChromeButton.tsx +0 -336
  293. package/src/react/fields/rowState.ts +0 -106
  294. package/src/react/fields/syncRowGates.test.ts +0 -202
  295. package/src/react/fields/syncRowGates.ts +0 -66
  296. package/src/react/fields/textInputControls.tsx +0 -238
  297. package/src/react/fields/useRowReorderDnd.ts +0 -78
  298. package/src/react/formStateHelpers.test.ts +0 -508
  299. package/src/react/formStateHelpers.ts +0 -381
  300. package/src/react/hooks/use-mobile.ts +0 -19
  301. package/src/react/icon-context.tsx +0 -60
  302. package/src/react/index.ts +0 -194
  303. package/src/react/layouts/SidebarLayout.tsx +0 -250
  304. package/src/react/layouts/TopbarLayout.tsx +0 -258
  305. package/src/react/navigate.tsx +0 -37
  306. package/src/react/onProviderSynced.test.ts +0 -90
  307. package/src/react/parseRecordEditUrl.test.ts +0 -122
  308. package/src/react/parseRecordEditUrl.ts +0 -94
  309. package/src/react/persistedState.ts +0 -40
  310. package/src/react/registry.ts +0 -48
  311. package/src/react/right-panel-registry.tsx +0 -47
  312. package/src/react/schemaRenderer/AlertRenderer.tsx +0 -112
  313. package/src/react/schemaRenderer/EntryRenderer.tsx +0 -501
  314. package/src/react/schemaRenderer/SectionRenderer.tsx +0 -120
  315. package/src/react/schemaRenderer/SimpleElements.tsx +0 -306
  316. package/src/react/schemaRenderer/TabsRenderer.tsx +0 -62
  317. package/src/react/schemaRenderer/WizardRenderer.tsx +0 -338
  318. package/src/react/schemaRenderer/action/ActionGroupTrigger.tsx +0 -177
  319. package/src/react/schemaRenderer/action/ActionModalDialog.tsx +0 -273
  320. package/src/react/schemaRenderer/action/ConfirmActionDialog.tsx +0 -61
  321. package/src/react/schemaRenderer/action/HandlerActionButton.tsx +0 -43
  322. package/src/react/schemaRenderer/action/MethodActionButton.tsx +0 -64
  323. package/src/react/schemaRenderer/action/buttons.tsx +0 -99
  324. package/src/react/schemaRenderer/action/helpers.ts +0 -140
  325. package/src/react/schemaRenderer/action/renderAction.tsx +0 -245
  326. package/src/react/schemaRenderer/columnFormat.ts +0 -65
  327. package/src/react/schemaRenderer/constants.ts +0 -50
  328. package/src/react/schemaRenderer/form/FormRenderer.tsx +0 -274
  329. package/src/react/schemaRenderer/form/renderField.tsx +0 -511
  330. package/src/react/schemaRenderer/helpers.tsx +0 -81
  331. package/src/react/schemaRenderer/table/CardsLayoutBody.tsx +0 -308
  332. package/src/react/schemaRenderer/table/TableRenderer.tsx +0 -123
  333. package/src/react/schemaRenderer/table/TableRendererBody.tsx +0 -974
  334. package/src/react/schemaRenderer/table/filters.tsx +0 -1233
  335. package/src/react/schemaRenderer/table/formatCell.tsx +0 -264
  336. package/src/react/schemaRenderer/table/links.tsx +0 -112
  337. package/src/react/schemaRenderer/table/renderRowActions.tsx +0 -52
  338. package/src/react/schemaRenderer/table/url.tsx +0 -143
  339. package/src/react/theme-preview/apply.ts +0 -99
  340. package/src/react/theme-preview/build-html.ts +0 -436
  341. package/src/react/ui/button.tsx +0 -51
  342. package/src/react/ui/calendar.tsx +0 -67
  343. package/src/react/ui/checkbox.tsx +0 -29
  344. package/src/react/ui/dialog.tsx +0 -108
  345. package/src/react/ui/dropdown-menu.tsx +0 -97
  346. package/src/react/ui/input.tsx +0 -20
  347. package/src/react/ui/label.tsx +0 -21
  348. package/src/react/ui/popover.tsx +0 -50
  349. package/src/react/ui/select.tsx +0 -169
  350. package/src/react/ui/separator.tsx +0 -25
  351. package/src/react/ui/sheet.tsx +0 -136
  352. package/src/react/ui/sidebar.tsx +0 -723
  353. package/src/react/ui/skeleton.tsx +0 -13
  354. package/src/react/ui/slider.tsx +0 -34
  355. package/src/react/ui/switch.tsx +0 -28
  356. package/src/react/ui/table.tsx +0 -105
  357. package/src/react/ui/tabs.tsx +0 -63
  358. package/src/react/ui/textarea.tsx +0 -18
  359. package/src/react/ui/tooltip.tsx +0 -64
  360. package/src/react/useResizableWidth.ts +0 -139
  361. package/src/react/utils.ts +0 -6
  362. package/src/react/widgetRegistry.test.ts +0 -43
  363. package/src/react/widgetRegistry.ts +0 -50
  364. package/src/react/widgets/StatsOverviewRenderer.tsx +0 -232
  365. package/src/react/widgets/TableWidgetRenderer.tsx +0 -231
  366. package/src/react/widgets/ViewRenderer.tsx +0 -71
  367. package/src/relationManagerData.test.ts +0 -1595
  368. package/src/richtext/index.ts +0 -8
  369. package/src/richtext/registry.ts +0 -89
  370. package/src/routes/globals.ts +0 -148
  371. package/src/routes/guard.test.ts +0 -325
  372. package/src/routes/helpers.ts +0 -704
  373. package/src/routes/pages.ts +0 -175
  374. package/src/routes/panel.ts +0 -204
  375. package/src/routes/relations.ts +0 -1243
  376. package/src/routes/resources.ts +0 -781
  377. package/src/routes/theme.ts +0 -91
  378. package/src/routes-nested-relations.test.ts +0 -676
  379. package/src/routes-relations.test.ts +0 -972
  380. package/src/routes.test.ts +0 -2027
  381. package/src/routes.ts +0 -303
  382. package/src/schema/Alert.test.ts +0 -109
  383. package/src/schema/Alert.ts +0 -131
  384. package/src/schema/Block.ts +0 -169
  385. package/src/schema/Breadcrumbs.ts +0 -40
  386. package/src/schema/Card.ts +0 -35
  387. package/src/schema/Divider.ts +0 -20
  388. package/src/schema/Element.ts +0 -219
  389. package/src/schema/EmptyState.test.ts +0 -37
  390. package/src/schema/EmptyState.ts +0 -63
  391. package/src/schema/Fieldset.ts +0 -43
  392. package/src/schema/Grid.ts +0 -43
  393. package/src/schema/Group.ts +0 -30
  394. package/src/schema/Heading.ts +0 -39
  395. package/src/schema/Html.ts +0 -67
  396. package/src/schema/Icon.ts +0 -54
  397. package/src/schema/Image.ts +0 -57
  398. package/src/schema/LinkTag.ts +0 -41
  399. package/src/schema/Markdown.ts +0 -85
  400. package/src/schema/MetaTag.ts +0 -41
  401. package/src/schema/RelationTabs.ts +0 -71
  402. package/src/schema/ScriptTag.ts +0 -55
  403. package/src/schema/Section.ts +0 -160
  404. package/src/schema/ServerDataElement.test.ts +0 -140
  405. package/src/schema/ServerDataElement.ts +0 -156
  406. package/src/schema/SlotComponent.test.ts +0 -77
  407. package/src/schema/SlotComponent.ts +0 -71
  408. package/src/schema/Split.ts +0 -50
  409. package/src/schema/Stat.test.ts +0 -118
  410. package/src/schema/Stat.ts +0 -154
  411. package/src/schema/StatsOverview.test.ts +0 -141
  412. package/src/schema/StatsOverview.ts +0 -119
  413. package/src/schema/StyleTag.ts +0 -35
  414. package/src/schema/TableWidget.test.ts +0 -297
  415. package/src/schema/TableWidget.ts +0 -289
  416. package/src/schema/Tabs.ts +0 -79
  417. package/src/schema/Text.ts +0 -58
  418. package/src/schema/UnorderedList.ts +0 -49
  419. package/src/schema/View.test.ts +0 -111
  420. package/src/schema/View.ts +0 -127
  421. package/src/schema/Wizard.ts +0 -220
  422. package/src/schema/containers.test.ts +0 -564
  423. package/src/schema/headTags.test.ts +0 -134
  424. package/src/schema/index.ts +0 -40
  425. package/src/schema/primes.test.ts +0 -269
  426. package/src/schema/resolveSchema.test.ts +0 -379
  427. package/src/schema/resolveSchema.ts +0 -917
  428. package/src/schema/sanitize.ts +0 -58
  429. package/src/search.test.ts +0 -446
  430. package/src/search.ts +0 -178
  431. package/src/sessionFilters.test.ts +0 -375
  432. package/src/sessionFilters.ts +0 -143
  433. package/src/slot-components/index.ts +0 -10
  434. package/src/slot-components/registry.ts +0 -56
  435. package/src/styles/file-upload.css +0 -13
  436. package/src/summarizers/Summarizer.test.ts +0 -84
  437. package/src/summarizers/Summarizer.ts +0 -123
  438. package/src/summarizers/index.ts +0 -11
  439. package/src/theme/base-colors.ts +0 -68
  440. package/src/theme/chart-colors.ts +0 -50
  441. package/src/theme/colors.ts +0 -447
  442. package/src/theme/generate-css.test.ts +0 -139
  443. package/src/theme/generate-css.ts +0 -44
  444. package/src/theme/generate-scale.test.ts +0 -106
  445. package/src/theme/generate-scale.ts +0 -97
  446. package/src/theme/icon-map.ts +0 -42
  447. package/src/theme/index.ts +0 -34
  448. package/src/theme/migrate.test.ts +0 -178
  449. package/src/theme/migrate.ts +0 -81
  450. package/src/theme/presets.ts +0 -135
  451. package/src/theme/radius.ts +0 -18
  452. package/src/theme/resolve.test.ts +0 -238
  453. package/src/theme/resolve.ts +0 -96
  454. package/src/theme/spacing.ts +0 -18
  455. package/src/theme/storage.test.ts +0 -126
  456. package/src/theme/storage.ts +0 -106
  457. package/src/theme/theme-colors.ts +0 -88
  458. package/src/theme/types.ts +0 -125
  459. package/src/uploads/UploadAdapter.ts +0 -35
  460. package/src/uploads/index.ts +0 -2
  461. package/src/uploads/localUpload.test.ts +0 -70
  462. package/src/uploads/localUpload.ts +0 -84
  463. package/src/validation/Validator.ts +0 -49
  464. package/src/validation/index.ts +0 -28
  465. package/src/validation/rules.ts +0 -78
  466. package/src/validation/runValidators.ts +0 -435
  467. package/src/validation/uniqueValidator.test.ts +0 -196
  468. package/src/validation/uniqueValidator.ts +0 -133
  469. package/src/validation/validators.test.ts +0 -268
  470. package/src/vite.test.ts +0 -184
  471. package/src/vite.ts +0 -787
  472. package/src/widgets/index.ts +0 -10
  473. package/src/widgets/registry.ts +0 -45
  474. package/src/widgets.test.ts +0 -592
  475. package/tsconfig.build.json +0 -11
  476. package/tsconfig.json +0 -4
  477. package/tsconfig.test.json +0 -10
  478. package/views/react/Dashboard.tsx +0 -27
  479. package/views/react/Resources/Form.tsx +0 -102
  480. package/views/react/Resources/Index.tsx +0 -49
@@ -1,842 +0,0 @@
1
- import type { Pilotiq, PilotiqConfig } from '../Pilotiq.js'
2
- import type { Page, PageCollabConfig } from '../Page.js'
3
- import type { ResourceClass, NavigationBadgeColor, ResourceCollabConfig } from '../Resource.js'
4
- import type { GlobalClass } from '../Global.js'
5
- import type { ClusterClass } from '../Cluster.js'
6
- import { resourceBasePath, globalBasePath, pageBasePath } from '../clusterPaths.js'
7
- import type { ElementMeta } from '../schema/Element.js'
8
- import { resolveSchema, type SchemaContext } from '../schema/resolveSchema.js'
9
- import { resolveTheme } from '../theme/resolve.js'
10
- import type { ThemeMeta } from '../theme/types.js'
11
- import { serializeIcon, type SerializedIcon } from '../icons/types.js'
12
- import {
13
- RIGHT_PANEL_DEFAULT_WIDTH,
14
- RIGHT_PANEL_MIN_WIDTH,
15
- RIGHT_PANEL_MAX_WIDTH,
16
- } from '../RightPanel.js'
17
- import type { UserMenuItemMeta } from '../UserMenuItem.js'
18
- import {
19
- resolveRenderHooks,
20
- CHROME_HOOK_NAMES,
21
- type RenderHookContext,
22
- type RenderHookMap,
23
- type RenderHookName,
24
- } from '../RenderHook.js'
25
- import { applyPageHooks, pageHooksFor, type PageRole } from '../applyPageHooks.js'
26
- import {
27
- notificationChannel,
28
- NOTIFICATION_CREATED_EVENT,
29
- } from '../notifications/broadcast.js'
30
- import { safeBool } from './helpers.js'
31
-
32
- // ─── Navigation chrome ──────────────────────────────────────
33
- //
34
- // `panelInfo` is the entry point every SSR + SPA-nav data hook calls
35
- // to build the static chrome envelope (branding / theme / navigation
36
- // tree / user menu / database-notifications meta / right sidebar meta
37
- // / page-role render hooks). Returns a snapshot that's safe to ship
38
- // over the wire; references like component classes get rendered down
39
- // to serializable shapes here.
40
-
41
-
42
- // ─── Shared helpers ──────────────────────────────────────────
43
-
44
- /**
45
- * Top-right user dropdown shipped to the renderer in `viewProps.panel`.
46
- * `null` when no `Pilotiq.user(req => …)` resolver is configured or the
47
- * resolver returns `null` (no logged-in user) — the renderer suppresses
48
- * the dropdown entirely in that case.
49
- *
50
- * `user.name / user.email / user.avatar` are duck-typed off the
51
- * resolver's return value; whichever fields are present round-trip into
52
- * the dropdown trigger (initials fall back to the first two letters of
53
- * `name` when no avatar URL is set).
54
- */
55
- export interface UserMenuMeta {
56
- user: { name?: string; email?: string; avatar?: string }
57
- items: UserMenuItemMeta[]
58
- signOut?: { url: string; label: string; method: 'POST' | 'GET' }
59
- }
60
-
61
- /**
62
- * Bell-icon dropdown configuration shipped under `viewProps.panel`. Sparse —
63
- * absent when `Pilotiq.databaseNotifications()` wasn't called OR when no
64
- * user resolves (anonymous request → no inbox to surface). Renderer mounts
65
- * the bell only when this is set.
66
- *
67
- * Routes are absolute URLs (panel `basePath` already applied). Client
68
- * substitutes `:id` per row when calling read / unread; `_widget`-style
69
- * params aren't used here because the bell only ever issues these four
70
- * fetch shapes.
71
- *
72
- * `polling` mirrors `DatabaseNotificationsConfig.polling` — `null` ships
73
- * over the wire to disable client-side polling. The bell still fetches on
74
- * mount + after every mark-read mutation.
75
- */
76
- export interface DatabaseNotificationsMeta {
77
- position: 'topbar' | 'sidebar'
78
- polling: number | null
79
- pageSize: number
80
- badgeColor: NavigationBadgeColor
81
- trigger?: { icon?: string; label?: string }
82
- listUrl: string
83
- readAllUrl: string
84
- /** Template URL with literal `:id` placeholder. Client replaces. */
85
- readUrl: string
86
- /** Template URL with literal `:id` placeholder. Client replaces. */
87
- unreadUrl: string
88
- /**
89
- * Template URL for the notification-action dispatch endpoint with
90
- * literal `:id` and `:actionName` placeholders. Bell client builds
91
- * per-action URLs by substituting both at render time. Used only by
92
- * `handler`-mode actions; `url` / `post` actions ride their own URL
93
- * verbatim.
94
- */
95
- actionUrl: string
96
- /**
97
- * Phase 2 — broadcast hint. Sparse — absent when
98
- * `databaseNotifications({ broadcast: true })` wasn't set OR when no
99
- * resolved user has an `id` to scope the channel to.
100
- *
101
- * Client connects to `wsUrl` via `@rudderjs/broadcast`'s
102
- * `RudderSocket`, subscribes to the `channel` (already includes the
103
- * `private-` prefix), and listens for `event` to trigger refetches.
104
- */
105
- broadcast?: {
106
- wsUrl: string
107
- channel: string
108
- event: string
109
- }
110
- }
111
-
112
- /**
113
- * Right-sidebar shipped under `viewProps.panel.rightSidebar`. Sparse —
114
- * absent from `panelInfo()` when no contributions are registered, every
115
- * registered contribution failed `canAccess(user)`, or every visible
116
- * contribution is `hidden: true`. Renderer mounts the chrome only when
117
- * this is set.
118
- *
119
- * The React component reference for each contribution does NOT travel
120
- * here — only its tab-strip metadata. The actual body component is
121
- * resolved client-side from the Vite plugin's `_components.ts` manifest
122
- * keyed by contribution `id`, mirroring the icon-class round-trip.
123
- *
124
- * `defaultWidth` rolls up: contribution-level value when one
125
- * contribution was registered with one, otherwise the panel-level
126
- * baseline (`RIGHT_PANEL_DEFAULT_WIDTH`). Client also clamps
127
- * localStorage values to `[minWidth, maxWidth]`.
128
- */
129
- export interface RightPanelMeta {
130
- id: string
131
- label: string
132
- icon?: SerializedIcon
133
- defaultWidth: number
134
- }
135
-
136
- export interface RightSidebarMeta {
137
- panels: RightPanelMeta[]
138
- defaultWidth: number
139
- minWidth: number
140
- maxWidth: number
141
- }
142
-
143
- /**
144
- * Single nav-tree entry. `name` is the JS class name (`R.name` /
145
- * `G.name` / `P.name`) — also the lookup key into the build-time
146
- * `_components.ts` manifest the Vite plugin emits, so component-typed
147
- * icons resolve from the same identifier.
148
- */
149
- export interface NavItem {
150
- name: string
151
- label: string
152
- url: string
153
- icon?: SerializedIcon
154
- group?: string
155
- sort?: number
156
- badge?: string
157
- badgeColor?: NavigationBadgeColor
158
- children?: NavItem[]
159
- }
160
-
161
- /**
162
- * Build the panel header summary + the unified navigation tree.
163
- *
164
- * Pipeline:
165
- * 1. flatten resources + globals + pages into raw NavItem records
166
- * 2. drop items whose `canAccess(user)` (Plan #10) returns false
167
- * 3. resolve `navigationParentItem` references → nest under parents
168
- * (cycles broken with a console warn; dangling parents render at top level)
169
- * 4. sort within each grouping (top-level *and* every parent's children)
170
- * by `navigationSort` ascending → registration order
171
- * 5. resolve every `navigationBadge()` in parallel via `Promise.all`;
172
- * handler errors are swallowed (badge omitted) so a flaky count
173
- * never blanks the page
174
- *
175
- * `req` is the active request; pilotiq calls `pilotiq.resolveUser(req)`
176
- * once and threads the user into every Resource/Global/Page `canAccess`
177
- * check. When `Pilotiq.user(fn)` isn't configured, user is `null` and the
178
- * default `canAccess` returns true → no items dropped.
179
- */
180
- /**
181
- * Optional route-context for `panelInfo()`. When set, render-hook
182
- * `scope: { resource | page | global }` filters fire correctly for the
183
- * active route. Missing keys mean the slot has no scope-able identifier
184
- * (chrome-only routes); scope-less hooks still fire either way.
185
- *
186
- * `url` defaults to `cfg.path` when unset. `recordId` rides through to
187
- * `RenderHookContext.recordId` for hooks that need it.
188
- */
189
- export interface PanelInfoRoute {
190
- resource?: ResourceClass
191
- page?: typeof Page
192
- global?: GlobalClass
193
- recordId?: string
194
- url?: string
195
- }
196
-
197
- /**
198
- * Per-resource collab opt-in map, keyed by the URL slug
199
- * `parseRecordPageUrl` produces. Non-clustered resource → `getSlug()`;
200
- * clustered resource → `${cluster.getSlug()}/${R.getSlug()}`.
201
- *
202
- * `RecordWrapperGate` reads this map to decide whether the page tree
203
- * needs the plugin-registered RecordWrapper (collab room, audit, …)
204
- * mounted around the record view/edit content area.
205
- *
206
- * Nested-relation edit URLs (`/articles/123/comments/456/edit`) have a
207
- * dynamic-id segment in the gate's URL slug and don't match here in v1.
208
- * Collab on nested-relation edits is a follow-up — top-level resource
209
- * edits are the common case and ship now.
210
- */
211
- export type RecordCollabMap = Record<string, ResourceCollabConfig>
212
-
213
- function resourceSlugForGate(R: ResourceClass): string {
214
- return R.cluster ? `${R.cluster.getSlug()}/${R.getSlug()}` : R.getSlug()
215
- }
216
-
217
- function buildRecordCollabMap(cfg: Readonly<PilotiqConfig>): RecordCollabMap | undefined {
218
- const map: RecordCollabMap = {}
219
- for (const R of cfg.resources) {
220
- const collab = R.getResolvedCollabConfig()
221
- if (collab) map[resourceSlugForGate(R)] = collab
222
- }
223
- return Object.keys(map).length > 0 ? map : undefined
224
- }
225
-
226
- /**
227
- * Per-custom-page collab opt-in map, keyed by the page's URL slug
228
- * (cluster-prefixed for clustered pages). `CustomPageWrapperGate`
229
- * reads this to decide whether to mount the plugin-registered
230
- * custom-page wrapper (collab room, audit trail, …) around the page
231
- * content area.
232
- *
233
- * Resource-bound default pages (List/Create/Edit/View) never appear
234
- * here — `Page.getResolvedCollabConfig()` returns `null` for them.
235
- * Record-scoped collab is governed by `Resource.collab` and lands on
236
- * `recordCollab`.
237
- */
238
- export type PageCollabMap = Record<string, PageCollabConfig>
239
-
240
- function pageSlugForGate(P: typeof Page): string {
241
- const slug = P.getSlug()
242
- return P.cluster ? `${P.cluster.getSlug()}/${slug}` : slug
243
- }
244
-
245
- function buildPageCollabMap(cfg: Readonly<PilotiqConfig>): PageCollabMap | undefined {
246
- const map: PageCollabMap = {}
247
- for (const P of cfg.pages) {
248
- const collab = P.getResolvedCollabConfig()
249
- if (collab) map[pageSlugForGate(P)] = collab
250
- }
251
- return Object.keys(map).length > 0 ? map : undefined
252
- }
253
-
254
- export async function panelInfo(
255
- pilotiq: Pilotiq,
256
- req?: unknown,
257
- route: PanelInfoRoute = {},
258
- ) {
259
- const cfg = pilotiq.getConfig()
260
- const merged = pilotiq.getMergedTheme()
261
- const theme: ThemeMeta | undefined = merged ? resolveTheme(merged) : undefined
262
- const user = await pilotiq.resolveUser(req)
263
- const [navigation, userMenu, renderHooks, rightSidebar] = await Promise.all([
264
- buildNavigation(pilotiq, user),
265
- buildUserMenu(pilotiq, user),
266
- resolveChromeHooks(pilotiq, user, route),
267
- buildRightSidebarMeta(cfg, user),
268
- ])
269
- const databaseNotifications = buildDatabaseNotificationsMeta(cfg, user)
270
- const recordCollab = buildRecordCollabMap(cfg)
271
- const pageCollab = buildPageCollabMap(cfg)
272
- // AI suggestion mode — sparse: omit when 'auto' (the default) so the
273
- // wire shape stays minimal for panels that don't opt into review mode.
274
- // Plugin clients (e.g. @pilotiq-pro/ai's `AiClientToolBindings`) read
275
- // this to decide whether to apply writes immediately or stage them as
276
- // PendingSuggestions for user approval.
277
- const aiSuggestionsMode = pilotiq.getAiSuggestionsMode()
278
- return {
279
- name: cfg.name,
280
- branding: cfg.branding,
281
- navigation,
282
- theme,
283
- themeEditor: cfg.themeEditor ?? false,
284
- ...(userMenu ? { userMenu } : {}),
285
- ...(databaseNotifications ? { databaseNotifications } : {}),
286
- ...(rightSidebar ? { rightSidebar } : {}),
287
- ...(recordCollab ? { recordCollab } : {}),
288
- ...(pageCollab ? { pageCollab } : {}),
289
- ...(Object.keys(renderHooks).length > 0 ? { renderHooks } : {}),
290
- ...(aiSuggestionsMode !== 'auto' ? { aiSuggestionsMode } : {}),
291
- }
292
- }
293
-
294
- /**
295
- * Build the bell-icon meta. Returns `null` when:
296
- * - `Pilotiq.databaseNotifications()` was never called, OR
297
- * - no user resolves (no inbox to surface).
298
- *
299
- * Defaults follow Filament: 30s polling, 25 rows per page, primary
300
- * badge color, topbar position.
301
- */
302
- export function buildDatabaseNotificationsMeta(
303
- cfg: Readonly<PilotiqConfig>,
304
- user: unknown,
305
- ): DatabaseNotificationsMeta | null {
306
- if (!cfg.databaseNotifications?.enabled) return null
307
- if (user === null || user === undefined) return null
308
-
309
- const dn = cfg.databaseNotifications
310
- const base = cfg.path
311
- const meta: DatabaseNotificationsMeta = {
312
- position: dn.position ?? 'topbar',
313
- polling: dn.polling === null ? null : (dn.polling ?? 30),
314
- pageSize: dn.pageSize ?? 25,
315
- badgeColor: dn.badgeColor ?? 'primary',
316
- listUrl: `${base}/_notifications`,
317
- readAllUrl: `${base}/_notifications/read-all`,
318
- readUrl: `${base}/_notifications/:id/read`,
319
- unreadUrl: `${base}/_notifications/:id/unread`,
320
- actionUrl: `${base}/_notifications/:id/_action/:actionName`,
321
- }
322
- if (dn.trigger) meta.trigger = { ...dn.trigger }
323
- // Phase 2 broadcast hint — only ship when broadcast is enabled AND the
324
- // resolved user has an `id` to scope the channel to. The client uses
325
- // `wsUrl` for the WebSocket connection and `channel` for the subscribe
326
- // call (the private- prefix is already baked in).
327
- if (dn.broadcast) {
328
- const userId = (user as { id?: unknown } | null | undefined)?.id
329
- if (userId !== undefined && userId !== null) {
330
- const wsUrl = typeof dn.broadcast === 'object' && dn.broadcast.wsUrl
331
- ? dn.broadcast.wsUrl
332
- : '' // empty = client falls back to same-origin /ws
333
- meta.broadcast = {
334
- wsUrl,
335
- channel: notificationChannel(String(userId)),
336
- event: NOTIFICATION_CREATED_EVENT,
337
- }
338
- }
339
- }
340
- return meta
341
- }
342
-
343
- /**
344
- * Build the right-sidebar meta from registered contributions. Returns
345
- * `null` when:
346
- *
347
- * - no contributions were registered, OR
348
- * - every contribution failed `canAccess(user)` (or its predicate
349
- * threw — fail-closed), OR
350
- * - every passing contribution is `hidden: true` (no tab-strip
351
- * surface to mount; programmatic-open consumers should ship at
352
- * least one visible tab).
353
- *
354
- * Visible contributions are sorted by `sort` ascending (default 100),
355
- * with registration order as a stable tiebreaker. Each entry's icon is
356
- * serialized through `serializeIcon` keyed on the contribution `id`
357
- * (Phase B's Vite plugin extends `_components.ts` to round-trip
358
- * component-typed icons under that key). `defaultWidth` rolls up:
359
- * panel-level baseline is `RIGHT_PANEL_DEFAULT_WIDTH`; per-contribution
360
- * overrides ride on `RightPanelMeta.defaultWidth`.
361
- *
362
- * Errors thrown by `canAccess` are swallowed (the contribution is
363
- * dropped + a single console warn is emitted) so a flaky predicate on
364
- * one pane never blanks the whole sidebar.
365
- */
366
- export async function buildRightSidebarMeta(
367
- cfg: Readonly<PilotiqConfig>,
368
- user: unknown,
369
- ): Promise<RightSidebarMeta | null> {
370
- const list = cfg.rightPanels ?? []
371
- if (list.length === 0) return null
372
-
373
- const indexed = list.map((c, idx) => ({ c, idx }))
374
- const gated = await Promise.all(
375
- indexed.map(async ({ c, idx }) => {
376
- if (c.canAccess) {
377
- try {
378
- const ok = await c.canAccess(user)
379
- if (!ok) return null
380
- } catch (err) {
381
- // eslint-disable-next-line no-console
382
- console.warn(`[Pilotiq] rightPanel "${c.id}" canAccess threw — dropping`, err)
383
- return null
384
- }
385
- }
386
- return { c, idx }
387
- }),
388
- )
389
-
390
- const visible = gated
391
- .filter((x): x is { c: typeof list[number]; idx: number } => x !== null)
392
- .filter((x) => !x.c.hidden)
393
- .sort((a, b) => {
394
- const sa = a.c.sort ?? 100
395
- const sb = b.c.sort ?? 100
396
- if (sa !== sb) return sa - sb
397
- return a.idx - b.idx
398
- })
399
-
400
- if (visible.length === 0) return null
401
-
402
- const panels: RightPanelMeta[] = visible.map(({ c }) => {
403
- const meta: RightPanelMeta = {
404
- id: c.id,
405
- label: c.label ?? c.id,
406
- defaultWidth: c.defaultWidth ?? RIGHT_PANEL_DEFAULT_WIDTH,
407
- }
408
- if (c.icon !== undefined) {
409
- meta.icon = serializeIcon(c.icon, c.id)
410
- }
411
- return meta
412
- })
413
-
414
- return {
415
- panels,
416
- defaultWidth: panels[0]?.defaultWidth ?? RIGHT_PANEL_DEFAULT_WIDTH,
417
- minWidth: RIGHT_PANEL_MIN_WIDTH,
418
- maxWidth: RIGHT_PANEL_MAX_WIDTH,
419
- }
420
- }
421
-
422
- /**
423
- * Resolve every chrome render hook (body / topbar / sidebar / user-menu
424
- * / footer / head). Returns a sparse map — slots with no matching
425
- * registered entries are omitted so the wire payload stays minimal on
426
- * panels that don't use render hooks at all.
427
- */
428
- export async function resolveChromeHooks(
429
- pilotiq: Pilotiq,
430
- user: unknown,
431
- route: PanelInfoRoute,
432
- ): Promise<RenderHookMap> {
433
- const cfg = pilotiq.getConfig()
434
- const entries = cfg.renderHooks ?? []
435
- if (entries.length === 0) return {}
436
- const ctx: RenderHookContext = {
437
- user,
438
- basePath: cfg.path,
439
- url: route.url ?? cfg.path,
440
- }
441
- if (route.resource !== undefined) ctx.resource = route.resource
442
- if (route.page !== undefined) ctx.page = route.page
443
- if (route.global !== undefined) ctx.global = route.global
444
- if (route.recordId !== undefined) ctx.recordId = route.recordId
445
- return resolveRenderHooks(entries, CHROME_HOOK_NAMES, ctx)
446
- }
447
-
448
- /**
449
- * Resolve a subset of page-role render hooks (e.g. `panels::page.start`
450
- * + the list-records / create-record / view-record / edit-record /
451
- * global-search slot families). Per-page-role data builders call this
452
- * after schema resolution and stamp the result on `viewProps.renderHooks`.
453
- *
454
- * `names` lets each builder declare exactly which slots it serves so a
455
- * list-page builder doesn't ship slots that only fire on the edit page.
456
- */
457
- /**
458
- * Per-builder one-shot — resolve the role's slot set + splice the
459
- * results into the resolved schema. Wraps the two steps a per-builder
460
- * data fn always does in lockstep:
461
- *
462
- * 1. `resolvePageHooks(pilotiq, user, pageHooksFor(role), route)`
463
- * 2. `applyPageHooks(schemaData, hooks, role)`
464
- *
465
- * Returns the wrapped `ElementMeta[]`. No-op when the panel has no
466
- * registered hooks. Pass through what you'd pass to `panelInfo()`'s
467
- * route arg — same shape.
468
- */
469
- export async function applyRoleHooks(
470
- pilotiq: Pilotiq,
471
- user: unknown,
472
- role: PageRole,
473
- schemaData: ElementMeta[],
474
- route: PanelInfoRoute = {},
475
- ): Promise<ElementMeta[]> {
476
- const cfg = pilotiq.getConfig()
477
- if (!cfg.renderHooks || cfg.renderHooks.length === 0) return schemaData
478
- const hooks = await resolvePageHooks(pilotiq, user, pageHooksFor(role), route)
479
- return applyPageHooks(schemaData, hooks, role)
480
- }
481
-
482
- export async function resolvePageHooks(
483
- pilotiq: Pilotiq,
484
- user: unknown,
485
- names: readonly RenderHookName[],
486
- route: PanelInfoRoute,
487
- ): Promise<RenderHookMap> {
488
- const cfg = pilotiq.getConfig()
489
- const entries = cfg.renderHooks ?? []
490
- if (entries.length === 0 || names.length === 0) return {}
491
- const ctx: RenderHookContext = {
492
- user,
493
- basePath: cfg.path,
494
- url: route.url ?? cfg.path,
495
- }
496
- if (route.resource !== undefined) ctx.resource = route.resource
497
- if (route.page !== undefined) ctx.page = route.page
498
- if (route.global !== undefined) ctx.global = route.global
499
- if (route.recordId !== undefined) ctx.recordId = route.recordId
500
- return resolveRenderHooks(entries, names, ctx)
501
- }
502
-
503
-
504
- /**
505
- * Build the top-right user-menu meta. Returns `null` when:
506
- * - `Pilotiq.user()` isn't configured, or
507
- * - the resolver returned `null` (anonymous request), or
508
- * - the user object has no extractable identity AND the panel
509
- * configured no items / no sign-out (nothing to render).
510
- *
511
- * Items resolve in parallel with their visibility predicates
512
- * (`UserMenuItem.visible`). Throwing predicates fail closed (item
513
- * dropped). Sort by `.sort(n)` ascending → registration order.
514
- */
515
- export async function buildUserMenu(pilotiq: Pilotiq, user: unknown): Promise<UserMenuMeta | null> {
516
- if (user === null || user === undefined) return null
517
-
518
- const cfg = pilotiq.getConfig()
519
- const items = cfg.userMenuItems ?? []
520
- const ctx = { user }
521
-
522
- // Resolve every item in parallel. `null` returns mean "filtered by
523
- // visibility predicate" — drop them. Indexed pre-sort so stable ties
524
- // resolve to registration order.
525
- const resolved = await Promise.all(
526
- items.map(async (item, idx) => {
527
- try {
528
- const meta = await item.resolve(ctx)
529
- return meta ? { meta, idx, sort: item.getSort() } : null
530
- } catch {
531
- return null
532
- }
533
- }),
534
- )
535
- const visibleItems = resolved
536
- .filter((x): x is { meta: UserMenuItemMeta; idx: number; sort: number | undefined } => x !== null)
537
- .sort((a, b) => {
538
- const aHas = a.sort !== undefined, bHas = b.sort !== undefined
539
- if (aHas && bHas) return a.sort! - b.sort! || a.idx - b.idx
540
- if (aHas) return -1
541
- if (bHas) return 1
542
- return a.idx - b.idx
543
- })
544
- .map(x => x.meta)
545
-
546
- // Auto-inject the profile entry from `cfg.profilePage` when set.
547
- // Prepended (Filament-style) so it always sits at the top of the
548
- // dropdown regardless of user-authored item ordering. Falls through
549
- // its own `canAccess(user)` so per-user gating works without the
550
- // user repeating the predicate at the menu level.
551
- const profileItem = await buildProfileMenuItem(cfg, user)
552
- const finalItems = profileItem ? [profileItem, ...visibleItems] : visibleItems
553
-
554
- const meta: UserMenuMeta = {
555
- user: extractUserIdentity(user),
556
- items: finalItems,
557
- }
558
- if (cfg.signOut) {
559
- meta.signOut = {
560
- url: cfg.signOut.url,
561
- label: cfg.signOut.label ?? 'Sign out',
562
- method: cfg.signOut.method ?? 'POST',
563
- }
564
- }
565
- return meta
566
- }
567
-
568
- /** Build the auto-injected profile entry from `cfg.profilePage`. The
569
- * Page's `static label` / `static icon` win; defaults `'Edit profile'`
570
- * + `'user-circle'` (registry-resolved). Returns `null` when no
571
- * profile page is configured or `Page.canAccess(user)` denies. */
572
- export async function buildProfileMenuItem(
573
- cfg: Readonly<PilotiqConfig>,
574
- user: unknown,
575
- ): Promise<UserMenuItemMeta | null> {
576
- const P = cfg.profilePage
577
- if (!P) return null
578
- if (!(await safeBool(() => P.canAccess(user)))) return null
579
- const url = pageBasePath(cfg.path, P)
580
- const icon = serializeIcon(P.icon ?? 'user-circle', P.name)
581
- const meta: UserMenuItemMeta = {
582
- name: '__profile',
583
- label: P.label ?? 'Edit profile',
584
- url,
585
- }
586
- if (icon !== undefined) meta.icon = icon
587
- return meta
588
- }
589
-
590
- /** Duck-type the user object for display fields. We never throw — a
591
- * user resolver might return literally anything (a primitive, a class
592
- * instance with getters, a plain object) and the dropdown should
593
- * degrade gracefully (initials fallback to '?' when no name found). */
594
- export function extractUserIdentity(user: unknown): { name?: string; email?: string; avatar?: string } {
595
- if (user === null || user === undefined) return {}
596
- if (typeof user !== 'object') return { name: String(user) }
597
- const obj = user as Record<string, unknown>
598
- const out: { name?: string; email?: string; avatar?: string } = {}
599
- const name = obj.name ?? obj.fullName ?? obj.displayName ?? obj.username
600
- if (typeof name === 'string' && name) out.name = name
601
- if (typeof obj.email === 'string' && obj.email) out.email = obj.email
602
- const avatar = obj.avatar ?? obj.avatarUrl ?? obj.image
603
- if (typeof avatar === 'string' && avatar) out.avatar = avatar
604
- return out
605
- }
606
-
607
- /** @internal Internal node before nesting; carries the registration index
608
- * so we can stable-sort by it as the tie-breaker. */
609
- interface RawNavItem extends NavItem {
610
- parent?: string
611
- /** Registration index across resources → globals → pages (in that order),
612
- * so resources beat globals on a sort tie within the same group. */
613
- _idx: number
614
- }
615
-
616
- /** Plan #10 — stamp the resolved user onto a SchemaContext so action
617
- * visibility predicates can see it during `resolveSchema`. The `user`
618
- * field is opaque (whatever `Pilotiq.user(req => …)` returns); skipped
619
- * when null/undefined to keep ctx tidy. */
620
- export async function buildNavigation(pilotiq: Pilotiq, user: unknown): Promise<NavItem[]> {
621
- const cfg = pilotiq.getConfig()
622
- const base = cfg.path
623
-
624
- // Flatten + resolve badges in parallel. We build the raw list first so
625
- // every entry has its identity (`name`) and parent set; badges resolve
626
- // alongside.
627
- const raw: RawNavItem[] = []
628
- let idx = 0
629
-
630
- const pushBadge: Array<{ item: RawNavItem; handler: () => unknown; owner: string }> = []
631
-
632
- // Plan #10 — pre-evaluate canAccess for every owner in parallel so we
633
- // can drop forbidden items before flattening. Failed predicates fail
634
- // closed (treated as `false`) so a thrown auth check doesn't accidentally
635
- // expose nav items. Clusters compose: a child gated through its
636
- // cluster's `canAccess` returning false drops the child even when the
637
- // child's own predicate would have passed.
638
- const [resourceAccess, globalAccess, pageAccess, clusterAccess] = await Promise.all([
639
- Promise.all(cfg.resources.map(R => safeBool(() => R.canAccess(user)))),
640
- Promise.all(cfg.globals.map(G => safeBool(() => G.canAccess(user)))),
641
- Promise.all(cfg.pages.map(P => safeBool(() => P.canAccess(user)))),
642
- Promise.all(cfg.clusters.map(C => safeBool(() => C.canAccess(user)))),
643
- ])
644
-
645
- // Identity-keyed so two clusters that happen to share a `.name`
646
- // (minifier collisions, hot-reload duplicate imports) don't clobber.
647
- const clusterAccessByClass = new Map<ClusterClass, boolean>()
648
- cfg.clusters.forEach((C, i) => clusterAccessByClass.set(C, !!clusterAccess[i]))
649
-
650
- const firstChildUrlByCluster = new Map<ClusterClass, string>()
651
- const recordChildUrl = (cluster: ClusterClass, url: string) => {
652
- if (!firstChildUrlByCluster.has(cluster)) firstChildUrlByCluster.set(cluster, url)
653
- }
654
-
655
- for (let i = 0; i < cfg.resources.length; i++) {
656
- const R = cfg.resources[i]!
657
- if (!resourceAccess[i]) continue
658
- if (R.cluster && !clusterAccessByClass.get(R.cluster)) continue
659
- const url = resourceBasePath(base, R)
660
- if (R.cluster) recordChildUrl(R.cluster, url)
661
- const item: RawNavItem = {
662
- name: R.name,
663
- label: R.getNavigationLabel(),
664
- url,
665
- icon: serializeIcon(R.getNavigationIcon(), R.name),
666
- _idx: idx++,
667
- }
668
- if (R.navigationGroup !== undefined) item.group = R.navigationGroup
669
- if (R.navigationSort !== undefined) item.sort = R.navigationSort
670
- // Cluster nesting wins over `navigationParentItem`. Both being set
671
- // is a misconfiguration; cluster placement is the structural one.
672
- if (R.cluster) item.parent = R.cluster.name
673
- else if (R.navigationParentItem !== undefined) item.parent = R.navigationParentItem
674
- if (R.navigationBadgeColor !== 'default') item.badgeColor = R.navigationBadgeColor
675
- if (R.navigationBadge) pushBadge.push({ item, handler: R.navigationBadge, owner: R.name })
676
- raw.push(item)
677
- }
678
-
679
- for (let i = 0; i < cfg.globals.length; i++) {
680
- if (!globalAccess[i]) continue
681
- const G = cfg.globals[i]!
682
- if (G.cluster && !clusterAccessByClass.get(G.cluster)) continue
683
- // Globals default `navigationGroup` to `'Settings'`. Allow `null` as
684
- // an explicit opt-out → render at top level.
685
- const group = G.navigationGroup === null ? undefined : G.navigationGroup
686
- const url = globalBasePath(base, G)
687
- if (G.cluster) recordChildUrl(G.cluster, url)
688
- const item: RawNavItem = {
689
- name: G.name,
690
- label: G.getNavigationLabel(),
691
- url,
692
- icon: serializeIcon(G.getNavigationIcon(), G.name),
693
- _idx: idx++,
694
- }
695
- if (group !== undefined) item.group = group
696
- if (G.navigationSort !== undefined) item.sort = G.navigationSort
697
- if (G.cluster) item.parent = G.cluster.name
698
- else if (G.navigationParentItem !== undefined) item.parent = G.navigationParentItem
699
- if (G.navigationBadgeColor !== 'default') item.badgeColor = G.navigationBadgeColor
700
- if (G.navigationBadge) pushBadge.push({ item, handler: G.navigationBadge, owner: G.name })
701
- raw.push(item)
702
- }
703
-
704
- for (let i = 0; i < cfg.pages.length; i++) {
705
- if (!pageAccess[i]) continue
706
- const P = cfg.pages[i]!
707
- if (P.cluster && !clusterAccessByClass.get(P.cluster)) continue
708
- // The dashboard page collapses its nav URL to `${base}` so the
709
- // sidebar entry deep-links to the panel root rather than
710
- // `${base}/${P.getSlug()}` (which would 404 — the slug route skips
711
- // the dashboard page at boot).
712
- const isDashboard = cfg.dashboardPage === P
713
- const url = isDashboard ? base : pageBasePath(base, P)
714
- if (P.cluster && !isDashboard) recordChildUrl(P.cluster, url)
715
- const item: RawNavItem = {
716
- name: P.name,
717
- label: P.getNavigationLabel(),
718
- url,
719
- icon: serializeIcon(P.getNavigationIcon(), P.name),
720
- _idx: idx++,
721
- }
722
- if (P.navigationGroup !== undefined) item.group = P.navigationGroup
723
- if (P.navigationSort !== undefined) item.sort = P.navigationSort
724
- if (P.cluster && !isDashboard) item.parent = P.cluster.name
725
- else if (P.navigationParentItem !== undefined) item.parent = P.navigationParentItem
726
- if (P.navigationBadgeColor !== 'default') item.badgeColor = P.navigationBadgeColor
727
- if (P.navigationBadge) pushBadge.push({ item, handler: P.navigationBadge, owner: P.name })
728
- raw.push(item)
729
- }
730
-
731
- // Clusters render as first-class nav items. Each gets a URL pointing
732
- // at its `landingPage` (when set + accessible) or its first accessible
733
- // child. Clusters whose every child was gated out are dropped silently
734
- // — same posture as `navigationParentItem` with no resolvable parent.
735
- for (let i = 0; i < cfg.clusters.length; i++) {
736
- if (!clusterAccess[i]) continue
737
- const C = cfg.clusters[i]!
738
- let url: string | undefined
739
- if (C.landingPage) {
740
- const lpIdx = cfg.pages.indexOf(C.landingPage)
741
- if (lpIdx !== -1 && pageAccess[lpIdx]) {
742
- url = cfg.dashboardPage === C.landingPage ? base : pageBasePath(base, C.landingPage)
743
- }
744
- }
745
- if (url === undefined) url = firstChildUrlByCluster.get(C)
746
- if (url === undefined) continue // empty cluster — drop entirely
747
- const item: RawNavItem = {
748
- name: C.name,
749
- label: C.getNavigationLabel(),
750
- url,
751
- icon: serializeIcon(C.getNavigationIcon(), C.name),
752
- _idx: idx++,
753
- }
754
- if (C.navigationGroup !== undefined) item.group = C.navigationGroup
755
- if (C.navigationSort !== undefined) item.sort = C.navigationSort
756
- if (C.navigationParentItem !== undefined) item.parent = C.navigationParentItem
757
- if (C.navigationBadgeColor !== 'default') item.badgeColor = C.navigationBadgeColor
758
- if (C.navigationBadge) pushBadge.push({ item, handler: C.navigationBadge, owner: C.name })
759
- raw.push(item)
760
- }
761
-
762
- await Promise.all(pushBadge.map(async ({ item, handler, owner }) => {
763
- try {
764
- const v = await pilotiq.resolveNavigationBadge(owner, user, async () => {
765
- const raw = await handler()
766
- return raw === undefined || raw === null ? undefined : String(raw)
767
- })
768
- if (v !== undefined) item.badge = v
769
- } catch {
770
- // Per-badge errors stay silent.
771
- }
772
- }))
773
-
774
- return nestAndSort(raw)
775
- }
776
-
777
- /**
778
- * Resolve `parent` references → nest, drop cycles, sort within each
779
- * grouping, then strip internal scaffolding (`parent`, `_idx`).
780
- */
781
- export function nestAndSort(raw: RawNavItem[]): NavItem[] {
782
- const byName = new Map<string, RawNavItem>()
783
- for (const it of raw) byName.set(it.name, it)
784
-
785
- // Detect parent cycles: walk upwards from each item; any name seen
786
- // twice → cycle. Items in a cycle get treated as top-level.
787
- const inCycle = new Set<string>()
788
- for (const it of raw) {
789
- if (it.parent === undefined) continue
790
- const seen = new Set<string>([it.name])
791
- let cur: string | undefined = it.parent
792
- while (cur !== undefined) {
793
- if (seen.has(cur)) {
794
- if (typeof console !== 'undefined' && typeof console.warn === 'function') {
795
- console.warn(`[Pilotiq] navigationParentItem cycle detected at "${it.name}" — rendering at top level.`)
796
- }
797
- inCycle.add(it.name)
798
- break
799
- }
800
- seen.add(cur)
801
- const parent = byName.get(cur)
802
- if (!parent) break
803
- cur = parent.parent
804
- }
805
- }
806
-
807
- const childrenOf = new Map<string, RawNavItem[]>()
808
- const top: RawNavItem[] = []
809
- for (const it of raw) {
810
- const parent = it.parent
811
- if (parent && byName.has(parent) && !inCycle.has(it.name)) {
812
- const list = childrenOf.get(parent) ?? []
813
- list.push(it)
814
- childrenOf.set(parent, list)
815
- } else {
816
- top.push(it)
817
- }
818
- }
819
-
820
- // Sort items in a sibling group by sort (asc), ties → registration order.
821
- const sortItems = (items: RawNavItem[]): RawNavItem[] => {
822
- return [...items].sort((a, b) => {
823
- const aHas = a.sort !== undefined, bHas = b.sort !== undefined
824
- if (aHas && bHas) return a.sort! - b.sort! || a._idx - b._idx
825
- if (aHas) return -1 // sorted items come before unsorted
826
- if (bHas) return 1
827
- return a._idx - b._idx
828
- })
829
- }
830
-
831
- // Strip internals + recurse into children.
832
- const finalize = (items: RawNavItem[]): NavItem[] =>
833
- sortItems(items).map(it => {
834
- const kids = childrenOf.get(it.name)
835
- const { parent, _idx, ...rest } = it
836
- const out: NavItem = { ...rest }
837
- if (kids && kids.length > 0) out.children = finalize(kids)
838
- return out
839
- })
840
-
841
- return finalize(top)
842
- }