@pilotiq/pilotiq 0.7.2 → 0.8.1

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 (371) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/CHANGELOG.md +208 -0
  3. package/CLAUDE.md +59 -3
  4. package/dist/Pilotiq.d.ts +83 -0
  5. package/dist/Pilotiq.d.ts.map +1 -1
  6. package/dist/Pilotiq.js +39 -0
  7. package/dist/Pilotiq.js.map +1 -1
  8. package/dist/actions/Action.d.ts +27 -99
  9. package/dist/actions/Action.d.ts.map +1 -1
  10. package/dist/actions/Action.js +52 -754
  11. package/dist/actions/Action.js.map +1 -1
  12. package/dist/actions/bulkFactories.d.ts +46 -0
  13. package/dist/actions/bulkFactories.d.ts.map +1 -0
  14. package/dist/actions/bulkFactories.js +144 -0
  15. package/dist/actions/bulkFactories.js.map +1 -0
  16. package/dist/actions/crudFactories.d.ts +94 -0
  17. package/dist/actions/crudFactories.d.ts.map +1 -0
  18. package/dist/actions/crudFactories.js +209 -0
  19. package/dist/actions/crudFactories.js.map +1 -0
  20. package/dist/actions/factoryHelpers.d.ts +108 -0
  21. package/dist/actions/factoryHelpers.d.ts.map +1 -0
  22. package/dist/actions/factoryHelpers.js +138 -0
  23. package/dist/actions/factoryHelpers.js.map +1 -0
  24. package/dist/actions/m2mFactories.d.ts +47 -0
  25. package/dist/actions/m2mFactories.d.ts.map +1 -0
  26. package/dist/actions/m2mFactories.js +173 -0
  27. package/dist/actions/m2mFactories.js.map +1 -0
  28. package/dist/actions/relationFactories.d.ts +93 -0
  29. package/dist/actions/relationFactories.d.ts.map +1 -0
  30. package/dist/actions/relationFactories.js +321 -0
  31. package/dist/actions/relationFactories.js.map +1 -0
  32. package/dist/elements/dispatchForm.js +1 -1
  33. package/dist/elements/dispatchForm.js.map +1 -1
  34. package/dist/elements/dispatchTable.js +1 -1
  35. package/dist/elements/dispatchTable.js.map +1 -1
  36. package/dist/fields/Field.d.ts +31 -0
  37. package/dist/fields/Field.d.ts.map +1 -1
  38. package/dist/fields/Field.js +25 -0
  39. package/dist/fields/Field.js.map +1 -1
  40. package/dist/pageData/breadcrumbs.d.ts +42 -0
  41. package/dist/pageData/breadcrumbs.d.ts.map +1 -0
  42. package/dist/pageData/breadcrumbs.js +172 -0
  43. package/dist/pageData/breadcrumbs.js.map +1 -0
  44. package/dist/pageData/forms.d.ts +137 -0
  45. package/dist/pageData/forms.d.ts.map +1 -0
  46. package/dist/pageData/forms.js +427 -0
  47. package/dist/pageData/forms.js.map +1 -0
  48. package/dist/pageData/helpers.d.ts +239 -0
  49. package/dist/pageData/helpers.d.ts.map +1 -0
  50. package/dist/pageData/helpers.js +703 -0
  51. package/dist/pageData/helpers.js.map +1 -0
  52. package/dist/pageData/misc.d.ts +76 -0
  53. package/dist/pageData/misc.d.ts.map +1 -0
  54. package/dist/pageData/misc.js +263 -0
  55. package/dist/pageData/misc.js.map +1 -0
  56. package/dist/pageData/navigation.d.ts +292 -0
  57. package/dist/pageData/navigation.d.ts.map +1 -0
  58. package/dist/pageData/navigation.js +591 -0
  59. package/dist/pageData/navigation.js.map +1 -0
  60. package/dist/pageData/relationPages.d.ts +172 -0
  61. package/dist/pageData/relationPages.d.ts.map +1 -0
  62. package/dist/pageData/relationPages.js +867 -0
  63. package/dist/pageData/relationPages.js.map +1 -0
  64. package/dist/pageData/relationTabs.d.ts +65 -0
  65. package/dist/pageData/relationTabs.d.ts.map +1 -0
  66. package/dist/pageData/relationTabs.js +258 -0
  67. package/dist/pageData/relationTabs.js.map +1 -0
  68. package/dist/pageData/resourcePages.d.ts +48 -0
  69. package/dist/pageData/resourcePages.d.ts.map +1 -0
  70. package/dist/pageData/resourcePages.js +504 -0
  71. package/dist/pageData/resourcePages.js.map +1 -0
  72. package/dist/pageData.d.ts +12 -792
  73. package/dist/pageData.d.ts.map +1 -1
  74. package/dist/pageData.js +24 -3797
  75. package/dist/pageData.js.map +1 -1
  76. package/dist/react/AppShell.d.ts +8 -0
  77. package/dist/react/AppShell.d.ts.map +1 -1
  78. package/dist/react/AppShell.js +11 -1
  79. package/dist/react/AppShell.js.map +1 -1
  80. package/dist/react/CollabExtensionFactoryRegistry.d.ts +47 -0
  81. package/dist/react/CollabExtensionFactoryRegistry.d.ts.map +1 -0
  82. package/dist/react/CollabExtensionFactoryRegistry.js +14 -0
  83. package/dist/react/CollabExtensionFactoryRegistry.js.map +1 -0
  84. package/dist/react/CollabRoomContext.d.ts +37 -0
  85. package/dist/react/CollabRoomContext.d.ts.map +1 -0
  86. package/dist/react/CollabRoomContext.js +12 -0
  87. package/dist/react/CollabRoomContext.js.map +1 -0
  88. package/dist/react/FormCollabBindingRegistry.d.ts +62 -0
  89. package/dist/react/FormCollabBindingRegistry.d.ts.map +1 -0
  90. package/dist/react/FormCollabBindingRegistry.js +14 -0
  91. package/dist/react/FormCollabBindingRegistry.js.map +1 -0
  92. package/dist/react/FormStateContext.d.ts.map +1 -1
  93. package/dist/react/FormStateContext.js +87 -0
  94. package/dist/react/FormStateContext.js.map +1 -1
  95. package/dist/react/RecordWrapperGate.d.ts +25 -0
  96. package/dist/react/RecordWrapperGate.d.ts.map +1 -0
  97. package/dist/react/RecordWrapperGate.js +30 -0
  98. package/dist/react/RecordWrapperGate.js.map +1 -0
  99. package/dist/react/RecordWrapperRegistry.d.ts +31 -0
  100. package/dist/react/RecordWrapperRegistry.d.ts.map +1 -0
  101. package/dist/react/RecordWrapperRegistry.js +15 -0
  102. package/dist/react/RecordWrapperRegistry.js.map +1 -0
  103. package/dist/react/SchemaRenderer.d.ts +17 -23
  104. package/dist/react/SchemaRenderer.d.ts.map +1 -1
  105. package/dist/react/SchemaRenderer.js +71 -3647
  106. package/dist/react/SchemaRenderer.js.map +1 -1
  107. package/dist/react/component-slots.d.ts +103 -0
  108. package/dist/react/component-slots.d.ts.map +1 -0
  109. package/dist/react/component-slots.js +18 -0
  110. package/dist/react/component-slots.js.map +1 -0
  111. package/dist/react/fields/BuilderInput.d.ts.map +1 -1
  112. package/dist/react/fields/BuilderInput.js +21 -117
  113. package/dist/react/fields/BuilderInput.js.map +1 -1
  114. package/dist/react/fields/MarkdownInput.d.ts.map +1 -1
  115. package/dist/react/fields/MarkdownInput.js +1 -3
  116. package/dist/react/fields/MarkdownInput.js.map +1 -1
  117. package/dist/react/fields/RepeaterInput.d.ts.map +1 -1
  118. package/dist/react/fields/RepeaterInput.js +22 -127
  119. package/dist/react/fields/RepeaterInput.js.map +1 -1
  120. package/dist/react/fields/rowState.d.ts +40 -0
  121. package/dist/react/fields/rowState.d.ts.map +1 -0
  122. package/dist/react/fields/rowState.js +60 -0
  123. package/dist/react/fields/rowState.js.map +1 -0
  124. package/dist/react/fields/useRowReorderDnd.d.ts +28 -0
  125. package/dist/react/fields/useRowReorderDnd.d.ts.map +1 -0
  126. package/dist/react/fields/useRowReorderDnd.js +51 -0
  127. package/dist/react/fields/useRowReorderDnd.js.map +1 -0
  128. package/dist/react/index.d.ts +9 -0
  129. package/dist/react/index.d.ts.map +1 -1
  130. package/dist/react/index.js +8 -0
  131. package/dist/react/index.js.map +1 -1
  132. package/dist/react/layouts/SidebarLayout.d.ts +1 -1
  133. package/dist/react/layouts/SidebarLayout.d.ts.map +1 -1
  134. package/dist/react/layouts/SidebarLayout.js +10 -2
  135. package/dist/react/layouts/SidebarLayout.js.map +1 -1
  136. package/dist/react/layouts/TopbarLayout.d.ts +1 -1
  137. package/dist/react/layouts/TopbarLayout.d.ts.map +1 -1
  138. package/dist/react/layouts/TopbarLayout.js +19 -11
  139. package/dist/react/layouts/TopbarLayout.js.map +1 -1
  140. package/dist/react/parseRecordEditUrl.d.ts +29 -0
  141. package/dist/react/parseRecordEditUrl.d.ts.map +1 -0
  142. package/dist/react/parseRecordEditUrl.js +25 -0
  143. package/dist/react/parseRecordEditUrl.js.map +1 -0
  144. package/dist/react/persistedState.d.ts +19 -0
  145. package/dist/react/persistedState.d.ts.map +1 -0
  146. package/dist/react/persistedState.js +51 -0
  147. package/dist/react/persistedState.js.map +1 -0
  148. package/dist/react/schemaRenderer/AlertRenderer.d.ts +12 -0
  149. package/dist/react/schemaRenderer/AlertRenderer.d.ts.map +1 -0
  150. package/dist/react/schemaRenderer/AlertRenderer.js +61 -0
  151. package/dist/react/schemaRenderer/AlertRenderer.js.map +1 -0
  152. package/dist/react/schemaRenderer/EntryRenderer.d.ts +13 -0
  153. package/dist/react/schemaRenderer/EntryRenderer.d.ts.map +1 -0
  154. package/dist/react/schemaRenderer/EntryRenderer.js +277 -0
  155. package/dist/react/schemaRenderer/EntryRenderer.js.map +1 -0
  156. package/dist/react/schemaRenderer/SectionRenderer.d.ts +16 -0
  157. package/dist/react/schemaRenderer/SectionRenderer.d.ts.map +1 -0
  158. package/dist/react/schemaRenderer/SectionRenderer.js +62 -0
  159. package/dist/react/schemaRenderer/SectionRenderer.js.map +1 -0
  160. package/dist/react/schemaRenderer/SimpleElements.d.ts +25 -0
  161. package/dist/react/schemaRenderer/SimpleElements.d.ts.map +1 -0
  162. package/dist/react/schemaRenderer/SimpleElements.js +147 -0
  163. package/dist/react/schemaRenderer/SimpleElements.js.map +1 -0
  164. package/dist/react/schemaRenderer/TabsRenderer.d.ts +17 -0
  165. package/dist/react/schemaRenderer/TabsRenderer.d.ts.map +1 -0
  166. package/dist/react/schemaRenderer/TabsRenderer.js +31 -0
  167. package/dist/react/schemaRenderer/TabsRenderer.js.map +1 -0
  168. package/dist/react/schemaRenderer/WizardRenderer.d.ts +34 -0
  169. package/dist/react/schemaRenderer/WizardRenderer.d.ts.map +1 -0
  170. package/dist/react/schemaRenderer/WizardRenderer.js +208 -0
  171. package/dist/react/schemaRenderer/WizardRenderer.js.map +1 -0
  172. package/dist/react/schemaRenderer/action/ActionGroupTrigger.d.ts +21 -0
  173. package/dist/react/schemaRenderer/action/ActionGroupTrigger.d.ts.map +1 -0
  174. package/dist/react/schemaRenderer/action/ActionGroupTrigger.js +82 -0
  175. package/dist/react/schemaRenderer/action/ActionGroupTrigger.js.map +1 -0
  176. package/dist/react/schemaRenderer/action/ActionModalDialog.d.ts +30 -0
  177. package/dist/react/schemaRenderer/action/ActionModalDialog.d.ts.map +1 -0
  178. package/dist/react/schemaRenderer/action/ActionModalDialog.js +182 -0
  179. package/dist/react/schemaRenderer/action/ActionModalDialog.js.map +1 -0
  180. package/dist/react/schemaRenderer/action/ConfirmActionDialog.d.ts +17 -0
  181. package/dist/react/schemaRenderer/action/ConfirmActionDialog.d.ts.map +1 -0
  182. package/dist/react/schemaRenderer/action/ConfirmActionDialog.js +19 -0
  183. package/dist/react/schemaRenderer/action/ConfirmActionDialog.js.map +1 -0
  184. package/dist/react/schemaRenderer/action/HandlerActionButton.d.ts +16 -0
  185. package/dist/react/schemaRenderer/action/HandlerActionButton.d.ts.map +1 -0
  186. package/dist/react/schemaRenderer/action/HandlerActionButton.js +16 -0
  187. package/dist/react/schemaRenderer/action/HandlerActionButton.js.map +1 -0
  188. package/dist/react/schemaRenderer/action/MethodActionButton.d.ts +22 -0
  189. package/dist/react/schemaRenderer/action/MethodActionButton.d.ts.map +1 -0
  190. package/dist/react/schemaRenderer/action/MethodActionButton.js +26 -0
  191. package/dist/react/schemaRenderer/action/MethodActionButton.js.map +1 -0
  192. package/dist/react/schemaRenderer/action/buttons.d.ts +18 -0
  193. package/dist/react/schemaRenderer/action/buttons.d.ts.map +1 -0
  194. package/dist/react/schemaRenderer/action/buttons.js +74 -0
  195. package/dist/react/schemaRenderer/action/buttons.js.map +1 -0
  196. package/dist/react/schemaRenderer/action/helpers.d.ts +26 -0
  197. package/dist/react/schemaRenderer/action/helpers.d.ts.map +1 -0
  198. package/dist/react/schemaRenderer/action/helpers.js +126 -0
  199. package/dist/react/schemaRenderer/action/helpers.js.map +1 -0
  200. package/dist/react/schemaRenderer/action/renderAction.d.ts +21 -0
  201. package/dist/react/schemaRenderer/action/renderAction.d.ts.map +1 -0
  202. package/dist/react/schemaRenderer/action/renderAction.js +102 -0
  203. package/dist/react/schemaRenderer/action/renderAction.js.map +1 -0
  204. package/dist/react/schemaRenderer/columnFormat.d.ts +10 -0
  205. package/dist/react/schemaRenderer/columnFormat.d.ts.map +1 -0
  206. package/dist/react/schemaRenderer/columnFormat.js +76 -0
  207. package/dist/react/schemaRenderer/columnFormat.js.map +1 -0
  208. package/dist/react/schemaRenderer/constants.d.ts +8 -0
  209. package/dist/react/schemaRenderer/constants.d.ts.map +1 -0
  210. package/dist/react/schemaRenderer/constants.js +45 -0
  211. package/dist/react/schemaRenderer/constants.js.map +1 -0
  212. package/dist/react/schemaRenderer/form/FormRenderer.d.ts +29 -0
  213. package/dist/react/schemaRenderer/form/FormRenderer.d.ts.map +1 -0
  214. package/dist/react/schemaRenderer/form/FormRenderer.js +163 -0
  215. package/dist/react/schemaRenderer/form/FormRenderer.js.map +1 -0
  216. package/dist/react/schemaRenderer/form/renderField.d.ts +6 -0
  217. package/dist/react/schemaRenderer/form/renderField.d.ts.map +1 -0
  218. package/dist/react/schemaRenderer/form/renderField.js +239 -0
  219. package/dist/react/schemaRenderer/form/renderField.js.map +1 -0
  220. package/dist/react/schemaRenderer/helpers.d.ts +32 -0
  221. package/dist/react/schemaRenderer/helpers.d.ts.map +1 -0
  222. package/dist/react/schemaRenderer/helpers.js +52 -0
  223. package/dist/react/schemaRenderer/helpers.js.map +1 -0
  224. package/dist/react/schemaRenderer/table/CardsLayoutBody.d.ts +60 -0
  225. package/dist/react/schemaRenderer/table/CardsLayoutBody.d.ts.map +1 -0
  226. package/dist/react/schemaRenderer/table/CardsLayoutBody.js +189 -0
  227. package/dist/react/schemaRenderer/table/CardsLayoutBody.js.map +1 -0
  228. package/dist/react/schemaRenderer/table/TableRenderer.d.ts +29 -0
  229. package/dist/react/schemaRenderer/table/TableRenderer.d.ts.map +1 -0
  230. package/dist/react/schemaRenderer/table/TableRenderer.js +85 -0
  231. package/dist/react/schemaRenderer/table/TableRenderer.js.map +1 -0
  232. package/dist/react/schemaRenderer/table/TableRendererBody.d.ts +18 -0
  233. package/dist/react/schemaRenderer/table/TableRendererBody.d.ts.map +1 -0
  234. package/dist/react/schemaRenderer/table/TableRendererBody.js +555 -0
  235. package/dist/react/schemaRenderer/table/TableRendererBody.js.map +1 -0
  236. package/dist/react/schemaRenderer/table/filters.d.ts +263 -0
  237. package/dist/react/schemaRenderer/table/filters.d.ts.map +1 -0
  238. package/dist/react/schemaRenderer/table/filters.js +497 -0
  239. package/dist/react/schemaRenderer/table/filters.js.map +1 -0
  240. package/dist/react/schemaRenderer/table/formatCell.d.ts +11 -0
  241. package/dist/react/schemaRenderer/table/formatCell.d.ts.map +1 -0
  242. package/dist/react/schemaRenderer/table/formatCell.js +172 -0
  243. package/dist/react/schemaRenderer/table/formatCell.js.map +1 -0
  244. package/dist/react/schemaRenderer/table/links.d.ts +42 -0
  245. package/dist/react/schemaRenderer/table/links.d.ts.map +1 -0
  246. package/dist/react/schemaRenderer/table/links.js +55 -0
  247. package/dist/react/schemaRenderer/table/links.js.map +1 -0
  248. package/dist/react/schemaRenderer/table/renderRowActions.d.ts +13 -0
  249. package/dist/react/schemaRenderer/table/renderRowActions.d.ts.map +1 -0
  250. package/dist/react/schemaRenderer/table/renderRowActions.js +25 -0
  251. package/dist/react/schemaRenderer/table/renderRowActions.js.map +1 -0
  252. package/dist/react/schemaRenderer/table/url.d.ts +41 -0
  253. package/dist/react/schemaRenderer/table/url.d.ts.map +1 -0
  254. package/dist/react/schemaRenderer/table/url.js +114 -0
  255. package/dist/react/schemaRenderer/table/url.js.map +1 -0
  256. package/dist/routes/globals.d.ts +13 -0
  257. package/dist/routes/globals.d.ts.map +1 -0
  258. package/dist/routes/globals.js +131 -0
  259. package/dist/routes/globals.js.map +1 -0
  260. package/dist/routes/helpers.d.ts +217 -0
  261. package/dist/routes/helpers.d.ts.map +1 -0
  262. package/dist/routes/helpers.js +498 -0
  263. package/dist/routes/helpers.js.map +1 -0
  264. package/dist/routes/pages.d.ts +15 -0
  265. package/dist/routes/pages.d.ts.map +1 -0
  266. package/dist/routes/pages.js +145 -0
  267. package/dist/routes/pages.js.map +1 -0
  268. package/dist/routes/panel.d.ts +19 -0
  269. package/dist/routes/panel.d.ts.map +1 -0
  270. package/dist/routes/panel.js +191 -0
  271. package/dist/routes/panel.js.map +1 -0
  272. package/dist/routes/relations.d.ts +21 -0
  273. package/dist/routes/relations.d.ts.map +1 -0
  274. package/dist/routes/relations.js +1239 -0
  275. package/dist/routes/relations.js.map +1 -0
  276. package/dist/routes/resources.d.ts +28 -0
  277. package/dist/routes/resources.d.ts.map +1 -0
  278. package/dist/routes/resources.js +741 -0
  279. package/dist/routes/resources.js.map +1 -0
  280. package/dist/routes/theme.d.ts +12 -0
  281. package/dist/routes/theme.d.ts.map +1 -0
  282. package/dist/routes/theme.js +82 -0
  283. package/dist/routes/theme.js.map +1 -0
  284. package/dist/routes.d.ts.map +1 -1
  285. package/dist/routes.js +64 -3078
  286. package/dist/routes.js.map +1 -1
  287. package/dist/vite.d.ts +1 -0
  288. package/dist/vite.d.ts.map +1 -1
  289. package/dist/vite.js +26 -5
  290. package/dist/vite.js.map +1 -1
  291. package/package.json +2 -1
  292. package/src/Pilotiq.ts +95 -0
  293. package/src/actions/Action.ts +79 -723
  294. package/src/actions/bulkFactories.ts +168 -0
  295. package/src/actions/crudFactories.ts +220 -0
  296. package/src/actions/factoryHelpers.ts +177 -0
  297. package/src/actions/m2mFactories.ts +193 -0
  298. package/src/actions/relationFactories.ts +372 -0
  299. package/src/elements/dispatchForm.ts +1 -1
  300. package/src/elements/dispatchTable.ts +1 -1
  301. package/src/fields/Field.ts +39 -0
  302. package/src/pageData/breadcrumbs.ts +288 -0
  303. package/src/pageData/forms.ts +578 -0
  304. package/src/pageData/helpers.ts +764 -0
  305. package/src/pageData/misc.ts +347 -0
  306. package/src/pageData/navigation.ts +779 -0
  307. package/src/pageData/relationPages.ts +1246 -0
  308. package/src/pageData/relationTabs.ts +286 -0
  309. package/src/pageData/resourcePages.ts +593 -0
  310. package/src/pageData.ts +122 -4731
  311. package/src/react/AppShell.tsx +27 -1
  312. package/src/react/CollabExtensionFactoryRegistry.ts +55 -0
  313. package/src/react/CollabRoomContext.ts +42 -0
  314. package/src/react/FormCollabBindingRegistry.ts +72 -0
  315. package/src/react/FormStateContext.tsx +91 -0
  316. package/src/react/RecordWrapperGate.tsx +40 -0
  317. package/src/react/RecordWrapperRegistry.ts +39 -0
  318. package/src/react/SchemaRenderer.tsx +230 -6479
  319. package/src/react/component-slots.test.ts +103 -0
  320. package/src/react/component-slots.ts +116 -0
  321. package/src/react/fields/BuilderInput.tsx +29 -117
  322. package/src/react/fields/MarkdownInput.tsx +0 -1
  323. package/src/react/fields/RepeaterInput.tsx +29 -130
  324. package/src/react/fields/rowState.ts +106 -0
  325. package/src/react/fields/useRowReorderDnd.ts +78 -0
  326. package/src/react/index.ts +38 -0
  327. package/src/react/layouts/SidebarLayout.tsx +39 -28
  328. package/src/react/layouts/TopbarLayout.tsx +70 -57
  329. package/src/react/parseRecordEditUrl.test.ts +75 -0
  330. package/src/react/parseRecordEditUrl.ts +55 -0
  331. package/src/react/persistedState.ts +40 -0
  332. package/src/react/schemaRenderer/AlertRenderer.tsx +112 -0
  333. package/src/react/schemaRenderer/EntryRenderer.tsx +501 -0
  334. package/src/react/schemaRenderer/SectionRenderer.tsx +120 -0
  335. package/src/react/schemaRenderer/SimpleElements.tsx +306 -0
  336. package/src/react/schemaRenderer/TabsRenderer.tsx +62 -0
  337. package/src/react/schemaRenderer/WizardRenderer.tsx +338 -0
  338. package/src/react/schemaRenderer/action/ActionGroupTrigger.tsx +177 -0
  339. package/src/react/schemaRenderer/action/ActionModalDialog.tsx +273 -0
  340. package/src/react/schemaRenderer/action/ConfirmActionDialog.tsx +61 -0
  341. package/src/react/schemaRenderer/action/HandlerActionButton.tsx +43 -0
  342. package/src/react/schemaRenderer/action/MethodActionButton.tsx +64 -0
  343. package/src/react/schemaRenderer/action/buttons.tsx +99 -0
  344. package/src/react/schemaRenderer/action/helpers.ts +140 -0
  345. package/src/react/schemaRenderer/action/renderAction.tsx +245 -0
  346. package/src/react/schemaRenderer/columnFormat.ts +65 -0
  347. package/src/react/schemaRenderer/constants.ts +50 -0
  348. package/src/react/schemaRenderer/form/FormRenderer.tsx +245 -0
  349. package/src/react/schemaRenderer/form/renderField.tsx +511 -0
  350. package/src/react/schemaRenderer/helpers.tsx +81 -0
  351. package/src/react/schemaRenderer/table/CardsLayoutBody.tsx +308 -0
  352. package/src/react/schemaRenderer/table/TableRenderer.tsx +123 -0
  353. package/src/react/schemaRenderer/table/TableRendererBody.tsx +974 -0
  354. package/src/react/schemaRenderer/table/filters.tsx +1233 -0
  355. package/src/react/schemaRenderer/table/formatCell.tsx +264 -0
  356. package/src/react/schemaRenderer/table/links.tsx +112 -0
  357. package/src/react/schemaRenderer/table/renderRowActions.tsx +52 -0
  358. package/src/react/schemaRenderer/table/url.tsx +143 -0
  359. package/src/routes/globals.ts +154 -0
  360. package/src/routes/helpers.ts +668 -0
  361. package/src/routes/pages.ts +173 -0
  362. package/src/routes/panel.ts +204 -0
  363. package/src/routes/relations.ts +1219 -0
  364. package/src/routes/resources.ts +786 -0
  365. package/src/routes/theme.ts +109 -0
  366. package/src/routes.test.ts +1 -1
  367. package/src/routes.ts +64 -3176
  368. package/src/schema/TableWidget.test.ts +2 -2
  369. package/src/theme/migrate.test.ts +178 -0
  370. package/src/vite.test.ts +184 -0
  371. package/src/vite.ts +26 -4
