@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,211 @@
1
+ import { appPath, FeedbackButton } from "@agent-native/core/client";
2
+ import { ExtensionsSidebarSection } from "@agent-native/core/client/extensions";
3
+ import { OrgSwitcher } from "@agent-native/core/client/org";
4
+ import {
5
+ IconCheckbox,
6
+ IconForms,
7
+ IconInbox,
8
+ IconLayoutSidebarLeftCollapse,
9
+ IconLayoutSidebarLeftExpand,
10
+ } from "@tabler/icons-react";
11
+ import { Link, useLocation } from "react-router";
12
+
13
+ import {
14
+ Tooltip,
15
+ TooltipContent,
16
+ TooltipTrigger,
17
+ } from "@/components/ui/tooltip";
18
+ import { APP_TITLE } from "@/lib/app-config";
19
+ import { cn } from "@/lib/utils";
20
+
21
+ const navItems = [
22
+ { icon: IconInbox, label: "Inbox", href: "/inbox" },
23
+ { icon: IconCheckbox, label: "Tasks", href: "/tasks" },
24
+ { icon: IconForms, label: "Fields", href: "/fields" },
25
+ ];
26
+
27
+ interface SidebarProps {
28
+ collapsed?: boolean;
29
+ collapsible?: boolean;
30
+ onCollapsedChange?: (collapsed: boolean) => void;
31
+ }
32
+
33
+ export function Sidebar({
34
+ collapsed = false,
35
+ collapsible = true,
36
+ onCollapsedChange,
37
+ }: SidebarProps) {
38
+ const location = useLocation();
39
+ const ToggleIcon = collapsed
40
+ ? IconLayoutSidebarLeftExpand
41
+ : IconLayoutSidebarLeftCollapse;
42
+ const navClass = ({ isActive }: { isActive: boolean }) =>
43
+ cn(
44
+ "flex items-center text-sm transition-colors",
45
+ collapsed
46
+ ? "relative h-10 w-full justify-center rounded-none border-l-2 px-0"
47
+ : "h-9 rounded-md gap-3 px-3",
48
+ isActive
49
+ ? collapsed
50
+ ? "border-l-sidebar-accent-foreground/80 bg-sidebar-accent text-sidebar-accent-foreground"
51
+ : "bg-sidebar-accent text-sidebar-accent-foreground"
52
+ : collapsed
53
+ ? "border-l-transparent text-sidebar-foreground/70 hover:bg-sidebar-accent/55 hover:text-sidebar-accent-foreground"
54
+ : "text-sidebar-foreground hover:bg-sidebar-accent/65 hover:text-sidebar-accent-foreground",
55
+ );
56
+ const collapseButton = collapsible ? (
57
+ <Tooltip>
58
+ <TooltipTrigger asChild>
59
+ <button
60
+ type="button"
61
+ onClick={() => onCollapsedChange?.(!collapsed)}
62
+ className={cn(
63
+ "flex shrink-0 items-center justify-center rounded-md text-sidebar-foreground/65 transition-colors hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
64
+ collapsed ? "size-8" : "size-7",
65
+ )}
66
+ aria-label={collapsed ? "Expand sidebar" : "Collapse sidebar"}
67
+ >
68
+ <ToggleIcon className="size-4" />
69
+ </button>
70
+ </TooltipTrigger>
71
+ <TooltipContent side="right">
72
+ {collapsed ? "Expand sidebar" : "Collapse sidebar"}
73
+ </TooltipContent>
74
+ </Tooltip>
75
+ ) : null;
76
+
77
+ return (
78
+ <aside
79
+ data-collapsed={collapsed ? "true" : "false"}
80
+ className={cn(
81
+ "flex h-full min-w-0 shrink-0 flex-col overflow-hidden border-r border-sidebar-border bg-sidebar text-sidebar-foreground transition-[width] duration-150",
82
+ collapsed ? "w-12" : "w-60",
83
+ )}
84
+ >
85
+ <div
86
+ className={cn(
87
+ "flex shrink-0 items-center border-b border-sidebar-border",
88
+ collapsed ? "h-12 justify-center px-0" : "h-14 px-3",
89
+ )}
90
+ >
91
+ <Link
92
+ to="/tasks"
93
+ className={cn(
94
+ "flex min-w-0 items-center rounded outline-none focus-visible:ring-2 focus-visible:ring-ring",
95
+ collapsed ? "size-7 justify-center" : "flex-1 gap-3",
96
+ )}
97
+ aria-label={collapsed ? APP_TITLE : undefined}
98
+ >
99
+ <img
100
+ src={appPath("/agent-native-icon-light.svg")}
101
+ alt=""
102
+ aria-hidden="true"
103
+ className="block h-4 w-auto shrink-0 dark:hidden"
104
+ />
105
+ <img
106
+ src={appPath("/agent-native-icon-dark.svg")}
107
+ alt=""
108
+ aria-hidden="true"
109
+ className="hidden h-4 w-auto shrink-0 dark:block"
110
+ />
111
+ <div className={cn("min-w-0", collapsed && "sr-only")}>
112
+ <p className="truncate text-sm font-semibold text-sidebar-accent-foreground">
113
+ {APP_TITLE}
114
+ </p>
115
+ </div>
116
+ </Link>
117
+ </div>
118
+
119
+ <nav
120
+ className={cn(
121
+ "flex-1 overflow-y-auto",
122
+ collapsed ? "px-0 py-2" : "px-2 py-3",
123
+ )}
124
+ >
125
+ <div className={cn("grid", collapsed ? "gap-0" : "gap-1")}>
126
+ {navItems.map((item) => {
127
+ const Icon = item.icon;
128
+ const isActive = location.pathname.startsWith(item.href);
129
+ const link = (
130
+ <Link
131
+ to={item.href}
132
+ className={navClass({ isActive })}
133
+ aria-current={isActive ? "page" : undefined}
134
+ aria-label={collapsed ? item.label : undefined}
135
+ >
136
+ <Icon className="size-4 shrink-0" />
137
+ <span className={collapsed ? "sr-only" : "truncate"}>
138
+ {item.label}
139
+ </span>
140
+ </Link>
141
+ );
142
+ return collapsed ? (
143
+ <Tooltip key={item.href}>
144
+ <TooltipTrigger asChild>{link}</TooltipTrigger>
145
+ <TooltipContent side="right">{item.label}</TooltipContent>
146
+ </Tooltip>
147
+ ) : (
148
+ <div key={item.href}>{link}</div>
149
+ );
150
+ })}
151
+ </div>
152
+ </nav>
153
+
154
+ <div
155
+ className={cn(
156
+ "mt-auto shrink-0",
157
+ collapsed && "border-t border-sidebar-border py-2",
158
+ )}
159
+ >
160
+ {!collapsed ? (
161
+ <div className="border-t border-sidebar-border px-2 py-1">
162
+ <ExtensionsSidebarSection />
163
+ </div>
164
+ ) : null}
165
+
166
+ <div
167
+ className={cn(
168
+ collapsed
169
+ ? "px-1 py-1"
170
+ : "border-t border-sidebar-border px-3 py-2",
171
+ )}
172
+ >
173
+ <OrgSwitcher
174
+ reserveSpace
175
+ className={
176
+ collapsed
177
+ ? "h-8 justify-center px-0 [&>span]:sr-only [&>svg:last-child]:hidden"
178
+ : undefined
179
+ }
180
+ />
181
+ </div>
182
+
183
+ <div
184
+ className={cn(
185
+ collapsed
186
+ ? "flex justify-center px-1 py-1"
187
+ : "border-t border-sidebar-border px-3 py-2",
188
+ )}
189
+ >
190
+ <FeedbackButton
191
+ variant={collapsed ? "icon" : "sidebar"}
192
+ side="right"
193
+ align={collapsed ? "center" : "end"}
194
+ />
195
+ </div>
196
+
197
+ {collapseButton ? (
198
+ <div
199
+ className={cn(
200
+ collapsed
201
+ ? "flex justify-center px-1 py-1"
202
+ : "flex justify-end border-t border-sidebar-border px-3 py-2",
203
+ )}
204
+ >
205
+ {collapseButton}
206
+ </div>
207
+ ) : null}
208
+ </div>
209
+ </aside>
210
+ );
211
+ }
@@ -0,0 +1,57 @@
1
+ import { useRef, useState, type FormEvent } from "react";
2
+ import { toast } from "sonner";
3
+
4
+ import { Button } from "@/components/ui/button";
5
+ import { Input } from "@/components/ui/input";
6
+
7
+ interface AddListItemInputProps {
8
+ disabled?: boolean;
9
+ onCreate: (title: string) => Promise<unknown>;
10
+ placeholder?: string;
11
+ buttonLabel?: string;
12
+ inputAriaLabel?: string;
13
+ errorMessage?: string;
14
+ }
15
+
16
+ export function AddListItemInput({
17
+ disabled = false,
18
+ onCreate,
19
+ placeholder = "Add a task...",
20
+ buttonLabel = "Add task",
21
+ inputAriaLabel = "New task title",
22
+ errorMessage = "Failed to create task. Please try again.",
23
+ }: AddListItemInputProps) {
24
+ const [title, setTitle] = useState("");
25
+ const inputRef = useRef<HTMLInputElement>(null);
26
+
27
+ function handleSubmit(event: FormEvent) {
28
+ event.preventDefault();
29
+ const trimmed = title.trim();
30
+ if (!trimmed) return;
31
+ setTitle("");
32
+ void onCreate(trimmed)
33
+ .catch(() => {
34
+ setTitle(trimmed);
35
+ toast.error(errorMessage);
36
+ })
37
+ .finally(() => {
38
+ inputRef.current?.focus();
39
+ });
40
+ }
41
+
42
+ return (
43
+ <form onSubmit={handleSubmit} className="flex gap-2 p-1">
44
+ <Input
45
+ ref={inputRef}
46
+ value={title}
47
+ onChange={(event) => setTitle(event.target.value)}
48
+ placeholder={placeholder}
49
+ aria-label={inputAriaLabel}
50
+ disabled={disabled}
51
+ />
52
+ <Button type="submit" disabled={disabled || !title.trim()}>
53
+ {buttonLabel}
54
+ </Button>
55
+ </form>
56
+ );
57
+ }
@@ -0,0 +1,53 @@
1
+ import type { ReactNode } from "react";
2
+
3
+ import {
4
+ AlertDialog as AlertDialogRoot,
5
+ AlertDialogAction,
6
+ AlertDialogCancel,
7
+ AlertDialogContent,
8
+ AlertDialogDescription,
9
+ AlertDialogFooter,
10
+ AlertDialogHeader,
11
+ AlertDialogTitle,
12
+ } from "@/components/ui/alert-dialog";
13
+
14
+ export interface AlertDialogProps {
15
+ open: boolean;
16
+ onOpenChange: (open: boolean) => void;
17
+ title: string;
18
+ description: ReactNode;
19
+ pending?: boolean;
20
+ onConfirm: () => void | Promise<void>;
21
+ }
22
+
23
+ export function AlertDialog({
24
+ open,
25
+ onOpenChange,
26
+ title,
27
+ description,
28
+ pending = false,
29
+ onConfirm,
30
+ }: AlertDialogProps) {
31
+ return (
32
+ <AlertDialogRoot open={open} onOpenChange={onOpenChange}>
33
+ <AlertDialogContent>
34
+ <AlertDialogHeader>
35
+ <AlertDialogTitle>{title}</AlertDialogTitle>
36
+ <AlertDialogDescription asChild={typeof description !== "string"}>
37
+ {description}
38
+ </AlertDialogDescription>
39
+ </AlertDialogHeader>
40
+ <AlertDialogFooter>
41
+ <AlertDialogCancel disabled={pending}>Cancel</AlertDialogCancel>
42
+ <AlertDialogAction
43
+ className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
44
+ disabled={pending}
45
+ onClick={() => void onConfirm()}
46
+ >
47
+ Delete
48
+ </AlertDialogAction>
49
+ </AlertDialogFooter>
50
+ </AlertDialogContent>
51
+ </AlertDialogRoot>
52
+ );
53
+ }
@@ -0,0 +1,76 @@
1
+ import {
2
+ AlertDialog,
3
+ type AlertDialogProps,
4
+ } from "@/components/shared/AlertDialog";
5
+
6
+ type SelectableItem = { id: string; title: string };
7
+
8
+ type BulkDeleteDialogProps = Omit<AlertDialogProps, "title" | "description"> & {
9
+ selectedItems: SelectableItem[];
10
+ entitySingular: string;
11
+ entityPlural: string;
12
+ };
13
+
14
+ function BulkDeleteDialogDescription({
15
+ selectedItems,
16
+ entitySingular,
17
+ entityPlural,
18
+ }: Pick<
19
+ BulkDeleteDialogProps,
20
+ "selectedItems" | "entitySingular" | "entityPlural"
21
+ >) {
22
+ const selectedCount = selectedItems.length;
23
+
24
+ return (
25
+ <div className="space-y-2 text-sm text-muted-foreground">
26
+ <p>
27
+ This permanently removes{" "}
28
+ {selectedCount === 1
29
+ ? `the selected ${entitySingular}`
30
+ : `all ${selectedCount} selected ${entityPlural}`}
31
+ .
32
+ </p>
33
+ {selectedItems.length > 0 ? (
34
+ <ul className="list-disc space-y-1 pl-5 text-foreground">
35
+ {selectedItems.slice(0, 5).map((item) => (
36
+ <li key={item.id} className="truncate">
37
+ {item.title}
38
+ </li>
39
+ ))}
40
+ {selectedItems.length > 5 ? (
41
+ <li className="text-muted-foreground">
42
+ and {selectedItems.length - 5} more
43
+ </li>
44
+ ) : null}
45
+ </ul>
46
+ ) : null}
47
+ </div>
48
+ );
49
+ }
50
+
51
+ export function BulkDeleteDialog({
52
+ open,
53
+ onOpenChange,
54
+ selectedItems,
55
+ entitySingular,
56
+ entityPlural,
57
+ pending,
58
+ onConfirm,
59
+ }: BulkDeleteDialogProps) {
60
+ return (
61
+ <AlertDialog
62
+ open={open}
63
+ onOpenChange={onOpenChange}
64
+ pending={pending}
65
+ title={`Delete ${selectedItems.length === 1 ? entitySingular : entityPlural}?`}
66
+ description={
67
+ <BulkDeleteDialogDescription
68
+ selectedItems={selectedItems}
69
+ entitySingular={entitySingular}
70
+ entityPlural={entityPlural}
71
+ />
72
+ }
73
+ onConfirm={onConfirm}
74
+ />
75
+ );
76
+ }
@@ -0,0 +1,240 @@
1
+ import {
2
+ closestCenter,
3
+ DndContext,
4
+ KeyboardSensor,
5
+ PointerSensor,
6
+ useSensor,
7
+ useSensors,
8
+ type DragEndEvent,
9
+ } from "@dnd-kit/core";
10
+ import {
11
+ arrayMove,
12
+ horizontalListSortingStrategy,
13
+ SortableContext,
14
+ sortableKeyboardCoordinates,
15
+ useSortable,
16
+ } from "@dnd-kit/sortable";
17
+ import { CSS } from "@dnd-kit/utilities";
18
+ import { IconChevronDown, IconGripVertical, IconX } from "@tabler/icons-react";
19
+ import { useMemo } from "react";
20
+
21
+ import { Button } from "@/components/ui/button";
22
+ import {
23
+ DropdownMenu,
24
+ DropdownMenuContent,
25
+ DropdownMenuItem,
26
+ DropdownMenuTrigger,
27
+ } from "@/components/ui/dropdown-menu";
28
+ import { Label } from "@/components/ui/label";
29
+ import { cn } from "@/lib/utils";
30
+
31
+ export type ChipSelectOption = {
32
+ id: string;
33
+ label: string;
34
+ };
35
+
36
+ type ChipSelectProps = {
37
+ label: string;
38
+ options: ChipSelectOption[];
39
+ selectedIds: string[];
40
+ onSelectedIdsChange: (ids: string[]) => void;
41
+ disabled?: boolean;
42
+ limit?: number;
43
+ addButtonLabel?: string;
44
+ emptyLabel?: string;
45
+ sortable?: boolean;
46
+ };
47
+
48
+ function ChipSelectChip({
49
+ option,
50
+ disabled,
51
+ sortable,
52
+ onRemove,
53
+ }: {
54
+ option: ChipSelectOption;
55
+ disabled: boolean;
56
+ sortable: boolean;
57
+ onRemove: () => void;
58
+ }) {
59
+ const {
60
+ attributes,
61
+ listeners,
62
+ setNodeRef,
63
+ transform,
64
+ transition,
65
+ isDragging,
66
+ } = useSortable({ id: option.id, disabled: disabled || !sortable });
67
+
68
+ return (
69
+ <div
70
+ ref={setNodeRef}
71
+ className={cn(
72
+ "inline-flex h-8 max-w-56 items-center gap-1.5 rounded-md border border-transparent bg-secondary px-2 text-sm font-medium text-secondary-foreground transition-colors",
73
+ sortable && !disabled && "cursor-pointer",
74
+ isDragging && "opacity-60 ring-1 ring-ring",
75
+ )}
76
+ style={{
77
+ transform: CSS.Transform.toString(transform),
78
+ transition,
79
+ }}
80
+ {...(sortable ? attributes : undefined)}
81
+ {...(sortable ? listeners : undefined)}
82
+ >
83
+ {sortable ? (
84
+ <IconGripVertical className="size-3.5 shrink-0 text-muted-foreground" />
85
+ ) : null}
86
+ <span className="min-w-0 truncate">{option.label}</span>
87
+ <Button
88
+ type="button"
89
+ variant="ghost"
90
+ size="icon"
91
+ disabled={disabled}
92
+ aria-label={`Remove ${option.label}`}
93
+ onPointerDown={(event) => event.stopPropagation()}
94
+ onClick={(event) => {
95
+ event.stopPropagation();
96
+ onRemove();
97
+ }}
98
+ className="ml-0.5 size-5 shrink-0 rounded-sm text-muted-foreground hover:bg-background hover:text-foreground [&_svg]:size-3.5"
99
+ >
100
+ <IconX />
101
+ </Button>
102
+ </div>
103
+ );
104
+ }
105
+
106
+ export function ChipSelect({
107
+ label,
108
+ options,
109
+ selectedIds,
110
+ onSelectedIdsChange,
111
+ disabled = false,
112
+ limit,
113
+ addButtonLabel = "Add",
114
+ emptyLabel = "None selected",
115
+ sortable = true,
116
+ }: ChipSelectProps) {
117
+ const optionsById = useMemo(
118
+ () => new Map(options.map((option) => [option.id, option])),
119
+ [options],
120
+ );
121
+ const selectedIdSet = useMemo(() => new Set(selectedIds), [selectedIds]);
122
+ const selectedOptions = useMemo(
123
+ () =>
124
+ selectedIds
125
+ .map((id) => optionsById.get(id))
126
+ .filter((option): option is ChipSelectOption => Boolean(option)),
127
+ [optionsById, selectedIds],
128
+ );
129
+ const availableOptions = useMemo(
130
+ () => options.filter((option) => !selectedIdSet.has(option.id)),
131
+ [options, selectedIdSet],
132
+ );
133
+ const atLimit = limit !== undefined && selectedOptions.length >= limit;
134
+ const sensors = useSensors(
135
+ useSensor(PointerSensor, { activationConstraint: { distance: 6 } }),
136
+ useSensor(KeyboardSensor, {
137
+ coordinateGetter: sortableKeyboardCoordinates,
138
+ }),
139
+ );
140
+
141
+ function addOption(option: ChipSelectOption) {
142
+ if (atLimit) return;
143
+ onSelectedIdsChange([...selectedIds, option.id]);
144
+ }
145
+
146
+ function removeOption(option: ChipSelectOption) {
147
+ onSelectedIdsChange(selectedIds.filter((id) => id !== option.id));
148
+ }
149
+
150
+ function handleDragEnd(event: DragEndEvent) {
151
+ const { active, over } = event;
152
+ if (disabled || !sortable || !over || active.id === over.id) return;
153
+
154
+ const activeId = String(active.id);
155
+ const overId = String(over.id);
156
+ const oldIndex = selectedOptions.findIndex(
157
+ (option) => option.id === activeId,
158
+ );
159
+ const newIndex = selectedOptions.findIndex(
160
+ (option) => option.id === overId,
161
+ );
162
+ if (oldIndex < 0 || newIndex < 0) return;
163
+
164
+ onSelectedIdsChange(
165
+ arrayMove(selectedOptions, oldIndex, newIndex).map((option) => option.id),
166
+ );
167
+ }
168
+
169
+ const chips = (
170
+ <div className="flex min-w-0 flex-wrap items-center gap-1.5">
171
+ {selectedOptions.map((option) => (
172
+ <ChipSelectChip
173
+ key={option.id}
174
+ option={option}
175
+ disabled={disabled}
176
+ sortable={sortable}
177
+ onRemove={() => removeOption(option)}
178
+ />
179
+ ))}
180
+ </div>
181
+ );
182
+
183
+ return (
184
+ <div className="grid gap-2 rounded-lg border border-border bg-card p-3">
185
+ <div className="flex min-w-0 items-center justify-between gap-3">
186
+ <Label className="text-sm font-medium">{label}</Label>
187
+ {limit !== undefined ? (
188
+ <span className="text-xs text-muted-foreground">
189
+ {selectedOptions.length}/{limit}
190
+ </span>
191
+ ) : null}
192
+ </div>
193
+ <div className="flex min-w-0 flex-wrap items-center gap-2">
194
+ {selectedOptions.length === 0 ? (
195
+ <span className="text-sm text-muted-foreground">{emptyLabel}</span>
196
+ ) : sortable ? (
197
+ <DndContext
198
+ sensors={sensors}
199
+ collisionDetection={closestCenter}
200
+ onDragEnd={handleDragEnd}
201
+ >
202
+ <SortableContext
203
+ items={selectedOptions.map((option) => option.id)}
204
+ strategy={horizontalListSortingStrategy}
205
+ >
206
+ {chips}
207
+ </SortableContext>
208
+ </DndContext>
209
+ ) : (
210
+ chips
211
+ )}
212
+ <DropdownMenu>
213
+ <DropdownMenuTrigger asChild>
214
+ <Button
215
+ type="button"
216
+ variant="ghost"
217
+ size="sm"
218
+ disabled={disabled || availableOptions.length === 0 || atLimit}
219
+ className="h-8 gap-1.5 px-2"
220
+ >
221
+ {addButtonLabel}
222
+ <IconChevronDown className="size-4" />
223
+ </Button>
224
+ </DropdownMenuTrigger>
225
+ <DropdownMenuContent align="start" className="w-64">
226
+ {availableOptions.map((option) => (
227
+ <DropdownMenuItem
228
+ key={option.id}
229
+ disabled={disabled}
230
+ onSelect={() => addOption(option)}
231
+ >
232
+ <span className="min-w-0 truncate">{option.label}</span>
233
+ </DropdownMenuItem>
234
+ ))}
235
+ </DropdownMenuContent>
236
+ </DropdownMenu>
237
+ </div>
238
+ </div>
239
+ );
240
+ }
@@ -0,0 +1,40 @@
1
+ import { type ReactNode } from "react";
2
+
3
+ import {
4
+ AlertDialog,
5
+ type AlertDialogProps,
6
+ } from "@/components/shared/AlertDialog";
7
+
8
+ type DeleteItemDialogProps = Omit<AlertDialogProps, "title" | "description"> & {
9
+ entityLabel: string;
10
+ itemTitle: string | null;
11
+ description?: ReactNode;
12
+ };
13
+
14
+ export function DeleteItemDialog({
15
+ open,
16
+ onOpenChange,
17
+ entityLabel,
18
+ itemTitle,
19
+ description,
20
+ pending,
21
+ onConfirm,
22
+ }: DeleteItemDialogProps) {
23
+ return (
24
+ <AlertDialog
25
+ open={open}
26
+ onOpenChange={onOpenChange}
27
+ pending={pending}
28
+ title={`Delete ${entityLabel}?`}
29
+ description={
30
+ description ??
31
+ (itemTitle ? (
32
+ <>This removes &quot;{itemTitle}&quot; permanently.</>
33
+ ) : (
34
+ `This removes the ${entityLabel} permanently.`
35
+ ))
36
+ }
37
+ onConfirm={onConfirm}
38
+ />
39
+ );
40
+ }