@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,306 @@
1
+ import type { FieldValueInput } from "../custom-fields/types.js";
2
+ import {
3
+ applyCustomFieldValuePatches,
4
+ deleteCustomFieldValuesByTaskIds,
5
+ prepareCustomFieldValuePatches,
6
+ updateCustomFieldValuesByTaskId,
7
+ } from "../custom-fields/values/store.js";
8
+ import { getDb } from "../db/index.js";
9
+ import type { StoredItem } from "../db/schema.js";
10
+ import type { DbHandle } from "../db/transaction.js";
11
+ import { NotFoundError, UserInputError } from "../errors.js";
12
+ import {
13
+ assertStoredItemsExist,
14
+ createStoredItem,
15
+ deleteStoredItems,
16
+ getStoredItem,
17
+ hasCompletedStoredItems,
18
+ listStoredItems,
19
+ reorderStoredItems,
20
+ requireUserEmail,
21
+ updateStoredItems,
22
+ } from "../stored-items/store.js";
23
+
24
+ export { requireUserEmail };
25
+
26
+ /** Action/UI view of a task on the task list (`promotedToTask = true` in storage). */
27
+ export type Task = Omit<StoredItem, "promotedToTask">;
28
+
29
+ const NOT_FOUND = "Task not found.";
30
+
31
+ export async function createTask(
32
+ input: {
33
+ ownerEmail: string;
34
+ title: string;
35
+ id?: string;
36
+ now?: string;
37
+ },
38
+ db: DbHandle = getDb(),
39
+ ): Promise<Task> {
40
+ const item = await createStoredItem(
41
+ {
42
+ ownerEmail: input.ownerEmail,
43
+ title: input.title,
44
+ id: input.id ?? crypto.randomUUID(),
45
+ now: input.now ?? new Date().toISOString(),
46
+ promotedToTask: true,
47
+ },
48
+ db,
49
+ );
50
+ return toTask(item);
51
+ }
52
+
53
+ export async function getTask(
54
+ input: {
55
+ ownerEmail: string;
56
+ id: string;
57
+ },
58
+ db: DbHandle = getDb(),
59
+ ): Promise<Task | null> {
60
+ const item = await getStoredItem(
61
+ {
62
+ ...input,
63
+ promotedToTask: true,
64
+ },
65
+ db,
66
+ );
67
+ return item ? toTask(item) : null;
68
+ }
69
+
70
+ export async function listTasks(
71
+ input: {
72
+ ownerEmail: string;
73
+ ids?: string[];
74
+ includeDone?: boolean;
75
+ },
76
+ db: DbHandle = getDb(),
77
+ ): Promise<Task[]> {
78
+ const items = await listStoredItems(
79
+ {
80
+ ...input,
81
+ promotedToTask: true,
82
+ notFoundMessage: NOT_FOUND,
83
+ },
84
+ db,
85
+ );
86
+ return items.map(toTask);
87
+ }
88
+
89
+ export async function hasCompletedTasks(
90
+ input: {
91
+ ownerEmail: string;
92
+ },
93
+ db: DbHandle = getDb(),
94
+ ): Promise<boolean> {
95
+ return hasCompletedStoredItems(
96
+ {
97
+ ownerEmail: input.ownerEmail,
98
+ promotedToTask: true,
99
+ },
100
+ db,
101
+ );
102
+ }
103
+
104
+ export async function updateTasks(
105
+ input: {
106
+ ownerEmail: string;
107
+ ids: string[];
108
+ title?: string;
109
+ done?: boolean;
110
+ now?: string;
111
+ },
112
+ db: DbHandle = getDb(),
113
+ ): Promise<Task[]> {
114
+ if (input.title === undefined && input.done === undefined) {
115
+ throw new UserInputError("Provide at least one of title or done.");
116
+ }
117
+
118
+ const items = await updateStoredItems(
119
+ {
120
+ ...input,
121
+ promotedToTask: true,
122
+ notFoundMessage: NOT_FOUND,
123
+ },
124
+ db,
125
+ );
126
+ return items.map(toTask);
127
+ }
128
+
129
+ /**
130
+ * Single-task update. Unlike `updateTasks` this also accepts `fieldValues`,
131
+ * which are per-task and so have no meaningful bulk form.
132
+ */
133
+ export async function updateTask(
134
+ input: {
135
+ ownerEmail: string;
136
+ id: string;
137
+ title?: string;
138
+ done?: boolean;
139
+ fieldValues?: Array<{ fieldId: string; value: FieldValueInput }>;
140
+ now?: string;
141
+ },
142
+ db: DbHandle = getDb(),
143
+ ): Promise<Task> {
144
+ const hasTaskPatch = input.title !== undefined || input.done !== undefined;
145
+ const hasFieldPatch = input.fieldValues !== undefined;
146
+
147
+ if (!hasTaskPatch && !hasFieldPatch) {
148
+ throw new UserInputError(
149
+ "Provide at least one of title, done, or fieldValues.",
150
+ );
151
+ }
152
+
153
+ if (!hasFieldPatch) {
154
+ const [task] = await updateTasks({ ...input, ids: [input.id] }, db);
155
+ if (!task) throw new NotFoundError(NOT_FOUND);
156
+ return task;
157
+ }
158
+
159
+ if (!hasTaskPatch) {
160
+ const task = await getTask(
161
+ { ownerEmail: input.ownerEmail, id: input.id },
162
+ db,
163
+ );
164
+ if (!task) throw new NotFoundError(NOT_FOUND);
165
+
166
+ await updateCustomFieldValuesByTaskId(
167
+ {
168
+ ownerEmail: input.ownerEmail,
169
+ taskId: input.id,
170
+ values: input.fieldValues!,
171
+ now: input.now,
172
+ },
173
+ db,
174
+ );
175
+ return task;
176
+ }
177
+
178
+ await assertStoredItemsExist(
179
+ {
180
+ ownerEmail: input.ownerEmail,
181
+ ids: [input.id],
182
+ promotedToTask: true,
183
+ notFoundMessage: NOT_FOUND,
184
+ },
185
+ db,
186
+ );
187
+
188
+ const patches = await prepareCustomFieldValuePatches(
189
+ {
190
+ ownerEmail: input.ownerEmail,
191
+ taskId: input.id,
192
+ values: input.fieldValues!,
193
+ },
194
+ db,
195
+ );
196
+ const timestamp = input.now ?? new Date().toISOString();
197
+
198
+ const task = await db.transaction(async (tx) => {
199
+ const [item] = await updateStoredItems(
200
+ {
201
+ ownerEmail: input.ownerEmail,
202
+ ids: [input.id],
203
+ promotedToTask: true,
204
+ title: input.title,
205
+ done: input.done,
206
+ now: timestamp,
207
+ notFoundMessage: NOT_FOUND,
208
+ },
209
+ tx,
210
+ );
211
+ await applyCustomFieldValuePatches(
212
+ {
213
+ ownerEmail: input.ownerEmail,
214
+ taskId: input.id,
215
+ patches,
216
+ updatedAt: timestamp,
217
+ },
218
+ tx,
219
+ );
220
+ return item ? toTask(item) : null;
221
+ });
222
+
223
+ if (!task) throw new NotFoundError(NOT_FOUND);
224
+ return task;
225
+ }
226
+
227
+ export async function deleteTasks(
228
+ input: {
229
+ ownerEmail: string;
230
+ ids: string[];
231
+ },
232
+ db: DbHandle = getDb(),
233
+ ): Promise<{ ok: true; deleted: number }> {
234
+ const ids = [...new Set(input.ids)];
235
+ await assertStoredItemsExist(
236
+ {
237
+ ownerEmail: input.ownerEmail,
238
+ ids,
239
+ promotedToTask: true,
240
+ notFoundMessage: NOT_FOUND,
241
+ },
242
+ db,
243
+ );
244
+
245
+ await db.transaction(async (tx) => {
246
+ await deleteCustomFieldValuesByTaskIds(
247
+ { ownerEmail: input.ownerEmail, taskIds: ids },
248
+ tx,
249
+ );
250
+ await deleteStoredItems(
251
+ {
252
+ ownerEmail: input.ownerEmail,
253
+ ids,
254
+ promotedToTask: true,
255
+ },
256
+ tx,
257
+ );
258
+ });
259
+
260
+ return { ok: true, deleted: ids.length };
261
+ }
262
+
263
+ export async function deleteTask(
264
+ input: {
265
+ ownerEmail: string;
266
+ id: string;
267
+ },
268
+ db: DbHandle = getDb(),
269
+ ): Promise<void> {
270
+ await deleteTasks({ ownerEmail: input.ownerEmail, ids: [input.id] }, db);
271
+ }
272
+
273
+ export async function reorderTasks(
274
+ input: {
275
+ ownerEmail: string;
276
+ taskIds: string[];
277
+ includeDone?: boolean;
278
+ },
279
+ db: DbHandle = getDb(),
280
+ ): Promise<{ tasks: Task[] }> {
281
+ const includeDone = input.includeDone === true;
282
+ await reorderStoredItems(
283
+ {
284
+ ownerEmail: input.ownerEmail,
285
+ promotedToTask: true,
286
+ orderedIds: input.taskIds,
287
+ includeDone,
288
+ idLabel: "taskIds",
289
+ },
290
+ db,
291
+ );
292
+
293
+ const tasksAfter = await listTasks(
294
+ {
295
+ ownerEmail: input.ownerEmail,
296
+ includeDone,
297
+ },
298
+ db,
299
+ );
300
+ return { tasks: tasksAfter };
301
+ }
302
+
303
+ export function toTask(item: StoredItem): Task {
304
+ const { promotedToTask: _, ...task } = item;
305
+ return task;
306
+ }
@@ -0,0 +1,138 @@
1
+ import { eq } from "drizzle-orm";
2
+
3
+ import {
4
+ DEFAULT_TASK_CARD_FIELD_NAMES,
5
+ TASK_CARD_FIELD_LIMIT,
6
+ } from "../../shared/visible-task-fields.js";
7
+ import { listCustomFields } from "../custom-fields/store.js";
8
+ import { getDb } from "../db/index.js";
9
+ import { timestamp } from "../db/record-utils.js";
10
+ import { userConfig } from "../db/schema.js";
11
+ import type { DbHandle } from "../db/transaction.js";
12
+ import { UserInputError } from "../errors.js";
13
+
14
+ export { DEFAULT_TASK_CARD_FIELD_NAMES, TASK_CARD_FIELD_LIMIT };
15
+
16
+ function fieldIdsForNames(
17
+ fieldNames: readonly string[],
18
+ fields: readonly { id: string; title: string }[],
19
+ ) {
20
+ const fieldsByName = new Map(
21
+ fields.map((field) => [field.title.toLowerCase(), field.id]),
22
+ );
23
+ return fieldNames
24
+ .map((name) => fieldsByName.get(name.toLowerCase()))
25
+ .filter((id): id is string => Boolean(id))
26
+ .slice(0, TASK_CARD_FIELD_LIMIT);
27
+ }
28
+
29
+ function parseStoredFieldIds(raw: string): string[] {
30
+ try {
31
+ const parsed = JSON.parse(raw);
32
+ if (!Array.isArray(parsed)) return [];
33
+ return parsed
34
+ .filter((value): value is string => typeof value === "string")
35
+ .slice(0, TASK_CARD_FIELD_LIMIT);
36
+ } catch {
37
+ return [];
38
+ }
39
+ }
40
+
41
+ function dedupeFieldIds(fieldIds: readonly string[]) {
42
+ return [...new Set(fieldIds)].slice(0, TASK_CARD_FIELD_LIMIT);
43
+ }
44
+
45
+ function filterKnownFieldIds(
46
+ fieldIds: readonly string[],
47
+ knownIds: ReadonlySet<string>,
48
+ ) {
49
+ return fieldIds.filter((fieldId) => knownIds.has(fieldId));
50
+ }
51
+
52
+ export async function getTaskCardFieldIds(
53
+ input: {
54
+ ownerEmail: string;
55
+ },
56
+ db: DbHandle = getDb(),
57
+ ): Promise<string[]> {
58
+ const { fields } = await listCustomFields({ ownerEmail: input.ownerEmail });
59
+ const knownIds = new Set(fields.map((field) => field.id));
60
+
61
+ const [row] = await db
62
+ .select()
63
+ .from(userConfig)
64
+ .where(eq(userConfig.ownerEmail, input.ownerEmail))
65
+ .limit(1);
66
+
67
+ if (!row) {
68
+ return fieldIdsForNames(DEFAULT_TASK_CARD_FIELD_NAMES, fields);
69
+ }
70
+
71
+ return filterKnownFieldIds(
72
+ parseStoredFieldIds(row.taskCardFieldIdsJson),
73
+ knownIds,
74
+ );
75
+ }
76
+
77
+ export async function setTaskCardFieldIds(
78
+ input: {
79
+ ownerEmail: string;
80
+ fieldIds: readonly string[];
81
+ now?: string;
82
+ },
83
+ db: DbHandle = getDb(),
84
+ ): Promise<string[]> {
85
+ const { fields } = await listCustomFields({ ownerEmail: input.ownerEmail });
86
+ const knownIds = new Set(fields.map((field) => field.id));
87
+ const next = dedupeFieldIds(input.fieldIds);
88
+
89
+ if (!next.every((fieldId) => knownIds.has(fieldId))) {
90
+ throw new UserInputError("fieldIds must reference existing custom fields.");
91
+ }
92
+
93
+ const updatedAt = timestamp(input.now);
94
+ const taskCardFieldIdsJson = JSON.stringify(next);
95
+
96
+ await db
97
+ .insert(userConfig)
98
+ .values({
99
+ ownerEmail: input.ownerEmail,
100
+ taskCardFieldIdsJson,
101
+ updatedAt,
102
+ })
103
+ .onConflictDoUpdate({
104
+ target: userConfig.ownerEmail,
105
+ set: { taskCardFieldIdsJson, updatedAt },
106
+ });
107
+
108
+ return next;
109
+ }
110
+
111
+ export async function removeTaskCardFieldId(
112
+ input: {
113
+ ownerEmail: string;
114
+ fieldId: string;
115
+ now?: string;
116
+ },
117
+ db: DbHandle = getDb(),
118
+ ): Promise<void> {
119
+ const [row] = await db
120
+ .select()
121
+ .from(userConfig)
122
+ .where(eq(userConfig.ownerEmail, input.ownerEmail))
123
+ .limit(1);
124
+
125
+ if (!row) return;
126
+
127
+ const next = parseStoredFieldIds(row.taskCardFieldIdsJson).filter(
128
+ (fieldId) => fieldId !== input.fieldId,
129
+ );
130
+
131
+ await db
132
+ .update(userConfig)
133
+ .set({
134
+ taskCardFieldIdsJson: JSON.stringify(next),
135
+ updatedAt: timestamp(input.now),
136
+ })
137
+ .where(eq(userConfig.ownerEmail, input.ownerEmail));
138
+ }
@@ -0,0 +1,15 @@
1
+ const SAFE_TAB_ID_RE = /^[A-Za-z0-9_-]{1,96}$/;
2
+
3
+ export function normalizeBrowserTabId(value: unknown): string | null {
4
+ if (typeof value !== "string") return null;
5
+ const trimmed = value.trim();
6
+ return SAFE_TAB_ID_RE.test(trimmed) ? trimmed : null;
7
+ }
8
+
9
+ export function appStateKeyForBrowserTab(
10
+ key: string,
11
+ browserTabId: unknown,
12
+ ): string {
13
+ const normalized = normalizeBrowserTabId(browserTabId);
14
+ return normalized ? `${key}:${normalized}` : key;
15
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Coerce booleans from HTTP query strings, CLI flags, or JSON bodies.
3
+ * Returns undefined when the value is absent.
4
+ */
5
+ export function coerceBooleanParam(
6
+ value: string | boolean | null | undefined,
7
+ ): boolean | undefined {
8
+ if (value === undefined || value === null || value === "") return undefined;
9
+ if (value === true || value === "true" || value === "1") return true;
10
+ if (value === false || value === "false" || value === "0") return false;
11
+ return undefined;
12
+ }
13
+
14
+ /** URL/query flag written when Show all is enabled. */
15
+ export const INCLUDE_DONE_QUERY_VALUE = "true";
16
+
17
+ /** Parse includeDone from URL search params; defaults to false when absent. */
18
+ export function parseIncludeDoneParam(
19
+ value: string | null | undefined,
20
+ ): boolean {
21
+ return coerceBooleanParam(value) ?? false;
22
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Max ids accepted by a single bulk or reorder action.
3
+ *
4
+ * Reorder actions require every visible id, so this also caps the size of a
5
+ * list that can be reordered or select-all'd in one call.
6
+ */
7
+ export const BULK_ID_LIMIT = 500;
@@ -0,0 +1,90 @@
1
+ export type AgentListSnapshot<TSummary> = {
2
+ totalCount: number;
3
+ truncated: boolean;
4
+ };
5
+
6
+ export function buildAgentListSnapshot<TItem, TSummary>(
7
+ items: TItem[],
8
+ cap: number,
9
+ toSummary: (item: TItem) => TSummary,
10
+ ): {
11
+ snapshot: TItem[];
12
+ list: AgentListSnapshot<TSummary> & { items: TSummary[] };
13
+ } {
14
+ const snapshot = items.slice(0, cap);
15
+ return {
16
+ snapshot,
17
+ list: {
18
+ totalCount: items.length,
19
+ truncated: items.length > cap,
20
+ items: snapshot.map(toSummary),
21
+ },
22
+ };
23
+ }
24
+
25
+ export async function resolveSelectedFromList<
26
+ TItem extends { id: string },
27
+ TSummary,
28
+ >(input: {
29
+ selectedId: string;
30
+ items: TItem[];
31
+ snapshot: TItem[];
32
+ toSummary: (item: TItem) => TSummary;
33
+ getById: (id: string) => Promise<TItem | null>;
34
+ inSnapshotKey: string;
35
+ }): Promise<Record<string, unknown> | null> {
36
+ const fromSnapshot = input.snapshot.find(
37
+ (item) => item.id === input.selectedId,
38
+ );
39
+ if (fromSnapshot) {
40
+ return {
41
+ ...input.toSummary(fromSnapshot),
42
+ [input.inSnapshotKey]: true,
43
+ };
44
+ }
45
+
46
+ const fromList = input.items.find((item) => item.id === input.selectedId);
47
+ if (fromList) {
48
+ return {
49
+ ...input.toSummary(fromList),
50
+ [input.inSnapshotKey]: false,
51
+ };
52
+ }
53
+
54
+ const fetched = await input.getById(input.selectedId);
55
+ if (!fetched) return null;
56
+
57
+ return {
58
+ ...input.toSummary(fetched),
59
+ [input.inSnapshotKey]: false,
60
+ };
61
+ }
62
+
63
+ export function buildSelectionSnapshot<
64
+ TItem extends { id: string },
65
+ TSummary,
66
+ >(input: {
67
+ selectedIds: string[];
68
+ items: TItem[];
69
+ toSummary: (item: TItem) => TSummary;
70
+ }): {
71
+ selectionMode: true;
72
+ selectedCount: number;
73
+ selectedItems: TSummary[];
74
+ selectedIdsNotInVisibleList: string[];
75
+ } {
76
+ const selectedIdSet = new Set(input.selectedIds);
77
+ const selectedInVisibleList = input.items.filter((item) =>
78
+ selectedIdSet.has(item.id),
79
+ );
80
+ const visibleIdSet = new Set(selectedInVisibleList.map((item) => item.id));
81
+
82
+ return {
83
+ selectionMode: true,
84
+ selectedCount: input.selectedIds.length,
85
+ selectedItems: selectedInVisibleList.map(input.toSummary),
86
+ selectedIdsNotInVisibleList: input.selectedIds.filter(
87
+ (id) => !visibleIdSet.has(id),
88
+ ),
89
+ };
90
+ }
@@ -0,0 +1,104 @@
1
+ /** Agent-facing view names for navigate + view-screen parity. */
2
+ import { INCLUDE_DONE_QUERY_VALUE } from "./boolean-param.js";
3
+
4
+ export const NAV_VIEWS = [
5
+ "tasks",
6
+ "inbox",
7
+ "fields",
8
+ "extensions",
9
+ "team",
10
+ ] as const;
11
+
12
+ export type NavView = (typeof NAV_VIEWS)[number];
13
+
14
+ export const VIEW_ROUTES: Record<NavView, string> = {
15
+ tasks: "/tasks",
16
+ inbox: "/inbox",
17
+ fields: "/fields",
18
+ extensions: "/extensions",
19
+ team: "/team",
20
+ };
21
+
22
+ const NAV_VIEW_ALIAS_NAMES = ["home", "ask"] as const;
23
+
24
+ export type NavViewAlias = (typeof NAV_VIEW_ALIAS_NAMES)[number];
25
+
26
+ export const NAV_VIEW_ALIASES: Record<string, NavView> = {
27
+ home: "tasks",
28
+ ask: "tasks",
29
+ };
30
+
31
+ export const NAV_VIEW_INPUTS = [...NAV_VIEWS, ...NAV_VIEW_ALIAS_NAMES] as const;
32
+
33
+ export type NavViewInput = (typeof NAV_VIEW_INPUTS)[number];
34
+
35
+ export function resolveNavView(view: NavViewInput): NavView {
36
+ return view in NAV_VIEW_ALIASES
37
+ ? NAV_VIEW_ALIASES[view as NavViewAlias]
38
+ : (view as NavView);
39
+ }
40
+
41
+ export interface NavigationState {
42
+ view: NavView;
43
+ path?: string;
44
+ includeDone?: boolean;
45
+ taskId?: string;
46
+ inboxItemId?: string;
47
+ fieldId?: string;
48
+ }
49
+
50
+ export interface NavigateCommand {
51
+ view?: NavView;
52
+ includeDone?: boolean;
53
+ taskId?: string;
54
+ inboxItemId?: string;
55
+ fieldId?: string;
56
+ }
57
+
58
+ /** UI bulk-selection state synced from list views for view-screen. */
59
+ export interface ListSelectionAppState {
60
+ selectionMode: boolean;
61
+ selectedIds: string[];
62
+ }
63
+
64
+ /** UI-selected custom field columns shown on task cards. */
65
+ export interface TaskCardFieldsState {
66
+ fieldIds: string[];
67
+ }
68
+
69
+ export function viewForPath(pathname: string): NavView {
70
+ for (const view of NAV_VIEWS) {
71
+ if (pathname.startsWith(VIEW_ROUTES[view])) return view;
72
+ }
73
+ return "tasks";
74
+ }
75
+
76
+ export function pathForView(view?: NavView): string {
77
+ if (view && view in VIEW_ROUTES) return VIEW_ROUTES[view];
78
+ return VIEW_ROUTES.tasks;
79
+ }
80
+
81
+ export function buildNavigatePath(
82
+ basePath: string,
83
+ command: NavigateCommand,
84
+ current?: Pick<NavigationState, "includeDone">,
85
+ ): string {
86
+ const params = new URLSearchParams();
87
+ if (command.taskId) params.set("task", command.taskId);
88
+ if (command.inboxItemId) params.set("inboxItem", command.inboxItemId);
89
+ if (command.fieldId) params.set("field", command.fieldId);
90
+ const includeDone =
91
+ command.includeDone !== undefined
92
+ ? command.includeDone
93
+ : navigatesToTasks(basePath) && current?.includeDone === true;
94
+ if (includeDone) params.set("includeDone", INCLUDE_DONE_QUERY_VALUE);
95
+ const query = params.toString();
96
+ return query ? `${basePath}?${query}` : basePath;
97
+ }
98
+
99
+ function navigatesToTasks(basePath: string): boolean {
100
+ return (
101
+ basePath === VIEW_ROUTES.tasks ||
102
+ basePath.startsWith(`${VIEW_ROUTES.tasks}/`)
103
+ );
104
+ }
@@ -0,0 +1,2 @@
1
+ export const DEFAULT_TASK_CARD_FIELD_NAMES = ["Priority", "Due date"] as const;
2
+ export const TASK_CARD_FIELD_LIMIT = 3;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * SSR entry point for Nitro.
3
+ * Wraps React Router's request handler so Nitro can use it as a service.
4
+ */
5
+ import { createRequestHandler } from "react-router";
6
+
7
+ const handler = createRequestHandler(
8
+ () => import("virtual:react-router/server-build"),
9
+ );
10
+
11
+ export default {
12
+ async fetch(request: Request) {
13
+ return handler(request);
14
+ },
15
+ };