@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,52 @@
1
+ import { IconGripVertical } from "@tabler/icons-react";
2
+
3
+ import type {
4
+ ListRowDrag,
5
+ ListRowRowSelection,
6
+ } from "@/components/shared/list/ListRow";
7
+ import { cn } from "@/lib/utils";
8
+
9
+ interface ListRowDragHandleProps {
10
+ rowDrag: ListRowDrag;
11
+ rowSelection: ListRowRowSelection;
12
+ displayTitle: string;
13
+ disabled?: boolean;
14
+ }
15
+
16
+ export function ListRowDragHandle({
17
+ rowDrag,
18
+ rowSelection,
19
+ displayTitle,
20
+ disabled = false,
21
+ }: ListRowDragHandleProps) {
22
+ const { dragHandleProps, isDragging } = rowDrag;
23
+ const { selectionMode } = rowSelection;
24
+
25
+ if (!dragHandleProps) return null;
26
+
27
+ return (
28
+ <button
29
+ type="button"
30
+ {...dragHandleProps}
31
+ disabled={disabled}
32
+ aria-label={`Reorder ${displayTitle}`}
33
+ onClick={(event) => {
34
+ if (selectionMode) {
35
+ event.stopPropagation();
36
+ }
37
+ }}
38
+ onPointerDown={(event) => {
39
+ dragHandleProps.onPointerDown?.(event);
40
+ if (selectionMode) {
41
+ event.stopPropagation();
42
+ }
43
+ }}
44
+ className={cn(
45
+ "flex size-8 shrink-0 cursor-grab items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-accent hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring active:cursor-grabbing disabled:cursor-not-allowed disabled:opacity-50",
46
+ isDragging && "cursor-grabbing",
47
+ )}
48
+ >
49
+ <IconGripVertical className="size-4" />
50
+ </button>
51
+ );
52
+ }
@@ -0,0 +1,11 @@
1
+ import { Skeleton } from "@/components/ui/skeleton";
2
+
3
+ export function ListSkeletonRows() {
4
+ return (
5
+ <div className="space-y-2">
6
+ {Array.from({ length: 3 }).map((_, index) => (
7
+ <Skeleton key={index} className="h-12 w-full rounded-lg" />
8
+ ))}
9
+ </div>
10
+ );
11
+ }
@@ -0,0 +1,32 @@
1
+ import { type ReactNode } from "react";
2
+
3
+ import { cn } from "@/lib/utils";
4
+
5
+ interface SortableListItemShellProps {
6
+ setNodeRef: (element: HTMLElement | null) => void;
7
+ style: React.CSSProperties | undefined;
8
+ isDragging: boolean;
9
+ hideForBlockDrag: boolean;
10
+ children: ReactNode;
11
+ }
12
+
13
+ export function SortableListItemShell({
14
+ setNodeRef,
15
+ style,
16
+ isDragging,
17
+ hideForBlockDrag,
18
+ children,
19
+ }: SortableListItemShellProps) {
20
+ return (
21
+ <div
22
+ ref={setNodeRef}
23
+ style={style}
24
+ className={cn(
25
+ "transition-[height,opacity,margin] duration-200 ease-out",
26
+ (isDragging || hideForBlockDrag) && "opacity-0",
27
+ )}
28
+ >
29
+ {children}
30
+ </div>
31
+ );
32
+ }
@@ -0,0 +1 @@
1
+ export type ListIdentifiable = { id: string; title?: string };
@@ -0,0 +1,158 @@
1
+ import { IconCircleCheck, IconCircle } from "@tabler/icons-react";
2
+ import { toast } from "sonner";
3
+
4
+ import { ListSelectionToolbar } from "@/components/shared/selection/ListSelectionToolbar";
5
+ import { type ListSelection } from "@/components/shared/selection/use-list-selection";
6
+ import { Button } from "@/components/ui/button";
7
+ import { useBulkMarkInboxItemsReady } from "@/hooks/use-inbox-items";
8
+ import { useBulkUpdateTasks } from "@/hooks/use-tasks";
9
+
10
+ interface ListSelectionBarProps<
11
+ T extends { id: string; title: string; done?: boolean },
12
+ > {
13
+ promotedToTask: boolean;
14
+ items: T[];
15
+ selection: ListSelection<T>;
16
+ toolbarBusy: boolean;
17
+ onOpenBulkDelete: () => void;
18
+ }
19
+
20
+ export function ListSelectionBar<
21
+ T extends { id: string; title: string; done?: boolean },
22
+ >({
23
+ promotedToTask,
24
+ items,
25
+ selection,
26
+ toolbarBusy,
27
+ onOpenBulkDelete,
28
+ }: ListSelectionBarProps<T>) {
29
+ const bulkUpdateTasks = useBulkUpdateTasks();
30
+ const bulkMarkInboxItemsReady = useBulkMarkInboxItemsReady();
31
+
32
+ const selectedItems = selection.state.selectedItems;
33
+ const selectedCount = selectedItems.length;
34
+ const selectedIdSet = new Set(selectedItems.map((item) => item.id));
35
+ const allVisibleSelected =
36
+ items.length > 0 && items.every((item) => selectedIdSet.has(item.id));
37
+
38
+ const entityLabel = promotedToTask ? "task" : "item";
39
+
40
+ async function markSelectedReady() {
41
+ if (selectedCount === 0) return;
42
+
43
+ try {
44
+ await bulkMarkInboxItemsReady.mutateAsync({
45
+ inboxItemIds: selectedItems.map((item) => item.id),
46
+ });
47
+ toast.success(
48
+ `Marked ${selectedCount} ${selectedCount === 1 ? "item" : "items"} ready`,
49
+ );
50
+ selection.actions.clearSelection();
51
+ } catch {
52
+ toast.error("Could not mark selected items ready.");
53
+ }
54
+ }
55
+
56
+ async function markSelectedDone(done: boolean) {
57
+ const applicableTasks = selectedItems.filter((task) => task.done !== done);
58
+ const skippedCount = selectedItems.length - applicableTasks.length;
59
+ if (applicableTasks.length === 0) {
60
+ toast.info(
61
+ done
62
+ ? "All selected tasks are already complete."
63
+ : "All selected tasks are already incomplete.",
64
+ );
65
+ return;
66
+ }
67
+
68
+ const taskIds = applicableTasks.map((task) => task.id);
69
+
70
+ try {
71
+ await bulkUpdateTasks.mutateAsync({ taskIds, done });
72
+
73
+ const countLabel = applicableTasks.length === 1 ? "task" : "tasks";
74
+ toast.success(
75
+ done
76
+ ? skippedCount > 0
77
+ ? `Marked ${applicableTasks.length} ${countLabel} complete (${skippedCount} already complete)`
78
+ : `Marked ${applicableTasks.length} ${countLabel} complete`
79
+ : skippedCount > 0
80
+ ? `Marked ${applicableTasks.length} ${countLabel} incomplete (${skippedCount} already incomplete)`
81
+ : `Marked ${applicableTasks.length} ${countLabel} incomplete`,
82
+ );
83
+ selection.actions.clearSelection();
84
+ } catch {
85
+ toast.error("Could not update selected tasks.");
86
+ }
87
+ }
88
+
89
+ const allSelectedComplete =
90
+ promotedToTask &&
91
+ selectedCount > 0 &&
92
+ selectedItems.every((task) => task.done === true);
93
+ const allSelectedIncomplete =
94
+ promotedToTask &&
95
+ selectedCount > 0 &&
96
+ selectedItems.every((task) => task.done !== true);
97
+
98
+ const toolbarDisabled =
99
+ toolbarBusy || (!promotedToTask && bulkMarkInboxItemsReady.isPending);
100
+
101
+ return (
102
+ <ListSelectionToolbar
103
+ ariaLabel={
104
+ promotedToTask ? "Task selection actions" : "Inbox selection actions"
105
+ }
106
+ emptySelectionHint={`Tap ${entityLabel}s to select them.`}
107
+ visibleCount={items.length}
108
+ selectedCount={selectedCount}
109
+ allVisibleSelected={allVisibleSelected}
110
+ toolbarDisabled={toolbarDisabled}
111
+ selectionActions={selection.actions}
112
+ onOpenBulkDelete={onOpenBulkDelete}
113
+ >
114
+ {promotedToTask ? (
115
+ <>
116
+ <Button
117
+ variant="ghost"
118
+ size="sm"
119
+ className="h-8 shrink-0 gap-1 px-2 text-xs"
120
+ disabled={
121
+ selectedCount === 0 || toolbarDisabled || allSelectedComplete
122
+ }
123
+ aria-label="Mark complete"
124
+ onClick={() => void markSelectedDone(true)}
125
+ >
126
+ <IconCircleCheck className="h-3.5 w-3.5" />
127
+ <span className="hidden sm:inline">Complete</span>
128
+ </Button>
129
+ <Button
130
+ variant="ghost"
131
+ size="sm"
132
+ className="h-8 shrink-0 gap-1 px-2 text-xs"
133
+ disabled={
134
+ selectedCount === 0 || toolbarDisabled || allSelectedIncomplete
135
+ }
136
+ aria-label="Mark incomplete"
137
+ onClick={() => void markSelectedDone(false)}
138
+ >
139
+ <IconCircle className="h-3.5 w-3.5" />
140
+ <span className="hidden sm:inline">Incomplete</span>
141
+ </Button>
142
+ </>
143
+ ) : (
144
+ <Button
145
+ variant="ghost"
146
+ size="sm"
147
+ className="h-8 shrink-0 gap-1 px-2 text-xs"
148
+ disabled={selectedCount === 0 || toolbarDisabled}
149
+ aria-label="Mark ready"
150
+ onClick={() => void markSelectedReady()}
151
+ >
152
+ <IconCircleCheck className="h-3.5 w-3.5" />
153
+ <span className="hidden sm:inline">Mark ready</span>
154
+ </Button>
155
+ )}
156
+ </ListSelectionToolbar>
157
+ );
158
+ }
@@ -0,0 +1,28 @@
1
+ import { IconChecks } from "@tabler/icons-react";
2
+
3
+ import { Button } from "@/components/ui/button";
4
+
5
+ interface ListSelectionHeaderToggleProps {
6
+ selectionMode: boolean;
7
+ disabled?: boolean;
8
+ onSelectionModeChange: (next: boolean) => void;
9
+ }
10
+
11
+ export function ListSelectionHeaderToggle({
12
+ selectionMode,
13
+ disabled,
14
+ onSelectionModeChange,
15
+ }: ListSelectionHeaderToggleProps) {
16
+ return (
17
+ <Button
18
+ variant={selectionMode ? "secondary" : "ghost"}
19
+ size="sm"
20
+ className="h-8 shrink-0 gap-1.5 text-xs"
21
+ disabled={disabled}
22
+ onClick={() => onSelectionModeChange(!selectionMode)}
23
+ >
24
+ <IconChecks className="h-3.5 w-3.5" />
25
+ {selectionMode ? "Done selecting" : "Select"}
26
+ </Button>
27
+ );
28
+ }
@@ -0,0 +1,81 @@
1
+ import { IconTrash, IconX } from "@tabler/icons-react";
2
+ import { type ReactNode } from "react";
3
+
4
+ import { type ListSelectionActions } from "@/components/shared/selection/use-list-selection";
5
+ import { Button } from "@/components/ui/button";
6
+
7
+ interface ListSelectionToolbarProps {
8
+ ariaLabel: string;
9
+ emptySelectionHint: string;
10
+ visibleCount: number;
11
+ selectedCount: number;
12
+ allVisibleSelected: boolean;
13
+ toolbarDisabled: boolean;
14
+ selectionActions: ListSelectionActions;
15
+ onOpenBulkDelete: () => void;
16
+ children?: ReactNode;
17
+ }
18
+
19
+ export function ListSelectionToolbar({
20
+ ariaLabel,
21
+ emptySelectionHint,
22
+ visibleCount,
23
+ selectedCount,
24
+ allVisibleSelected,
25
+ toolbarDisabled,
26
+ selectionActions,
27
+ onOpenBulkDelete,
28
+ children,
29
+ }: ListSelectionToolbarProps) {
30
+ const statusText =
31
+ selectedCount === 0
32
+ ? emptySelectionHint
33
+ : selectedCount > 1
34
+ ? `${selectedCount} selected · drag to reorder`
35
+ : `${selectedCount} selected`;
36
+
37
+ return (
38
+ <div
39
+ className="flex h-12 items-center gap-2 px-1"
40
+ role="toolbar"
41
+ aria-label={ariaLabel}
42
+ >
43
+ <p className="min-w-0 flex-1 truncate text-sm text-muted-foreground">
44
+ {statusText}
45
+ </p>
46
+ <div className="flex shrink-0 items-center gap-1 overflow-x-auto">
47
+ <Button
48
+ variant="ghost"
49
+ size="sm"
50
+ className="h-8 shrink-0 px-2 text-xs"
51
+ disabled={toolbarDisabled || visibleCount === 0}
52
+ onClick={selectionActions.selectAll}
53
+ >
54
+ {allVisibleSelected ? "Clear all" : "Select all"}
55
+ </Button>
56
+ {children}
57
+ <Button
58
+ variant="ghost"
59
+ size="sm"
60
+ className="h-8 shrink-0 gap-1 px-2 text-xs text-destructive hover:text-destructive"
61
+ disabled={selectedCount === 0 || toolbarDisabled}
62
+ aria-label="Delete"
63
+ onClick={onOpenBulkDelete}
64
+ >
65
+ <IconTrash className="h-3.5 w-3.5" />
66
+ <span className="hidden sm:inline">Delete</span>
67
+ </Button>
68
+ <Button
69
+ variant="ghost"
70
+ size="icon"
71
+ className="h-8 w-8 shrink-0"
72
+ disabled={toolbarDisabled}
73
+ onClick={selectionActions.clearSelection}
74
+ aria-label="Exit selection mode"
75
+ >
76
+ <IconX className="h-3.5 w-3.5" />
77
+ </Button>
78
+ </div>
79
+ </div>
80
+ );
81
+ }
@@ -0,0 +1,41 @@
1
+ import type { MouseEvent } from "react";
2
+
3
+ import { cn } from "@/lib/utils";
4
+
5
+ export type ListRowSelectionContainerUi = {
6
+ className: string;
7
+ role?: "option";
8
+ "aria-selected"?: boolean;
9
+ "aria-label"?: string;
10
+ onClick?: (event: MouseEvent<Element>) => void;
11
+ };
12
+
13
+ type GetListRowSelectionUiArgs = {
14
+ selectionMode: boolean;
15
+ selected: boolean;
16
+ itemLabel: string;
17
+ onRowSelect?: (event: MouseEvent<Element>) => void;
18
+ };
19
+
20
+ export function getListRowSelectionUi({
21
+ selectionMode,
22
+ selected,
23
+ itemLabel,
24
+ onRowSelect,
25
+ }: GetListRowSelectionUiArgs): ListRowSelectionContainerUi {
26
+ if (!selectionMode) {
27
+ return { className: "" };
28
+ }
29
+
30
+ return {
31
+ className: cn(
32
+ "cursor-pointer select-none",
33
+ selected &&
34
+ "border-primary/40 bg-primary/[0.08] ring-2 ring-inset ring-primary/20",
35
+ ),
36
+ role: "option",
37
+ "aria-selected": selected,
38
+ "aria-label": itemLabel,
39
+ onClick: onRowSelect,
40
+ };
41
+ }
@@ -0,0 +1,196 @@
1
+ import { setClientAppState } from "@agent-native/core/client/application-state";
2
+ import { appStateKeyForBrowserTab } from "@shared/app-state-tabs";
3
+ import { type ListSelectionAppState } from "@shared/navigation";
4
+ import {
5
+ useCallback,
6
+ useEffect,
7
+ useMemo,
8
+ useRef,
9
+ useState,
10
+ type MouseEvent,
11
+ } from "react";
12
+
13
+ import { TAB_ID } from "@/lib/tab-id";
14
+
15
+ function useSelectionAppStateSync(
16
+ appStateKey: string | null,
17
+ selectionMode: boolean,
18
+ selectedIds: Set<string>,
19
+ ) {
20
+ const selectedIdsArray = Array.from(selectedIds);
21
+
22
+ useEffect(() => {
23
+ if (appStateKey === null) return;
24
+
25
+ const scopedKey = appStateKeyForBrowserTab(appStateKey, TAB_ID);
26
+ const hasSelection = selectionMode || selectedIdsArray.length > 0;
27
+ const value: ListSelectionAppState | null = hasSelection
28
+ ? { selectionMode, selectedIds: selectedIdsArray }
29
+ : null;
30
+
31
+ void setClientAppState(scopedKey, value, {
32
+ requestSource: TAB_ID,
33
+ });
34
+ }, [appStateKey, selectionMode, selectedIdsArray.join("\0")]);
35
+ }
36
+
37
+ export type ListSelectionState<T extends { id: string }> = {
38
+ selectionMode: boolean;
39
+ selectedItems: T[];
40
+ };
41
+
42
+ export type ListSelectionActions = {
43
+ clearSelection: () => void;
44
+ setSelectionModeFromHeader: (next: boolean) => void;
45
+ selectRow: (id: string, event: MouseEvent<Element>) => void;
46
+ startSelection: (id: string) => void;
47
+ selectAll: () => void;
48
+ };
49
+
50
+ export type ListSelection<T extends { id: string }> = {
51
+ state: ListSelectionState<T>;
52
+ actions: ListSelectionActions;
53
+ };
54
+
55
+ export function useListSelection<T extends { id: string }>(
56
+ items: T[],
57
+ appStateKey: string | null,
58
+ ): ListSelection<T> {
59
+ const [selectionMode, setSelectionMode] = useState(false);
60
+ const [selectedIds, setSelectedIds] = useState<Set<string>>(() => new Set());
61
+ const lastSelectedIndexRef = useRef<number | null>(null);
62
+ const itemsRef = useRef(items);
63
+ itemsRef.current = items;
64
+
65
+ const selectedItems = useMemo(
66
+ () => items.filter((item) => selectedIds.has(item.id)),
67
+ [items, selectedIds],
68
+ );
69
+
70
+ useSelectionAppStateSync(appStateKey, selectionMode, selectedIds);
71
+
72
+ useEffect(() => {
73
+ setSelectedIds((prev) => {
74
+ if (prev.size === 0) return prev;
75
+ const visibleIds = new Set(items.map((item) => item.id));
76
+ const next = new Set([...prev].filter((id) => visibleIds.has(id)));
77
+ return next.size === prev.size ? prev : next;
78
+ });
79
+ }, [items]);
80
+
81
+ const clearSelection = useCallback(() => {
82
+ setSelectedIds(new Set());
83
+ setSelectionMode(false);
84
+ lastSelectedIndexRef.current = null;
85
+ }, []);
86
+
87
+ const setSelectionModeFromHeader = useCallback((next: boolean) => {
88
+ if (!next) {
89
+ setSelectedIds(new Set());
90
+ lastSelectedIndexRef.current = null;
91
+ }
92
+ setSelectionMode(next);
93
+ }, []);
94
+
95
+ const selectRow = useCallback((id: string, event: MouseEvent<Element>) => {
96
+ const visibleItems = itemsRef.current;
97
+ const index = visibleItems.findIndex((item) => item.id === id);
98
+ if (index < 0) return;
99
+
100
+ if (event.shiftKey && lastSelectedIndexRef.current !== null) {
101
+ const start = Math.min(lastSelectedIndexRef.current, index);
102
+ const end = Math.max(lastSelectedIndexRef.current, index);
103
+ const rangeIds = visibleItems
104
+ .slice(start, end + 1)
105
+ .map((item) => item.id);
106
+ setSelectedIds((prev) => new Set([...prev, ...rangeIds]));
107
+ return;
108
+ }
109
+
110
+ setSelectedIds((prev) => {
111
+ const next = new Set(prev);
112
+ if (next.has(id)) {
113
+ next.delete(id);
114
+ } else {
115
+ next.add(id);
116
+ }
117
+ return next;
118
+ });
119
+ lastSelectedIndexRef.current = index;
120
+ }, []);
121
+
122
+ const startSelection = useCallback((id: string) => {
123
+ const visibleItems = itemsRef.current;
124
+ const index = visibleItems.findIndex((item) => item.id === id);
125
+ setSelectionMode(true);
126
+ setSelectedIds(new Set([id]));
127
+ lastSelectedIndexRef.current = index >= 0 ? index : null;
128
+ }, []);
129
+
130
+ const selectAll = useCallback(() => {
131
+ const visibleItems = itemsRef.current;
132
+ setSelectedIds((prev) => {
133
+ const allVisibleSelected =
134
+ visibleItems.length > 0 &&
135
+ visibleItems.every((item) => prev.has(item.id));
136
+ if (allVisibleSelected) {
137
+ return new Set();
138
+ }
139
+ return new Set(visibleItems.map((item) => item.id));
140
+ });
141
+ }, []);
142
+
143
+ const actions = useMemo(
144
+ () => ({
145
+ clearSelection,
146
+ setSelectionModeFromHeader,
147
+ selectRow,
148
+ startSelection,
149
+ selectAll,
150
+ }),
151
+ [
152
+ clearSelection,
153
+ setSelectionModeFromHeader,
154
+ selectRow,
155
+ startSelection,
156
+ selectAll,
157
+ ],
158
+ );
159
+
160
+ useEffect(() => {
161
+ if (!selectionMode) return;
162
+
163
+ function handleKeyDown(event: KeyboardEvent) {
164
+ if (event.key === "Escape") {
165
+ event.preventDefault();
166
+ clearSelection();
167
+ return;
168
+ }
169
+
170
+ if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "a") {
171
+ const target = event.target;
172
+ if (
173
+ target instanceof HTMLElement &&
174
+ (target.isContentEditable ||
175
+ target.tagName === "INPUT" ||
176
+ target.tagName === "TEXTAREA")
177
+ ) {
178
+ return;
179
+ }
180
+ event.preventDefault();
181
+ selectAll();
182
+ }
183
+ }
184
+
185
+ document.addEventListener("keydown", handleKeyDown);
186
+ return () => document.removeEventListener("keydown", handleKeyDown);
187
+ }, [selectionMode, clearSelection, selectAll]);
188
+
189
+ return {
190
+ state: {
191
+ selectionMode,
192
+ selectedItems,
193
+ },
194
+ actions,
195
+ };
196
+ }