@pilotiq/pilotiq 0.24.1 → 0.24.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (480) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/boost/guidelines.md +566 -0
  3. package/boost/skills/pilotiq-fields/SKILL.md +47 -0
  4. package/boost/skills/pilotiq-fields/rules/field-catalog.md +288 -0
  5. package/boost/skills/pilotiq-fields/rules/reactive-fields.md +199 -0
  6. package/boost/skills/pilotiq-fields/rules/validation.md +198 -0
  7. package/boost/skills/pilotiq-relations/SKILL.md +47 -0
  8. package/boost/skills/pilotiq-relations/rules/relation-managers.md +256 -0
  9. package/boost/skills/pilotiq-relations/rules/repeater-relationship.md +177 -0
  10. package/boost/skills/pilotiq-resource/SKILL.md +61 -0
  11. package/boost/skills/pilotiq-resource/rules/authorization.md +242 -0
  12. package/boost/skills/pilotiq-resource/rules/defining-resources.md +228 -0
  13. package/boost/skills/pilotiq-resource/rules/page-overrides.md +296 -0
  14. package/package.json +6 -1
  15. package/.turbo/turbo-build.log +0 -8
  16. package/CLAUDE.md +0 -265
  17. package/src/Cluster.test.ts +0 -283
  18. package/src/Cluster.ts +0 -83
  19. package/src/Column.test.ts +0 -199
  20. package/src/Column.ts +0 -710
  21. package/src/Global.test.ts +0 -367
  22. package/src/Global.ts +0 -169
  23. package/src/Page.test.ts +0 -114
  24. package/src/Page.ts +0 -208
  25. package/src/Pilotiq.perf.test.ts +0 -252
  26. package/src/Pilotiq.test.ts +0 -129
  27. package/src/Pilotiq.ts +0 -1158
  28. package/src/PilotiqRegistry.ts +0 -36
  29. package/src/PilotiqServiceProvider.ts +0 -121
  30. package/src/RelationManager.test.ts +0 -400
  31. package/src/RelationManager.ts +0 -527
  32. package/src/RenderHook.test.ts +0 -252
  33. package/src/RenderHook.ts +0 -242
  34. package/src/Resource.test.ts +0 -284
  35. package/src/Resource.ts +0 -526
  36. package/src/RightPanel.test.ts +0 -202
  37. package/src/RightPanel.ts +0 -132
  38. package/src/Tab.test.ts +0 -91
  39. package/src/Tab.ts +0 -156
  40. package/src/UserMenuItem.ts +0 -145
  41. package/src/actions/Action.test.ts +0 -2526
  42. package/src/actions/Action.ts +0 -1515
  43. package/src/actions/ActionGroup.test.ts +0 -112
  44. package/src/actions/ActionGroup.ts +0 -173
  45. package/src/actions/attachFactory.ts +0 -172
  46. package/src/actions/bulkFactories.ts +0 -168
  47. package/src/actions/crudFactories.ts +0 -220
  48. package/src/actions/exportFactory.ts +0 -225
  49. package/src/actions/factoryHelpers.ts +0 -177
  50. package/src/actions/importFactory.ts +0 -243
  51. package/src/actions/index.ts +0 -17
  52. package/src/actions/m2mFactories.ts +0 -193
  53. package/src/actions/relationFactories.ts +0 -372
  54. package/src/applyPageHooks.test.ts +0 -463
  55. package/src/applyPageHooks.ts +0 -330
  56. package/src/authorization.test.ts +0 -483
  57. package/src/breadcrumbs.test.ts +0 -238
  58. package/src/cells/coerce.test.ts +0 -85
  59. package/src/cells/coerce.ts +0 -84
  60. package/src/clusterPaths.ts +0 -35
  61. package/src/columns/BadgeColumn.test.ts +0 -54
  62. package/src/columns/BadgeColumn.ts +0 -32
  63. package/src/columns/BooleanColumn.test.ts +0 -41
  64. package/src/columns/BooleanColumn.ts +0 -18
  65. package/src/columns/ColorColumn.test.ts +0 -37
  66. package/src/columns/ColorColumn.ts +0 -38
  67. package/src/columns/IconColumn.test.ts +0 -54
  68. package/src/columns/IconColumn.ts +0 -37
  69. package/src/columns/ImageColumn.test.ts +0 -41
  70. package/src/columns/ImageColumn.ts +0 -28
  71. package/src/columns/SelectColumn.ts +0 -98
  72. package/src/columns/TextColumn.test.ts +0 -190
  73. package/src/columns/TextColumn.ts +0 -20
  74. package/src/columns/TextInputColumn.ts +0 -68
  75. package/src/columns/ToggleColumn.ts +0 -46
  76. package/src/columns/editableColumns.test.ts +0 -238
  77. package/src/columns/index.ts +0 -9
  78. package/src/defaultGlobalPages.ts +0 -95
  79. package/src/defaultPages.test.ts +0 -634
  80. package/src/defaultPages.ts +0 -617
  81. package/src/defaultViewPage.test.ts +0 -147
  82. package/src/elements/Form.test.ts +0 -223
  83. package/src/elements/Form.ts +0 -416
  84. package/src/elements/ListTabs.ts +0 -28
  85. package/src/elements/Table.test.ts +0 -422
  86. package/src/elements/Table.ts +0 -850
  87. package/src/elements/TableGroup.test.ts +0 -260
  88. package/src/elements/TableGroup.ts +0 -334
  89. package/src/elements/dispatchAction.test.ts +0 -463
  90. package/src/elements/dispatchAction.ts +0 -355
  91. package/src/elements/dispatchForm.test.ts +0 -477
  92. package/src/elements/dispatchForm.ts +0 -1993
  93. package/src/elements/dispatchTable.test.ts +0 -1514
  94. package/src/elements/dispatchTable.ts +0 -745
  95. package/src/elements/index.ts +0 -21
  96. package/src/entries/BadgeEntry.ts +0 -39
  97. package/src/entries/CodeEntry.test.ts +0 -40
  98. package/src/entries/CodeEntry.ts +0 -52
  99. package/src/entries/ColorEntry.ts +0 -63
  100. package/src/entries/ComponentEntry.test.ts +0 -173
  101. package/src/entries/ComponentEntry.ts +0 -95
  102. package/src/entries/Entry.ts +0 -304
  103. package/src/entries/IconEntry.ts +0 -49
  104. package/src/entries/ImageEntry.ts +0 -61
  105. package/src/entries/KeyValueEntry.ts +0 -47
  106. package/src/entries/RepeatableEntry.test.ts +0 -239
  107. package/src/entries/RepeatableEntry.ts +0 -173
  108. package/src/entries/TextEntry.test.ts +0 -394
  109. package/src/entries/TextEntry.ts +0 -60
  110. package/src/entries/index.ts +0 -12
  111. package/src/entries/leaves.test.ts +0 -306
  112. package/src/entries/registry.ts +0 -54
  113. package/src/fields/BuilderField.test.ts +0 -1188
  114. package/src/fields/BuilderField.ts +0 -605
  115. package/src/fields/BuilderRelationship.test.ts +0 -811
  116. package/src/fields/CheckboxField.test.ts +0 -44
  117. package/src/fields/CheckboxField.ts +0 -27
  118. package/src/fields/CheckboxListField.test.ts +0 -99
  119. package/src/fields/CheckboxListField.ts +0 -66
  120. package/src/fields/ColorPickerField.test.ts +0 -33
  121. package/src/fields/ColorPickerField.ts +0 -25
  122. package/src/fields/DateField.ts +0 -54
  123. package/src/fields/DateTimeField.test.ts +0 -55
  124. package/src/fields/EmailField.ts +0 -16
  125. package/src/fields/Field.test.ts +0 -654
  126. package/src/fields/Field.ts +0 -817
  127. package/src/fields/FileUploadField.test.ts +0 -143
  128. package/src/fields/FileUploadField.ts +0 -159
  129. package/src/fields/HiddenField.test.ts +0 -27
  130. package/src/fields/HiddenField.ts +0 -28
  131. package/src/fields/KeyValueField.test.ts +0 -105
  132. package/src/fields/KeyValueField.ts +0 -55
  133. package/src/fields/MarkdownField.test.ts +0 -167
  134. package/src/fields/MarkdownField.ts +0 -162
  135. package/src/fields/NumberField.ts +0 -33
  136. package/src/fields/RadioField.test.ts +0 -94
  137. package/src/fields/RadioField.ts +0 -67
  138. package/src/fields/RepeaterField.test.ts +0 -1806
  139. package/src/fields/RepeaterField.ts +0 -939
  140. package/src/fields/RepeaterRelationship.test.ts +0 -1923
  141. package/src/fields/RepeaterSimple.test.ts +0 -248
  142. package/src/fields/RowButton.test.ts +0 -219
  143. package/src/fields/RowButton.ts +0 -135
  144. package/src/fields/SelectField.test.ts +0 -192
  145. package/src/fields/SelectField.ts +0 -235
  146. package/src/fields/SliderField.test.ts +0 -50
  147. package/src/fields/SliderField.ts +0 -53
  148. package/src/fields/SlugField.ts +0 -24
  149. package/src/fields/TagsInputField.test.ts +0 -154
  150. package/src/fields/TagsInputField.ts +0 -133
  151. package/src/fields/TextField.test.ts +0 -213
  152. package/src/fields/TextField.ts +0 -177
  153. package/src/fields/TextareaField.test.ts +0 -58
  154. package/src/fields/TextareaField.ts +0 -59
  155. package/src/fields/ToggleButtonsField.test.ts +0 -106
  156. package/src/fields/ToggleButtonsField.ts +0 -59
  157. package/src/fields/ToggleField.ts +0 -16
  158. package/src/fields/disableOptionsWhenSelectedInSiblingRepeaterItems.test.ts +0 -319
  159. package/src/fields/optionsResolver.ts +0 -95
  160. package/src/fields/resolveField.ts +0 -28
  161. package/src/filters/BooleanFilter.ts +0 -35
  162. package/src/filters/DateRangeFilter.test.ts +0 -194
  163. package/src/filters/DateRangeFilter.ts +0 -148
  164. package/src/filters/Filter.test.ts +0 -268
  165. package/src/filters/Filter.ts +0 -184
  166. package/src/filters/FormFilter.test.ts +0 -238
  167. package/src/filters/FormFilter.ts +0 -215
  168. package/src/filters/MultiSelectFilter.test.ts +0 -119
  169. package/src/filters/MultiSelectFilter.ts +0 -78
  170. package/src/filters/QueryBuilderFilter.test.ts +0 -662
  171. package/src/filters/QueryBuilderFilter.ts +0 -398
  172. package/src/filters/SelectFilter.ts +0 -46
  173. package/src/filters/TernaryFilter.test.ts +0 -160
  174. package/src/filters/TernaryFilter.ts +0 -72
  175. package/src/filters/TrashedFilter.test.ts +0 -149
  176. package/src/filters/TrashedFilter.ts +0 -55
  177. package/src/filters/queryBuilder/BooleanConstraint.ts +0 -31
  178. package/src/filters/queryBuilder/Constraint.ts +0 -115
  179. package/src/filters/queryBuilder/DateConstraint.ts +0 -69
  180. package/src/filters/queryBuilder/NumberConstraint.ts +0 -66
  181. package/src/filters/queryBuilder/SelectConstraint.ts +0 -72
  182. package/src/filters/queryBuilder/TextConstraint.ts +0 -64
  183. package/src/filters/queryBuilder/index.ts +0 -12
  184. package/src/icons/index.ts +0 -2
  185. package/src/icons/lucide.ts +0 -204
  186. package/src/icons/registry.test.ts +0 -56
  187. package/src/icons/registry.ts +0 -41
  188. package/src/icons/types.ts +0 -47
  189. package/src/index.ts +0 -525
  190. package/src/io/csv.test.ts +0 -142
  191. package/src/io/csv.ts +0 -170
  192. package/src/nestedRelationManagerData.test.ts +0 -547
  193. package/src/notifications/Notification.test.ts +0 -210
  194. package/src/notifications/Notification.ts +0 -354
  195. package/src/notifications/broadcast.test.ts +0 -110
  196. package/src/notifications/broadcast.ts +0 -95
  197. package/src/notifications/database.test.ts +0 -383
  198. package/src/notifications/database.ts +0 -398
  199. package/src/notifications/databaseNotifications.test.ts +0 -187
  200. package/src/notifications/dispatchNotificationAction.test.ts +0 -341
  201. package/src/notifications/dispatchNotificationAction.ts +0 -142
  202. package/src/notifications/flash.test.ts +0 -89
  203. package/src/notifications/flash.ts +0 -71
  204. package/src/notifications/index.ts +0 -45
  205. package/src/notifications/registerBroadcastAuth.test.ts +0 -134
  206. package/src/notifications/registerBroadcastAuth.ts +0 -100
  207. package/src/notifications/resolveSavedNotification.test.ts +0 -82
  208. package/src/notifications/resolveSavedNotification.ts +0 -59
  209. package/src/notifications/types.ts +0 -93
  210. package/src/orm/m2mAccessor.ts +0 -66
  211. package/src/orm/modelDefaults.test.ts +0 -633
  212. package/src/orm/modelDefaults.ts +0 -666
  213. package/src/pageData/breadcrumbs.ts +0 -288
  214. package/src/pageData/forms.ts +0 -578
  215. package/src/pageData/helpers.ts +0 -857
  216. package/src/pageData/misc.ts +0 -347
  217. package/src/pageData/navigation.ts +0 -842
  218. package/src/pageData/relationPages.ts +0 -1248
  219. package/src/pageData/relationTabs.ts +0 -286
  220. package/src/pageData/resourcePages.ts +0 -609
  221. package/src/pageData.test.ts +0 -1545
  222. package/src/pageData.ts +0 -341
  223. package/src/plugins/index.ts +0 -8
  224. package/src/plugins/themeEditor.test.ts +0 -36
  225. package/src/plugins/themeEditor.ts +0 -45
  226. package/src/react/AppShell.tsx +0 -251
  227. package/src/react/CollabExtensionFactoryRegistry.ts +0 -55
  228. package/src/react/CollabRoomContext.ts +0 -98
  229. package/src/react/CollabTextRendererRegistry.ts +0 -102
  230. package/src/react/CommandPalette.tsx +0 -375
  231. package/src/react/CurrentUserContext.tsx +0 -50
  232. package/src/react/CustomPageWrapperGate.tsx +0 -69
  233. package/src/react/CustomPageWrapperRegistry.ts +0 -45
  234. package/src/react/FieldFocusReporterRegistry.ts +0 -37
  235. package/src/react/FieldLabelSlotRegistry.ts +0 -30
  236. package/src/react/FieldPresenceRegistry.ts +0 -46
  237. package/src/react/FormCollabBindingRegistry.ts +0 -242
  238. package/src/react/FormStateContext.tsx +0 -591
  239. package/src/react/HeadHooks.tsx +0 -126
  240. package/src/react/MarkdownEditorRegistry.test.ts +0 -38
  241. package/src/react/MarkdownEditorRegistry.ts +0 -107
  242. package/src/react/NotificationActionStrip.tsx +0 -263
  243. package/src/react/NotificationBell.tsx +0 -426
  244. package/src/react/PendingSuggestionApplierRegistry.test.ts +0 -97
  245. package/src/react/PendingSuggestionApplierRegistry.ts +0 -98
  246. package/src/react/PendingSuggestionOverlayRegistry.ts +0 -54
  247. package/src/react/PendingSuggestionsContext.tsx +0 -172
  248. package/src/react/RecordWrapperGate.tsx +0 -58
  249. package/src/react/RecordWrapperRegistry.ts +0 -39
  250. package/src/react/RenderHookSlot.tsx +0 -32
  251. package/src/react/RightSidebar.tsx +0 -257
  252. package/src/react/RightSidebarContext.tsx +0 -234
  253. package/src/react/RightSidebarTrigger.tsx +0 -53
  254. package/src/react/RowCoordsContext.tsx +0 -23
  255. package/src/react/SchemaRenderer.tsx +0 -549
  256. package/src/react/SearchTrigger.tsx +0 -46
  257. package/src/react/ThemeProvider.tsx +0 -93
  258. package/src/react/ThemeSettingsPage.tsx +0 -579
  259. package/src/react/ThemeToggle.tsx +0 -20
  260. package/src/react/Toaster.tsx +0 -158
  261. package/src/react/UserMenu.tsx +0 -196
  262. package/src/react/WidgetDataContext.tsx +0 -157
  263. package/src/react/cells/EditableCell.tsx +0 -389
  264. package/src/react/component-slots.test.ts +0 -103
  265. package/src/react/component-slots.ts +0 -116
  266. package/src/react/fieldJsHandler.test.ts +0 -166
  267. package/src/react/fieldJsHandler.ts +0 -79
  268. package/src/react/fields/BuilderInput.tsx +0 -1078
  269. package/src/react/fields/CheckboxInput.tsx +0 -39
  270. package/src/react/fields/CheckboxListInput.tsx +0 -102
  271. package/src/react/fields/ColorInput.tsx +0 -71
  272. package/src/react/fields/DateFieldInput.tsx +0 -70
  273. package/src/react/fields/DateTimeInput.tsx +0 -62
  274. package/src/react/fields/FieldShell.tsx +0 -348
  275. package/src/react/fields/FileUploadInput.tsx +0 -639
  276. package/src/react/fields/HiddenInput.tsx +0 -17
  277. package/src/react/fields/KeyValueInput.tsx +0 -230
  278. package/src/react/fields/MarkdownInput.tsx +0 -560
  279. package/src/react/fields/RadioInput.tsx +0 -81
  280. package/src/react/fields/RepeaterInput.test.ts +0 -116
  281. package/src/react/fields/RepeaterInput.tsx +0 -1420
  282. package/src/react/fields/SelectFieldInput.tsx +0 -280
  283. package/src/react/fields/SliderInput.tsx +0 -81
  284. package/src/react/fields/TagsInput.tsx +0 -283
  285. package/src/react/fields/TextLikeInput.tsx +0 -256
  286. package/src/react/fields/ToggleButtonsInput.tsx +0 -60
  287. package/src/react/fields/ToggleFieldInput.tsx +0 -56
  288. package/src/react/fields/relationshipRenameDispatch.test.ts +0 -106
  289. package/src/react/fields/relationshipRenameDispatch.ts +0 -97
  290. package/src/react/fields/repeaterReconcile.test.ts +0 -114
  291. package/src/react/fields/repeaterReconcile.ts +0 -104
  292. package/src/react/fields/rowChromeButton.tsx +0 -336
  293. package/src/react/fields/rowState.ts +0 -106
  294. package/src/react/fields/syncRowGates.test.ts +0 -202
  295. package/src/react/fields/syncRowGates.ts +0 -66
  296. package/src/react/fields/textInputControls.tsx +0 -238
  297. package/src/react/fields/useRowReorderDnd.ts +0 -78
  298. package/src/react/formStateHelpers.test.ts +0 -508
  299. package/src/react/formStateHelpers.ts +0 -381
  300. package/src/react/hooks/use-mobile.ts +0 -19
  301. package/src/react/icon-context.tsx +0 -60
  302. package/src/react/index.ts +0 -194
  303. package/src/react/layouts/SidebarLayout.tsx +0 -250
  304. package/src/react/layouts/TopbarLayout.tsx +0 -258
  305. package/src/react/navigate.tsx +0 -37
  306. package/src/react/onProviderSynced.test.ts +0 -90
  307. package/src/react/parseRecordEditUrl.test.ts +0 -122
  308. package/src/react/parseRecordEditUrl.ts +0 -94
  309. package/src/react/persistedState.ts +0 -40
  310. package/src/react/registry.ts +0 -48
  311. package/src/react/right-panel-registry.tsx +0 -47
  312. package/src/react/schemaRenderer/AlertRenderer.tsx +0 -112
  313. package/src/react/schemaRenderer/EntryRenderer.tsx +0 -501
  314. package/src/react/schemaRenderer/SectionRenderer.tsx +0 -120
  315. package/src/react/schemaRenderer/SimpleElements.tsx +0 -306
  316. package/src/react/schemaRenderer/TabsRenderer.tsx +0 -62
  317. package/src/react/schemaRenderer/WizardRenderer.tsx +0 -338
  318. package/src/react/schemaRenderer/action/ActionGroupTrigger.tsx +0 -177
  319. package/src/react/schemaRenderer/action/ActionModalDialog.tsx +0 -273
  320. package/src/react/schemaRenderer/action/ConfirmActionDialog.tsx +0 -61
  321. package/src/react/schemaRenderer/action/HandlerActionButton.tsx +0 -43
  322. package/src/react/schemaRenderer/action/MethodActionButton.tsx +0 -64
  323. package/src/react/schemaRenderer/action/buttons.tsx +0 -99
  324. package/src/react/schemaRenderer/action/helpers.ts +0 -140
  325. package/src/react/schemaRenderer/action/renderAction.tsx +0 -245
  326. package/src/react/schemaRenderer/columnFormat.ts +0 -65
  327. package/src/react/schemaRenderer/constants.ts +0 -50
  328. package/src/react/schemaRenderer/form/FormRenderer.tsx +0 -274
  329. package/src/react/schemaRenderer/form/renderField.tsx +0 -511
  330. package/src/react/schemaRenderer/helpers.tsx +0 -81
  331. package/src/react/schemaRenderer/table/CardsLayoutBody.tsx +0 -308
  332. package/src/react/schemaRenderer/table/TableRenderer.tsx +0 -123
  333. package/src/react/schemaRenderer/table/TableRendererBody.tsx +0 -974
  334. package/src/react/schemaRenderer/table/filters.tsx +0 -1233
  335. package/src/react/schemaRenderer/table/formatCell.tsx +0 -264
  336. package/src/react/schemaRenderer/table/links.tsx +0 -112
  337. package/src/react/schemaRenderer/table/renderRowActions.tsx +0 -52
  338. package/src/react/schemaRenderer/table/url.tsx +0 -143
  339. package/src/react/theme-preview/apply.ts +0 -99
  340. package/src/react/theme-preview/build-html.ts +0 -436
  341. package/src/react/ui/button.tsx +0 -51
  342. package/src/react/ui/calendar.tsx +0 -67
  343. package/src/react/ui/checkbox.tsx +0 -29
  344. package/src/react/ui/dialog.tsx +0 -108
  345. package/src/react/ui/dropdown-menu.tsx +0 -97
  346. package/src/react/ui/input.tsx +0 -20
  347. package/src/react/ui/label.tsx +0 -21
  348. package/src/react/ui/popover.tsx +0 -50
  349. package/src/react/ui/select.tsx +0 -169
  350. package/src/react/ui/separator.tsx +0 -25
  351. package/src/react/ui/sheet.tsx +0 -136
  352. package/src/react/ui/sidebar.tsx +0 -723
  353. package/src/react/ui/skeleton.tsx +0 -13
  354. package/src/react/ui/slider.tsx +0 -34
  355. package/src/react/ui/switch.tsx +0 -28
  356. package/src/react/ui/table.tsx +0 -105
  357. package/src/react/ui/tabs.tsx +0 -63
  358. package/src/react/ui/textarea.tsx +0 -18
  359. package/src/react/ui/tooltip.tsx +0 -64
  360. package/src/react/useResizableWidth.ts +0 -139
  361. package/src/react/utils.ts +0 -6
  362. package/src/react/widgetRegistry.test.ts +0 -43
  363. package/src/react/widgetRegistry.ts +0 -50
  364. package/src/react/widgets/StatsOverviewRenderer.tsx +0 -232
  365. package/src/react/widgets/TableWidgetRenderer.tsx +0 -231
  366. package/src/react/widgets/ViewRenderer.tsx +0 -71
  367. package/src/relationManagerData.test.ts +0 -1595
  368. package/src/richtext/index.ts +0 -8
  369. package/src/richtext/registry.ts +0 -89
  370. package/src/routes/globals.ts +0 -148
  371. package/src/routes/guard.test.ts +0 -325
  372. package/src/routes/helpers.ts +0 -704
  373. package/src/routes/pages.ts +0 -175
  374. package/src/routes/panel.ts +0 -204
  375. package/src/routes/relations.ts +0 -1243
  376. package/src/routes/resources.ts +0 -781
  377. package/src/routes/theme.ts +0 -91
  378. package/src/routes-nested-relations.test.ts +0 -676
  379. package/src/routes-relations.test.ts +0 -972
  380. package/src/routes.test.ts +0 -2027
  381. package/src/routes.ts +0 -303
  382. package/src/schema/Alert.test.ts +0 -109
  383. package/src/schema/Alert.ts +0 -131
  384. package/src/schema/Block.ts +0 -169
  385. package/src/schema/Breadcrumbs.ts +0 -40
  386. package/src/schema/Card.ts +0 -35
  387. package/src/schema/Divider.ts +0 -20
  388. package/src/schema/Element.ts +0 -219
  389. package/src/schema/EmptyState.test.ts +0 -37
  390. package/src/schema/EmptyState.ts +0 -63
  391. package/src/schema/Fieldset.ts +0 -43
  392. package/src/schema/Grid.ts +0 -43
  393. package/src/schema/Group.ts +0 -30
  394. package/src/schema/Heading.ts +0 -39
  395. package/src/schema/Html.ts +0 -67
  396. package/src/schema/Icon.ts +0 -54
  397. package/src/schema/Image.ts +0 -57
  398. package/src/schema/LinkTag.ts +0 -41
  399. package/src/schema/Markdown.ts +0 -85
  400. package/src/schema/MetaTag.ts +0 -41
  401. package/src/schema/RelationTabs.ts +0 -71
  402. package/src/schema/ScriptTag.ts +0 -55
  403. package/src/schema/Section.ts +0 -160
  404. package/src/schema/ServerDataElement.test.ts +0 -140
  405. package/src/schema/ServerDataElement.ts +0 -156
  406. package/src/schema/SlotComponent.test.ts +0 -77
  407. package/src/schema/SlotComponent.ts +0 -71
  408. package/src/schema/Split.ts +0 -50
  409. package/src/schema/Stat.test.ts +0 -118
  410. package/src/schema/Stat.ts +0 -154
  411. package/src/schema/StatsOverview.test.ts +0 -141
  412. package/src/schema/StatsOverview.ts +0 -119
  413. package/src/schema/StyleTag.ts +0 -35
  414. package/src/schema/TableWidget.test.ts +0 -297
  415. package/src/schema/TableWidget.ts +0 -289
  416. package/src/schema/Tabs.ts +0 -79
  417. package/src/schema/Text.ts +0 -58
  418. package/src/schema/UnorderedList.ts +0 -49
  419. package/src/schema/View.test.ts +0 -111
  420. package/src/schema/View.ts +0 -127
  421. package/src/schema/Wizard.ts +0 -220
  422. package/src/schema/containers.test.ts +0 -564
  423. package/src/schema/headTags.test.ts +0 -134
  424. package/src/schema/index.ts +0 -40
  425. package/src/schema/primes.test.ts +0 -269
  426. package/src/schema/resolveSchema.test.ts +0 -379
  427. package/src/schema/resolveSchema.ts +0 -917
  428. package/src/schema/sanitize.ts +0 -58
  429. package/src/search.test.ts +0 -446
  430. package/src/search.ts +0 -178
  431. package/src/sessionFilters.test.ts +0 -375
  432. package/src/sessionFilters.ts +0 -143
  433. package/src/slot-components/index.ts +0 -10
  434. package/src/slot-components/registry.ts +0 -56
  435. package/src/styles/file-upload.css +0 -13
  436. package/src/summarizers/Summarizer.test.ts +0 -84
  437. package/src/summarizers/Summarizer.ts +0 -123
  438. package/src/summarizers/index.ts +0 -11
  439. package/src/theme/base-colors.ts +0 -68
  440. package/src/theme/chart-colors.ts +0 -50
  441. package/src/theme/colors.ts +0 -447
  442. package/src/theme/generate-css.test.ts +0 -139
  443. package/src/theme/generate-css.ts +0 -44
  444. package/src/theme/generate-scale.test.ts +0 -106
  445. package/src/theme/generate-scale.ts +0 -97
  446. package/src/theme/icon-map.ts +0 -42
  447. package/src/theme/index.ts +0 -34
  448. package/src/theme/migrate.test.ts +0 -178
  449. package/src/theme/migrate.ts +0 -81
  450. package/src/theme/presets.ts +0 -135
  451. package/src/theme/radius.ts +0 -18
  452. package/src/theme/resolve.test.ts +0 -238
  453. package/src/theme/resolve.ts +0 -96
  454. package/src/theme/spacing.ts +0 -18
  455. package/src/theme/storage.test.ts +0 -126
  456. package/src/theme/storage.ts +0 -106
  457. package/src/theme/theme-colors.ts +0 -88
  458. package/src/theme/types.ts +0 -125
  459. package/src/uploads/UploadAdapter.ts +0 -35
  460. package/src/uploads/index.ts +0 -2
  461. package/src/uploads/localUpload.test.ts +0 -70
  462. package/src/uploads/localUpload.ts +0 -84
  463. package/src/validation/Validator.ts +0 -49
  464. package/src/validation/index.ts +0 -28
  465. package/src/validation/rules.ts +0 -78
  466. package/src/validation/runValidators.ts +0 -435
  467. package/src/validation/uniqueValidator.test.ts +0 -196
  468. package/src/validation/uniqueValidator.ts +0 -133
  469. package/src/validation/validators.test.ts +0 -268
  470. package/src/vite.test.ts +0 -184
  471. package/src/vite.ts +0 -787
  472. package/src/widgets/index.ts +0 -10
  473. package/src/widgets/registry.ts +0 -45
  474. package/src/widgets.test.ts +0 -592
  475. package/tsconfig.build.json +0 -11
  476. package/tsconfig.json +0 -4
  477. package/tsconfig.test.json +0 -10
  478. package/views/react/Dashboard.tsx +0 -27
  479. package/views/react/Resources/Form.tsx +0 -102
  480. package/views/react/Resources/Index.tsx +0 -49
