@agent-native/core 0.101.14 → 0.102.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 (316) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +12 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/cli/templates-meta.ts +13 -0
  5. package/corpus/core/src/client/agent-page/AgentTabsPage.tsx +1 -1
  6. package/corpus/core/src/client/org/OrgSwitcher.tsx +2 -0
  7. package/corpus/templates/analytics/changelog/2026-07-15-fixed-the-agent-discarding-a-successful-dashboard-edit-and-r.md +6 -0
  8. package/corpus/templates/analytics/server/lib/real-data-actions.ts +30 -1
  9. package/corpus/templates/analytics/server/plugins/agent-chat.ts +15 -0
  10. package/corpus/templates/content/actions/_database-source-utils.ts +59 -4
  11. package/corpus/templates/content/actions/prepare-builder-source-review.ts +9 -0
  12. package/corpus/templates/content/actions/preview-builder-source-review.ts +6 -4
  13. package/corpus/templates/content/app/components/editor/DescriptionField.tsx +10 -2
  14. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +4 -0
  15. package/corpus/templates/content/changelog/2026-07-15-page-and-database-descriptions-now-use-a-clean-single-line-f.md +6 -0
  16. package/corpus/templates/content/changelog/2026-07-15-selected-builder-reviews-now-load-only-the-chosen-rows-avoid.md +6 -0
  17. package/corpus/templates/content/shared/api.ts +1 -0
  18. package/corpus/templates/tasks/.agents/commands/visual-plan.md +9 -0
  19. package/corpus/templates/tasks/.agents/commands/visual-recap.md +8 -0
  20. package/corpus/templates/tasks/.agents/skills/actions/SKILL.md +492 -0
  21. package/corpus/templates/tasks/.agents/skills/adding-a-feature/SKILL.md +189 -0
  22. package/corpus/templates/tasks/.agents/skills/agent-native-docs/SKILL.md +76 -0
  23. package/corpus/templates/tasks/.agents/skills/agent-watchdog/README.md +43 -0
  24. package/corpus/templates/tasks/.agents/skills/agent-watchdog/SKILL.md +110 -0
  25. package/corpus/templates/tasks/.agents/skills/agent-watchdog/agents/openai.yaml +4 -0
  26. package/corpus/templates/tasks/.agents/skills/capture-learnings/SKILL.md +89 -0
  27. package/corpus/templates/tasks/.agents/skills/change-summary/SKILL.md +83 -0
  28. package/corpus/templates/tasks/.agents/skills/create-skill/SKILL.md +221 -0
  29. package/corpus/templates/tasks/.agents/skills/delegate-to-agent/SKILL.md +263 -0
  30. package/corpus/templates/tasks/.agents/skills/efficient-fable/README.md +75 -0
  31. package/corpus/templates/tasks/.agents/skills/efficient-fable/SKILL.md +95 -0
  32. package/corpus/templates/tasks/.agents/skills/efficient-frontier/README.md +53 -0
  33. package/corpus/templates/tasks/.agents/skills/efficient-frontier/SKILL.md +73 -0
  34. package/corpus/templates/tasks/.agents/skills/frontend-design/SKILL.md +112 -0
  35. package/corpus/templates/tasks/.agents/skills/plan-arbiter/README.md +41 -0
  36. package/corpus/templates/tasks/.agents/skills/plan-arbiter/SKILL.md +114 -0
  37. package/corpus/templates/tasks/.agents/skills/plan-arbiter/agents/openai.yaml +4 -0
  38. package/corpus/templates/tasks/.agents/skills/plow-ahead/README.md +42 -0
  39. package/corpus/templates/tasks/.agents/skills/plow-ahead/SKILL.md +104 -0
  40. package/corpus/templates/tasks/.agents/skills/plow-ahead/agents/openai.yaml +4 -0
  41. package/corpus/templates/tasks/.agents/skills/quick-recap/README.md +55 -0
  42. package/corpus/templates/tasks/.agents/skills/quick-recap/SKILL.md +54 -0
  43. package/corpus/templates/tasks/.agents/skills/read-the-damn-docs/README.md +54 -0
  44. package/corpus/templates/tasks/.agents/skills/read-the-damn-docs/SKILL.md +126 -0
  45. package/corpus/templates/tasks/.agents/skills/read-the-damn-docs/agents/openai.yaml +4 -0
  46. package/corpus/templates/tasks/.agents/skills/real-time-sync/SKILL.md +232 -0
  47. package/corpus/templates/tasks/.agents/skills/security/SKILL.md +280 -0
  48. package/corpus/templates/tasks/.agents/skills/self-modifying-code/SKILL.md +106 -0
  49. package/corpus/templates/tasks/.agents/skills/shadcn-ui/SKILL.md +95 -0
  50. package/corpus/templates/tasks/.agents/skills/stay-within-limits/README.md +61 -0
  51. package/corpus/templates/tasks/.agents/skills/stay-within-limits/SKILL.md +83 -0
  52. package/corpus/templates/tasks/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  53. package/corpus/templates/tasks/.agents/skills/visual-plan/SKILL.md +547 -0
  54. package/corpus/templates/tasks/.agents/skills/visual-plan/agent-native-skill.json +12 -0
  55. package/corpus/templates/tasks/.agents/skills/visual-plan/references/canvas.md +121 -0
  56. package/corpus/templates/tasks/.agents/skills/visual-plan/references/document-quality.md +178 -0
  57. package/corpus/templates/tasks/.agents/skills/visual-plan/references/exemplar.md +62 -0
  58. package/corpus/templates/tasks/.agents/skills/visual-plan/references/wireframe.md +264 -0
  59. package/corpus/templates/tasks/.agents/skills/visual-recap/SKILL.md +634 -0
  60. package/corpus/templates/tasks/.agents/skills/visual-recap/agent-native-skill.json +12 -0
  61. package/corpus/templates/tasks/.agents/skills/visual-recap/references/wireframe.md +264 -0
  62. package/corpus/templates/tasks/.claude/commands/visual-plan.md +9 -0
  63. package/corpus/templates/tasks/.claude/commands/visual-recap.md +8 -0
  64. package/corpus/templates/tasks/.env.example +5 -0
  65. package/corpus/templates/tasks/.ignore +0 -0
  66. package/corpus/templates/tasks/.prettierrc +5 -0
  67. package/corpus/templates/tasks/AGENTS.md +158 -0
  68. package/corpus/templates/tasks/DEVELOPING.md +185 -0
  69. package/corpus/templates/tasks/README.md +16 -0
  70. package/corpus/templates/tasks/actions/bulk-delete-inbox-items.ts +24 -0
  71. package/corpus/templates/tasks/actions/bulk-delete-tasks.ts +21 -0
  72. package/corpus/templates/tasks/actions/bulk-mark-inbox-items-ready.ts +27 -0
  73. package/corpus/templates/tasks/actions/bulk-update-tasks.ts +35 -0
  74. package/corpus/templates/tasks/actions/create-custom-field.ts +22 -0
  75. package/corpus/templates/tasks/actions/create-inbox-item.ts +30 -0
  76. package/corpus/templates/tasks/actions/create-task.ts +33 -0
  77. package/corpus/templates/tasks/actions/delete-custom-field.ts +19 -0
  78. package/corpus/templates/tasks/actions/delete-inbox-item.ts +17 -0
  79. package/corpus/templates/tasks/actions/delete-task.ts +17 -0
  80. package/corpus/templates/tasks/actions/lib/boolean-query-param.ts +30 -0
  81. package/corpus/templates/tasks/actions/list-custom-fields.ts +18 -0
  82. package/corpus/templates/tasks/actions/list-inbox-items.ts +16 -0
  83. package/corpus/templates/tasks/actions/list-tasks.ts +52 -0
  84. package/corpus/templates/tasks/actions/list-visible-task-fields.ts +18 -0
  85. package/corpus/templates/tasks/actions/mark-inbox-item-ready.ts +31 -0
  86. package/corpus/templates/tasks/actions/navigate.ts +51 -0
  87. package/corpus/templates/tasks/actions/reorder-custom-fields.ts +27 -0
  88. package/corpus/templates/tasks/actions/reorder-inbox-items.ts +24 -0
  89. package/corpus/templates/tasks/actions/reorder-tasks.ts +29 -0
  90. package/corpus/templates/tasks/actions/run.ts +2 -0
  91. package/corpus/templates/tasks/actions/update-custom-field.ts +33 -0
  92. package/corpus/templates/tasks/actions/update-inbox-item.ts +24 -0
  93. package/corpus/templates/tasks/actions/update-task.ts +68 -0
  94. package/corpus/templates/tasks/actions/update-visible-task-fields.ts +27 -0
  95. package/corpus/templates/tasks/actions/view-screen-helpers.ts +68 -0
  96. package/corpus/templates/tasks/actions/view-screen.ts +207 -0
  97. package/corpus/templates/tasks/app/components/custom-fields/FieldCreateBar.tsx +121 -0
  98. package/corpus/templates/tasks/app/components/custom-fields/FieldListRow.tsx +116 -0
  99. package/corpus/templates/tasks/app/components/custom-fields/FieldsList.tsx +225 -0
  100. package/corpus/templates/tasks/app/components/custom-fields/FieldsListSkeleton.tsx +18 -0
  101. package/corpus/templates/tasks/app/components/custom-fields/FieldsPage.tsx +42 -0
  102. package/corpus/templates/tasks/app/components/custom-fields/editor/FieldEditorSidebar.tsx +69 -0
  103. package/corpus/templates/tasks/app/components/custom-fields/editor/FieldTitleSection.tsx +32 -0
  104. package/corpus/templates/tasks/app/components/custom-fields/editor/config/CurrencyConfigControl.tsx +49 -0
  105. package/corpus/templates/tasks/app/components/custom-fields/editor/config/FieldConfigControl.tsx +56 -0
  106. package/corpus/templates/tasks/app/components/custom-fields/editor/config/NumberConfigControl.tsx +52 -0
  107. package/corpus/templates/tasks/app/components/custom-fields/editor/config/PercentConfigControl.tsx +30 -0
  108. package/corpus/templates/tasks/app/components/custom-fields/editor/config/SelectConfigControl.tsx +135 -0
  109. package/corpus/templates/tasks/app/components/custom-fields/editor/config/select-colors.ts +23 -0
  110. package/corpus/templates/tasks/app/components/custom-fields/editor/config/utils.ts +52 -0
  111. package/corpus/templates/tasks/app/components/custom-fields/editor/types.ts +7 -0
  112. package/corpus/templates/tasks/app/components/dnd/SortableItem.tsx +72 -0
  113. package/corpus/templates/tasks/app/components/dnd/SortableList.tsx +136 -0
  114. package/corpus/templates/tasks/app/components/dnd/reorder-moving-items.ts +160 -0
  115. package/corpus/templates/tasks/app/components/dnd/sortable-drag-props.ts +60 -0
  116. package/corpus/templates/tasks/app/components/inbox/InboxList.tsx +222 -0
  117. package/corpus/templates/tasks/app/components/inbox/InboxListPage.tsx +39 -0
  118. package/corpus/templates/tasks/app/components/inbox/InboxListRow.tsx +122 -0
  119. package/corpus/templates/tasks/app/components/layout/Header.tsx +55 -0
  120. package/corpus/templates/tasks/app/components/layout/HeaderActions.tsx +84 -0
  121. package/corpus/templates/tasks/app/components/layout/Layout.tsx +134 -0
  122. package/corpus/templates/tasks/app/components/layout/Sidebar.tsx +211 -0
  123. package/corpus/templates/tasks/app/components/shared/AddListItemInput.tsx +57 -0
  124. package/corpus/templates/tasks/app/components/shared/AlertDialog.tsx +53 -0
  125. package/corpus/templates/tasks/app/components/shared/BulkDeleteDialog.tsx +76 -0
  126. package/corpus/templates/tasks/app/components/shared/ChipSelect.tsx +240 -0
  127. package/corpus/templates/tasks/app/components/shared/DeleteItemDialog.tsx +40 -0
  128. package/corpus/templates/tasks/app/components/shared/InlineEditable.tsx +166 -0
  129. package/corpus/templates/tasks/app/components/shared/ListEmptyState.tsx +13 -0
  130. package/corpus/templates/tasks/app/components/shared/ListErrorMessage.tsx +15 -0
  131. package/corpus/templates/tasks/app/components/shared/ListViewHeader.tsx +75 -0
  132. package/corpus/templates/tasks/app/components/shared/PageHeader.tsx +25 -0
  133. package/corpus/templates/tasks/app/components/shared/RowActionsMenu.tsx +42 -0
  134. package/corpus/templates/tasks/app/components/shared/SidePanel.tsx +99 -0
  135. package/corpus/templates/tasks/app/components/shared/dnd/ListRowPreview.tsx +37 -0
  136. package/corpus/templates/tasks/app/components/shared/list/List.tsx +63 -0
  137. package/corpus/templates/tasks/app/components/shared/list/ListRow.tsx +159 -0
  138. package/corpus/templates/tasks/app/components/shared/list/ListRowDragHandle.tsx +52 -0
  139. package/corpus/templates/tasks/app/components/shared/list/ListSkeletonRows.tsx +11 -0
  140. package/corpus/templates/tasks/app/components/shared/list/SortableListItemShell.tsx +32 -0
  141. package/corpus/templates/tasks/app/components/shared/list/types.ts +1 -0
  142. package/corpus/templates/tasks/app/components/shared/selection/ListSelectionBar.tsx +158 -0
  143. package/corpus/templates/tasks/app/components/shared/selection/ListSelectionHeaderToggle.tsx +28 -0
  144. package/corpus/templates/tasks/app/components/shared/selection/ListSelectionToolbar.tsx +81 -0
  145. package/corpus/templates/tasks/app/components/shared/selection/get-list-row-selection-ui.ts +41 -0
  146. package/corpus/templates/tasks/app/components/shared/selection/use-list-selection.ts +196 -0
  147. package/corpus/templates/tasks/app/components/tasks/TaskList.tsx +333 -0
  148. package/corpus/templates/tasks/app/components/tasks/TaskListHeaderRow.tsx +46 -0
  149. package/corpus/templates/tasks/app/components/tasks/TaskListPage.tsx +115 -0
  150. package/corpus/templates/tasks/app/components/tasks/TaskListRow.css +49 -0
  151. package/corpus/templates/tasks/app/components/tasks/TaskListRow.tsx +307 -0
  152. package/corpus/templates/tasks/app/components/tasks/fields/TaskFieldsSidebar.tsx +102 -0
  153. package/corpus/templates/tasks/app/components/tasks/fields/TaskTitleSection.tsx +27 -0
  154. package/corpus/templates/tasks/app/components/tasks/fields/controls/DateValueControl.tsx +21 -0
  155. package/corpus/templates/tasks/app/components/tasks/fields/controls/FieldValueControl.tsx +76 -0
  156. package/corpus/templates/tasks/app/components/tasks/fields/controls/MultiSelectValueControl.tsx +52 -0
  157. package/corpus/templates/tasks/app/components/tasks/fields/controls/NumberValueControl.tsx +56 -0
  158. package/corpus/templates/tasks/app/components/tasks/fields/controls/RichTextValueControl.tsx +115 -0
  159. package/corpus/templates/tasks/app/components/tasks/fields/controls/SingleSelectValueControl.tsx +61 -0
  160. package/corpus/templates/tasks/app/components/tasks/fields/controls/TextValueControl.tsx +20 -0
  161. package/corpus/templates/tasks/app/components/tasks/use-task-row-completion-animation.ts +181 -0
  162. package/corpus/templates/tasks/app/components/ui/accordion.tsx +56 -0
  163. package/corpus/templates/tasks/app/components/ui/alert-dialog.tsx +1 -0
  164. package/corpus/templates/tasks/app/components/ui/alert.tsx +1 -0
  165. package/corpus/templates/tasks/app/components/ui/aspect-ratio.tsx +5 -0
  166. package/corpus/templates/tasks/app/components/ui/avatar.tsx +1 -0
  167. package/corpus/templates/tasks/app/components/ui/badge.tsx +1 -0
  168. package/corpus/templates/tasks/app/components/ui/breadcrumb.tsx +115 -0
  169. package/corpus/templates/tasks/app/components/ui/button.tsx +1 -0
  170. package/corpus/templates/tasks/app/components/ui/calendar.tsx +70 -0
  171. package/corpus/templates/tasks/app/components/ui/card.tsx +1 -0
  172. package/corpus/templates/tasks/app/components/ui/carousel.tsx +260 -0
  173. package/corpus/templates/tasks/app/components/ui/chart.tsx +379 -0
  174. package/corpus/templates/tasks/app/components/ui/checkbox.tsx +1 -0
  175. package/corpus/templates/tasks/app/components/ui/collapsible.tsx +1 -0
  176. package/corpus/templates/tasks/app/components/ui/command.tsx +1 -0
  177. package/corpus/templates/tasks/app/components/ui/context-menu.tsx +1 -0
  178. package/corpus/templates/tasks/app/components/ui/dialog.tsx +1 -0
  179. package/corpus/templates/tasks/app/components/ui/drawer.tsx +1 -0
  180. package/corpus/templates/tasks/app/components/ui/dropdown-menu.tsx +1 -0
  181. package/corpus/templates/tasks/app/components/ui/form.tsx +177 -0
  182. package/corpus/templates/tasks/app/components/ui/hover-card.tsx +27 -0
  183. package/corpus/templates/tasks/app/components/ui/input-otp.tsx +69 -0
  184. package/corpus/templates/tasks/app/components/ui/input.tsx +1 -0
  185. package/corpus/templates/tasks/app/components/ui/label.tsx +1 -0
  186. package/corpus/templates/tasks/app/components/ui/menubar.tsx +254 -0
  187. package/corpus/templates/tasks/app/components/ui/navigation-menu.tsx +128 -0
  188. package/corpus/templates/tasks/app/components/ui/pagination.tsx +121 -0
  189. package/corpus/templates/tasks/app/components/ui/popover.tsx +1 -0
  190. package/corpus/templates/tasks/app/components/ui/progress.tsx +1 -0
  191. package/corpus/templates/tasks/app/components/ui/radio-group.tsx +1 -0
  192. package/corpus/templates/tasks/app/components/ui/resizable.tsx +43 -0
  193. package/corpus/templates/tasks/app/components/ui/scroll-area.tsx +1 -0
  194. package/corpus/templates/tasks/app/components/ui/select.tsx +1 -0
  195. package/corpus/templates/tasks/app/components/ui/separator.tsx +1 -0
  196. package/corpus/templates/tasks/app/components/ui/sheet.tsx +1 -0
  197. package/corpus/templates/tasks/app/components/ui/sidebar.tsx +782 -0
  198. package/corpus/templates/tasks/app/components/ui/skeleton.tsx +1 -0
  199. package/corpus/templates/tasks/app/components/ui/slider.tsx +1 -0
  200. package/corpus/templates/tasks/app/components/ui/sonner.tsx +1 -0
  201. package/corpus/templates/tasks/app/components/ui/spinner.tsx +1 -0
  202. package/corpus/templates/tasks/app/components/ui/switch.tsx +1 -0
  203. package/corpus/templates/tasks/app/components/ui/table.tsx +1 -0
  204. package/corpus/templates/tasks/app/components/ui/tabs.tsx +1 -0
  205. package/corpus/templates/tasks/app/components/ui/textarea.tsx +1 -0
  206. package/corpus/templates/tasks/app/components/ui/toast.tsx +1 -0
  207. package/corpus/templates/tasks/app/components/ui/toaster.tsx +1 -0
  208. package/corpus/templates/tasks/app/components/ui/toggle-group.tsx +1 -0
  209. package/corpus/templates/tasks/app/components/ui/toggle.tsx +43 -0
  210. package/corpus/templates/tasks/app/components/ui/tooltip.tsx +1 -0
  211. package/corpus/templates/tasks/app/components/ui/use-toast.ts +3 -0
  212. package/corpus/templates/tasks/app/entry.client.tsx +19 -0
  213. package/corpus/templates/tasks/app/entry.server.tsx +10 -0
  214. package/corpus/templates/tasks/app/global.css +93 -0
  215. package/corpus/templates/tasks/app/hooks/cache.ts +31 -0
  216. package/corpus/templates/tasks/app/hooks/use-custom-fields.ts +93 -0
  217. package/corpus/templates/tasks/app/hooks/use-inbox-items.ts +196 -0
  218. package/corpus/templates/tasks/app/hooks/use-mobile.tsx +21 -0
  219. package/corpus/templates/tasks/app/hooks/use-navigation-state.ts +40 -0
  220. package/corpus/templates/tasks/app/hooks/use-tasks.ts +276 -0
  221. package/corpus/templates/tasks/app/hooks/use-toast.ts +188 -0
  222. package/corpus/templates/tasks/app/hooks/use-visible-task-fields.ts +140 -0
  223. package/corpus/templates/tasks/app/lib/app-config.ts +11 -0
  224. package/corpus/templates/tasks/app/lib/tab-id.ts +1 -0
  225. package/corpus/templates/tasks/app/lib/utils.ts +1 -0
  226. package/corpus/templates/tasks/app/root.tsx +130 -0
  227. package/corpus/templates/tasks/app/routes/_index.tsx +22 -0
  228. package/corpus/templates/tasks/app/routes/extensions.$id.$slug.tsx +2 -0
  229. package/corpus/templates/tasks/app/routes/extensions.$id.tsx +11 -0
  230. package/corpus/templates/tasks/app/routes/extensions._index.tsx +11 -0
  231. package/corpus/templates/tasks/app/routes/extensions.tsx +5 -0
  232. package/corpus/templates/tasks/app/routes/fields.tsx +16 -0
  233. package/corpus/templates/tasks/app/routes/inbox.tsx +16 -0
  234. package/corpus/templates/tasks/app/routes/tasks.tsx +17 -0
  235. package/corpus/templates/tasks/app/routes/team.tsx +17 -0
  236. package/corpus/templates/tasks/app/routes.ts +4 -0
  237. package/corpus/templates/tasks/app/vite-env.d.ts +6 -0
  238. package/corpus/templates/tasks/components.json +20 -0
  239. package/corpus/templates/tasks/data/sync-config.json +1 -0
  240. package/corpus/templates/tasks/docs/features/README.md +81 -0
  241. package/corpus/templates/tasks/docs/features/f1-tasks.mdx +67 -0
  242. package/corpus/templates/tasks/docs/features/f2-custom-fields.mdx +68 -0
  243. package/corpus/templates/tasks/docs/features/f3-projects.mdx +33 -0
  244. package/corpus/templates/tasks/docs/features/f4-inbox.mdx +55 -0
  245. package/corpus/templates/tasks/docs/features/f5-views.mdx +65 -0
  246. package/corpus/templates/tasks/docs/features/f6-search.mdx +43 -0
  247. package/corpus/templates/tasks/docs/features/f7-first-run.mdx +66 -0
  248. package/corpus/templates/tasks/docs/features/f8-chat.mdx +120 -0
  249. package/corpus/templates/tasks/e2e/global-setup.ts +42 -0
  250. package/corpus/templates/tasks/e2e/helpers/inbox.ts +71 -0
  251. package/corpus/templates/tasks/e2e/helpers/tasks.ts +99 -0
  252. package/corpus/templates/tasks/learnings.defaults.md +5 -0
  253. package/corpus/templates/tasks/netlify.toml +10 -0
  254. package/corpus/templates/tasks/package.json +104 -0
  255. package/corpus/templates/tasks/playwright.config.ts +34 -0
  256. package/corpus/templates/tasks/public/agent-native-icon-dark.svg +10 -0
  257. package/corpus/templates/tasks/public/agent-native-icon-light.svg +10 -0
  258. package/corpus/templates/tasks/public/agent-native-logo-dark.svg +21 -0
  259. package/corpus/templates/tasks/public/agent-native-logo-light.svg +21 -0
  260. package/corpus/templates/tasks/public/favicon.svg +1 -0
  261. package/corpus/templates/tasks/public/icon-180.svg +1 -0
  262. package/corpus/templates/tasks/public/icon-192.svg +1 -0
  263. package/corpus/templates/tasks/public/icon-512.svg +1 -0
  264. package/corpus/templates/tasks/public/manifest.json +21 -0
  265. package/corpus/templates/tasks/react-router.config.ts +7 -0
  266. package/corpus/templates/tasks/server/custom-fields/normalize.ts +163 -0
  267. package/corpus/templates/tasks/server/custom-fields/parse.ts +146 -0
  268. package/corpus/templates/tasks/server/custom-fields/schema.ts +147 -0
  269. package/corpus/templates/tasks/server/custom-fields/store.ts +425 -0
  270. package/corpus/templates/tasks/server/custom-fields/task-fields.ts +104 -0
  271. package/corpus/templates/tasks/server/custom-fields/types.ts +98 -0
  272. package/corpus/templates/tasks/server/custom-fields/validate.ts +210 -0
  273. package/corpus/templates/tasks/server/custom-fields/values/store.ts +386 -0
  274. package/corpus/templates/tasks/server/db/bulk-write.ts +30 -0
  275. package/corpus/templates/tasks/server/db/index.ts +5 -0
  276. package/corpus/templates/tasks/server/db/record-utils.ts +7 -0
  277. package/corpus/templates/tasks/server/db/schema.ts +67 -0
  278. package/corpus/templates/tasks/server/db/test-tasks-table.ts +76 -0
  279. package/corpus/templates/tasks/server/db/transaction.ts +6 -0
  280. package/corpus/templates/tasks/server/errors.ts +31 -0
  281. package/corpus/templates/tasks/server/inbox/store.ts +216 -0
  282. package/corpus/templates/tasks/server/middleware/auth.ts +15 -0
  283. package/corpus/templates/tasks/server/plugins/agent-chat.ts +46 -0
  284. package/corpus/templates/tasks/server/plugins/auth.ts +15 -0
  285. package/corpus/templates/tasks/server/plugins/db.ts +90 -0
  286. package/corpus/templates/tasks/server/routes/[...page].get.ts +5 -0
  287. package/corpus/templates/tasks/server/stored-items/store.ts +548 -0
  288. package/corpus/templates/tasks/server/tasks/store.ts +306 -0
  289. package/corpus/templates/tasks/server/user-config/store.ts +138 -0
  290. package/corpus/templates/tasks/shared/app-state-tabs.ts +15 -0
  291. package/corpus/templates/tasks/shared/boolean-param.ts +22 -0
  292. package/corpus/templates/tasks/shared/bulk-limits.ts +7 -0
  293. package/corpus/templates/tasks/shared/list-screen-snapshot.ts +90 -0
  294. package/corpus/templates/tasks/shared/navigation.ts +104 -0
  295. package/corpus/templates/tasks/shared/visible-task-fields.ts +2 -0
  296. package/corpus/templates/tasks/ssr-entry.ts +15 -0
  297. package/corpus/templates/tasks/tsconfig.json +24 -0
  298. package/corpus/templates/tasks/vite.config.ts +19 -0
  299. package/corpus/templates/tasks/vitest.config.ts +8 -0
  300. package/dist/cli/templates-meta.d.ts.map +1 -1
  301. package/dist/cli/templates-meta.js +13 -0
  302. package/dist/cli/templates-meta.js.map +1 -1
  303. package/dist/client/agent-page/AgentTabsPage.js +1 -1
  304. package/dist/client/agent-page/AgentTabsPage.js.map +1 -1
  305. package/dist/client/org/OrgSwitcher.d.ts.map +1 -1
  306. package/dist/client/org/OrgSwitcher.js +2 -1
  307. package/dist/client/org/OrgSwitcher.js.map +1 -1
  308. package/dist/collab/routes.d.ts +1 -1
  309. package/dist/collab/struct-routes.d.ts +1 -1
  310. package/dist/notifications/routes.d.ts +2 -2
  311. package/dist/observability/routes.d.ts +5 -5
  312. package/dist/resources/handlers.d.ts +3 -3
  313. package/dist/secrets/routes.d.ts +9 -9
  314. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  315. package/dist/server/transcribe-voice.d.ts +1 -1
  316. package/package.json +1 -1
