@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
@@ -0,0 +1,177 @@
1
+ # Dispatch Modes
2
+
3
+ Every `Action` has exactly one of four dispatch modes. They're mutually exclusive — calling a setter from a different mode replaces the prior choice. Pick the mode by what the action does, not where it appears.
4
+
5
+ ## 1. `href(url)` — link
6
+
7
+ ```ts
8
+ Action.make('docs')
9
+ .label('Documentation')
10
+ .icon('book')
11
+ .href('https://docs.example.com')
12
+ ```
13
+
14
+ Renders as `<a href>`. No server round-trip. Cmd+click / right-click "open in new tab" works because it's a real anchor.
15
+
16
+ `.openInNewTab()` adds `target="_blank" rel="noopener"`. `.tooltip(text)` adds a hover hint.
17
+
18
+ Use for: external doc links, marketing CTAs, sibling-app deep links.
19
+
20
+ ## 2. `method(verb).action(url)` — form-post
21
+
22
+ ```ts
23
+ Action.make('archive')
24
+ .label('Archive')
25
+ .color('warning')
26
+ .method('POST')
27
+ .action(`${base}/articles/${row.id}/archive`)
28
+ .confirm('Archive this article? You can restore it later.')
29
+ ```
30
+
31
+ Renders as a hidden `<form>` with a submit button. The form body is empty (or carries `Action.formField(name, value)` pairs — see below). Server responds 303 → browser follows → page re-renders.
32
+
33
+ This is the **only mode that survives no-JavaScript clients** — useful for back-compat with progressively-enhanced flows or for actions that must remain accessible from raw HTML email links. The framework's `Action.delete` factory ships in this mode for that reason.
34
+
35
+ `.confirm(message)` wraps the submit in a confirmation Dialog. The submit doesn't fire until the user OKs.
36
+
37
+ ## 3. `handler(ctx => …)` — JSON dispatch
38
+
39
+ ```ts
40
+ Action.make('publish')
41
+ .label('Publish')
42
+ .color('primary')
43
+ .icon('send')
44
+ .handler(async (ctx) => {
45
+ const article = await ArticleModel.find(ctx.record.id)
46
+ article.publishedAt = new Date()
47
+ await article.save()
48
+ return {
49
+ notify: { title: 'Published', body: article.title, kind: 'success' },
50
+ redirect: `${ctx.basePath}/articles/${article.id}/edit`,
51
+ }
52
+ })
53
+ ```
54
+
55
+ Clicking POSTs `Accept: application/json` to `{basePath}/{slug}/_action/{name}`. The framework routes through `dispatchAction(action, body, ctx)`, calls the handler, normalizes the return shape, ships it back.
56
+
57
+ **Return shape** (all keys optional):
58
+
59
+ ```ts
60
+ {
61
+ notify?: NotificationLike | NotificationLike[],
62
+ redirect?: string, // URL to navigate to after success
63
+ download?: { filename, contentType, body }, // triggers <a download> on the client
64
+ ok?: boolean, // false short-circuits to error toast
65
+ error?: string, // surfaced as toast when ok:false
66
+ // ...any extras get round-tripped via additional ActionResult slots
67
+ }
68
+ ```
69
+
70
+ Client-side: drains notifications via `useToast()`, then `useNavigate(redirect)`. **No page reload** — SPA-only flow. If the response carries a `download` payload, the framework synthesizes a temporary `<a download>` blob and clicks it.
71
+
72
+ **`ctx` shape:**
73
+
74
+ ```ts
75
+ {
76
+ user: OpaqueUser | null, // from Pilotiq.user() resolver
77
+ record?: Record, // row placement — current row
78
+ records?: Record[], // bulk placement — selected rows
79
+ basePath: string, // panel base ("/admin")
80
+ resource?: ResourceLike, // when in a resource scope
81
+ relation?: { parent, parentId, relationship }, // when inside a relation manager
82
+ body?: unknown, // raw POST body (FormData-parsed) — modal-form values land here
83
+ }
84
+ ```
85
+
86
+ Use for: anything that needs server work but is conceptually a one-shot operation, not a page navigation.
87
+
88
+ ## 4. `submit()` — trigger an enclosing form
89
+
90
+ ```ts
91
+ // inside CreatePage.getFormActions(R)
92
+ Action.make('createAnother')
93
+ .label('Create & create another')
94
+ .outlined()
95
+ .submit()
96
+ .formField('_continueCreate', '1') // rides the form body so the handler can branch
97
+ ```
98
+
99
+ Renders as `<button type="submit">`. No dispatch URL — it submits the enclosing `<form>`. Used in page headers / form footers where the form already wires its own POST.
100
+
101
+ `.form(formId)` targets a form outside the natural enclosing `<form>` (HTML `form=` attribute). Useful when the submit button lives in the page header but the form lives further down the tree.
102
+
103
+ `.formField(name, value='1')` attaches a hidden `name`/`value` pair to the form body — the click sets `event.submitter` so `new FormData(form, submitter)` picks it up. Confirm-gated submits intentionally **don't** honor `formField` (programmatic `requestSubmit()` has no submitter).
104
+
105
+ ## Modal-form actions (flavor of handler)
106
+
107
+ Add `.schema([Field, …])` to a handler action and it switches to modal-form mode:
108
+
109
+ ```ts
110
+ Action.make('addNote')
111
+ .label('Add note')
112
+ .icon('plus')
113
+ .schema([
114
+ TextField.make('subject').required(),
115
+ TextareaField.make('body').required(),
116
+ SelectField.make('priority').options({ low: 'Low', med: 'Med', high: 'High' }).default('med'),
117
+ ])
118
+ .handler(async (ctx) => {
119
+ // ctx.body has the parsed + validated form values
120
+ await Note.create({ ...ctx.body, articleId: ctx.record.id })
121
+ return { notify: { title: 'Note added', kind: 'success' } }
122
+ })
123
+ ```
124
+
125
+ Clicking the trigger opens a Dialog with the schema mounted as a real pilotiq form. Submit fetches `Accept: application/json`. Server responses:
126
+
127
+ - **200 `{ ok: true, redirect, notifications }`** — drain notifications, SPA-navigate.
128
+ - **422 `{ ok: false, errors: { field: [msg, …] } }`** — stamp inline field errors.
129
+ - **5xx `{ ok: false, error }`** — error toast.
130
+
131
+ The form runs every `pilotiq-fields`-style validator (required / email / unique / distinct) before reaching the handler. Field-level `live()` works inside the modal.
132
+
133
+ ### Modal chrome
134
+
135
+ 12 setters customize the modal:
136
+
137
+ ```ts
138
+ Action.make('addNote')
139
+ .modalHeading('Add a note')
140
+ .modalDescription('Notes are visible to all editors.')
141
+ .modalIcon('sticky-note')
142
+ .modalIconColor('warning')
143
+ .modalWidth('lg') // 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl' | 'screen'
144
+ .modalSubmitActionLabel('Save note')
145
+ .modalCancelActionLabel('Discard')
146
+ .modalCloseable(false) // user can't close via Esc / outside-click
147
+ .modalContentFooter([ // Element[] rendered below the form body
148
+ Text.make('Notes auto-archive after 90 days.').size('xs').color('muted'),
149
+ ])
150
+ // …
151
+ ```
152
+
153
+ Setters with no special chrome you want, just omit — sensible defaults ship.
154
+
155
+ ## Confirm
156
+
157
+ Both handler and method modes accept `.confirm(message)`:
158
+
159
+ ```ts
160
+ Action.make('publish')
161
+ .handler(/* … */)
162
+ .confirm('Publish this article? It will be visible to everyone.')
163
+ ```
164
+
165
+ `.modalConfirmIcon` / `.modalConfirmIconColor` customize the chrome. Confirm dialogs share the same `<ActionModalDialog>` primitive as modal-form — the difference is content: confirm shows a message + Cancel/OK; modal-form mounts a `<FormFields>`.
166
+
167
+ ## Pitfalls
168
+
169
+ - **`.handler()` on a method action.** Calling `.method('POST').action(...)` then `.handler(...)` will override the method, NOT compose — handler wins. Pick one mode.
170
+ - **`.submit().formField(...)` with `.confirm(...)`.** Confirm-gated submits use `form.requestSubmit()` programmatically, which doesn't carry a `submitter`. The `formField` pair will silently drop. If you need both, use `.handler()` + `.confirm()` instead.
171
+ - **Modal-form fields inside the modal can't access the outer page's form state.** The modal is a fresh form scope; `$get('outerFieldName')` won't see the page's form. If you need cross-form data, pass it via `ctx.record` instead.
172
+ - **Handler returns are async-aware but not stream-aware.** Don't expect to ship progressive output from a long-running handler. For batch operations that take >5s, return immediately with `{ notify: 'Queued', redirect }` and process out-of-band.
173
+
174
+ ## See also
175
+
176
+ - `visibility-and-authorization.md` — gating which dispatch modes fire for which user / record.
177
+ - `factories.md` — pre-built factories you usually want instead of writing dispatch from scratch.
@@ -0,0 +1,130 @@
1
+ # Built-in Action Factories
2
+
3
+ Pilotiq ships ~25 pre-built `Action.*` factories for the operations every admin panel needs. They handle dispatch wiring, visibility gating, chrome, and notification copy — most of the time you reach for one of these instead of `Action.make(...)`.
4
+
5
+ ## CRUD basics (single row)
6
+
7
+ ```ts
8
+ import { Action } from '@pilotiq/pilotiq'
9
+
10
+ Action.create(R, base) // header — "Create <Label>"; href = `${base}/${slug}/create`
11
+ Action.edit(R, base, recordId) // row — pencil icon; href = `${base}/${slug}/:id/edit`
12
+ Action.view(R, base, recordId) // row — eye icon; href = `${base}/${slug}/:id`
13
+ Action.delete(R, base, recordId) // row — trash icon; method POST → `${base}/${slug}/:id/delete` with .confirm()
14
+ ```
15
+
16
+ Each delegates visibility to the matching Resource policy (`canCreate` / `canEdit` / `canView` / `canDelete`) automatically — see `visibility-and-authorization.md` § Composing with Resource policies.
17
+
18
+ ## Replicate
19
+
20
+ ```ts
21
+ Action.replicate(R, base, recordId, {
22
+ excludeAttributes: ['name', 'slug'], // strip these in addition to PK + soft-delete column
23
+ beforeReplicaSaved: async (replica, source) => {
24
+ replica.name = `Copy of ${source.name}`
25
+ replica.slug = await generateSlug(replica.name)
26
+ },
27
+ getCreatedNotificationTitle: ({ replica, source }) => `Cloned "${source.title}"`,
28
+ getRedirectUrl: ({ replica }) => `${base}/articles/${replica.id}/edit`,
29
+ })
30
+ ```
31
+
32
+ Clones the source row via `R.model.create(...)`. Strips PK + soft-delete column + `opts.excludeAttributes`, runs `beforeReplicaSaved` to mutate, persists, redirects to the new record's edit page. Visibility delegates to `R.canCreate`.
33
+
34
+ `Action.bulkReplicate(R, base, opts?)` is the bulk variant — iterates `ctx.records`, applies the same strip-mutate-create pipeline per row, skips rows that throw or fail per-row `canCreate`, notifies with the count.
35
+
36
+ `opts.getCreatedNotificationTitle` and `opts.getRedirectUrl` are both sync-or-async; receive `{ replica, source }` (single) or `{ count, records }` (bulk). Returning `undefined` falls back to the default copy. Empty string is honored — won't be swallowed by `??`.
37
+
38
+ ## Soft delete (when `Resource.softDeletes = true`)
39
+
40
+ ```ts
41
+ Action.restore(R, base, recordId) // row — only visible on trashed rows
42
+ Action.forceDelete(R, base, recordId) // row — only visible on trashed rows; method POST with .confirm()
43
+
44
+ Action.bulkRestore(R, base) // bulk — restores selected trashed rows
45
+ Action.bulkForceDelete(R, base) // bulk
46
+ ```
47
+
48
+ `Action.delete` auto-hides on already-trashed rows (per Plan #13 soft-delete wiring); restore / forceDelete auto-hide on non-trashed rows. The toggling is structural, not optional — opt out by overriding `.visible()` after the factory.
49
+
50
+ ## Import / Export
51
+
52
+ ```ts
53
+ Action.export(R, base, {
54
+ format: 'csv', // 'csv' | 'json'
55
+ columns: ['id', 'title', 'publishedAt'], // omit → all columns from R.table()
56
+ filename: () => `articles-${new Date().toISOString().slice(0, 10)}.csv`,
57
+ })
58
+
59
+ Action.bulkExport(R, base, opts?) // bulk — exports only selected rows
60
+
61
+ Action.import(R, base, {
62
+ format: 'csv', // omit → auto-detect from filename
63
+ upsertBy: ['email'], // turns it into an upsert action; mode-select appears in the modal
64
+ maxRows: 10_000, // default cap
65
+ })
66
+ ```
67
+
68
+ `Action.export` reads from the resource's `Table.records(ctx)` (so filters / search / sort flow through); writes the body to a `download` envelope that the client synthesizes as `<a download>`. CSV via the in-tree `src/io/csv.ts` (RFC 4180, in-memory only).
69
+
70
+ `Action.import` auto-builds a modal-form schema with a `FileUpload` field (and a `Mode` select when `upsertBy` is set). Handler reads `ctx.values.file`, fetches the URL the upload stamped, parses CSV/JSON, walks rows through `R.model.create` (or `R.model.update` for matched upserts). Per-row `validate / beforeCreate / beforeUpdate` lifecycle hooks fire from the resource's form config. Partial-failure-soft: rows that throw / fail validate accumulate in `summary.errors` and the import keeps going. v1 has **no transaction wrapper** — partial imports leave the DB in a partially-applied state.
71
+
72
+ ## Relation factories (inside RelationManagers)
73
+
74
+ ```ts
75
+ // inside a RelationManager static table(table, ctx)
76
+ Action.relationCreate(M, ctx) // header — "Create <Label>"; opens create form in tab
77
+ Action.relationEdit(M, ctx, recordId) // row — pencil
78
+ Action.relationDelete(M, ctx, recordId) // row — trash
79
+ Action.relationRestore(M, ctx, recordId) // row — soft-delete trash only
80
+ Action.relationForceDelete(M, ctx, recordId) // row — soft-delete trash only
81
+
82
+ Action.relationReplicate(M, ctx, recordId, opts?)
83
+ Action.relationBulkReplicate(M, ctx, opts?)
84
+
85
+ // M2M only (auto-hide outside `belongsToMany / morphToMany / morphedByMany`)
86
+ Action.relationAttach(M, ctx) // header — modal-form picker
87
+ Action.relationDetach(M, ctx, recordId) // row
88
+ Action.relationBulkDetach(M, ctx) // bulk
89
+ ```
90
+
91
+ `ctx` is the `RelationManagerContext` injected into `M.table(table, ctx)` — it carries `basePath / parentSlug / parentId / relationship / parentRecord / related? / mode`. The factories thread URLs + parent attachment automatically.
92
+
93
+ `relationReplicate` force-pins the parent FK back onto the replica AFTER strip + BEFORE `beforeReplicaSaved`, so a tampered source row can't slip a different parent in by riding its own FK column. Auto-hides on M2M (replicate doesn't fit pivot semantics) and on `morphTo` (no single owner to pin to).
94
+
95
+ For the broader RelationManager surface (when to override `canDelete` vs `canDetach`, how `ctx.mode` derives), see [[pilotiq-relations]].
96
+
97
+ ## Common chrome customizations
98
+
99
+ The factory result is just an `Action` instance — every chain method composes after the factory call:
100
+
101
+ ```ts
102
+ Action.delete(R, base, row.id)
103
+ .label('Move to trash') // override default copy
104
+ .color('warning') // override 'destructive'
105
+ .tooltip('Trashed items auto-purge after 30 days')
106
+ .confirm('Move this article to trash?') // override default confirm copy
107
+ .visible(({ user }) => user?.role === 'admin')
108
+ ```
109
+
110
+ The `.visible()` you set wins over the factory's auto-attached policy gate.
111
+
112
+ ## When NOT to use a factory
113
+
114
+ - **Custom modal-form** — the import factory's modal schema is fixed (FileUpload + maybe Mode). For richer modals (multi-field policy decisions, conditional reactivity), use `Action.make('foo').schema([…]).handler(…)` directly.
115
+ - **Cross-record orchestration** — bulk factories iterate row-by-row. For "select 50 rows, run one SQL UPDATE" semantics, write a handler that consumes `ctx.records` and dispatches a single ORM call.
116
+ - **Compound flows** — anything that needs multi-step UX (confirm → modal-form → second confirm) doesn't compose from factories. Build it from `Action.make()` primitives.
117
+
118
+ ## Pitfalls
119
+
120
+ - **`opts.beforeReplicaSaved` mutates a plain object, not a model instance.** It's `Record<string, unknown>`, pre-create. Don't expect `replica.save()` / lifecycle hooks — those are framework-internal post-mutation.
121
+ - **Bulk factories don't wrap in a transaction.** Partial failures leave the DB partially updated. The notification shows the count succeeded; the rest accumulate in `summary.errors`. If you need atomicity, write a handler that opens a transaction explicitly via your ORM.
122
+ - **`Action.import`'s `upsertBy` requires `R.model.update` to exist.** Without it, the import will throw on the first matching row. Boot-time guard catches the missing method.
123
+ - **Relation factories' `ctx.mode` lies about `morphOne` / `hasOne`.** Both collapse into `'hasMany'` for action dispatch — `morphOne / hasOne` semantically still allow one child, but the factory shape is the same.
124
+
125
+ ## See also
126
+
127
+ - `dispatch-modes.md` — what each factory does under the hood.
128
+ - `visibility-and-authorization.md` — how factory auto-visibility composes with manual `.visible()`.
129
+ - [[pilotiq-relations]] — broader RelationManager + `Repeater.relationship` patterns.
130
+ - [[pilotiq-resource]] — the `Resource.softDeletes` + `can*` static surface that factories key off.
@@ -0,0 +1,125 @@
1
+ # Visibility & Authorization
2
+
3
+ Every action exposes four conditional setters. They differ in *intent*; all use the same predicate shape.
4
+
5
+ ```ts
6
+ type VisibilityRule =
7
+ | boolean
8
+ | ((ctx: ActionVisibilityContext) => boolean | Promise<boolean>)
9
+
10
+ type ActionVisibilityContext = {
11
+ record?: Record // present on row-placement
12
+ records?: Record[] // present on bulk-placement
13
+ user?: OpaqueUser | null // from Pilotiq.user()
14
+ }
15
+ ```
16
+
17
+ ## The four setters
18
+
19
+ ```ts
20
+ Action.make('publish')
21
+ .visible(({ record, user }) => !record.publishedAt && user?.role === 'editor')
22
+ .hidden(({ record }) => record.archived)
23
+ .disabled(({ record }) => record.locked)
24
+ .authorize(async ({ user, record }) => await canPublish(user, record))
25
+ ```
26
+
27
+ - **`.visible(rule)`** — mounts the button only when rule resolves truthy. Default: visible.
28
+ - **`.hidden(rule)`** — sugar inverse of `visible`. Last write wins; if you set both, the more recent setter is the active one.
29
+ - **`.disabled(rule)`** — renders the button but as a non-interactive (greyed) chip. The user sees it exists but can't fire it.
30
+ - **`.authorize(rule)`** — semantically equivalent to `.visible()` but reads as a permission gate at call sites. Use when the predicate is policy-shaped (`canEdit`, `canDelete`).
31
+
32
+ `visible` + `authorize` both gate *presence*. Composing them is fine; both must resolve truthy. Default is `true` for both, so omitting them leaves the action always-visible.
33
+
34
+ ## Fail-closed semantics
35
+
36
+ Predicates that throw or reject → button hides (or for `.disabled`, treats as disabled-true). This is the **opposite** of the layout `visible()` posture in `pilotiq-fields`, which fails *open* for in-progress data safety. Actions are operations, not data — silently hiding a bad button is safer than rendering one with broken logic.
37
+
38
+ ```ts
39
+ Action.make('publish')
40
+ .visible(async ({ record }) => {
41
+ if (!record) throw new Error('record missing') // silently hides; no toast, no log
42
+ return record.status === 'draft'
43
+ })
44
+ ```
45
+
46
+ If you want errors loud, log inside the predicate yourself. Don't expect a framework-level toast.
47
+
48
+ ## Per-row gating on tables
49
+
50
+ Row-placement actions evaluate predicates **per row** during `loadTableRecords`. The framework:
51
+
52
+ 1. Walks the registered row actions (from `Resource.table().recordActions([…])` or page-override `getRowActions()`).
53
+ 2. Filters to actions with at least one conditional rule (`visible` / `hidden` / `disabled` / `authorize`).
54
+ 3. Calls each rule in parallel via `Promise.all` for each row.
55
+ 4. Stamps the row with `_visibleActions: name[]` (names that resolved truthy on visible/authorize) and `_disabledActions: name[]` (names that resolved truthy on disabled).
56
+ 5. The renderer's `renderRowActions` filters its strip against `_visibleActions` and applies disabled styling per `_disabledActions`.
57
+
58
+ **Performance** — every conditional rule × every row × every page load. Heavy predicates (DB queries, network calls) inside `visible()` will dominate list-page latency. Prefer reading from the row record itself when possible:
59
+
60
+ ```ts
61
+ // Cheap — reads from the record server-side stamped fields
62
+ .visible(({ record }) => record.status === 'draft')
63
+
64
+ // Expensive — DB query per row
65
+ .visible(async ({ record }) => await UserPolicy.canEdit(user, record))
66
+ ```
67
+
68
+ If you need expensive checks, stamp the result on the row via `Column.formatStateUsing` upstream and read from `record._formatted[col]` instead.
69
+
70
+ ## Bulk-placement gating
71
+
72
+ Bulk-action predicates receive `records: Record[]` instead of `record`. They evaluate ONCE per page render (against the rendered row set), not per row:
73
+
74
+ ```ts
75
+ Action.make('bulkDelete')
76
+ .label('Delete selected')
77
+ .color('destructive')
78
+ .handler(async ({ records, user }) => {
79
+ for (const r of records) {
80
+ if (!await canDelete(user, r)) continue
81
+ await Article.delete(r.id)
82
+ }
83
+ return { notify: { title: `Deleted ${records.length}`, kind: 'success' } }
84
+ })
85
+ .visible(({ user }) => user?.role === 'editor') // gates the bulk button
86
+ ```
87
+
88
+ For *per-row* exclusion inside a bulk handler, the convention is: iterate `records` in the handler, run the per-row predicate yourself, skip / accumulate errors. The framework doesn't pre-filter `records` against row-side predicates — the bulk button either shows or doesn't, and the handler owns row-level safety.
89
+
90
+ ## Composing with Resource policies
91
+
92
+ Resource statics (`canView` / `canEdit` / `canDelete` / `canCreate` etc.) return `boolean | Promise<boolean>` against `(user, record?)`. Built-in factories (`Action.delete`, `Action.edit`, `Action.replicate`) auto-attach a `.visible()` rule that consults the matching policy:
93
+
94
+ ```ts
95
+ // Resource
96
+ class ArticleResource extends Resource {
97
+ static async canDelete(user, record) {
98
+ return user?.role === 'editor' && !record.locked
99
+ }
100
+ }
101
+
102
+ // page-override row actions
103
+ Action.delete(ArticleResource, base, row.id)
104
+ // equivalent to: Action.make('delete').method('POST').action(...).visible(({ record, user }) => ArticleResource.canDelete(user, record))
105
+ ```
106
+
107
+ Override the auto-attach by setting `.visible(...)` explicitly — your setter wins.
108
+
109
+ ## Async + parallel
110
+
111
+ All four predicates may be `async`. Per-row eval runs every row's predicates in parallel via `Promise.all`. Don't fan-out further inside a predicate — each row already pays one round-trip if you go async. Batch reads upstream if you need them.
112
+
113
+ ## Pitfalls
114
+
115
+ - **`hidden(true)` does NOT permanently hide.** It's just the inverse of `visible(true)`. To permanently disable an action conditionally on config rather than data, hide it at the schema-builder level (don't include it in `recordActions([…])` at all).
116
+ - **`disabled(true)` still POSTS if you mount it elsewhere.** The disabled flag is purely chrome. The action's dispatch URL is still live server-side — if a user crafts the POST manually, the handler still fires. Always gate inside the handler too for security.
117
+ - **Predicates can see `record` as `undefined`** when an action is placed in a context that doesn't have a record (e.g. inline placement on a non-row page). Guard with `if (!record) return false` or use `?.` chaining.
118
+ - **`user` may be `null`** when no user resolver runs or the resolver returns null. Predicates that need a user must guard.
119
+ - **Don't read mutable state in predicates** (counter increments, request-scoped caches). They run multiple times per render in parallel — the same predicate may fire 5× concurrently for 5 rows.
120
+
121
+ ## See also
122
+
123
+ - `dispatch-modes.md` — what the button does when allowed to fire.
124
+ - `factories.md` — built-in factories auto-attach visibility rules; this rule covers the manual setter form.
125
+ - `pilotiq-resource/rules/authorization.md` — Resource-level `can*` policies that feed factory auto-visibility.
@@ -0,0 +1,47 @@
1
+ ---
2
+ name: pilotiq-fields
3
+ description: Form fields in pilotiq — the 24 built-in field types, common setters, validators, and reactive ($get/$set + live + afterStateUpdated) patterns
4
+ license: MIT
5
+ appliesTo:
6
+ - '@pilotiq/pilotiq'
7
+ trigger: defining or editing a form field inside a `Resource.form()` schema — `TextField` / `SelectField` / `Repeater` / etc., setting `validate()` / `unique()` / `distinct()`, or wiring reactive `live()` / `afterStateUpdated`
8
+ skip: working with read-only display primitives (`TextEntry` / `BadgeEntry` / etc.) — those are infolist entries, not form fields
9
+ metadata:
10
+ author: pilotiq
11
+ ---
12
+
13
+ # Pilotiq Fields
14
+
15
+ ## When to use this skill
16
+
17
+ Load when you're:
18
+
19
+ - Picking a field type for a form (text vs textarea vs markdown vs richtext, single-select vs multi-select)
20
+ - Setting common chrome — labels, placeholders, helper text, prefixes, suffixes
21
+ - Adding validators (`required`, `email`, `min/maxLength`, `pattern`, `unique`, `distinct`)
22
+ - Wiring reactive behavior — `live()` to re-resolve schema on change, `afterStateUpdated()` to imperatively update sibling fields
23
+
24
+ For Resource-level concerns (when to override `form()` itself), use `pilotiq-resource`. For relation-backed array fields (`Repeater.relationship()`), see `pilotiq-relations`.
25
+
26
+ ## Quick Reference
27
+
28
+ | Task | Open |
29
+ |---|---|
30
+ | Field types — full catalog of 24 built-ins, when to use each | `rules/field-catalog.md` |
31
+ | Validation — built-in validators, `unique()` async DB probe, `distinct()` cross-row uniqueness | `rules/validation.md` |
32
+ | Reactive fields — `live()`, `afterStateUpdated`, `afterStateUpdatedJs`, `$get` / `$set`, multi-form `formId` | `rules/reactive-fields.md` |
33
+
34
+ ## Key concepts (load once)
35
+
36
+ - **Every field is a static `make(name)` builder.** `TextField.make('title').required().label('Title')` — the `name` is the form field name AND the model column name.
37
+ - **Common setters cascade from `Field` base.** `.label() / .helperText() / .placeholder() / .default() / .prefix() / .suffix() / .required() / .validate() / .visible() / .hidden() / .disabled() / .columnSpan() / .live() / .afterStateUpdated() / .dehydrated() / .formatStateUsing() / .autofocus() / .hiddenLabel() / .disabledOn() / .hiddenOn() / .visibleOn()` work on every subclass.
38
+ - **Operation-aware shortcuts.** `disabledOn(['edit'])` / `hiddenOn(['view'])` / `visibleOn(['create', 'edit'])` resolve against page mode (`'create' | 'edit' | 'view'`). They no-op on custom Pages (mode unset). `readonly()` still wins over `disabledOn`.
39
+ - **Validators are async.** `Validator = (value, ctx?) => string | null | Promise<string | null>` — return a message string to fail, `null` to pass. `validateSchema()` awaits each field's validators in declaration order.
40
+ - **Reactive fields require `live()`.** Without it, `afterStateUpdated` only fires on submit. With it, every change POSTs to a partial-resolve endpoint that re-runs the schema with fresh `$get/$set` state.
41
+ - **Live forms must pin `formId`.** `Form.make().formId('details')` is required when more than one form lives on a single page. The auto-fallback covers single-form pages.
42
+
43
+ ## Examples
44
+
45
+ - `playground/app/Pilotiq/Articles/Schemas/form.ts` — typical form with TextField / Textarea / SelectField / DateField.
46
+ - `playground/app/Pilotiq/Posts/Schemas/form.ts` — reactive fields (`live()` + `afterStateUpdated`) for title→slug.
47
+ - `playground/app/Pilotiq/BlocksDemo/Schemas/form.ts` — Builder field with heterogeneous block types.