@pilotiq/pilotiq 0.23.1 → 0.24.2

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