@@ -1,426 +0,0 @@
1
- 'use client'
2
-
3
- import React from 'react'
4
- import {
5
- DropdownMenu,
6
- DropdownMenuContent,
7
- DropdownMenuTrigger,
8
- } from './ui/dropdown-menu.js'
9
- import { useNavigate } from './navigate.js'
10
- import { useIconFor } from './icon-context.js'
11
- import { cn } from './utils.js'
12
- import type { DatabaseNotificationsMeta } from '../pageData.js'
13
- import type { DatabaseNotificationMeta } from '../notifications/database.js'
14
- import type { NavigationBadgeColor } from '../Resource.js'
15
- import type { NotificationMeta as NotificationMetaImport } from '../notifications/Notification.js'
16
- import { NotificationActionStrip } from './NotificationActionStrip.js'
17
- import { useToast } from './Toaster.js'
18
-
19
- const BADGE_COLOR: Record<NavigationBadgeColor, string> = {
20
- default: 'bg-muted text-muted-foreground',
21
- primary: 'bg-primary text-primary-foreground',
22
- success: 'bg-emerald-500 text-white',
23
- warning: 'bg-amber-500 text-white',
24
- destructive: 'bg-red-500 text-white',
25
- info: 'bg-sky-500 text-white',
26
- }
27
-
28
- const TYPE_DOT: Record<NonNullable<DatabaseNotificationMeta['type']>, string> = {
29
- info: 'bg-sky-500',
30
- success: 'bg-emerald-500',
31
- warning: 'bg-amber-500',
32
- error: 'bg-red-500',
33
- }
34
-
35
- /**
36
- * Bell-icon dropdown for `Pilotiq.databaseNotifications()`. Renders
37
- * nothing when `meta` is absent — `panelInfo()` only ships the meta
38
- * when the panel opted in AND a user resolved.
39
- *
40
- * Lifecycle:
41
- * - First mount: fetch the current list via `meta.listUrl`.
42
- * - Polling: when `meta.polling > 0`, refetch every N seconds.
43
- * Pauses while `document.visibilityState !== 'visible'` so a
44
- * backgrounded tab doesn't keep tickling the server.
45
- * - Mark-read: optimistic — flips `readAt` immediately, refetches
46
- * after the POST so the unread count rebases against the server.
47
- * - Click-through: when a row carries a `url`, the click marks-read
48
- * + SPA-navigates in the same step.
49
- */
50
- export function NotificationBell({ meta }: { meta?: DatabaseNotificationsMeta }) {
51
- if (!meta) return null
52
- const navigate = useNavigate()
53
-
54
- const [open, setOpen] = React.useState(false)
55
- const [data, setData] = React.useState<{
56
- notifications: DatabaseNotificationMeta[]
57
- unreadCount: number
58
- } | null>(null)
59
- const [loading, setLoading] = React.useState(false)
60
-
61
- // ── Fetch helpers ─────────────────────────────────────
62
- // Latest-wins seq tracking — same pattern as the global-search
63
- // palette / live-form state. A slow first request can't clobber a
64
- // fresh second one.
65
- const seqRef = React.useRef(0)
66
- const latestRef = React.useRef(0)
67
- const refetch = React.useCallback(async () => {
68
- const seq = ++seqRef.current
69
- setLoading(true)
70
- try {
71
- const r = await fetch(meta.listUrl, {
72
- headers: { Accept: 'application/json' },
73
- credentials: 'same-origin',
74
- })
75
- if (!r.ok) return
76
- const json = await r.json() as {
77
- notifications?: DatabaseNotificationMeta[]
78
- unreadCount?: number
79
- }
80
- if (seq < latestRef.current) return
81
- latestRef.current = seq
82
- setData({
83
- notifications: json.notifications ?? [],
84
- unreadCount: json.unreadCount ?? 0,
85
- })
86
- } catch { /* network-flake; the next poll retries */ }
87
- finally {
88
- if (seq >= latestRef.current) setLoading(false)
89
- }
90
- }, [meta.listUrl])
91
-
92
- // ── First mount + polling ────────────────────────────
93
- React.useEffect(() => {
94
- void refetch()
95
- }, [refetch])
96
-
97
- React.useEffect(() => {
98
- if (meta.polling === null || meta.polling === 0) return
99
- const ms = meta.polling * 1000
100
- const tick = () => {
101
- if (typeof document !== 'undefined' && document.visibilityState !== 'visible') return
102
- void refetch()
103
- }
104
- const id = window.setInterval(tick, ms)
105
- return () => window.clearInterval(id)
106
- }, [meta.polling, refetch])
107
-
108
- // ── Phase 2 broadcast subscription ───────────────────
109
- // When `meta.broadcast` is present, connect to the panel's WebSocket
110
- // and listen for `notification.created` on the user's private channel.
111
- // Triggers the same `refetch()` already wired for polling — broadcast
112
- // is "low-latency refetch hint", not a payload-pushing transport.
113
- //
114
- // RudderSocket loads via dynamic import so the bell doesn't bundle
115
- // broadcast for apps that don't enable it. Failures (package missing,
116
- // wsUrl unreachable, auth rejected) silently fall back to polling.
117
- React.useEffect(() => {
118
- if (!meta.broadcast) return
119
- if (typeof window === 'undefined') return
120
- let cancelled = false
121
- let socket: { disconnect(): void } | null = null
122
-
123
- const wsUrl = meta.broadcast.wsUrl || sameOriginWsUrl()
124
- const channelName = meta.broadcast.channel
125
- const eventName = meta.broadcast.event
126
-
127
- void (async () => {
128
- try {
129
- const RudderSocket = await loadRudderSocket()
130
- if (!RudderSocket || cancelled) return
131
- const s = new RudderSocket(wsUrl)
132
- socket = s
133
- // Strip the `private-` prefix — `RudderSocket.private(name)`
134
- // re-adds it. We pass an empty token because the auth callback
135
- // we registered server-side reads the upgrade request's cookies,
136
- // not a per-message bearer token.
137
- const bareName = channelName.replace(/^private-/, '')
138
- s.private(bareName, '').on(eventName, () => {
139
- if (cancelled) return
140
- void refetch()
141
- })
142
- } catch { /* package not vendored / connect failed — polling covers it */ }
143
- })()
144
-
145
- return () => {
146
- cancelled = true
147
- socket?.disconnect()
148
- }
149
- }, [meta.broadcast?.wsUrl, meta.broadcast?.channel, meta.broadcast?.event, refetch])
150
-
151
- // ── Mutations ────────────────────────────────────────
152
- const markRead = React.useCallback(async (id: string) => {
153
- // Optimistic flip — the bell stays responsive even on slow networks.
154
- setData(prev => prev && {
155
- notifications: prev.notifications.map(n =>
156
- n.id === id ? { ...n, readAt: n.readAt ?? new Date().toISOString() } : n,
157
- ),
158
- unreadCount: Math.max(0, prev.unreadCount - 1),
159
- })
160
- try {
161
- await fetch(meta.readUrl.replace(':id', encodeURIComponent(id)), {
162
- method: 'POST',
163
- headers: { Accept: 'application/json' },
164
- credentials: 'same-origin',
165
- })
166
- } catch { /* swallow — next poll resyncs */ }
167
- void refetch()
168
- }, [meta.readUrl, refetch])
169
-
170
- const markAllRead = React.useCallback(async () => {
171
- setData(prev => prev && {
172
- notifications: prev.notifications.map(n =>
173
- n.readAt ? n : { ...n, readAt: new Date().toISOString() }),
174
- unreadCount: 0,
175
- })
176
- try {
177
- await fetch(meta.readAllUrl, {
178
- method: 'POST',
179
- headers: { Accept: 'application/json' },
180
- credentials: 'same-origin',
181
- })
182
- } catch { /* swallow */ }
183
- void refetch()
184
- }, [meta.readAllUrl, refetch])
185
-
186
- const unreadCount = data?.unreadCount ?? 0
187
- const items = data?.notifications ?? []
188
- const toast = useToast()
189
-
190
- return (
191
- <DropdownMenu open={open} onOpenChange={setOpen}>
192
- <DropdownMenuTrigger
193
- className="relative inline-flex items-center justify-center size-9 rounded-md hover:bg-accent hover:text-accent-foreground transition-colors focus:outline-none"
194
- aria-label={meta.trigger?.label ?? 'Notifications'}
195
- >
196
- <BellIcon icon={meta.trigger?.icon} />
197
- {unreadCount > 0 && (
198
- <span
199
- className={cn(
200
- 'absolute -top-0.5 -end-0.5 inline-flex min-w-[1rem] h-4 items-center justify-center rounded-full px-1 text-[10px] font-semibold leading-none',
201
- BADGE_COLOR[meta.badgeColor],
202
- )}
203
- aria-label={`${unreadCount} unread`}
204
- >
205
- {unreadCount > 99 ? '99+' : unreadCount}
206
- </span>
207
- )}
208
- </DropdownMenuTrigger>
209
-
210
- <DropdownMenuContent align="end" className="w-80 p-0">
211
- <header className="flex items-center justify-between px-3 py-2 border-b">
212
- <span className="text-sm font-medium">Notifications</span>
213
- {unreadCount > 0 && (
214
- <button
215
- type="button"
216
- className="text-xs text-muted-foreground hover:text-foreground transition-colors"
217
- onClick={() => { void markAllRead() }}
218
- >
219
- Mark all as read
220
- </button>
221
- )}
222
- </header>
223
-
224
- <div className="max-h-96 overflow-y-auto">
225
- {items.length === 0
226
- ? <EmptyState loading={loading} />
227
- : items.map(n => (
228
- <NotificationRow
229
- key={n.id}
230
- notification={n}
231
- actionUrlTemplate={meta.actionUrl}
232
- onMarkRead={markRead}
233
- onNavigate={(href) => { setOpen(false); navigate(href) }}
234
- onNotify={(notifs) => notifs.forEach(t => toast.notify(t))}
235
- onAfterClick={() => setOpen(false)}
236
- />
237
- ))
238
- }
239
- </div>
240
- </DropdownMenuContent>
241
- </DropdownMenu>
242
- )
243
- }
244
-
245
- function NotificationRow({
246
- notification: n,
247
- actionUrlTemplate,
248
- onMarkRead,
249
- onNavigate,
250
- onNotify,
251
- onAfterClick,
252
- }: {
253
- notification: DatabaseNotificationMeta
254
- actionUrlTemplate?: string
255
- onMarkRead: (id: string) => void
256
- onNavigate: (url: string) => void
257
- onNotify: (notifs: NotificationMetaImport[]) => void
258
- onAfterClick: () => void
259
- }) {
260
- const RowIcon = useIconFor(n.icon)
261
- const unread = !n.readAt
262
-
263
- const onClick = (e: React.MouseEvent) => {
264
- // Modified clicks (cmd/ctrl/middle) preserve native semantics.
265
- if (e.defaultPrevented || e.metaKey || e.ctrlKey || e.shiftKey) return
266
- e.preventDefault()
267
- if (unread) onMarkRead(n.id)
268
- if (n.url) onNavigate(n.url)
269
- }
270
-
271
- // Body wrapper is `<a>` when the row has a click-through url, plain
272
- // `<button>` otherwise. Form-mode + handler-mode action buttons in
273
- // the strip can't validly nest inside an `<a>`/`<button>`, so the
274
- // strip lives as a sibling under a shared chrome `<div>`.
275
- const Tag: 'a' | 'button' = n.url ? 'a' : 'button'
276
- const tagProps: React.AnchorHTMLAttributes<HTMLAnchorElement> & React.ButtonHTMLAttributes<HTMLButtonElement> = {
277
- onClick,
278
- className: 'w-full text-start flex items-start gap-2 focus:outline-none',
279
- }
280
- if (Tag === 'a') {
281
- tagProps.href = n.url!
282
- } else {
283
- tagProps.type = 'button'
284
- }
285
-
286
- return (
287
- <div
288
- className={cn(
289
- 'px-3 py-2 hover:bg-accent transition-colors border-b last:border-b-0',
290
- unread && 'bg-primary/5',
291
- )}
292
- >
293
- <Tag {...(tagProps as React.HTMLAttributes<HTMLElement>)}>
294
- <span
295
- className={cn(
296
- 'mt-1.5 inline-block size-2 rounded-full shrink-0',
297
- n.type ? TYPE_DOT[n.type] : 'bg-muted-foreground/40',
298
- )}
299
- aria-hidden="true"
300
- />
301
- <span className="flex-1 min-w-0">
302
- <span className="flex items-center gap-1.5">
303
- {RowIcon && <RowIcon className="size-3.5 text-muted-foreground" aria-hidden="true" />}
304
- <span className={cn('text-sm leading-tight', unread ? 'font-medium' : '')}>
305
- {n.title || 'Notification'}
306
- </span>
307
- </span>
308
- {n.body && (
309
- <span className="block text-xs text-muted-foreground mt-0.5 line-clamp-2">
310
- {n.body}
311
- </span>
312
- )}
313
- <span className="block text-[11px] text-muted-foreground/70 mt-1">
314
- {formatRelative(n.createdAt)}
315
- </span>
316
- </span>
317
- </Tag>
318
- {n.actions && n.actions.length > 0 && (
319
- <div className="ms-4">
320
- <NotificationActionStrip
321
- actions={n.actions}
322
- {...(actionUrlTemplate !== undefined ? { actionUrlTemplate } : {})}
323
- notificationId={n.id}
324
- onMarkAsRead={onMarkRead}
325
- onNotify={onNotify}
326
- onAfterClick={onAfterClick}
327
- />
328
- </div>
329
- )}
330
- </div>
331
- )
332
- }
333
-
334
- function EmptyState({ loading }: { loading: boolean }) {
335
- return (
336
- <div className="px-3 py-8 text-center text-sm text-muted-foreground">
337
- {loading ? 'Loading…' : 'No notifications'}
338
- </div>
339
- )
340
- }
341
-
342
- function BellIcon({ icon }: { icon: string | undefined }) {
343
- // Honor `meta.trigger.icon` when set (string registry name); fall
344
- // through to the bundled bell glyph otherwise. The registry-resolved
345
- // hook returns `null` when no entry is registered, so a misspelt name
346
- // still shows the default bell — better than a silent blank.
347
- const Icon = useIconFor(icon)
348
- if (Icon) return <Icon className="size-5" aria-hidden="true" />
349
- return (
350
- <svg
351
- xmlns="http://www.w3.org/2000/svg"
352
- width="20" height="20" viewBox="0 0 24 24" fill="none"
353
- stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"
354
- aria-hidden="true"
355
- >
356
- <path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9" />
357
- <path d="M10.3 21a1.94 1.94 0 0 0 3.4 0" />
358
- </svg>
359
- )
360
- }
361
-
362
- /** Soft-load `RudderSocket` from whichever path the consuming app
363
- * vendored it to. `@rudderjs/broadcast` ships the client as
364
- * `client/RudderSocket.ts`, intended for vendor:publish copy into the
365
- * app's `src/`. We try a stack of common locations in order, returning
366
- * the first that resolves; falls through to `null` when nothing's
367
- * reachable so the bell silently continues with polling.
368
- *
369
- * Apps that vendor the file under a custom path can register their
370
- * own loader by setting `window.__pilotiqRudderSocket` to the class
371
- * constructor before the bell mounts. */
372
- type RudderSocketCtor = new (url: string) => {
373
- private(name: string, token: string): {
374
- on(event: string, handler: (data: unknown) => void): unknown
375
- }
376
- disconnect(): void
377
- }
378
-
379
- async function loadRudderSocket(): Promise<RudderSocketCtor | null> {
380
- // Allow apps to register a custom-path RudderSocket without us
381
- // having to guess at the import path.
382
- const w = typeof window !== 'undefined'
383
- ? (window as unknown as { __pilotiqRudderSocket?: RudderSocketCtor })
384
- : null
385
- if (w?.__pilotiqRudderSocket) return w.__pilotiqRudderSocket
386
-
387
- // Apps that vendor:publish the broadcast client typically end up
388
- // with `src/RudderSocket.ts` (or `.js`); try common shapes via the
389
- // app's import-map / module resolution. The dynamic import is
390
- // wrapped in a try/catch so missing modules don't surface.
391
- const candidates = [
392
- '@rudderjs/broadcast/client/RudderSocket.js',
393
- '@rudderjs/broadcast/client/RudderSocket',
394
- ]
395
- for (const id of candidates) {
396
- try {
397
- const mod = await import(/* @vite-ignore */ id) as { RudderSocket?: RudderSocketCtor }
398
- if (mod.RudderSocket) return mod.RudderSocket
399
- } catch { /* try next */ }
400
- }
401
- return null
402
- }
403
-
404
- /** Same-origin `ws://…/ws` (or `wss://` on https). Used when
405
- * `meta.broadcast.wsUrl` is empty — the default for apps that boot
406
- * `BroadcastingProvider` with no custom path. */
407
- function sameOriginWsUrl(): string {
408
- if (typeof window === 'undefined') return ''
409
- const proto = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
410
- return `${proto}//${window.location.host}/ws`
411
- }
412
-
413
- function formatRelative(ts: number): string {
414
- const now = Date.now()
415
- const diff = Math.max(0, now - ts)
416
- const sec = Math.floor(diff / 1000)
417
- if (sec < 45) return 'Just now'
418
- if (sec < 90) return '1 minute ago'
419
- const min = Math.floor(sec / 60)
420
- if (min < 60) return `${min} minutes ago`
421
- const hr = Math.floor(min / 60)
422
- if (hr < 24) return `${hr} hour${hr === 1 ? '' : 's'} ago`
423
- const day = Math.floor(hr / 24)
424
- if (day < 7) return `${day} day${day === 1 ? '' : 's'} ago`
425
- return new Date(ts).toLocaleDateString()
426
- }
@@ -1,97 +0,0 @@
1
- import { describe, it, beforeEach } from 'node:test'
2
- import assert from 'node:assert/strict'
3
-
4
- import {
5
- registerPendingSuggestionApplier,
6
- getPendingSuggestionApplier,
7
- _clearAppliersForTests,
8
- } from './PendingSuggestionApplierRegistry.js'
9
-
10
- describe('PendingSuggestionApplierRegistry', () => {
11
- beforeEach(() => { _clearAppliersForTests() })
12
-
13
- it('returns the scoped applier when both formId and fieldName match', () => {
14
- const apply = (): void => {}
15
- registerPendingSuggestionApplier('form-1', 'bio', apply)
16
- assert.equal(getPendingSuggestionApplier('form-1', 'bio'), apply)
17
- })
18
-
19
- it('routes scoped lookups to the matching scoped entry across multi-form pages', () => {
20
- // Two editors in two forms register under the same field name but
21
- // different formIds — the routing has to disambiguate.
22
- const applyA = (): void => {}
23
- const applyB = (): void => {}
24
- registerPendingSuggestionApplier('form-A', 'summary', applyA)
25
- registerPendingSuggestionApplier('form-B', 'summary', applyB)
26
- assert.equal(getPendingSuggestionApplier('form-A', 'summary'), applyA)
27
- assert.equal(getPendingSuggestionApplier('form-B', 'summary'), applyB)
28
- })
29
-
30
- it('falls through to the wildcard slot when no scoped match exists', () => {
31
- const wild = (): void => {}
32
- registerPendingSuggestionApplier(undefined, 'bio', wild)
33
- // formId provided but the registry only has a wildcard entry —
34
- // the lookup should still resolve so historic single-form pages keep
35
- // working even when consumers thread a formId.
36
- assert.equal(getPendingSuggestionApplier('form-1', 'bio'), wild)
37
- })
38
-
39
- it('global producer + scoped consumer: undefined-formId lookup finds the scoped entry', () => {
40
- // Regression guard for the multi-form fix: after threading `formId`
41
- // through the Tiptap adapter hooks, every editor registers scoped
42
- // by its surrounding `FormRenderer`'s id. A global producer (no
43
- // `formId` stamped on the suggestion) calls
44
- // `getPendingSuggestionApplier(undefined, fieldName)` — without the
45
- // fallback, the wildcard slot is empty and the suggestion silently
46
- // never applies. The fallback returns any matching scoped entry.
47
- const scopedApply = (): void => {}
48
- registerPendingSuggestionApplier('form-edit', 'bio', scopedApply)
49
- assert.equal(getPendingSuggestionApplier(undefined, 'bio'), scopedApply)
50
- })
51
-
52
- it('global lookup prefers the explicit wildcard slot over scoped fallback', () => {
53
- const wild = (): void => {}
54
- const scoped = (): void => {}
55
- registerPendingSuggestionApplier('form-edit', 'bio', scoped)
56
- registerPendingSuggestionApplier(undefined, 'bio', wild)
57
- // Wildcard wins — it was the intent of the original API ("formId
58
- // defaults to '*'") and a deliberately-registered wildcard applier
59
- // is presumed authoritative.
60
- assert.equal(getPendingSuggestionApplier(undefined, 'bio'), wild)
61
- })
62
-
63
- it('scoped lookup prefers exact match over wildcard slot', () => {
64
- const wild = (): void => {}
65
- const scoped = (): void => {}
66
- registerPendingSuggestionApplier(undefined, 'bio', wild)
67
- registerPendingSuggestionApplier('form-edit', 'bio', scoped)
68
- assert.equal(getPendingSuggestionApplier('form-edit', 'bio'), scoped)
69
- })
70
-
71
- it('returns undefined when no entry matches the fieldName at all', () => {
72
- registerPendingSuggestionApplier('form-A', 'bio', () => {})
73
- assert.equal(getPendingSuggestionApplier('form-A', 'subtitle'), undefined)
74
- assert.equal(getPendingSuggestionApplier(undefined, 'subtitle'), undefined)
75
- })
76
-
77
- it('unregister cleanup drops the entry', () => {
78
- const apply = (): void => {}
79
- const unregister = registerPendingSuggestionApplier('form-1', 'bio', apply)
80
- assert.equal(getPendingSuggestionApplier('form-1', 'bio'), apply)
81
- unregister()
82
- assert.equal(getPendingSuggestionApplier('form-1', 'bio'), undefined)
83
- })
84
-
85
- it('re-registering replaces the previous entry and its unregister no-ops', () => {
86
- const first = (): void => {}
87
- const second = (): void => {}
88
- const off1 = registerPendingSuggestionApplier('form-1', 'bio', first)
89
- registerPendingSuggestionApplier('form-1', 'bio', second) // wins
90
- assert.equal(getPendingSuggestionApplier('form-1', 'bio'), second)
91
- // First's unregister must NOT delete the second's entry — the
92
- // registry tracks identity to defend against unmount-after-remount
93
- // racing the cleanup of the just-replaced entry.
94
- off1()
95
- assert.equal(getPendingSuggestionApplier('form-1', 'bio'), second)
96
- })
97
- })
@@ -1,98 +0,0 @@
1
- import type { PendingSuggestion } from './PendingSuggestionsContext.js'
2
-
3
- /**
4
- * A function that applies a `PendingSuggestion` to its target field —
5
- * registered by the field renderer (or editor adapter) on mount, looked
6
- * up by aggregate consumers (e.g. a chat-sidebar pending-pill's
7
- * "Approve all" button) that live outside the form's React tree.
8
- *
9
- * The applier is responsible for the apply *only*. Dismissing the
10
- * suggestion from the queue is the caller's job — the apply path is
11
- * decoupled from the queue-side bookkeeping so a future Phase that
12
- * mirrors approvals to a server can do both via different code paths.
13
- */
14
- export type PendingSuggestionApplier = (suggestion: PendingSuggestion) => void
15
-
16
- interface RegistryEntry {
17
- formId: string | undefined
18
- fieldName: string
19
- apply: PendingSuggestionApplier
20
- }
21
-
22
- const _entries = new Map<string, RegistryEntry>()
23
-
24
- /**
25
- * Compose the registry key. `formId` defaults to `'*'` (global form
26
- * scope) so renderers in non-multi-form pages don't have to thread an
27
- * id. Form-scoped registrations always win over the wildcard when both
28
- * exist for the same field name.
29
- */
30
- function keyFor(formId: string | undefined, fieldName: string): string {
31
- return `${formId ?? '*'}::${fieldName}`
32
- }
33
-
34
- /**
35
- * Register an applier for `(formId, fieldName)`. Returns an unregister
36
- * function for `useEffect` cleanup. Re-registering with the same key
37
- * replaces the previous entry — the most recently mounted renderer
38
- * wins (typical in multi-instance form scenarios where an old form
39
- * unmounts after a new one mounts during navigation).
40
- */
41
- export function registerPendingSuggestionApplier(
42
- formId: string | undefined,
43
- fieldName: string,
44
- apply: PendingSuggestionApplier,
45
- ): () => void {
46
- const key = keyFor(formId, fieldName)
47
- const entry: RegistryEntry = { formId, fieldName, apply }
48
- _entries.set(key, entry)
49
- return () => {
50
- // Only delete if this entry is still the one we registered — a
51
- // re-register from another instance may have replaced us.
52
- if (_entries.get(key) === entry) _entries.delete(key)
53
- }
54
- }
55
-
56
- /**
57
- * Look up an applier for `(formId, fieldName)`. Tries the form-scoped
58
- * key first; falls back to the wildcard form ('*') so a producer that
59
- * pushed a suggestion without `formId` still resolves an applier from
60
- * a single-form page.
61
- *
62
- * Global-producer fallback: when the lookup `formId` is `undefined` AND
63
- * no wildcard entry is registered, return any single scoped entry
64
- * matching `fieldName`. This mirrors the consumer-side filter in
65
- * `usePendingSuggestionsForField` which lets undefined formId on either
66
- * side pass-through. Editors today register scoped by their surrounding
67
- * `FormRenderer`'s id (`useFormId()`), so the wildcard slot is almost
68
- * always empty — without this fallback, a producer that pushes without
69
- * a formId on a single-form page would silently fail to resolve any
70
- * applier. We pick the first scoped match (Map insertion order); when
71
- * the page genuinely has multiple forms with the same field name,
72
- * producers SHOULD stamp `formId` to disambiguate.
73
- */
74
- export function getPendingSuggestionApplier(
75
- formId: string | undefined,
76
- fieldName: string,
77
- ): PendingSuggestionApplier | undefined {
78
- if (formId !== undefined) {
79
- const scoped = _entries.get(keyFor(formId, fieldName))
80
- if (scoped) return scoped.apply
81
- }
82
- const wild = _entries.get(keyFor(undefined, fieldName))
83
- if (wild) return wild.apply
84
- if (formId === undefined) {
85
- for (const entry of _entries.values()) {
86
- if (entry.fieldName === fieldName) return entry.apply
87
- }
88
- }
89
- return undefined
90
- }
91
-
92
- /**
93
- * Test seam — clear the registry between tests. Not part of the public
94
- * API.
95
- */
96
- export function _clearAppliersForTests(): void {
97
- _entries.clear()
98
- }
@@ -1,54 +0,0 @@
1
- import type { ComponentType } from 'react'
2
- import type { PendingSuggestion } from './PendingSuggestionsContext.js'
3
- import type { ElementMeta } from '../schema/Element.js'
4
-
5
- /**
6
- * Props the per-field overlay component receives from `FieldShell` when
7
- * one or more pending suggestions target the field.
8
- *
9
- * The overlay is responsible for both rendering AND applying the
10
- * suggestion. On Approve, call `onApprove()` (which dismisses the
11
- * suggestion from the queue) AFTER mutating the form's field value to
12
- * `suggestion.suggestedValue` — the queue is just a notification surface,
13
- * applying is the renderer's job.
14
- *
15
- * The Tiptap `RichTextField` renderer skips this slot entirely — it
16
- * mirrors suggestions into the editor's inline AiSuggestion extension
17
- * instead. Other field types (Text / Textarea / Select / Number / …)
18
- * render the registered overlay below their input.
19
- */
20
- export interface PendingSuggestionOverlayProps {
21
- /** First suggestion targeting this field. Aggregate UIs handle stacks. */
22
- suggestion: PendingSuggestion
23
- /** Drop from queue (callable after applying). */
24
- onApprove: () => void
25
- /** Drop from queue without applying. */
26
- onReject: () => void
27
- /** Field type string (`text` / `select` / `toggle` / `slider` / `color` /
28
- * …) so per-fieldType overlay renderers can branch. Sparse — older
29
- * hosts may omit. Phase C of ai-review-mode. */
30
- fieldType?: string
31
- /** Resolved field meta — gives the overlay access to per-field config
32
- * (`options` for Select, `min/max` for Slider, etc.) needed to render
33
- * human-friendly comparisons rather than raw values. Sparse — older
34
- * hosts may omit. */
35
- el?: ElementMeta
36
- }
37
-
38
- let _component: ComponentType<PendingSuggestionOverlayProps> | null = null
39
-
40
- /**
41
- * Register a component to render below any `FieldShell` whose field has at
42
- * least one matching pending suggestion in the
43
- * `<PendingSuggestionsContext>` queue. Called once at boot by a plugin
44
- * (e.g. `@pilotiq-pro/ai`). No-op when no plugin registers — `FieldShell`
45
- * skips the overlay slot.
46
- */
47
- export function registerPendingSuggestionOverlay(C: ComponentType<PendingSuggestionOverlayProps>): void {
48
- _component = C
49
- }
50
-
51
- /** Returns the registered overlay component, or `null`. */
52
- export function getPendingSuggestionOverlay(): ComponentType<PendingSuggestionOverlayProps> | null {
53
- return _component
54
- }