@@ -0,0 +1,135 @@
1
+ import { IconPlus, IconTrash } from "@tabler/icons-react";
2
+
3
+ import { Button } from "@/components/ui/button";
4
+ import { Input } from "@/components/ui/input";
5
+ import { Label } from "@/components/ui/label";
6
+ import {
7
+ Select,
8
+ SelectContent,
9
+ SelectGroup,
10
+ SelectItem,
11
+ SelectTrigger,
12
+ SelectValue,
13
+ } from "@/components/ui/select";
14
+ import type { SelectColorToken } from "@/hooks/use-custom-fields";
15
+ import { cn } from "@/lib/utils";
16
+
17
+ import { SELECT_COLOR_OPTIONS } from "./select-colors";
18
+ import {
19
+ type FieldConfigControlProps,
20
+ optionsFromConfig,
21
+ withSortedOptions,
22
+ } from "./utils";
23
+
24
+ export function SelectConfigControl({
25
+ config,
26
+ onChange,
27
+ disabled = false,
28
+ }: FieldConfigControlProps) {
29
+ const options = optionsFromConfig(config);
30
+
31
+ function updateOption(
32
+ index: number,
33
+ patch: Partial<(typeof options)[number]>,
34
+ ) {
35
+ onChange({
36
+ options: withSortedOptions(
37
+ options.map((option, optionIndex) =>
38
+ optionIndex === index ? { ...option, ...patch } : option,
39
+ ),
40
+ ),
41
+ });
42
+ }
43
+
44
+ function removeOption(index: number) {
45
+ onChange({
46
+ options: withSortedOptions(
47
+ options.filter((_, optionIndex) => optionIndex !== index),
48
+ ),
49
+ });
50
+ }
51
+
52
+ function addOption() {
53
+ const nextIndex = options.length + 1;
54
+ onChange({
55
+ options: [
56
+ ...options,
57
+ {
58
+ id: `opt_${crypto.randomUUID()}`,
59
+ name: `Option ${nextIndex}`,
60
+ color: "gray",
61
+ sortOrder: options.length * 1000,
62
+ },
63
+ ],
64
+ });
65
+ }
66
+
67
+ return (
68
+ <div className="grid gap-2">
69
+ <Label>Options</Label>
70
+ <div className="grid gap-2">
71
+ {options.map((option, index) => (
72
+ <div
73
+ key={`${option.id || "new"}-${index}`}
74
+ className="grid grid-cols-[1fr_136px_32px] items-center gap-2"
75
+ >
76
+ <Input
77
+ disabled={disabled}
78
+ value={option.name}
79
+ onChange={(event) =>
80
+ updateOption(index, { name: event.currentTarget.value })
81
+ }
82
+ aria-label={`Option ${index + 1} name`}
83
+ />
84
+ <Select
85
+ disabled={disabled}
86
+ value={option.color ?? "gray"}
87
+ onValueChange={(value) =>
88
+ updateOption(index, { color: value as SelectColorToken })
89
+ }
90
+ >
91
+ <SelectTrigger aria-label={`Option ${option.name} color`}>
92
+ <SelectValue />
93
+ </SelectTrigger>
94
+ <SelectContent>
95
+ <SelectGroup>
96
+ {SELECT_COLOR_OPTIONS.map((color) => (
97
+ <SelectItem key={color.value} value={color.value}>
98
+ <span className="flex items-center gap-2">
99
+ <span
100
+ className={cn("size-2 rounded-full", color.className)}
101
+ />
102
+ {color.label}
103
+ </span>
104
+ </SelectItem>
105
+ ))}
106
+ </SelectGroup>
107
+ </SelectContent>
108
+ </Select>
109
+ <Button
110
+ type="button"
111
+ variant="ghost"
112
+ size="icon"
113
+ disabled={disabled}
114
+ onClick={() => removeOption(index)}
115
+ aria-label={`Remove ${option.name}`}
116
+ >
117
+ <IconTrash className="size-4" />
118
+ </Button>
119
+ </div>
120
+ ))}
121
+ <Button
122
+ type="button"
123
+ variant="outline"
124
+ size="sm"
125
+ disabled={disabled}
126
+ onClick={addOption}
127
+ className="justify-self-start gap-2"
128
+ >
129
+ <IconPlus className="size-4" />
130
+ Add option
131
+ </Button>
132
+ </div>
133
+ </div>
134
+ );
135
+ }
@@ -0,0 +1,23 @@
1
+ import type { SelectColorToken } from "@/hooks/use-custom-fields";
2
+
3
+ export const SELECT_COLOR_OPTIONS: Array<{
4
+ value: SelectColorToken;
5
+ label: string;
6
+ className: string;
7
+ }> = [
8
+ { value: "red", label: "Red", className: "bg-red-500" },
9
+ { value: "orange", label: "Orange", className: "bg-orange-500" },
10
+ { value: "yellow", label: "Yellow", className: "bg-yellow-500" },
11
+ { value: "green", label: "Green", className: "bg-green-500" },
12
+ { value: "blue", label: "Blue", className: "bg-blue-500" },
13
+ { value: "purple", label: "Purple", className: "bg-purple-500" },
14
+ { value: "pink", label: "Pink", className: "bg-pink-500" },
15
+ { value: "gray", label: "Gray", className: "bg-muted-foreground" },
16
+ ];
17
+
18
+ export function selectColorClass(color?: SelectColorToken) {
19
+ return (
20
+ SELECT_COLOR_OPTIONS.find((option) => option.value === color)?.className ??
21
+ "bg-muted-foreground"
22
+ );
23
+ }
@@ -0,0 +1,52 @@
1
+ import type {
2
+ FieldConfig,
3
+ FieldType,
4
+ SelectOption,
5
+ } from "@/hooks/use-custom-fields";
6
+
7
+ export type FieldConfigControlProps = {
8
+ config: FieldConfig;
9
+ onChange: (config: FieldConfig) => void;
10
+ disabled?: boolean;
11
+ };
12
+
13
+ export function precisionFromConfig(config: FieldConfig) {
14
+ return "precision" in config && typeof config.precision === "number"
15
+ ? config.precision
16
+ : 0;
17
+ }
18
+
19
+ export function positiveOnlyFromConfig(config: FieldConfig) {
20
+ return "positiveOnly" in config && config.positiveOnly === true;
21
+ }
22
+
23
+ export function symbolFromConfig(config: FieldConfig) {
24
+ return "symbol" in config && typeof config.symbol === "string"
25
+ ? config.symbol
26
+ : "$";
27
+ }
28
+
29
+ export function withSortedOptions(options: SelectOption[]) {
30
+ return options.map((option, index) => ({
31
+ ...option,
32
+ sortOrder: index * 1000,
33
+ }));
34
+ }
35
+
36
+ export function optionsFromConfig(config: FieldConfig): SelectOption[] {
37
+ return "options" in config ? config.options : [];
38
+ }
39
+
40
+ export function defaultConfigForType(type: FieldType): FieldConfig {
41
+ if (type === "currency") return { symbol: "$", precision: 2 };
42
+ if (type === "number" || type === "percent") return { precision: 0 };
43
+ if (type === "single_select" || type === "multi_select") {
44
+ return { options: [] };
45
+ }
46
+ return {};
47
+ }
48
+
49
+ export function normalizedInitialConfig(type: FieldType, config?: FieldConfig) {
50
+ if (config) return config;
51
+ return defaultConfigForType(type);
52
+ }
@@ -0,0 +1,7 @@
1
+ import type { FieldConfig, FieldType } from "@/hooks/use-custom-fields";
2
+
3
+ export type FieldDraft = {
4
+ title: string;
5
+ type: FieldType;
6
+ config: FieldConfig;
7
+ };
@@ -0,0 +1,72 @@
1
+ import type { DraggableAttributes } from "@dnd-kit/core";
2
+ import { useSortable } from "@dnd-kit/sortable";
3
+ import { CSS } from "@dnd-kit/utilities";
4
+ import type { CSSProperties, ReactNode } from "react";
5
+ import type { KeyboardEventHandler, PointerEventHandler } from "react";
6
+
7
+ import { isBlockDragActive } from "@/components/dnd/reorder-moving-items";
8
+
9
+ type DragListeners = {
10
+ onPointerDown?: PointerEventHandler<HTMLElement>;
11
+ onKeyDown?: KeyboardEventHandler<HTMLElement>;
12
+ };
13
+
14
+ export type SortableItemRenderProps = {
15
+ setNodeRef: (element: HTMLElement | null) => void;
16
+ style: CSSProperties;
17
+ isDragging: boolean;
18
+ hideForBlockDrag: boolean;
19
+ attributes: DraggableAttributes;
20
+ listeners: DragListeners | undefined;
21
+ };
22
+
23
+ /**
24
+ * Inert props for rendering a row outside a sortable context — e.g. the drag
25
+ * overlay, which mounts the real row for its preview but must not register a
26
+ * second sortable node or apply drag transforms.
27
+ */
28
+ export const INERT_SORTABLE_PROPS: SortableItemRenderProps = {
29
+ setNodeRef: () => {},
30
+ style: {},
31
+ isDragging: false,
32
+ hideForBlockDrag: false,
33
+ attributes: {} as DraggableAttributes,
34
+ listeners: undefined,
35
+ };
36
+
37
+ interface SortableItemProps {
38
+ id: string;
39
+ activeId: string | null;
40
+ movingIds: Set<string>;
41
+ children: (props: SortableItemRenderProps) => ReactNode;
42
+ }
43
+
44
+ export function SortableItem({
45
+ id,
46
+ activeId,
47
+ movingIds,
48
+ children,
49
+ }: SortableItemProps) {
50
+ const {
51
+ attributes,
52
+ listeners,
53
+ setNodeRef,
54
+ transform,
55
+ transition,
56
+ isDragging,
57
+ } = useSortable({ id });
58
+ const hideForBlockDrag =
59
+ isBlockDragActive(activeId, movingIds) && movingIds.has(id);
60
+
61
+ return children({
62
+ setNodeRef,
63
+ style: {
64
+ transform: CSS.Transform.toString(transform),
65
+ transition,
66
+ },
67
+ isDragging,
68
+ hideForBlockDrag,
69
+ attributes,
70
+ listeners,
71
+ });
72
+ }
@@ -0,0 +1,136 @@
1
+ import {
2
+ DndContext,
3
+ DragOverlay,
4
+ KeyboardSensor,
5
+ PointerSensor,
6
+ closestCenter,
7
+ useSensor,
8
+ useSensors,
9
+ type DragEndEvent,
10
+ type DragStartEvent,
11
+ } from "@dnd-kit/core";
12
+ import {
13
+ SortableContext,
14
+ sortableKeyboardCoordinates,
15
+ verticalListSortingStrategy,
16
+ } from "@dnd-kit/sortable";
17
+ import { useState, type ReactNode } from "react";
18
+ import { createPortal } from "react-dom";
19
+
20
+ import {
21
+ getDragOverlayItem,
22
+ isBlockDragActive,
23
+ reorderMovingItems,
24
+ } from "@/components/dnd/reorder-moving-items";
25
+ import {
26
+ SortableItem,
27
+ type SortableItemRenderProps,
28
+ } from "@/components/dnd/SortableItem";
29
+
30
+ export interface SortableListRenderItemProps<T extends { id: string }> {
31
+ item: T;
32
+ sortable: SortableItemRenderProps;
33
+ }
34
+
35
+ export interface SortableListRenderOverlayProps<T extends { id: string }> {
36
+ item: T;
37
+ blockDragCount?: number;
38
+ }
39
+
40
+ export interface SortableListProps<T extends { id: string }> {
41
+ items: T[];
42
+ movingIds: Set<string>;
43
+ onReorder: (nextItems: T[]) => void;
44
+ renderItem: (props: SortableListRenderItemProps<T>) => ReactNode;
45
+ renderOverlay?: (props: SortableListRenderOverlayProps<T>) => ReactNode;
46
+ listClassName?: string;
47
+ }
48
+
49
+ export function SortableList<T extends { id: string }>({
50
+ items,
51
+ movingIds,
52
+ onReorder,
53
+ renderItem,
54
+ renderOverlay,
55
+ listClassName = "space-y-2 pb-6",
56
+ }: SortableListProps<T>) {
57
+ const [activeId, setActiveId] = useState<string | null>(null);
58
+ const orderedIds = items.map((item) => item.id);
59
+ const blockDragActive = isBlockDragActive(activeId, movingIds);
60
+ const overlayItem = getDragOverlayItem(items, activeId, movingIds);
61
+ const blockDragCount = blockDragActive ? movingIds.size : undefined;
62
+
63
+ const sensors = useSensors(
64
+ useSensor(PointerSensor, { activationConstraint: { distance: 6 } }),
65
+ useSensor(KeyboardSensor, {
66
+ coordinateGetter: sortableKeyboardCoordinates,
67
+ }),
68
+ );
69
+
70
+ function handleDragStart(event: DragStartEvent) {
71
+ setActiveId(String(event.active.id));
72
+ }
73
+
74
+ function handleDragCancel() {
75
+ setActiveId(null);
76
+ }
77
+
78
+ function handleDragEnd(event: DragEndEvent) {
79
+ setActiveId(null);
80
+ const { active, over } = event;
81
+ if (!over || active.id === over.id) return;
82
+
83
+ const nextItems = reorderMovingItems(
84
+ items,
85
+ String(active.id),
86
+ String(over.id),
87
+ movingIds,
88
+ );
89
+ const unchanged =
90
+ nextItems.length === items.length &&
91
+ nextItems.every((item, index) => item.id === items[index]?.id);
92
+ if (unchanged) return;
93
+
94
+ onReorder(nextItems);
95
+ }
96
+
97
+ return (
98
+ <DndContext
99
+ sensors={sensors}
100
+ collisionDetection={closestCenter}
101
+ onDragStart={handleDragStart}
102
+ onDragEnd={handleDragEnd}
103
+ onDragCancel={handleDragCancel}
104
+ >
105
+ <SortableContext
106
+ items={orderedIds}
107
+ strategy={verticalListSortingStrategy}
108
+ >
109
+ <div className={listClassName}>
110
+ {items.map((item) => (
111
+ <SortableItem
112
+ key={item.id}
113
+ id={item.id}
114
+ activeId={activeId}
115
+ movingIds={movingIds}
116
+ >
117
+ {(sortable) => renderItem({ item, sortable })}
118
+ </SortableItem>
119
+ ))}
120
+ </div>
121
+ </SortableContext>
122
+ {renderOverlay && typeof document !== "undefined"
123
+ ? createPortal(
124
+ <DragOverlay dropAnimation={null} zIndex={1000}>
125
+ {overlayItem ? (
126
+ <div aria-hidden="true" className="w-full">
127
+ {renderOverlay({ item: overlayItem, blockDragCount })}
128
+ </div>
129
+ ) : null}
130
+ </DragOverlay>,
131
+ document.body,
132
+ )
133
+ : null}
134
+ </DndContext>
135
+ );
136
+ }
@@ -0,0 +1,160 @@
1
+ import { arrayMove } from "@dnd-kit/sortable";
2
+
3
+ /**
4
+ * Multi-item drag reorder for sortable lists.
5
+ *
6
+ * Single-item drag delegates to dnd-kit `arrayMove`.
7
+ *
8
+ * Multi-item drag (active id is in `movingIds` and size > 1):
9
+ * 1. Split the visible list into `moving` (moving ids, in list order) and
10
+ * `withoutMoving` (everything else).
11
+ * 2. Compute where to splice `moving` into `withoutMoving` from the drop target
12
+ * (`overId`) and drag direction (compare active vs over index).
13
+ * 3. Drop on another moving row → no reorder (insert at current block start).
14
+ * 4. Drag down onto a row below the block → insert after that row.
15
+ * 5. Drag up onto a row above the block → insert before that row, except when
16
+ * the target is more than one row above the block (then insert after it so
17
+ * "drop below row X" lands under X). Drop on the top row with a large gap
18
+ * before the block → insert at list start (move block to top).
19
+ *
20
+ * The branching looks worse than it is because dnd-kit reports the hovered row,
21
+ * not an explicit before/after slot; these rules mirror what users expect when
22
+ * moving a contiguous block.
23
+ */
24
+
25
+ function countItemsBeforeIndex<T extends { id: string }>(
26
+ items: T[],
27
+ endIndex: number,
28
+ movingIds: Set<string>,
29
+ ) {
30
+ return items.slice(0, endIndex).filter((item) => !movingIds.has(item.id))
31
+ .length;
32
+ }
33
+
34
+ function gapBeforeBlock<T extends { id: string }>(
35
+ items: T[],
36
+ overIndex: number,
37
+ firstMovingIndex: number,
38
+ movingIds: Set<string>,
39
+ ) {
40
+ return items
41
+ .slice(overIndex + 1, firstMovingIndex)
42
+ .filter((item) => !movingIds.has(item.id)).length;
43
+ }
44
+
45
+ function findLastMovingIndex<T extends { id: string }>(
46
+ items: T[],
47
+ movingIds: Set<string>,
48
+ ) {
49
+ for (let index = items.length - 1; index >= 0; index -= 1) {
50
+ if (movingIds.has(items[index]!.id)) return index;
51
+ }
52
+ return -1;
53
+ }
54
+
55
+ function computeBlockInsertIndex<T extends { id: string }>(
56
+ items: T[],
57
+ activeId: string,
58
+ overId: string,
59
+ movingIds: Set<string>,
60
+ withoutMoving: T[],
61
+ ) {
62
+ const activeIndex = items.findIndex((item) => item.id === activeId);
63
+ const overIndex = items.findIndex((item) => item.id === overId);
64
+ if (overIndex < 0 || activeIndex < 0) {
65
+ return null;
66
+ }
67
+
68
+ const firstMovingIndex = items.findIndex((item) => movingIds.has(item.id));
69
+ const lastMovingIndex = findLastMovingIndex(items, movingIds);
70
+ const draggingDown = activeIndex < overIndex;
71
+ const draggingUp = activeIndex > overIndex;
72
+
73
+ if (movingIds.has(overId)) {
74
+ return countItemsBeforeIndex(items, firstMovingIndex, movingIds);
75
+ }
76
+
77
+ const overInWithout = withoutMoving.findIndex((item) => item.id === overId);
78
+ if (overInWithout < 0) {
79
+ return countItemsBeforeIndex(items, overIndex, movingIds);
80
+ }
81
+
82
+ if (draggingDown && overIndex > lastMovingIndex) {
83
+ return overInWithout + 1;
84
+ }
85
+
86
+ if (draggingUp && overIndex < firstMovingIndex) {
87
+ if (overIndex === 0) {
88
+ const gap = gapBeforeBlock(items, overIndex, firstMovingIndex, movingIds);
89
+ if (gap === 1) {
90
+ return overInWithout + 1;
91
+ }
92
+ return 0;
93
+ }
94
+
95
+ if (overIndex < firstMovingIndex - 1) {
96
+ return overInWithout + 1;
97
+ }
98
+
99
+ return overInWithout;
100
+ }
101
+
102
+ return countItemsBeforeIndex(items, overIndex, movingIds);
103
+ }
104
+
105
+ /** Reorder a list, moving one item or a contiguous block identified by `movingIds`. */
106
+ export function reorderMovingItems<T extends { id: string }>(
107
+ items: T[],
108
+ activeId: string,
109
+ overId: string,
110
+ movingIds: Set<string>,
111
+ ) {
112
+ const isBlockDrag = movingIds.has(activeId) && movingIds.size > 1;
113
+ if (!isBlockDrag) {
114
+ const oldIndex = items.findIndex((item) => item.id === activeId);
115
+ const newIndex = items.findIndex((item) => item.id === overId);
116
+ if (oldIndex < 0 || newIndex < 0 || oldIndex === newIndex) {
117
+ return items;
118
+ }
119
+ return arrayMove(items, oldIndex, newIndex);
120
+ }
121
+
122
+ const moving = items.filter((item) => movingIds.has(item.id));
123
+ const withoutMoving = items.filter((item) => !movingIds.has(item.id));
124
+ const insertIndex = computeBlockInsertIndex(
125
+ items,
126
+ activeId,
127
+ overId,
128
+ movingIds,
129
+ withoutMoving,
130
+ );
131
+ if (insertIndex === null) {
132
+ return items;
133
+ }
134
+
135
+ const next = [...withoutMoving];
136
+ next.splice(insertIndex, 0, ...moving);
137
+ return next;
138
+ }
139
+
140
+ export function isBlockDragActive(
141
+ activeId: string | null,
142
+ movingIds: Set<string>,
143
+ ) {
144
+ return Boolean(activeId && movingIds.has(activeId) && movingIds.size > 1);
145
+ }
146
+
147
+ /** Overlay content item: topmost moving row during block drag, else the grabbed row. */
148
+ export function getDragOverlayItem<T extends { id: string }>(
149
+ items: T[],
150
+ activeId: string | null,
151
+ movingIds: Set<string>,
152
+ ) {
153
+ if (!activeId) return null;
154
+
155
+ if (isBlockDragActive(activeId, movingIds)) {
156
+ return items.find((item) => movingIds.has(item.id)) ?? null;
157
+ }
158
+
159
+ return items.find((item) => item.id === activeId) ?? null;
160
+ }
@@ -0,0 +1,60 @@
1
+ import type { DraggableAttributes } from "@dnd-kit/core";
2
+ import type {
3
+ HTMLAttributes,
4
+ KeyboardEventHandler,
5
+ PointerEvent,
6
+ PointerEventHandler,
7
+ } from "react";
8
+
9
+ type DragListeners = {
10
+ onPointerDown?: PointerEventHandler<HTMLElement>;
11
+ onKeyDown?: KeyboardEventHandler<HTMLElement>;
12
+ };
13
+
14
+ function sharedSortableProps(
15
+ attributes: DraggableAttributes,
16
+ listeners: DragListeners | undefined,
17
+ ): HTMLAttributes<HTMLElement> {
18
+ return {
19
+ ...attributes,
20
+ ...(listeners?.onKeyDown
21
+ ? {
22
+ onKeyDown: listeners.onKeyDown as KeyboardEventHandler<HTMLElement>,
23
+ }
24
+ : {}),
25
+ };
26
+ }
27
+
28
+ /** Map dnd-kit sortable listeners onto common drag surfaces (grip, row, title). */
29
+ export function getSortableDragProps(
30
+ attributes: DraggableAttributes,
31
+ listeners: DragListeners | undefined,
32
+ ) {
33
+ const shared = sharedSortableProps(attributes, listeners);
34
+ const pointerDown = listeners?.onPointerDown;
35
+
36
+ return {
37
+ dragHandleProps: {
38
+ ...shared,
39
+ ...(pointerDown
40
+ ? {
41
+ onPointerDown:
42
+ pointerDown as PointerEventHandler<HTMLButtonElement>,
43
+ }
44
+ : {}),
45
+ },
46
+ rowDragProps: pointerDown
47
+ ? {
48
+ onPointerDown: pointerDown as PointerEventHandler<HTMLDivElement>,
49
+ }
50
+ : undefined,
51
+ titleDragProps: pointerDown
52
+ ? {
53
+ onPointerDown: (event: PointerEvent<HTMLButtonElement>) => {
54
+ pointerDown(event);
55
+ event.stopPropagation();
56
+ },
57
+ }
58
+ : undefined,
59
+ };
60
+ }