@pilotiq/pilotiq 0.23.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 (500) hide show
  1. package/CHANGELOG.md +91 -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/dist/actions/exportFactory.d.ts +10 -0
  15. package/dist/actions/exportFactory.d.ts.map +1 -1
  16. package/dist/actions/exportFactory.js +10 -0
  17. package/dist/actions/exportFactory.js.map +1 -1
  18. package/dist/react/CollabRoomContext.d.ts +5 -5
  19. package/dist/react/index.d.ts +0 -1
  20. package/dist/react/index.d.ts.map +1 -1
  21. package/dist/react/index.js +0 -1
  22. package/dist/react/index.js.map +1 -1
  23. package/dist/routes/helpers.d.ts.map +1 -1
  24. package/dist/routes/helpers.js +6 -2
  25. package/dist/routes/helpers.js.map +1 -1
  26. package/dist/routes/relations.d.ts.map +1 -1
  27. package/dist/routes/relations.js +12 -0
  28. package/dist/routes/relations.js.map +1 -1
  29. package/package.json +6 -1
  30. package/.turbo/turbo-build.log +0 -8
  31. package/CLAUDE.md +0 -265
  32. package/dist/react/useCollabSeed.d.ts +0 -23
  33. package/dist/react/useCollabSeed.d.ts.map +0 -1
  34. package/dist/react/useCollabSeed.js +0 -82
  35. package/dist/react/useCollabSeed.js.map +0 -1
  36. package/src/Cluster.test.ts +0 -283
  37. package/src/Cluster.ts +0 -83
  38. package/src/Column.test.ts +0 -199
  39. package/src/Column.ts +0 -710
  40. package/src/Global.test.ts +0 -367
  41. package/src/Global.ts +0 -169
  42. package/src/Page.test.ts +0 -114
  43. package/src/Page.ts +0 -208
  44. package/src/Pilotiq.perf.test.ts +0 -252
  45. package/src/Pilotiq.test.ts +0 -129
  46. package/src/Pilotiq.ts +0 -1158
  47. package/src/PilotiqRegistry.ts +0 -36
  48. package/src/PilotiqServiceProvider.ts +0 -121
  49. package/src/RelationManager.test.ts +0 -400
  50. package/src/RelationManager.ts +0 -527
  51. package/src/RenderHook.test.ts +0 -252
  52. package/src/RenderHook.ts +0 -242
  53. package/src/Resource.test.ts +0 -284
  54. package/src/Resource.ts +0 -526
  55. package/src/RightPanel.test.ts +0 -202
  56. package/src/RightPanel.ts +0 -132
  57. package/src/Tab.test.ts +0 -91
  58. package/src/Tab.ts +0 -156
  59. package/src/UserMenuItem.ts +0 -145
  60. package/src/actions/Action.test.ts +0 -2526
  61. package/src/actions/Action.ts +0 -1515
  62. package/src/actions/ActionGroup.test.ts +0 -112
  63. package/src/actions/ActionGroup.ts +0 -173
  64. package/src/actions/attachFactory.ts +0 -172
  65. package/src/actions/bulkFactories.ts +0 -168
  66. package/src/actions/crudFactories.ts +0 -220
  67. package/src/actions/exportFactory.ts +0 -215
  68. package/src/actions/factoryHelpers.ts +0 -177
  69. package/src/actions/importFactory.ts +0 -243
  70. package/src/actions/index.ts +0 -17
  71. package/src/actions/m2mFactories.ts +0 -193
  72. package/src/actions/relationFactories.ts +0 -372
  73. package/src/applyPageHooks.test.ts +0 -463
  74. package/src/applyPageHooks.ts +0 -330
  75. package/src/authorization.test.ts +0 -483
  76. package/src/breadcrumbs.test.ts +0 -238
  77. package/src/cells/coerce.test.ts +0 -85
  78. package/src/cells/coerce.ts +0 -84
  79. package/src/clusterPaths.ts +0 -35
  80. package/src/columns/BadgeColumn.test.ts +0 -54
  81. package/src/columns/BadgeColumn.ts +0 -32
  82. package/src/columns/BooleanColumn.test.ts +0 -41
  83. package/src/columns/BooleanColumn.ts +0 -18
  84. package/src/columns/ColorColumn.test.ts +0 -37
  85. package/src/columns/ColorColumn.ts +0 -38
  86. package/src/columns/IconColumn.test.ts +0 -54
  87. package/src/columns/IconColumn.ts +0 -37
  88. package/src/columns/ImageColumn.test.ts +0 -41
  89. package/src/columns/ImageColumn.ts +0 -28
  90. package/src/columns/SelectColumn.ts +0 -98
  91. package/src/columns/TextColumn.test.ts +0 -190
  92. package/src/columns/TextColumn.ts +0 -20
  93. package/src/columns/TextInputColumn.ts +0 -68
  94. package/src/columns/ToggleColumn.ts +0 -46
  95. package/src/columns/editableColumns.test.ts +0 -238
  96. package/src/columns/index.ts +0 -9
  97. package/src/defaultGlobalPages.ts +0 -95
  98. package/src/defaultPages.test.ts +0 -634
  99. package/src/defaultPages.ts +0 -617
  100. package/src/defaultViewPage.test.ts +0 -147
  101. package/src/elements/Form.test.ts +0 -223
  102. package/src/elements/Form.ts +0 -416
  103. package/src/elements/ListTabs.ts +0 -28
  104. package/src/elements/Table.test.ts +0 -422
  105. package/src/elements/Table.ts +0 -850
  106. package/src/elements/TableGroup.test.ts +0 -260
  107. package/src/elements/TableGroup.ts +0 -334
  108. package/src/elements/dispatchAction.test.ts +0 -463
  109. package/src/elements/dispatchAction.ts +0 -355
  110. package/src/elements/dispatchForm.test.ts +0 -477
  111. package/src/elements/dispatchForm.ts +0 -1993
  112. package/src/elements/dispatchTable.test.ts +0 -1514
  113. package/src/elements/dispatchTable.ts +0 -745
  114. package/src/elements/index.ts +0 -21
  115. package/src/entries/BadgeEntry.ts +0 -39
  116. package/src/entries/CodeEntry.test.ts +0 -40
  117. package/src/entries/CodeEntry.ts +0 -52
  118. package/src/entries/ColorEntry.ts +0 -63
  119. package/src/entries/ComponentEntry.test.ts +0 -173
  120. package/src/entries/ComponentEntry.ts +0 -95
  121. package/src/entries/Entry.ts +0 -304
  122. package/src/entries/IconEntry.ts +0 -49
  123. package/src/entries/ImageEntry.ts +0 -61
  124. package/src/entries/KeyValueEntry.ts +0 -47
  125. package/src/entries/RepeatableEntry.test.ts +0 -239
  126. package/src/entries/RepeatableEntry.ts +0 -173
  127. package/src/entries/TextEntry.test.ts +0 -394
  128. package/src/entries/TextEntry.ts +0 -60
  129. package/src/entries/index.ts +0 -12
  130. package/src/entries/leaves.test.ts +0 -306
  131. package/src/entries/registry.ts +0 -54
  132. package/src/fields/BuilderField.test.ts +0 -1188
  133. package/src/fields/BuilderField.ts +0 -605
  134. package/src/fields/BuilderRelationship.test.ts +0 -811
  135. package/src/fields/CheckboxField.test.ts +0 -44
  136. package/src/fields/CheckboxField.ts +0 -27
  137. package/src/fields/CheckboxListField.test.ts +0 -99
  138. package/src/fields/CheckboxListField.ts +0 -66
  139. package/src/fields/ColorPickerField.test.ts +0 -33
  140. package/src/fields/ColorPickerField.ts +0 -25
  141. package/src/fields/DateField.ts +0 -54
  142. package/src/fields/DateTimeField.test.ts +0 -55
  143. package/src/fields/EmailField.ts +0 -16
  144. package/src/fields/Field.test.ts +0 -654
  145. package/src/fields/Field.ts +0 -817
  146. package/src/fields/FileUploadField.test.ts +0 -143
  147. package/src/fields/FileUploadField.ts +0 -159
  148. package/src/fields/HiddenField.test.ts +0 -27
  149. package/src/fields/HiddenField.ts +0 -28
  150. package/src/fields/KeyValueField.test.ts +0 -105
  151. package/src/fields/KeyValueField.ts +0 -55
  152. package/src/fields/MarkdownField.test.ts +0 -167
  153. package/src/fields/MarkdownField.ts +0 -162
  154. package/src/fields/NumberField.ts +0 -33
  155. package/src/fields/RadioField.test.ts +0 -94
  156. package/src/fields/RadioField.ts +0 -67
  157. package/src/fields/RepeaterField.test.ts +0 -1806
  158. package/src/fields/RepeaterField.ts +0 -939
  159. package/src/fields/RepeaterRelationship.test.ts +0 -1923
  160. package/src/fields/RepeaterSimple.test.ts +0 -248
  161. package/src/fields/RowButton.test.ts +0 -219
  162. package/src/fields/RowButton.ts +0 -135
  163. package/src/fields/SelectField.test.ts +0 -192
  164. package/src/fields/SelectField.ts +0 -235
  165. package/src/fields/SliderField.test.ts +0 -50
  166. package/src/fields/SliderField.ts +0 -53
  167. package/src/fields/SlugField.ts +0 -24
  168. package/src/fields/TagsInputField.test.ts +0 -154
  169. package/src/fields/TagsInputField.ts +0 -133
  170. package/src/fields/TextField.test.ts +0 -213
  171. package/src/fields/TextField.ts +0 -177
  172. package/src/fields/TextareaField.test.ts +0 -58
  173. package/src/fields/TextareaField.ts +0 -59
  174. package/src/fields/ToggleButtonsField.test.ts +0 -106
  175. package/src/fields/ToggleButtonsField.ts +0 -59
  176. package/src/fields/ToggleField.ts +0 -16
  177. package/src/fields/disableOptionsWhenSelectedInSiblingRepeaterItems.test.ts +0 -319
  178. package/src/fields/optionsResolver.ts +0 -95
  179. package/src/fields/resolveField.ts +0 -28
  180. package/src/filters/BooleanFilter.ts +0 -35
  181. package/src/filters/DateRangeFilter.test.ts +0 -194
  182. package/src/filters/DateRangeFilter.ts +0 -148
  183. package/src/filters/Filter.test.ts +0 -268
  184. package/src/filters/Filter.ts +0 -184
  185. package/src/filters/FormFilter.test.ts +0 -238
  186. package/src/filters/FormFilter.ts +0 -215
  187. package/src/filters/MultiSelectFilter.test.ts +0 -119
  188. package/src/filters/MultiSelectFilter.ts +0 -78
  189. package/src/filters/QueryBuilderFilter.test.ts +0 -662
  190. package/src/filters/QueryBuilderFilter.ts +0 -398
  191. package/src/filters/SelectFilter.ts +0 -46
  192. package/src/filters/TernaryFilter.test.ts +0 -160
  193. package/src/filters/TernaryFilter.ts +0 -72
  194. package/src/filters/TrashedFilter.test.ts +0 -149
  195. package/src/filters/TrashedFilter.ts +0 -55
  196. package/src/filters/queryBuilder/BooleanConstraint.ts +0 -31
  197. package/src/filters/queryBuilder/Constraint.ts +0 -115
  198. package/src/filters/queryBuilder/DateConstraint.ts +0 -69
  199. package/src/filters/queryBuilder/NumberConstraint.ts +0 -66
  200. package/src/filters/queryBuilder/SelectConstraint.ts +0 -72
  201. package/src/filters/queryBuilder/TextConstraint.ts +0 -64
  202. package/src/filters/queryBuilder/index.ts +0 -12
  203. package/src/icons/index.ts +0 -2
  204. package/src/icons/lucide.ts +0 -204
  205. package/src/icons/registry.test.ts +0 -56
  206. package/src/icons/registry.ts +0 -41
  207. package/src/icons/types.ts +0 -47
  208. package/src/index.ts +0 -525
  209. package/src/io/csv.test.ts +0 -142
  210. package/src/io/csv.ts +0 -170
  211. package/src/nestedRelationManagerData.test.ts +0 -547
  212. package/src/notifications/Notification.test.ts +0 -210
  213. package/src/notifications/Notification.ts +0 -354
  214. package/src/notifications/broadcast.test.ts +0 -110
  215. package/src/notifications/broadcast.ts +0 -95
  216. package/src/notifications/database.test.ts +0 -383
  217. package/src/notifications/database.ts +0 -398
  218. package/src/notifications/databaseNotifications.test.ts +0 -187
  219. package/src/notifications/dispatchNotificationAction.test.ts +0 -341
  220. package/src/notifications/dispatchNotificationAction.ts +0 -142
  221. package/src/notifications/flash.test.ts +0 -89
  222. package/src/notifications/flash.ts +0 -71
  223. package/src/notifications/index.ts +0 -45
  224. package/src/notifications/registerBroadcastAuth.test.ts +0 -134
  225. package/src/notifications/registerBroadcastAuth.ts +0 -100
  226. package/src/notifications/resolveSavedNotification.test.ts +0 -82
  227. package/src/notifications/resolveSavedNotification.ts +0 -59
  228. package/src/notifications/types.ts +0 -93
  229. package/src/orm/m2mAccessor.ts +0 -66
  230. package/src/orm/modelDefaults.test.ts +0 -633
  231. package/src/orm/modelDefaults.ts +0 -666
  232. package/src/pageData/breadcrumbs.ts +0 -288
  233. package/src/pageData/forms.ts +0 -578
  234. package/src/pageData/helpers.ts +0 -857
  235. package/src/pageData/misc.ts +0 -347
  236. package/src/pageData/navigation.ts +0 -842
  237. package/src/pageData/relationPages.ts +0 -1248
  238. package/src/pageData/relationTabs.ts +0 -286
  239. package/src/pageData/resourcePages.ts +0 -609
  240. package/src/pageData.test.ts +0 -1545
  241. package/src/pageData.ts +0 -341
  242. package/src/plugins/index.ts +0 -8
  243. package/src/plugins/themeEditor.test.ts +0 -36
  244. package/src/plugins/themeEditor.ts +0 -45
  245. package/src/react/AppShell.tsx +0 -251
  246. package/src/react/CollabExtensionFactoryRegistry.ts +0 -55
  247. package/src/react/CollabRoomContext.ts +0 -98
  248. package/src/react/CollabTextRendererRegistry.ts +0 -102
  249. package/src/react/CommandPalette.tsx +0 -375
  250. package/src/react/CurrentUserContext.tsx +0 -50
  251. package/src/react/CustomPageWrapperGate.tsx +0 -69
  252. package/src/react/CustomPageWrapperRegistry.ts +0 -45
  253. package/src/react/FieldFocusReporterRegistry.ts +0 -37
  254. package/src/react/FieldLabelSlotRegistry.ts +0 -30
  255. package/src/react/FieldPresenceRegistry.ts +0 -46
  256. package/src/react/FormCollabBindingRegistry.ts +0 -242
  257. package/src/react/FormStateContext.tsx +0 -591
  258. package/src/react/HeadHooks.tsx +0 -126
  259. package/src/react/MarkdownEditorRegistry.test.ts +0 -38
  260. package/src/react/MarkdownEditorRegistry.ts +0 -107
  261. package/src/react/NotificationActionStrip.tsx +0 -263
  262. package/src/react/NotificationBell.tsx +0 -426
  263. package/src/react/PendingSuggestionApplierRegistry.test.ts +0 -97
  264. package/src/react/PendingSuggestionApplierRegistry.ts +0 -98
  265. package/src/react/PendingSuggestionOverlayRegistry.ts +0 -54
  266. package/src/react/PendingSuggestionsContext.tsx +0 -172
  267. package/src/react/RecordWrapperGate.tsx +0 -58
  268. package/src/react/RecordWrapperRegistry.ts +0 -39
  269. package/src/react/RenderHookSlot.tsx +0 -32
  270. package/src/react/RightSidebar.tsx +0 -257
  271. package/src/react/RightSidebarContext.tsx +0 -234
  272. package/src/react/RightSidebarTrigger.tsx +0 -53
  273. package/src/react/RowCoordsContext.tsx +0 -23
  274. package/src/react/SchemaRenderer.tsx +0 -549
  275. package/src/react/SearchTrigger.tsx +0 -46
  276. package/src/react/ThemeProvider.tsx +0 -93
  277. package/src/react/ThemeSettingsPage.tsx +0 -579
  278. package/src/react/ThemeToggle.tsx +0 -20
  279. package/src/react/Toaster.tsx +0 -158
  280. package/src/react/UserMenu.tsx +0 -196
  281. package/src/react/WidgetDataContext.tsx +0 -157
  282. package/src/react/cells/EditableCell.tsx +0 -389
  283. package/src/react/component-slots.test.ts +0 -103
  284. package/src/react/component-slots.ts +0 -116
  285. package/src/react/fieldJsHandler.test.ts +0 -166
  286. package/src/react/fieldJsHandler.ts +0 -79
  287. package/src/react/fields/BuilderInput.tsx +0 -1078
  288. package/src/react/fields/CheckboxInput.tsx +0 -39
  289. package/src/react/fields/CheckboxListInput.tsx +0 -102
  290. package/src/react/fields/ColorInput.tsx +0 -71
  291. package/src/react/fields/DateFieldInput.tsx +0 -70
  292. package/src/react/fields/DateTimeInput.tsx +0 -62
  293. package/src/react/fields/FieldShell.tsx +0 -348
  294. package/src/react/fields/FileUploadInput.tsx +0 -639
  295. package/src/react/fields/HiddenInput.tsx +0 -17
  296. package/src/react/fields/KeyValueInput.tsx +0 -230
  297. package/src/react/fields/MarkdownInput.tsx +0 -560
  298. package/src/react/fields/RadioInput.tsx +0 -81
  299. package/src/react/fields/RepeaterInput.test.ts +0 -116
  300. package/src/react/fields/RepeaterInput.tsx +0 -1420
  301. package/src/react/fields/SelectFieldInput.tsx +0 -280
  302. package/src/react/fields/SliderInput.tsx +0 -81
  303. package/src/react/fields/TagsInput.tsx +0 -283
  304. package/src/react/fields/TextLikeInput.tsx +0 -256
  305. package/src/react/fields/ToggleButtonsInput.tsx +0 -60
  306. package/src/react/fields/ToggleFieldInput.tsx +0 -56
  307. package/src/react/fields/relationshipRenameDispatch.test.ts +0 -106
  308. package/src/react/fields/relationshipRenameDispatch.ts +0 -97
  309. package/src/react/fields/repeaterReconcile.test.ts +0 -114
  310. package/src/react/fields/repeaterReconcile.ts +0 -104
  311. package/src/react/fields/rowChromeButton.tsx +0 -336
  312. package/src/react/fields/rowState.ts +0 -106
  313. package/src/react/fields/syncRowGates.test.ts +0 -202
  314. package/src/react/fields/syncRowGates.ts +0 -66
  315. package/src/react/fields/textInputControls.tsx +0 -238
  316. package/src/react/fields/useRowReorderDnd.ts +0 -78
  317. package/src/react/formStateHelpers.test.ts +0 -508
  318. package/src/react/formStateHelpers.ts +0 -381
  319. package/src/react/hooks/use-mobile.ts +0 -19
  320. package/src/react/icon-context.tsx +0 -60
  321. package/src/react/index.ts +0 -195
  322. package/src/react/layouts/SidebarLayout.tsx +0 -250
  323. package/src/react/layouts/TopbarLayout.tsx +0 -258
  324. package/src/react/navigate.tsx +0 -37
  325. package/src/react/onProviderSynced.test.ts +0 -90
  326. package/src/react/parseRecordEditUrl.test.ts +0 -122
  327. package/src/react/parseRecordEditUrl.ts +0 -94
  328. package/src/react/persistedState.ts +0 -40
  329. package/src/react/registry.ts +0 -48
  330. package/src/react/right-panel-registry.tsx +0 -47
  331. package/src/react/schemaRenderer/AlertRenderer.tsx +0 -112
  332. package/src/react/schemaRenderer/EntryRenderer.tsx +0 -501
  333. package/src/react/schemaRenderer/SectionRenderer.tsx +0 -120
  334. package/src/react/schemaRenderer/SimpleElements.tsx +0 -306
  335. package/src/react/schemaRenderer/TabsRenderer.tsx +0 -62
  336. package/src/react/schemaRenderer/WizardRenderer.tsx +0 -338
  337. package/src/react/schemaRenderer/action/ActionGroupTrigger.tsx +0 -177
  338. package/src/react/schemaRenderer/action/ActionModalDialog.tsx +0 -273
  339. package/src/react/schemaRenderer/action/ConfirmActionDialog.tsx +0 -61
  340. package/src/react/schemaRenderer/action/HandlerActionButton.tsx +0 -43
  341. package/src/react/schemaRenderer/action/MethodActionButton.tsx +0 -64
  342. package/src/react/schemaRenderer/action/buttons.tsx +0 -99
  343. package/src/react/schemaRenderer/action/helpers.ts +0 -140
  344. package/src/react/schemaRenderer/action/renderAction.tsx +0 -245
  345. package/src/react/schemaRenderer/columnFormat.ts +0 -65
  346. package/src/react/schemaRenderer/constants.ts +0 -50
  347. package/src/react/schemaRenderer/form/FormRenderer.tsx +0 -274
  348. package/src/react/schemaRenderer/form/renderField.tsx +0 -511
  349. package/src/react/schemaRenderer/helpers.tsx +0 -81
  350. package/src/react/schemaRenderer/table/CardsLayoutBody.tsx +0 -308
  351. package/src/react/schemaRenderer/table/TableRenderer.tsx +0 -123
  352. package/src/react/schemaRenderer/table/TableRendererBody.tsx +0 -974
  353. package/src/react/schemaRenderer/table/filters.tsx +0 -1233
  354. package/src/react/schemaRenderer/table/formatCell.tsx +0 -264
  355. package/src/react/schemaRenderer/table/links.tsx +0 -112
  356. package/src/react/schemaRenderer/table/renderRowActions.tsx +0 -52
  357. package/src/react/schemaRenderer/table/url.tsx +0 -143
  358. package/src/react/theme-preview/apply.ts +0 -99
  359. package/src/react/theme-preview/build-html.ts +0 -436
  360. package/src/react/ui/button.tsx +0 -51
  361. package/src/react/ui/calendar.tsx +0 -67
  362. package/src/react/ui/checkbox.tsx +0 -29
  363. package/src/react/ui/dialog.tsx +0 -108
  364. package/src/react/ui/dropdown-menu.tsx +0 -97
  365. package/src/react/ui/input.tsx +0 -20
  366. package/src/react/ui/label.tsx +0 -21
  367. package/src/react/ui/popover.tsx +0 -50
  368. package/src/react/ui/select.tsx +0 -169
  369. package/src/react/ui/separator.tsx +0 -25
  370. package/src/react/ui/sheet.tsx +0 -136
  371. package/src/react/ui/sidebar.tsx +0 -723
  372. package/src/react/ui/skeleton.tsx +0 -13
  373. package/src/react/ui/slider.tsx +0 -34
  374. package/src/react/ui/switch.tsx +0 -28
  375. package/src/react/ui/table.tsx +0 -105
  376. package/src/react/ui/tabs.tsx +0 -63
  377. package/src/react/ui/textarea.tsx +0 -18
  378. package/src/react/ui/tooltip.tsx +0 -64
  379. package/src/react/useCollabSeed.ts +0 -86
  380. package/src/react/useResizableWidth.ts +0 -139
  381. package/src/react/utils.ts +0 -6
  382. package/src/react/widgetRegistry.test.ts +0 -43
  383. package/src/react/widgetRegistry.ts +0 -50
  384. package/src/react/widgets/StatsOverviewRenderer.tsx +0 -232
  385. package/src/react/widgets/TableWidgetRenderer.tsx +0 -231
  386. package/src/react/widgets/ViewRenderer.tsx +0 -71
  387. package/src/relationManagerData.test.ts +0 -1595
  388. package/src/richtext/index.ts +0 -8
  389. package/src/richtext/registry.ts +0 -89
  390. package/src/routes/globals.ts +0 -148
  391. package/src/routes/guard.test.ts +0 -325
  392. package/src/routes/helpers.ts +0 -700
  393. package/src/routes/pages.ts +0 -175
  394. package/src/routes/panel.ts +0 -204
  395. package/src/routes/relations.ts +0 -1227
  396. package/src/routes/resources.ts +0 -781
  397. package/src/routes/theme.ts +0 -91
  398. package/src/routes-nested-relations.test.ts +0 -676
  399. package/src/routes-relations.test.ts +0 -972
  400. package/src/routes.test.ts +0 -2027
  401. package/src/routes.ts +0 -303
  402. package/src/schema/Alert.test.ts +0 -109
  403. package/src/schema/Alert.ts +0 -131
  404. package/src/schema/Block.ts +0 -169
  405. package/src/schema/Breadcrumbs.ts +0 -40
  406. package/src/schema/Card.ts +0 -35
  407. package/src/schema/Divider.ts +0 -20
  408. package/src/schema/Element.ts +0 -219
  409. package/src/schema/EmptyState.test.ts +0 -37
  410. package/src/schema/EmptyState.ts +0 -63
  411. package/src/schema/Fieldset.ts +0 -43
  412. package/src/schema/Grid.ts +0 -43
  413. package/src/schema/Group.ts +0 -30
  414. package/src/schema/Heading.ts +0 -39
  415. package/src/schema/Html.ts +0 -67
  416. package/src/schema/Icon.ts +0 -54
  417. package/src/schema/Image.ts +0 -57
  418. package/src/schema/LinkTag.ts +0 -41
  419. package/src/schema/Markdown.ts +0 -85
  420. package/src/schema/MetaTag.ts +0 -41
  421. package/src/schema/RelationTabs.ts +0 -71
  422. package/src/schema/ScriptTag.ts +0 -55
  423. package/src/schema/Section.ts +0 -160
  424. package/src/schema/ServerDataElement.test.ts +0 -140
  425. package/src/schema/ServerDataElement.ts +0 -156
  426. package/src/schema/SlotComponent.test.ts +0 -77
  427. package/src/schema/SlotComponent.ts +0 -71
  428. package/src/schema/Split.ts +0 -50
  429. package/src/schema/Stat.test.ts +0 -118
  430. package/src/schema/Stat.ts +0 -154
  431. package/src/schema/StatsOverview.test.ts +0 -141
  432. package/src/schema/StatsOverview.ts +0 -119
  433. package/src/schema/StyleTag.ts +0 -35
  434. package/src/schema/TableWidget.test.ts +0 -297
  435. package/src/schema/TableWidget.ts +0 -289
  436. package/src/schema/Tabs.ts +0 -79
  437. package/src/schema/Text.ts +0 -58
  438. package/src/schema/UnorderedList.ts +0 -49
  439. package/src/schema/View.test.ts +0 -111
  440. package/src/schema/View.ts +0 -127
  441. package/src/schema/Wizard.ts +0 -220
  442. package/src/schema/containers.test.ts +0 -564
  443. package/src/schema/headTags.test.ts +0 -134
  444. package/src/schema/index.ts +0 -40
  445. package/src/schema/primes.test.ts +0 -269
  446. package/src/schema/resolveSchema.test.ts +0 -379
  447. package/src/schema/resolveSchema.ts +0 -917
  448. package/src/schema/sanitize.ts +0 -58
  449. package/src/search.test.ts +0 -446
  450. package/src/search.ts +0 -178
  451. package/src/sessionFilters.test.ts +0 -375
  452. package/src/sessionFilters.ts +0 -143
  453. package/src/slot-components/index.ts +0 -10
  454. package/src/slot-components/registry.ts +0 -56
  455. package/src/styles/file-upload.css +0 -13
  456. package/src/summarizers/Summarizer.test.ts +0 -84
  457. package/src/summarizers/Summarizer.ts +0 -123
  458. package/src/summarizers/index.ts +0 -11
  459. package/src/theme/base-colors.ts +0 -68
  460. package/src/theme/chart-colors.ts +0 -50
  461. package/src/theme/colors.ts +0 -447
  462. package/src/theme/generate-css.test.ts +0 -139
  463. package/src/theme/generate-css.ts +0 -44
  464. package/src/theme/generate-scale.test.ts +0 -106
  465. package/src/theme/generate-scale.ts +0 -97
  466. package/src/theme/icon-map.ts +0 -42
  467. package/src/theme/index.ts +0 -34
  468. package/src/theme/migrate.test.ts +0 -178
  469. package/src/theme/migrate.ts +0 -81
  470. package/src/theme/presets.ts +0 -135
  471. package/src/theme/radius.ts +0 -18
  472. package/src/theme/resolve.test.ts +0 -238
  473. package/src/theme/resolve.ts +0 -96
  474. package/src/theme/spacing.ts +0 -18
  475. package/src/theme/storage.test.ts +0 -126
  476. package/src/theme/storage.ts +0 -106
  477. package/src/theme/theme-colors.ts +0 -88
  478. package/src/theme/types.ts +0 -125
  479. package/src/uploads/UploadAdapter.ts +0 -35
  480. package/src/uploads/index.ts +0 -2
  481. package/src/uploads/localUpload.test.ts +0 -70
  482. package/src/uploads/localUpload.ts +0 -84
  483. package/src/validation/Validator.ts +0 -49
  484. package/src/validation/index.ts +0 -28
  485. package/src/validation/rules.ts +0 -78
  486. package/src/validation/runValidators.ts +0 -435
  487. package/src/validation/uniqueValidator.test.ts +0 -196
  488. package/src/validation/uniqueValidator.ts +0 -133
  489. package/src/validation/validators.test.ts +0 -268
  490. package/src/vite.test.ts +0 -184
  491. package/src/vite.ts +0 -787
  492. package/src/widgets/index.ts +0 -10
  493. package/src/widgets/registry.ts +0 -45
  494. package/src/widgets.test.ts +0 -592
  495. package/tsconfig.build.json +0 -11
  496. package/tsconfig.json +0 -4
  497. package/tsconfig.test.json +0 -10
  498. package/views/react/Dashboard.tsx +0 -27
  499. package/views/react/Resources/Form.tsx +0 -102
  500. 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
- }