@pilotiq/pilotiq 0.24.1 → 0.24.3

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