@@ -0,0 +1,578 @@
1
+ import type { Pilotiq } from '../Pilotiq.js'
2
+ import type { Page } from '../Page.js'
3
+ import { Element } from '../schema/Element.js'
4
+ import { resolveSchema, type SchemaContext, type RenderContext } from '../schema/resolveSchema.js'
5
+ import { Form } from '../elements/Form.js'
6
+ import { applyStateUpdate, coerceFormValues, findForms, findWizardStep, selectFormById } from '../elements/dispatchForm.js'
7
+ import { findRecord } from '../orm/modelDefaults.js'
8
+ import { isRepeaterField, RepeaterField } from '../fields/RepeaterField.js'
9
+ import { isBuilderField, BuilderField } from '../fields/BuilderField.js'
10
+ import { SelectField } from '../fields/SelectField.js'
11
+ import { validateSchema } from '../validation/index.js'
12
+ import { callPageSchema, uploadCtx, userCtx } from './helpers.js'
13
+
14
+ // ─── Shared scope → page+form+record resolver ────────────────
15
+ //
16
+ // Every form-subresource builder (`formStateData / formWizardData /
17
+ // formCreateOptionData / mentionResolveData`) needs to: resolve the
18
+ // `FormStateScope` to a concrete `PageClass`, build the upload/user-aware
19
+ // `SchemaContext`, call the page's `schema()`, and pick the requested form
20
+ // by id. `resolveScopeForm` does that whole prelude in one async step.
21
+
22
+ interface ResolveScopeFormSuccess {
23
+ ok: true
24
+ PageClass: typeof Page
25
+ baseCtx: SchemaContext
26
+ elements: Element[]
27
+ form: Form
28
+ record: unknown
29
+ mode: 'create' | 'edit'
30
+ }
31
+
32
+ interface ResolveScopeFormFormMissing {
33
+ ok: false
34
+ status: 404
35
+ error: string
36
+ }
37
+
38
+ /**
39
+ * Resolve `FormStateScope` → `{ PageClass, baseCtx, elements, form, record, mode }`.
40
+ *
41
+ * Returns `null` when the scope's slug doesn't resolve to a real
42
+ * resource/global/page or the matching page-role (`create`/`edit`) isn't
43
+ * registered — caller returns 404 to the client. Returns
44
+ * `{ ok: false, status: 404, error }` when the page resolves but the
45
+ * requested `formId` isn't on it.
46
+ */
47
+ async function resolveScopeForm(
48
+ pilotiq: Pilotiq,
49
+ scope: FormStateScope,
50
+ formId: string,
51
+ user: unknown,
52
+ ): Promise<ResolveScopeFormSuccess | ResolveScopeFormFormMissing | null> {
53
+ const cfg = pilotiq.getConfig()
54
+
55
+ let PageClass: typeof Page | undefined
56
+ let mode: 'create' | 'edit'
57
+ let record: unknown = undefined
58
+ let baseCtxExtras: Record<string, unknown> = {}
59
+
60
+ if (scope.kind === 'resource-create' || scope.kind === 'resource-edit') {
61
+ const R = cfg.resources.find(r => r.getSlug() === scope.slug)
62
+ if (!R) return null
63
+ const pages = R.resolvePages()
64
+ if (scope.kind === 'resource-create') {
65
+ if (!pages.create) return null
66
+ PageClass = pages.create
67
+ mode = 'create'
68
+ } else {
69
+ if (!pages.edit) return null
70
+ PageClass = pages.edit
71
+ mode = 'edit'
72
+ baseCtxExtras = { recordId: scope.recordId }
73
+ if (R.model) {
74
+ try { record = await findRecord(R, scope.recordId, { user }) } catch { /* ignore */ }
75
+ } else {
76
+ record = { id: scope.recordId }
77
+ }
78
+ }
79
+ } else if (scope.kind === 'global-edit') {
80
+ const G = cfg.globals.find(g => g.getSlug() === scope.slug)
81
+ if (!G) return null
82
+ const pages = G.resolvePages()
83
+ if (!pages.edit) return null
84
+ PageClass = pages.edit
85
+ mode = 'edit'
86
+ } else {
87
+ const P = cfg.pages.find(p => p.getSlug() === scope.pageSlug)
88
+ if (!P) return null
89
+ PageClass = P
90
+ // Custom pages don't have a record/edit-mode concept — pass mode
91
+ // 'edit' so resolveSchema treats fields as form inputs (not table
92
+ // cells / view-mode read-only).
93
+ mode = 'edit'
94
+ }
95
+
96
+ const baseCtx: SchemaContext = uploadCtx(userCtx({ mode, basePath: cfg.path, ...baseCtxExtras }, user), cfg)
97
+ const elements = await callPageSchema(PageClass, baseCtx)
98
+ const form = selectFormById(findForms(elements), formId)
99
+ if (!form) return { ok: false, status: 404, error: `Form "${formId}" not found on page` }
100
+
101
+ return { ok: true, PageClass, baseCtx, elements, form, record, mode }
102
+ }
103
+
104
+ // ─── Form-related data builders ─────────────────────────────
105
+ //
106
+ // Plan #5 partial-resolve (formStateData), Plan #8 wizard step-validate
107
+ // (formWizardData), inline-create-option (formCreateOptionData), and
108
+ // async-mention resolve (mentionResolveData). All four sit downstream
109
+ // of `resolveSchema` and re-run the form lifecycle in narrower modes
110
+ // (just the changed field; just the requested step; etc.).
111
+
112
+
113
+ // ─── Plan #5 partial-resolve data builder ────────────────────
114
+
115
+ export type FormStateScope =
116
+ | { kind: 'resource-create'; slug: string }
117
+ | { kind: 'resource-edit'; slug: string; recordId: string }
118
+ | { kind: 'global-edit'; slug: string }
119
+ | { kind: 'page'; pageSlug: string }
120
+
121
+ export interface FormStateRequest {
122
+ formId: string
123
+ changed: string
124
+ values: Record<string, unknown>
125
+ }
126
+
127
+ export interface FormStateResult {
128
+ ok: true
129
+ form: Record<string, unknown> // resolved FormMeta
130
+ dirty: string[]
131
+ }
132
+
133
+ export interface FormStateError {
134
+ ok: false
135
+ status: 404 | 422
136
+ error: string
137
+ }
138
+
139
+ /**
140
+ * Plan #5 — handle a partial-resolve roundtrip from a `live()` field.
141
+ *
142
+ * Locates the page's schema, finds the targeted form by `formId`, runs
143
+ * `applyStateUpdate` to apply the changed value + run
144
+ * `afterStateUpdated`, then re-resolves the form's children with the
145
+ * mutated values + bound `$get / $set` so dependent options /
146
+ * conditional visibility re-evaluate. Returns the resolved FormMeta the
147
+ * client uses to replace its rendered form.
148
+ *
149
+ * Returns `null` when the route prefix doesn't resolve to a real
150
+ * resource/global/page — the route handler turns this into a 404. The
151
+ * inner `{ status: 422 }` failure is for "form found but `changed`
152
+ * field doesn't exist on it" — also a client-side bug.
153
+ */
154
+ export async function formStateData(
155
+ pilotiq: Pilotiq,
156
+ scope: FormStateScope,
157
+ body: FormStateRequest,
158
+ req?: unknown,
159
+ ): Promise<FormStateResult | FormStateError | null> {
160
+ const user = await pilotiq.resolveUser(req)
161
+ const loaded = await resolveScopeForm(pilotiq, scope, body.formId, user)
162
+ if (!loaded) return null
163
+ if (!loaded.ok) return loaded
164
+ const { baseCtx, form, record } = loaded
165
+
166
+ const update = await applyStateUpdate(form, body.values, body.changed, {
167
+ ...(record !== undefined ? { record } : {}),
168
+ ...(user !== null ? { user } : {}),
169
+ request: req,
170
+ })
171
+ if (!update) {
172
+ return { ok: false, status: 422, error: `Field "${body.changed}" not found on form "${body.formId}"` }
173
+ }
174
+
175
+ // Re-resolve the form with the mutated values bound. We bind
176
+ // `$get / $set` against the post-update values map so further
177
+ // resolve-time logic (SelectField.options(fn), reactive
178
+ // visibility) reads current state.
179
+ const $get = (name: string): unknown => update.values[name]
180
+ // $set on the resolve pass is a no-op — only afterStateUpdated
181
+ // mutations survive into the response. Resolve-time `$set` would
182
+ // race against the client's view of the world.
183
+ const $set = (_name: string, _v: unknown): void => { /* intentional no-op */ }
184
+
185
+ const resolveCtx = {
186
+ ...baseCtx,
187
+ values: update.values,
188
+ $get,
189
+ $set,
190
+ changed: body.changed,
191
+ ...(record !== undefined ? { record } : {}),
192
+ }
193
+ // Snapshot values onto the form so its FormMeta carries them.
194
+ form.withValues(update.values)
195
+ const resolved = await resolveSchema([form], resolveCtx)
196
+ const formMeta = resolved[0]
197
+ if (!formMeta || formMeta.type !== 'form') {
198
+ return { ok: false, status: 422, error: 'Form re-resolved to non-form meta' }
199
+ }
200
+
201
+ return { ok: true, form: formMeta, dirty: update.dirty }
202
+ }
203
+
204
+ // ─── Plan #8 wizard step-validate data builder ────────────────
205
+
206
+ export interface FormWizardRequest {
207
+ formId: string
208
+ step: number
209
+ values: Record<string, unknown>
210
+ }
211
+
212
+ export interface FormWizardSuccess {
213
+ ok: true
214
+ }
215
+
216
+ export interface FormWizardFailure {
217
+ ok: false
218
+ status: 404 | 422
219
+ error?: string
220
+ errors?: Record<string, string[]>
221
+ }
222
+
223
+ /**
224
+ * Plan #8 — handle a Wizard step-validate POST. Locates the form by id,
225
+ * walks to the Wizard descendant, validates only the fields inside step
226
+ * `step` against `values`. Returns `{ ok: true }` on success or
227
+ * `{ ok: false, status: 422, errors }` when fields fail validation.
228
+ *
229
+ * Errors are keyed by field name, same shape as the form-submit 422 path,
230
+ * so the client (`FormStateApi.applyErrors`) can surface them in-place.
231
+ */
232
+ export async function formWizardData(
233
+ pilotiq: Pilotiq,
234
+ scope: FormStateScope,
235
+ body: FormWizardRequest,
236
+ req?: unknown,
237
+ ): Promise<FormWizardSuccess | FormWizardFailure | null> {
238
+ const user = await pilotiq.resolveUser(req)
239
+ const loaded = await resolveScopeForm(pilotiq, scope, body.formId, user)
240
+ if (!loaded) return null
241
+ if (!loaded.ok) return loaded
242
+ const { form, record } = loaded
243
+
244
+ const formChildren = form.getChildren() ?? []
245
+ const step = findWizardStep(formChildren, body.step)
246
+ if (!step) return { ok: false, status: 404, error: `Step ${body.step} not found on form "${body.formId}"` }
247
+
248
+ // Step.beforeValidation — runs before validators. May mutate `body.values`
249
+ // in place (the validator reads from the same object), or throw to halt
250
+ // with a 422 stamped under the reserved `_step` key.
251
+ type StepHook = (values: Record<string, unknown>, ctx: { record?: unknown; user?: unknown }) => void | Promise<void>
252
+ const stepHooks = step as {
253
+ getBeforeValidation?: () => StepHook | undefined
254
+ getAfterValidation?: () => StepHook | undefined
255
+ }
256
+ const beforeHook = stepHooks.getBeforeValidation?.call(step)
257
+ if (beforeHook) {
258
+ try { await beforeHook(body.values, { record, user }) }
259
+ catch (err) {
260
+ return { ok: false, status: 422, errors: { _step: [stepHookErrorMessage(err)] } }
261
+ }
262
+ }
263
+
264
+ const errors = await validateSchema(step.getChildren() ?? [], body.values, record)
265
+ if (Object.keys(errors).length > 0) {
266
+ return { ok: false, status: 422, errors }
267
+ }
268
+
269
+ // Step.afterValidation — fires only when validators pass. Same throw →
270
+ // 422 contract as beforeValidation.
271
+ const afterHook = stepHooks.getAfterValidation?.call(step)
272
+ if (afterHook) {
273
+ try { await afterHook(body.values, { record, user }) }
274
+ catch (err) {
275
+ return { ok: false, status: 422, errors: { _step: [stepHookErrorMessage(err)] } }
276
+ }
277
+ }
278
+
279
+ return { ok: true }
280
+ }
281
+
282
+ function stepHookErrorMessage(err: unknown): string {
283
+ if (err instanceof Error && err.message) return err.message
284
+ if (typeof err === 'string' && err.length > 0) return err
285
+ return 'Step validation failed'
286
+ }
287
+
288
+ // ─── SelectField inline-create-option data builder ───────────
289
+
290
+ export interface FormCreateOptionRequest {
291
+ formId: string
292
+ fieldName: string
293
+ values: Record<string, unknown>
294
+ }
295
+
296
+ export interface FormCreateOptionSuccess {
297
+ ok: true
298
+ option: { value: string; label: string }
299
+ }
300
+
301
+ export interface FormCreateOptionFailure {
302
+ ok: false
303
+ status: 403 | 404 | 422 | 500
304
+ error?: string
305
+ errors?: Record<string, string[]>
306
+ }
307
+
308
+ /** Find a `SelectField` by name inside a form's children, walking through
309
+ * layout containers but stopping at Repeater / Builder boundaries
310
+ * (parallel to `tagSelectCreateOptionUrls`'s walker). Returns the first
311
+ * match or `undefined`. */
312
+ function findSelectFieldByName(elements: Element[], name: string): SelectField | undefined {
313
+ for (const el of elements) {
314
+ if (el instanceof SelectField) {
315
+ if (el.name === name) return el
316
+ continue
317
+ }
318
+ if (el instanceof RepeaterField) continue
319
+ if (el instanceof BuilderField) continue
320
+ const children = el.getChildren()
321
+ if (children && children.length > 0) {
322
+ const found = findSelectFieldByName(children as Element[], name)
323
+ if (found) return found
324
+ }
325
+ }
326
+ return undefined
327
+ }
328
+
329
+ /**
330
+ * Audit row 2026-05-07 cont'd⁸ — handle a `SelectField.createOptionForm()`
331
+ * modal submit. Locates the parent form by `formId`, finds the SelectField
332
+ * by `fieldName`, re-evaluates the `createOptionAuthorize` rule (so a
333
+ * tampered URL can't bypass), coerces + validates the body against the
334
+ * sub-form's fields, then calls `createOptionUsing(handler)` and returns
335
+ * `{ option }` for the client to append + select.
336
+ *
337
+ * Returns `null` when the route prefix doesn't resolve to a real
338
+ * resource/global/page (route handler turns into 404).
339
+ */
340
+ export async function formCreateOptionData(
341
+ pilotiq: Pilotiq,
342
+ scope: FormStateScope,
343
+ body: FormCreateOptionRequest,
344
+ req?: unknown,
345
+ ): Promise<FormCreateOptionSuccess | FormCreateOptionFailure | null> {
346
+ const user = await pilotiq.resolveUser(req)
347
+ const loaded = await resolveScopeForm(pilotiq, scope, body.formId, user)
348
+ if (!loaded) return null
349
+ if (!loaded.ok) return loaded
350
+ const { baseCtx, form, record } = loaded
351
+
352
+ const field = findSelectFieldByName(form.getChildren() as Element[] ?? [], body.fieldName)
353
+ if (!field) return { ok: false, status: 404, error: `SelectField "${body.fieldName}" not found on form "${body.formId}"` }
354
+ if (!field.hasCreateOption()) return { ok: false, status: 404, error: `SelectField "${body.fieldName}" does not configure createOptionForm()` }
355
+
356
+ const createForm = field.getCreateOptionForm()!
357
+ const handler = field.getCreateOptionHandler()
358
+ if (!handler) {
359
+ return { ok: false, status: 500, error: `SelectField "${body.fieldName}" has createOptionForm() but no createOptionUsing() handler` }
360
+ }
361
+
362
+ // Re-evaluate authorize. Build the same ActionVisibilityContext shape
363
+ // the field's `toMeta` did — keeps server / meta-build paths consistent.
364
+ const authorize = field.getCreateOptionAuthorize()
365
+ if (authorize !== undefined) {
366
+ const authVisible = await (async () => {
367
+ if (typeof authorize !== 'function') return authorize
368
+ const visCtx: import('../actions/Action.js').ActionVisibilityContext = {}
369
+ if (record !== undefined) visCtx.record = record
370
+ if (user !== null ) visCtx.user = user
371
+ try { return await authorize(visCtx) } catch { return false }
372
+ })()
373
+ if (!authVisible) return { ok: false, status: 403, error: 'createOptionAuthorize denied' }
374
+ }
375
+
376
+ // Coerce + validate body against the sub-form's fields. The createOption
377
+ // sub-schema is detached from the parent form so we run it against its
378
+ // own children only — coerceFormValues mutates `out` to normalize toggle
379
+ // / number / date / etc. shapes (same shape parent forms use).
380
+ const coerced = coerceFormValues(createForm, { ...body.values })
381
+ const errors = await validateSchema(createForm, coerced, undefined)
382
+ if (Object.keys(errors).length > 0) {
383
+ return { ok: false, status: 422, errors }
384
+ }
385
+
386
+ const ctx: RenderContext = {
387
+ ...baseCtx,
388
+ values: coerced,
389
+ ...(record !== undefined ? { record } : {}),
390
+ }
391
+ let option: { value: string; label: string }
392
+ try {
393
+ option = await handler(coerced, ctx)
394
+ } catch (e) {
395
+ return { ok: false, status: 500, error: e instanceof Error ? e.message : String(e) }
396
+ }
397
+
398
+ if (!option || typeof option.value !== 'string' || typeof option.label !== 'string') {
399
+ return { ok: false, status: 500, error: `createOptionUsing must return { value: string, label: string }` }
400
+ }
401
+
402
+ return { ok: true, option }
403
+ }
404
+
405
+ // ─── Async-mention resolve data builder ──────────────────────
406
+
407
+ export interface MentionResolveRequest {
408
+ formId: string
409
+ field: string
410
+ trigger: string
411
+ query: string
412
+ }
413
+
414
+ /** Wire-side shape for a single resolved item — mirrors `MentionItem` from
415
+ * `@pilotiq/tiptap`. Pilotiq core doesn't import that package, so the
416
+ * duck-typed shape lives here. */
417
+ export interface MentionResolveItem {
418
+ id: string
419
+ label: string
420
+ group?: string
421
+ }
422
+
423
+ export interface MentionResolveSuccess {
424
+ ok: true
425
+ items: MentionResolveItem[]
426
+ }
427
+
428
+ export interface MentionResolveError {
429
+ ok: false
430
+ status: 404 | 422
431
+ error: string
432
+ }
433
+
434
+ interface AsyncMentionResolverField {
435
+ resolveMention(
436
+ trigger: string,
437
+ query: string,
438
+ ctx: { user?: unknown; record?: unknown; request?: unknown },
439
+ ): Promise<MentionResolveItem[] | null>
440
+ }
441
+
442
+ function isMentionResolverField(el: Element): el is Element & AsyncMentionResolverField {
443
+ if (el.getType() !== 'richtext') return false
444
+ const candidate = el as unknown as Partial<AsyncMentionResolverField>
445
+ return typeof candidate.resolveMention === 'function'
446
+ }
447
+
448
+ /**
449
+ * Walk a form's tree looking for the named field. Descends into Repeater /
450
+ * Builder rows when the requested name carries the row-prefix shape:
451
+ *
452
+ * - Repeater rows: `<repeaterName>.<index>.<innerPath>` — looks up
453
+ * `<innerPath>` against the Repeater's template schema. Field config
454
+ * (providers, async resolver) is shared across rows, so any row index
455
+ * resolves to the same template field.
456
+ * - Builder rows: `<builderName>.<index>.data.<innerPath>` — looks up
457
+ * `<innerPath>` against every block's schema; first match wins. Block
458
+ * schemas often share leaf names — if two blocks define a RichTextField
459
+ * with the same name and different async-mention providers, only the
460
+ * first block in declaration order is reachable here. Authors needing
461
+ * per-block resolution should give the leaves distinct names.
462
+ *
463
+ * Mirrors the boundary-stopping posture of `findFieldByName` inside
464
+ * `dispatchForm.ts` for top-level matches — only the dotted-prefix branch
465
+ * crosses into row schemas.
466
+ */
467
+ function findRichTextFieldByName(
468
+ elements: ReadonlyArray<Element>,
469
+ name: string,
470
+ ): (Element & AsyncMentionResolverField) | undefined {
471
+ for (const el of elements) {
472
+ if (isMentionResolverField(el) && (el as unknown as { name: string }).name === name) {
473
+ return el
474
+ }
475
+ if (isRepeaterField(el)) {
476
+ const inner = stripRepeaterRowPrefix(name, (el as RepeaterField).name)
477
+ if (inner !== undefined) {
478
+ const hit = findRichTextFieldByName((el as RepeaterField).getInnerSchema(), inner)
479
+ if (hit) return hit
480
+ }
481
+ continue
482
+ }
483
+ if (isBuilderField(el)) {
484
+ const inner = stripBuilderRowPrefix(name, (el as BuilderField).name)
485
+ if (inner !== undefined) {
486
+ for (const block of (el as BuilderField).getBlocks()) {
487
+ const hit = findRichTextFieldByName(block.getSchema(), inner)
488
+ if (hit) return hit
489
+ }
490
+ }
491
+ continue
492
+ }
493
+ const children = el.getChildren()
494
+ if (children && children.length > 0) {
495
+ const hit = findRichTextFieldByName(children, name)
496
+ if (hit) return hit
497
+ }
498
+ }
499
+ return undefined
500
+ }
501
+
502
+ /**
503
+ * `items.0.body` → `body`. Returns `undefined` when the path doesn't match
504
+ * the `<repeaterName>.<digits>.<rest>` shape so the walker keeps searching
505
+ * other branches instead of misinterpreting an unrelated dotted name.
506
+ */
507
+ function stripRepeaterRowPrefix(path: string, repeaterName: string): string | undefined {
508
+ const parts = path.split('.')
509
+ if (parts.length < 3) return undefined
510
+ if (parts[0] !== repeaterName) return undefined
511
+ if (!/^\d+$/.test(parts[1] ?? '')) return undefined
512
+ return parts.slice(2).join('.')
513
+ }
514
+
515
+ /**
516
+ * `blocks.0.data.heading` → `heading`. The literal `data` segment matches
517
+ * Builder's wire shape (`{ __id, type, data: {…} }`) and distinguishes a
518
+ * Builder leaf from a Repeater leaf at the same depth.
519
+ */
520
+ function stripBuilderRowPrefix(path: string, builderName: string): string | undefined {
521
+ const parts = path.split('.')
522
+ if (parts.length < 4) return undefined
523
+ if (parts[0] !== builderName) return undefined
524
+ if (!/^\d+$/.test(parts[1] ?? '')) return undefined
525
+ if (parts[2] !== 'data') return undefined
526
+ return parts.slice(3).join('.')
527
+ }
528
+
529
+ /**
530
+ * Resolve one async-mention round-trip. Locates the page's schema, finds
531
+ * the form by `formId` and the RichTextField by `field`, calls its
532
+ * `resolveMention(trigger, query, ctx)`. Returns `{ ok, items }`, a 404
533
+ * when the form / field / trigger isn't present, or `null` for a missing
534
+ * page (the route handler turns `null` into a 404 too).
535
+ *
536
+ * The dispatcher is duck-typed against the contract in `@pilotiq/tiptap`'s
537
+ * `RichTextField` — pilotiq core never imports the adapter. Any future
538
+ * field-type that ships an async-resolve trigger can implement the same
539
+ * shape and pick up routing for free.
540
+ */
541
+ export async function mentionResolveData(
542
+ pilotiq: Pilotiq,
543
+ scope: FormStateScope,
544
+ body: MentionResolveRequest,
545
+ req?: unknown,
546
+ ): Promise<MentionResolveSuccess | MentionResolveError | null> {
547
+ const user = await pilotiq.resolveUser(req)
548
+ const loaded = await resolveScopeForm(pilotiq, scope, body.formId, user)
549
+ if (!loaded) return null
550
+ if (!loaded.ok) return loaded
551
+ const { form, record } = loaded
552
+
553
+ const field = findRichTextFieldByName(form.getChildren() ?? [], body.field)
554
+ if (!field) {
555
+ return { ok: false, status: 404, error: `Rich-text field "${body.field}" not found on form "${body.formId}"` }
556
+ }
557
+
558
+ let items: MentionResolveItem[] | null
559
+ try {
560
+ items = await field.resolveMention(body.trigger, body.query, {
561
+ ...(record !== undefined ? { record } : {}),
562
+ ...(user !== null ? { user } : {}),
563
+ request: req,
564
+ })
565
+ } catch (err) {
566
+ return {
567
+ ok: false,
568
+ status: 422,
569
+ error: err instanceof Error ? err.message : 'Mention resolver threw',
570
+ }
571
+ }
572
+
573
+ if (items === null) {
574
+ return { ok: false, status: 404, error: `No mention provider for trigger "${body.trigger}" on field "${body.field}"` }
575
+ }
576
+
577
+ return { ok: true, items }
578
+ }