@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,857 +0,0 @@
1
- import type {
2
- Pilotiq,
3
- PilotiqConfig,
4
- EditPageHydrator,
5
- EditPageHydratorContext,
6
- } from '../Pilotiq.js'
7
- import type { Page } from '../Page.js'
8
- import { Element } from '../schema/Element.js'
9
- import { Field } from '../fields/Field.js'
10
- import type { SchemaContext, RenderContext } from '../schema/resolveSchema.js'
11
- import { Form } from '../elements/Form.js'
12
- import { Table } from '../elements/Table.js'
13
- import { Column } from '../Column.js'
14
- import { SelectField } from '../fields/SelectField.js'
15
- import { isRepeaterField, RepeaterField } from '../fields/RepeaterField.js'
16
- import { isBuilderField, BuilderField } from '../fields/BuilderField.js'
17
- import { isServerDataElement, type ServerDataElement } from '../schema/ServerDataElement.js'
18
- import { findActions, findRowExtraActions } from '../elements/dispatchAction.js'
19
- import { findForms, loadRelationRows } from '../elements/dispatchForm.js'
20
- import { findTables } from '../elements/dispatchTable.js'
21
- import {
22
- getMorphRelationDescriptor,
23
- getPrimaryKey,
24
- type ModelLike,
25
- } from '../orm/modelDefaults.js'
26
-
27
- // ─── pageData shared helpers ────────────────────────────────
28
- //
29
- // URL-tag helpers (stamp dispatch URLs / cell-edit endpoints / wizard
30
- // endpoints / mention-resolve endpoints), the load-record fill pipeline
31
- // (`applyFillPipeline` + relationship Repeater/Builder fills),
32
- // server-data widget resolution, and the two `*Ctx` helpers that the
33
- // per-role builders use to construct the `SchemaContext` they pass into
34
- // `resolveSchema`. Pure (mostly stateless) helpers — no per-page-role
35
- // orchestration.
36
-
37
-
38
- export function userCtx<C extends SchemaContext>(ctx: C, user: unknown): C {
39
- if (user === null || user === undefined) return ctx
40
- return { ...ctx, user: user as NonNullable<SchemaContext['user']> }
41
- }
42
-
43
- /**
44
- * Run a (possibly async) boolean predicate; fail closed when it throws.
45
- *
46
- * Every page-builder pre-flight that consults `canX(user, …)` needs this
47
- * shape — predicates may be sync booleans, Promises, or throw at the
48
- * remote-auth layer; a throw must never propagate to the wire as an
49
- * unhandled 500.
50
- */
51
- export async function safeBool(fn: () => boolean | Promise<boolean>): Promise<boolean> {
52
- try { return Boolean(await fn()) } catch { return false }
53
- }
54
-
55
- /** Plan #6 — stamp the panel-wide upload URL so `FileUpload` fields
56
- * emit it on their meta. Single URL for the whole panel; no per-field
57
- * variation. The route is always registered (see `_uploads` in
58
- * `routes.ts`) — meta is stamped regardless of whether an adapter is
59
- * configured so the renderer can show a clear error rather than
60
- * silently breaking. The companion `hasUploadAdapter` flag distinguishes
61
- * "URL exists but adapter missing" so fields with optional upload
62
- * affordances (e.g. `MarkdownField`'s `attachFiles` button) can hide
63
- * themselves rather than render a broken control. */
64
- export function uploadCtx<C extends SchemaContext>(ctx: C, cfg: PilotiqConfig): C {
65
- return {
66
- ...ctx,
67
- uploadUrl: `${cfg.path}/_uploads`,
68
- ...(cfg.uploads ? { hasUploadAdapter: true } : {}),
69
- }
70
- }
71
-
72
-
73
- export async function callPageSchema(PageClass: typeof Page, ctx: SchemaContext): Promise<Element[]> {
74
- return Promise.resolve(PageClass.schema(ctx))
75
- }
76
-
77
- /** Mark every Form on the page with its action URL so the rendered <form> posts to itself. */
78
- export function tagFormActions(elements: ReadonlyArray<Element>, action: string): void {
79
- for (const form of findForms(elements)) {
80
- if (!form.getAction()) form.action(action)
81
- }
82
- }
83
-
84
- /**
85
- * Plan #5 — stamp the partial-resolve endpoint URL on every form whose
86
- * descendants include at least one `live()` field. The client uses
87
- * `FormMeta.stateUrl` to flip into controlled-state mode; forms without
88
- * any live fields stay uncontrolled (zero-cost legacy path).
89
- *
90
- * `urlBuilder(formId)` lets the caller compose a per-form URL — the
91
- * endpoint shape is `${base}/${slug}/_form/${formId}/state` so each
92
- * form on a multi-form page gets its own route segment.
93
- */
94
- export function tagFormStateUrls(
95
- elements: ReadonlyArray<Element>,
96
- urlBuilder: (formId: string) => string,
97
- ): void {
98
- for (const form of findForms(elements)) {
99
- if (formHasLiveField(form)) {
100
- form.withStateUrl(urlBuilder(form.getFormId()))
101
- }
102
- }
103
- }
104
-
105
- /**
106
- * Reorderable rows — stamp the POST-reorder URL on every `Table` that
107
- * has `Table.reorderable()` set. The renderer reads `TableMeta.reorderUrl`
108
- * to wire the drop handler; tables that aren't reorderable skip wiring
109
- * entirely. Same shape as `tagFormStateUrls` so the call site stays
110
- * consistent.
111
- */
112
- export function tagTableReorderUrls(
113
- elements: ReadonlyArray<Element>,
114
- url: string,
115
- ): void {
116
- for (const table of findTables(elements)) {
117
- if (table.isReorderable() && !table.getReorderUrl()) {
118
- table.withReorderUrl(url)
119
- }
120
- }
121
- }
122
-
123
- // Marks every Table on the page deferred and stamps the URL the
124
- // renderer will fetch from after mount. Must run BEFORE `loadTableRecords`
125
- // so the records handler short-circuits.
126
- export function tagTableDeferred(
127
- elements: ReadonlyArray<Element>,
128
- url: string,
129
- ): void {
130
- for (const table of findTables(elements)) {
131
- table.withDeferred(true)
132
- table.withTableUrl(url)
133
- }
134
- }
135
-
136
- /**
137
- * Editable cell columns — walk every table on the page and stamp
138
- * `_cellEditUrls[colName]` per row, but only on rows that already
139
- * carry a `_cellEditable[colName]` marker (set by `loadTableRecords`
140
- * after `R.canEdit(user, row)` passed). The dispatcher stays
141
- * URL-shape-agnostic; URL building lives here parallel to
142
- * `tagFormStateUrls / tagTableReorderUrls`.
143
- *
144
- * `idOf` extracts the per-row primary key. Defaults to reading `id` —
145
- * works for the rudder ORM convention. Resources with a different
146
- * primary-key column should pass an override (none in v1).
147
- */
148
- export function tagCellEditUrls(
149
- elements: ReadonlyArray<Element>,
150
- resourceUrl: string,
151
- idOf: (row: Record<string, unknown>) => unknown = row => row['id'],
152
- ): void {
153
- for (const table of findTables(elements)) {
154
- const rows = table.getRows() as ReadonlyArray<Record<string, unknown>> | undefined
155
- if (!rows || rows.length === 0) continue
156
- // Optimisation: skip the table when none of its columns are editable.
157
- const editable = (table.getChildren() ?? []).some(c => c instanceof Column && c.isEditable())
158
- if (!editable) continue
159
- for (const row of rows) {
160
- const editableMap = row['_cellEditable'] as Record<string, true> | undefined
161
- if (!editableMap) continue
162
- const id = idOf(row)
163
- if (id === undefined || id === null || id === '') continue
164
- const urls: Record<string, string> = {}
165
- for (const colName of Object.keys(editableMap)) {
166
- urls[colName] = `${resourceUrl}/${encodeURIComponent(String(id))}/_cell/${encodeURIComponent(colName)}`
167
- }
168
- ;(row as Record<string, unknown>)['_cellEditUrls'] = urls
169
- }
170
- }
171
- }
172
-
173
- /**
174
- * Plan #8 — stamp the wizard step-validate endpoint URL on every form
175
- * whose descendants include a `Wizard` element. `FormMeta.wizardUrl` is
176
- * what the client posts to on Next-button clicks; forms without a wizard
177
- * descendant skip wiring.
178
- */
179
- export function tagFormWizardUrls(
180
- elements: ReadonlyArray<Element>,
181
- urlBuilder: (formId: string) => string,
182
- ): void {
183
- for (const form of findForms(elements)) {
184
- if (formHasWizard(form)) {
185
- form.withWizardUrl(urlBuilder(form.getFormId()))
186
- }
187
- }
188
- }
189
-
190
- /**
191
- * Stamp `_agentRunBase` on every field element in the resolved
192
- * `ElementMeta[]` tree that carries `aiActions`. Operates on the
193
- * post-`resolveSchema` wire shape (plain objects) rather than on
194
- * `Element` instances — `aiActions` is added by the `field-ai.ts`
195
- * wrapper during `toMeta()`, so it isn't visible to pre-resolve walkers.
196
- *
197
- * Only called on edit pages where a `recordId` is known. Create pages
198
- * deliberately skip it — field AI actions target existing content.
199
- */
200
- export function tagFieldAiUrls(
201
- elements: ReadonlyArray<Record<string, unknown>>,
202
- agentBase: string,
203
- ): void {
204
- for (const el of elements) {
205
- if (Array.isArray(el['aiActions']) && (el['aiActions'] as unknown[]).length > 0) {
206
- ;(el as Record<string, unknown>)['_agentRunBase'] = agentBase
207
- }
208
- const children = el['children']
209
- if (Array.isArray(children)) tagFieldAiUrls(children as Record<string, unknown>[], agentBase)
210
- // Repeater rows
211
- const rows = el['rows']
212
- if (Array.isArray(rows)) {
213
- for (const row of rows as Record<string, unknown>[]) {
214
- const rowChildren = row['children']
215
- if (Array.isArray(rowChildren)) tagFieldAiUrls(rowChildren as Record<string, unknown>[], agentBase)
216
- }
217
- }
218
- }
219
- }
220
-
221
- /**
222
- * Audit row 2026-05-07 cont'd⁸ — stamp the inline-create-option endpoint
223
- * URL on every `SelectField` that has called `createOptionForm()`. Walks
224
- * every form on the page so the URL carries the parent form's id; URL
225
- * shape `${formScopeUrl}/_form/${formId}/create-option/${fieldName}` so
226
- * the route handler can pick the form by id and the field by name.
227
- *
228
- * Mirrors `tagFormStateUrls / tagFormWizardUrls` — operates on the
229
- * un-resolved Element tree, mutates field-instance state via
230
- * `field.withCreateOptionUrl(url)`, and the field's `toMeta()` reads it
231
- * back to emit `createOption.url`.
232
- *
233
- * Stops at Repeater / Builder boundaries (parallel to the form-state /
234
- * wizard walkers): inside-row schemas are dispatched per-row and the
235
- * createOption shape doesn't compose with row body coercion in v1.
236
- */
237
- export function tagSelectCreateOptionUrls(
238
- elements: ReadonlyArray<Element>,
239
- urlBuilder: (formId: string, fieldName: string) => string,
240
- ): void {
241
- for (const form of findForms(elements)) {
242
- const formId = form.getFormId()
243
- walkSelectFields(form.getChildren() as Element[] ?? [], (field) => {
244
- if (field.hasCreateOption() && !field.getCreateOptionUrl()) {
245
- field.withCreateOptionUrl(urlBuilder(formId, field.name))
246
- }
247
- })
248
- }
249
- }
250
-
251
- export function walkSelectFields(elements: Element[], visit: (f: SelectField) => void): void {
252
- for (const el of elements) {
253
- if (el instanceof SelectField) {
254
- visit(el)
255
- // SelectField has no children of its own — no recursion needed.
256
- continue
257
- }
258
- // Stop at row-array boundaries — see comment on `tagSelectCreateOptionUrls`.
259
- if (el instanceof RepeaterField) continue
260
- if (el instanceof BuilderField) continue
261
- const children = el.getChildren()
262
- if (children && children.length > 0) walkSelectFields(children as Element[], visit)
263
- }
264
- }
265
-
266
- /**
267
- * Adapter-package async-resolve walker. Stamps the per-form mentions URL
268
- * on every field that ducks like a "rich text with at least one async
269
- * mention provider". The duck-typed contract lives here (as opposed to
270
- * importing from `@pilotiq/tiptap`) so pilotiq core stays adapter-free —
271
- * any future field type with an async-resolve trigger can satisfy the
272
- * same shape and pick up URL stamping for free.
273
- *
274
- * Contract:
275
- * - `getType() === 'richtext'` (fast filter)
276
- * - `hasAsyncMentions(): boolean`
277
- * - `withMentionsUrl(url: string): unknown`
278
- *
279
- * Walks every form on the page so the URL builder can mint a per-form
280
- * URL (mirrors `tagFormStateUrls / tagFormWizardUrls`). The route handler
281
- * uses formId in the URL to select the form; the body carries `field`
282
- * + `trigger` + `query`. One URL per (form, scope), reused across every
283
- * async-mention field on that form.
284
- */
285
- interface AsyncMentionFieldLike {
286
- hasAsyncMentions(): boolean
287
- withMentionsUrl(url: string): unknown
288
- }
289
-
290
- export function isAsyncMentionField(el: Element): el is Element & AsyncMentionFieldLike {
291
- if (el.getType() !== 'richtext') return false
292
- const candidate = el as unknown as Partial<AsyncMentionFieldLike>
293
- return typeof candidate.hasAsyncMentions === 'function'
294
- && typeof candidate.withMentionsUrl === 'function'
295
- }
296
-
297
- export function tagRichTextMentionUrls(
298
- elements: ReadonlyArray<Element>,
299
- urlBuilder: (formId: string) => string,
300
- ): void {
301
- for (const form of findForms(elements)) {
302
- const url = urlBuilder(form.getFormId())
303
- const visit = (els: ReadonlyArray<Element>): void => {
304
- for (const el of els) {
305
- // Don't cross into nested forms — each form gets its own URL.
306
- if (el !== form && el.getType() === 'form') continue
307
- if (isAsyncMentionField(el) && el.hasAsyncMentions()) {
308
- el.withMentionsUrl(url)
309
- }
310
- // Builder.getChildren() returns undefined to keep the field-level
311
- // walkers from treating heterogeneous rows as flat children. Manual
312
- // descent into each block's schema covers the URL-stamping path
313
- // without changing the no-cross posture for save/coerce.
314
- if (isBuilderField(el)) {
315
- for (const block of (el as BuilderField).getBlocks()) visit(block.getSchema())
316
- continue
317
- }
318
- const children = el.getChildren()
319
- if (children) visit(children)
320
- }
321
- }
322
- const children = form.getChildren()
323
- if (children) visit(children)
324
- }
325
- }
326
-
327
- export function formHasLiveField(form: Form): boolean {
328
- let found = false
329
- const visit = (els: ReadonlyArray<Element>): void => {
330
- for (const el of els) {
331
- if (found) return
332
- // Either a server-side `live()` (drives a roundtrip) OR a
333
- // client-side `afterStateUpdatedJs(body)` (JS-only) is enough to
334
- // mount the controlled-form path: the FormStateProvider holds the
335
- // values map either path needs, and the client gates the actual
336
- // network POST on `live` separately. Cost of the over-stamp for
337
- // JS-only forms is one unused endpoint URL per form — endpoint
338
- // never gets hit because the client only POSTs on `live`.
339
- if (el instanceof Field && (el.isLive() || el.getAfterStateUpdatedJs() !== undefined)) {
340
- found = true
341
- return
342
- }
343
- const children = el.getChildren()
344
- if (children) visit(children)
345
- }
346
- }
347
- const children = form.getChildren()
348
- if (children) visit(children)
349
- return found
350
- }
351
-
352
- export function formHasWizard(form: Form): boolean {
353
- let found = false
354
- const visit = (els: ReadonlyArray<Element>): void => {
355
- for (const el of els) {
356
- if (found) return
357
- if (el.getType() === 'wizard') { found = true; return }
358
- const children = el.getChildren()
359
- if (children) visit(children)
360
- }
361
- }
362
- const children = form.getChildren()
363
- if (children) visit(children)
364
- return found
365
- }
366
-
367
- /**
368
- * Run the edit-mode fill pipeline on a loaded record:
369
- * mutateFormDataBeforeFill → fillFromRecord → mutateFormDataAfterFill
370
- *
371
- * `fillFromRecord` defaults to `{ ...record }` when not configured. Both
372
- * mutators are optional and may be async. `ctx.record` is the loaded
373
- * record so mutators can read from fields the form doesn't surface.
374
- */
375
- export async function applyFillPipeline<R>(
376
- form: Form<R>,
377
- record: R,
378
- ): Promise<Record<string, unknown>> {
379
- const recordObj = record as unknown as Record<string, unknown>
380
- let values: Record<string, unknown> = { ...recordObj }
381
-
382
- const before = form.getMutateFormDataBeforeFill()
383
- if (before) values = await before(values, { values, record })
384
-
385
- const fill = form.getFillFromRecord()
386
- if (fill) values = fill(record)
387
-
388
- const after = form.getMutateFormDataAfterFill()
389
- if (after) values = await after(values, { values, record })
390
-
391
- return normalizeArrayFieldStrings(form, values)
392
- }
393
-
394
- /**
395
- * Walk the form for Repeater / Builder field names whose value on `values`
396
- * is a JSON string (the shape a `String?` column produces when records are
397
- * seeded outside the pilotiq save path — raw SQL, migrations, imports).
398
- * Pilotiq's save path stringifies these arrays via `coerceFormValues`, but
399
- * the load path doesn't auto-parse — so a fresh `String?` column lands here
400
- * as a string, then `resolveRepeaterRows` sees `Array.isArray(string) === false`
401
- * and falls through to empty rows on first paint. The collab path is
402
- * symmetric: the string lands in the form-data Y.Map, `migrateLegacyArrays`
403
- * sees `Array.isArray(string) === false`, and silently skips migration.
404
- *
405
- * Parse defensively — anything that doesn't deserialize to an array is left
406
- * verbatim so a stray non-JSON string doesn't get clobbered. Non-string
407
- * values pass through untouched (already-array, null, undefined, number).
408
- */
409
- export function normalizeArrayFieldStrings<R>(
410
- form: Form<R>,
411
- values: Record<string, unknown>,
412
- ): Record<string, unknown> {
413
- const arrayFieldNames = collectArrayFieldNames(form.getChildren() ?? [])
414
- if (arrayFieldNames.length === 0) return values
415
- let out: Record<string, unknown> | null = null
416
- for (const name of arrayFieldNames) {
417
- const raw = values[name]
418
- if (typeof raw !== 'string') continue
419
- let parsed: unknown
420
- try { parsed = JSON.parse(raw) } catch { continue }
421
- if (!Array.isArray(parsed)) continue
422
- if (!out) out = { ...values }
423
- out[name] = parsed
424
- }
425
- return out ?? values
426
- }
427
-
428
- /** Walk the form's children for top-level Repeater + Builder field names.
429
- * Stops at array-row boundaries — nested Repeater/Builder fields live
430
- * inside their parent's inner schema and aren't top-level form fields. */
431
- function collectArrayFieldNames(elements: ReadonlyArray<Element>): string[] {
432
- const out: string[] = []
433
- const walk = (els: ReadonlyArray<Element>): void => {
434
- for (const el of els) {
435
- if (isRepeaterField(el) || isBuilderField(el)) {
436
- const name = (el as RepeaterField | BuilderField).name
437
- if (name) out.push(name)
438
- continue
439
- }
440
- const children = el.getChildren()
441
- if (children && children.length > 0) walk(children)
442
- }
443
- }
444
- walk(elements)
445
- return out
446
- }
447
-
448
- /**
449
- * Walk every hydrator registered via `Pilotiq.editPageHydrator(fn)` and
450
- * merge non-null returns onto `currentValues`. Hydrators run sequentially
451
- * in registration order; later returns override keys from earlier ones.
452
- *
453
- * Failure mode is permissive: a hydrator that throws or returns `null`
454
- * contributes nothing; the page still renders against the fill-pipeline
455
- * values it received. Errors emit a `console.warn` so the page isn't
456
- * silently relying on missing data.
457
- *
458
- * Returns the merged overlay (NOT the final values). The caller composes
459
- * the overlay onto the form via `form.withValues({ ...current, ...overlay })`
460
- * — keeps the merge order explicit at the call site (overlay wins).
461
- *
462
- * Empty hydrators array / no overrides → returns `{}` so callers can
463
- * skip the `withValues` call cheaply.
464
- */
465
- export async function applyEditPageHydrators(
466
- hydrators: ReadonlyArray<EditPageHydrator>,
467
- ctx: EditPageHydratorContext,
468
- ): Promise<Record<string, unknown>> {
469
- if (hydrators.length === 0) return {}
470
- let overlay: Record<string, unknown> = {}
471
- for (const fn of hydrators) {
472
- try {
473
- const result = await fn(ctx)
474
- if (result && typeof result === 'object') overlay = { ...overlay, ...result }
475
- } catch (err) {
476
- console.warn('[pilotiq] editPageHydrator threw — falling back to fill-pipeline values', err)
477
- }
478
- }
479
- return overlay
480
- }
481
-
482
- /**
483
- * Walk the form's top-level Repeaters and replace `values[fieldName]`
484
- * with rows fetched from `parent.related(name)` for any
485
- * relationship-backed Repeater. Each loaded row stamps `__id` to the
486
- * child's primary key so the renderer can round-trip identity through
487
- * a hidden input and the save-side diff can match submitted rows back
488
- * to existing records.
489
- *
490
- * No-op when the parent record is null (create mode), when no
491
- * relationship-backed Repeaters exist on the form, or when the
492
- * resource has no `R.model` (relation queries need it).
493
- *
494
- * Mutates and returns a fresh values object — never the input.
495
- */
496
- export async function applyRelationshipRepeaterFill(
497
- form: Form,
498
- values: Record<string, unknown>,
499
- record: unknown,
500
- parentModel: ModelLike | undefined,
501
- ): Promise<Record<string, unknown>> {
502
- if (record == null) return values
503
- if (!parentModel) return values
504
- const repeaters = findRelationshipRepeaters(form.getChildren() ?? [])
505
- if (repeaters.length === 0) return values
506
-
507
- const out: Record<string, unknown> = { ...values }
508
- for (const repeater of repeaters) {
509
- const cfg = repeater.getRelationship()!
510
- const pivotColumns = cfg.pivotColumns
511
- let rows: unknown[]
512
- try {
513
- rows = await loadRelationRows(parentModel, record, cfg.name, pivotColumns)
514
- } catch {
515
- // Failed lookup (e.g. missing `relations` map on a test stub)
516
- // — fall back to whatever value applyFillPipeline produced
517
- // rather than wiping the field. Better to render stale data
518
- // than to silently empty the row list.
519
- continue
520
- }
521
-
522
- // The child model is opaque here — we don't have the full
523
- // descriptor at this seam, so use the configured override or
524
- // peek the parent's relations map for the FK column. Strip it
525
- // (and the PK) from each row's payload so the inner schema
526
- // doesn't surface them as form values. For morphMany the
527
- // attachment is two columns instead of one — strip both.
528
- const pkColumn = pickChildPrimaryKey(parentModel, cfg.name) ?? 'id'
529
- const fkColumn = cfg.foreignKey ?? pickChildForeignKey(parentModel, cfg.name)
530
- const morph = getMorphRelationDescriptor(parentModel, cfg.name)
531
- const morphIdCol = morph ? `${morph.morphName}Id` : undefined
532
- const morphTyCol = morph ? `${morph.morphName}Type` : undefined
533
-
534
- out[repeater.name] = rows.map(row => {
535
- const r = (row && typeof row === 'object') ? { ...(row as Record<string, unknown>) } : {}
536
- const pkValue = r[pkColumn]
537
- delete r[pkColumn]
538
- if (fkColumn) delete r[fkColumn]
539
- if (morphIdCol) delete r[morphIdCol]
540
- if (morphTyCol) delete r[morphTyCol]
541
- // M2M pivot extras — flatten `row.pivot[col]` onto the row's data
542
- // so each pivot column round-trips through the inner schema as a
543
- // regular form field. The pivot envelope itself is dropped from
544
- // the values shape — the persist side splits pivot vs child
545
- // columns by name lookup against `cfg.pivotColumns`.
546
- const pivotEnvelope = r['pivot']
547
- delete r['pivot']
548
- const stamped: Record<string, unknown> = { ...r }
549
- if (pivotColumns && pivotColumns.length > 0
550
- && pivotEnvelope && typeof pivotEnvelope === 'object'
551
- ) {
552
- const pe = pivotEnvelope as Record<string, unknown>
553
- for (const col of pivotColumns) {
554
- if (col in pe) stamped[col] = pe[col]
555
- }
556
- }
557
- if (pkValue !== undefined && pkValue !== null) {
558
- stamped['__id'] = String(pkValue)
559
- }
560
- return stamped
561
- })
562
- }
563
- return out
564
- }
565
-
566
- /** Walk the form's children for top-level relationship-backed Repeaters. */
567
- export function findRelationshipRepeaters(elements: ReadonlyArray<Element>): RepeaterField[] {
568
- const out: RepeaterField[] = []
569
- const walk = (els: ReadonlyArray<Element>): void => {
570
- for (const el of els) {
571
- if (isRepeaterField(el)) {
572
- const r = el as RepeaterField
573
- if (r.getRelationship()) out.push(r)
574
- // Don't dive into Repeater children — relationship-on-relationship
575
- // isn't supported in v1.
576
- continue
577
- }
578
- // Don't dive into Builder children either — relationship-backed
579
- // Builders are resolved separately by `findRelationshipBuilders`.
580
- if (isBuilderField(el)) continue
581
- const children = el.getChildren()
582
- if (children && children.length > 0) walk(children)
583
- }
584
- }
585
- walk(elements)
586
- return out
587
- }
588
-
589
- /**
590
- * Walk the form's top-level Builders and replace `values[fieldName]` with
591
- * rows fetched from `parent.related(name)` for any relationship-backed
592
- * Builder. Each loaded row stamps `__id` (child PK) + `type` (block
593
- * discriminator) + `data` (per-block JSON payload) so the renderer can
594
- * round-trip the heterogeneous envelope.
595
- *
596
- * Mirrors `applyRelationshipRepeaterFill`. No-op when the parent record
597
- * is null (create mode), the resource has no `R.model`, or no
598
- * relationship-backed Builders exist on the form.
599
- */
600
- export async function applyRelationshipBuilderFill(
601
- form: Form,
602
- values: Record<string, unknown>,
603
- record: unknown,
604
- parentModel: ModelLike | undefined,
605
- ): Promise<Record<string, unknown>> {
606
- if (record == null) return values
607
- if (!parentModel) return values
608
- const builders = findRelationshipBuilders(form.getChildren() ?? [])
609
- if (builders.length === 0) return values
610
-
611
- const out: Record<string, unknown> = { ...values }
612
- for (const builder of builders) {
613
- const cfg = builder.getRelationship()!
614
- let rows: unknown[]
615
- try {
616
- rows = await loadRelationRows(parentModel, record, cfg.name)
617
- } catch {
618
- // Failed lookup (e.g. missing `relations` map on a test stub) —
619
- // fall back to whatever value applyFillPipeline produced rather
620
- // than wiping the field. Better stale than silently empty.
621
- continue
622
- }
623
-
624
- const pkColumn = pickChildPrimaryKey(parentModel, cfg.name) ?? 'id'
625
- const fkColumn = cfg.foreignKey ?? pickChildForeignKey(parentModel, cfg.name)
626
- const typeColumn = cfg.typeColumn ?? 'type'
627
- const dataColumn = cfg.dataColumn ?? 'data'
628
-
629
- out[builder.name] = rows.map(row => {
630
- const r = (row && typeof row === 'object') ? { ...(row as Record<string, unknown>) } : {}
631
- const pkValue = r[pkColumn]
632
- const blockType = typeof r[typeColumn] === 'string' ? (r[typeColumn] as string) : ''
633
- const dataRaw = r[dataColumn]
634
- const blockData = parseBuilderDataPayload(dataRaw)
635
-
636
- const stamped: Record<string, unknown> = {
637
- type: blockType,
638
- data: blockData,
639
- }
640
- if (pkValue !== undefined && pkValue !== null) {
641
- stamped['__id'] = String(pkValue)
642
- }
643
- // Non-`type` / `data` / FK / PK columns aren't surfaced — the
644
- // JSON envelope is the source of truth for per-block fields. If
645
- // a user denormalizes a column, they handle it via per-block
646
- // mutate hooks, not by leaking the column into row values.
647
- void fkColumn
648
- return stamped
649
- })
650
- }
651
- return out
652
- }
653
-
654
- /**
655
- * Normalize the JSON payload column into a plain object. Prisma
656
- * hydrates `Json` columns to objects; some adapters return strings.
657
- * Anything that isn't a parseable object falls back to `{}` so the
658
- * inner schema renders fresh defaults.
659
- */
660
- export function parseBuilderDataPayload(raw: unknown): Record<string, unknown> {
661
- if (raw && typeof raw === 'object' && !Array.isArray(raw)) {
662
- return raw as Record<string, unknown>
663
- }
664
- if (typeof raw === 'string') {
665
- try {
666
- const parsed: unknown = JSON.parse(raw)
667
- if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
668
- return parsed as Record<string, unknown>
669
- }
670
- } catch {
671
- // fall through to {}
672
- }
673
- }
674
- return {}
675
- }
676
-
677
- /** Walk the form's children for top-level relationship-backed Builders. */
678
- export function findRelationshipBuilders(elements: ReadonlyArray<Element>): BuilderField[] {
679
- const out: BuilderField[] = []
680
- const walk = (els: ReadonlyArray<Element>): void => {
681
- for (const el of els) {
682
- if (isBuilderField(el)) {
683
- const b = el as BuilderField
684
- if (b.getRelationship()) out.push(b)
685
- continue
686
- }
687
- // Don't dive into Repeater children either — both array-row
688
- // boundaries are walker stops here.
689
- if (isRepeaterField(el)) continue
690
- const children = el.getChildren()
691
- if (children && children.length > 0) walk(children)
692
- }
693
- }
694
- walk(elements)
695
- return out
696
- }
697
-
698
- /** Read the child model's PK column from the parent's relations map, when present. */
699
- export function pickChildPrimaryKey(parentModel: ModelLike, name: string): string | undefined {
700
- const relations = (parentModel as unknown as Record<string, unknown>)['relations']
701
- if (!relations || typeof relations !== 'object') return undefined
702
- const entry = (relations as Record<string, unknown>)[name]
703
- if (!entry || typeof entry !== 'object') return undefined
704
- const e = entry as Record<string, unknown>
705
- if (typeof e['model'] !== 'function') return undefined
706
- try {
707
- const child = (e['model'] as () => ModelLike)()
708
- return getPrimaryKey(child)
709
- } catch {
710
- return undefined
711
- }
712
- }
713
-
714
- /** Read the FK column from the parent's relations map, when present. */
715
- export function pickChildForeignKey(parentModel: ModelLike, name: string): string | undefined {
716
- const relations = (parentModel as unknown as Record<string, unknown>)['relations']
717
- if (!relations || typeof relations !== 'object') return undefined
718
- const entry = (relations as Record<string, unknown>)[name]
719
- if (!entry || typeof entry !== 'object') return undefined
720
- const e = entry as Record<string, unknown>
721
- return typeof e['foreignKey'] === 'string' ? (e['foreignKey'] as string) : undefined
722
- }
723
-
724
- // ─── Plan #15 server-data widgets ─────────────────────────────
725
-
726
- /** Wire-shape of the per-widget data map shipped to the client.
727
- * Lazy elements stamp `null` (renderer paints skeleton + fetches);
728
- * eager elements stamp their resolved payload. Errors stamp
729
- * `{ error: '<message>' }` so the renderer can surface a per-widget
730
- * failure without blanking the page. */
731
- export type ServerDataMap = Record<string, unknown>
732
-
733
- /**
734
- * Plan #15 — collect every `ServerDataElement` in the schema tree and
735
- * resolve their `getServerData(ctx)` payloads in parallel. Returns a
736
- * map keyed by element id, ready to ship as `viewProps._widgetData`.
737
- *
738
- * Lazy elements (default — `lazy(false)` opts out) skip the hook and
739
- * stamp `null` so the renderer paints a skeleton and fetches the
740
- * payload via `POST {base}/_widget/:id` on mount. Eager elements
741
- * resolve synchronously and ship the data with the page.
742
- *
743
- * Per-widget errors are caught and surfaced as `{ error: '...' }` —
744
- * one flaky `getStats()` shouldn't 500 the entire dashboard.
745
- *
746
- * Visibility is **not** re-evaluated here. The schema resolver
747
- * (`resolveSchema → evaluateVisibility`) drops hidden layout elements
748
- * before any widget code runs. Widgets inside still-rendered branches
749
- * always resolve (or stamp lazy null).
750
- */
751
- export async function resolveServerDataElements(
752
- elements: ReadonlyArray<Element>,
753
- ctx: RenderContext,
754
- ): Promise<ServerDataMap> {
755
- const widgets = collectServerDataElements(elements)
756
- if (widgets.length === 0) return {}
757
- const out: ServerDataMap = {}
758
- await Promise.all(widgets.map(async (el) => {
759
- const id = el.getId()
760
- if (el.isLazy()) {
761
- out[id] = null // sentinel — renderer paints skeleton, fetches on mount
762
- return
763
- }
764
- try {
765
- out[id] = await el.resolveServerData(ctx)
766
- } catch (err) {
767
- out[id] = { error: err instanceof Error ? err.message : 'Widget failed to load' }
768
- }
769
- }))
770
- return out
771
- }
772
-
773
- /** Walk the tree collecting every `ServerDataElement`. Walks into
774
- * containers but stops at Form/Repeater/Builder boundaries — widgets
775
- * inside an editable form don't make sense in v1. */
776
- export function collectServerDataElements(elements: ReadonlyArray<Element>): ServerDataElement[] {
777
- const out: ServerDataElement[] = []
778
- const walk = (els: ReadonlyArray<Element>): void => {
779
- for (const el of els) {
780
- if (isServerDataElement(el)) {
781
- out.push(el)
782
- // Don't recurse into a widget's children — `View` etc. are leaves
783
- // for v1 (no nested widgets inside widgets).
784
- continue
785
- }
786
- // Skip walkers that imply per-row resolution — widgets inside
787
- // Repeater/Builder rows don't have a stable id space.
788
- const type = el.getType()
789
- if (type === 'form' || type === 'repeater' || type === 'builder' || type === 'table' || type === 'tableWidget') continue
790
- const children = el.getChildren()
791
- if (children) walk(children)
792
- }
793
- }
794
- walk(elements)
795
- return out
796
- }
797
-
798
- /**
799
- * Plan #15 — stamp the polling-endpoint URL on every `ServerDataElement`
800
- * in the tree. Mirrors `tagFormStateUrls / tagTableReorderUrls`. Walks
801
- * with the same boundaries as `collectServerDataElements` so the wire
802
- * stays in sync (no orphan widgets without URLs and vice versa).
803
- *
804
- * `urlBuilder(id)` typically produces `${base}/_widget/${id}` for
805
- * dashboard widgets and `${base}/${pageSlug}/_widget/${id}` for
806
- * custom-page widgets — the route handlers for both shapes are wired up
807
- * in `routes.ts` (see Phase A.4).
808
- */
809
- export function tagWidgetUrls(
810
- elements: ReadonlyArray<Element>,
811
- urlBuilder: (id: string) => string,
812
- ): void {
813
- for (const widget of collectServerDataElements(elements)) {
814
- if (widget.getWidgetUrl()) continue // user-set wins
815
- widget.withWidgetUrl(urlBuilder(widget.getId()))
816
- }
817
- }
818
-
819
- /**
820
- * Stamp every form-subresource URL the page might need in one pass:
821
- * - `_form/${formId}/state` (Plan #5 partial-resolve)
822
- * - `_form/${formId}/wizard` (Plan #8 step-validate)
823
- * - `_form/${formId}/mentions` (async-mention typeahead)
824
- * - `_form/${formId}/create-option/${fieldName}` (SelectField inline-create)
825
- *
826
- * Every page-builder that mounts a form (dashboard / resource create+edit /
827
- * global edit / custom page) needs all four — the underlying taggers skip
828
- * forms that don't carry the matching feature, so this is always safe to
829
- * call. `base` is the route prefix (e.g. `${cfg.path}` / `${resourceBase}` /
830
- * `${resourceBase}/${recordId}` / `${pageUrl}`).
831
- */
832
- export function tagFormSubresourceUrls(elements: ReadonlyArray<Element>, base: string): void {
833
- tagFormStateUrls(elements, formId => `${base}/_form/${formId}/state`)
834
- tagFormWizardUrls(elements, formId => `${base}/_form/${formId}/wizard`)
835
- tagRichTextMentionUrls(elements, formId => `${base}/_form/${formId}/mentions`)
836
- tagSelectCreateOptionUrls(elements, (formId, fieldName) => `${base}/_form/${formId}/create-option/${fieldName}`)
837
- }
838
-
839
- /** Stamp dispatchUrl on every handler-style Action so the client knows where to POST. */
840
- export function tagActionDispatch(elements: ReadonlyArray<Element>, baseUrl: string): void {
841
- for (const action of findActions(elements)) {
842
- if (!action.getHandler()) continue
843
- if (action.getHref() || action.getMethod()) continue
844
- if (action.getDispatchUrl()) continue
845
- action.dispatchUrl(`${baseUrl}/_action/${action.name}`)
846
- }
847
- // Row-scoped extraItemActions (Repeater/Builder). Stamped here too so
848
- // the client can POST to the same `_action/:name` route — the renderer
849
- // attaches `_rowPath=<fieldName>.<index>` per click; the server's
850
- // dispatcher uses that to walk into the right row when building
851
- // `ctx.row`. See `findRowExtraActions` in `dispatchAction.ts`.
852
- for (const { action } of findRowExtraActions(elements)) {
853
- if (!action.getHandler()) continue
854
- if (action.getDispatchUrl()) continue
855
- action.dispatchUrl(`${baseUrl}/_action/${action.name}`)
856
- }
857
- }