@agent-native/core 0.123.0 → 0.123.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 (263) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +16 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/cli/templates-meta.ts +12 -0
  5. package/corpus/templates/crm/.agents/skills/agent-native-docs/SKILL.md +115 -0
  6. package/corpus/templates/crm/.agents/skills/agent-native-toolkit/SKILL.md +177 -0
  7. package/corpus/templates/crm/.agents/skills/crm/SKILL.md +205 -0
  8. package/corpus/templates/crm/.agents/skills/customizing-agent-native/SKILL.md +220 -0
  9. package/corpus/templates/crm/.agents/skills/feature-flags/SKILL.md +169 -0
  10. package/corpus/templates/crm/.agents/skills/sharing/SKILL.md +228 -0
  11. package/corpus/templates/crm/.agents/skills/storing-data/SKILL.md +178 -0
  12. package/corpus/templates/crm/.agents/skills/upgrade-agent-native/SKILL.md +122 -0
  13. package/corpus/templates/crm/.env.example +5 -0
  14. package/corpus/templates/crm/AGENTS.md +82 -0
  15. package/corpus/templates/crm/CHANGELOG.md +25 -0
  16. package/corpus/templates/crm/DEVELOPING.md +66 -0
  17. package/corpus/templates/crm/README.md +122 -0
  18. package/corpus/templates/crm/_gitignore +12 -0
  19. package/corpus/templates/crm/actions/_crm-action-utils.ts +98 -0
  20. package/corpus/templates/crm/actions/_crm-attribute-utils.ts +219 -0
  21. package/corpus/templates/crm/actions/_crm-dashboard.ts +69 -0
  22. package/corpus/templates/crm/actions/_crm-data-program-actions.ts +15 -0
  23. package/corpus/templates/crm/actions/_crm-list-utils.ts +775 -0
  24. package/corpus/templates/crm/actions/_crm-signal-utils.ts +79 -0
  25. package/corpus/templates/crm/actions/add-crm-record-to-list.ts +164 -0
  26. package/corpus/templates/crm/actions/apply-crm-proposals.ts +291 -0
  27. package/corpus/templates/crm/actions/archive-crm-attribute.ts +69 -0
  28. package/corpus/templates/crm/actions/attach-call-evidence.ts +170 -0
  29. package/corpus/templates/crm/actions/configure-crm-connection.ts +126 -0
  30. package/corpus/templates/crm/actions/configure-native-crm.ts +44 -0
  31. package/corpus/templates/crm/actions/create-crm-attribute.ts +350 -0
  32. package/corpus/templates/crm/actions/create-crm-list.ts +194 -0
  33. package/corpus/templates/crm/actions/create-crm-record.ts +148 -0
  34. package/corpus/templates/crm/actions/create-crm-signal-tracker.ts +58 -0
  35. package/corpus/templates/crm/actions/delete-crm-saved-view.ts +68 -0
  36. package/corpus/templates/crm/actions/delete-staged-dataset.ts +14 -0
  37. package/corpus/templates/crm/actions/estimate-crm-enrichment.ts +229 -0
  38. package/corpus/templates/crm/actions/find-crm-duplicates.ts +87 -0
  39. package/corpus/templates/crm/actions/get-crm-automation-recipe.ts +36 -0
  40. package/corpus/templates/crm/actions/get-crm-dashboard-panel.ts +35 -0
  41. package/corpus/templates/crm/actions/get-crm-dashboard.ts +14 -0
  42. package/corpus/templates/crm/actions/get-crm-overview.ts +13 -0
  43. package/corpus/templates/crm/actions/get-crm-pipeline-data.ts +61 -0
  44. package/corpus/templates/crm/actions/get-crm-record-page.ts +287 -0
  45. package/corpus/templates/crm/actions/get-crm-record.ts +75 -0
  46. package/corpus/templates/crm/actions/get-crm-workspace.ts +364 -0
  47. package/corpus/templates/crm/actions/install-crm-pipeline-dashboard.ts +81 -0
  48. package/corpus/templates/crm/actions/list-crm-attributes.ts +71 -0
  49. package/corpus/templates/crm/actions/list-crm-connections.ts +68 -0
  50. package/corpus/templates/crm/actions/list-crm-dashboard-revisions.ts +15 -0
  51. package/corpus/templates/crm/actions/list-crm-dashboards.ts +15 -0
  52. package/corpus/templates/crm/actions/list-crm-enrichment-slots.ts +28 -0
  53. package/corpus/templates/crm/actions/list-crm-list-entries.ts +192 -0
  54. package/corpus/templates/crm/actions/list-crm-lists.ts +80 -0
  55. package/corpus/templates/crm/actions/list-crm-proposals.ts +55 -0
  56. package/corpus/templates/crm/actions/list-crm-record-field-history.ts +201 -0
  57. package/corpus/templates/crm/actions/list-crm-record-values.ts +200 -0
  58. package/corpus/templates/crm/actions/list-crm-records.ts +103 -0
  59. package/corpus/templates/crm/actions/list-crm-saved-views.ts +66 -0
  60. package/corpus/templates/crm/actions/list-crm-signal-hits.ts +43 -0
  61. package/corpus/templates/crm/actions/list-crm-signal-trackers.ts +32 -0
  62. package/corpus/templates/crm/actions/list-crm-tasks.ts +22 -0
  63. package/corpus/templates/crm/actions/list-staged-datasets.ts +14 -0
  64. package/corpus/templates/crm/actions/list-workspace-connections.ts +47 -0
  65. package/corpus/templates/crm/actions/manage-crm-attribute-option.ts +272 -0
  66. package/corpus/templates/crm/actions/manage-crm-signal-tracker.ts +87 -0
  67. package/corpus/templates/crm/actions/manage-crm-task.ts +132 -0
  68. package/corpus/templates/crm/actions/merge-crm-records.ts +691 -0
  69. package/corpus/templates/crm/actions/navigate.ts +54 -0
  70. package/corpus/templates/crm/actions/provider-api-catalog.ts +22 -0
  71. package/corpus/templates/crm/actions/provider-api-docs.ts +54 -0
  72. package/corpus/templates/crm/actions/provider-api-request.ts +92 -0
  73. package/corpus/templates/crm/actions/query-staged-dataset.ts +44 -0
  74. package/corpus/templates/crm/actions/record-crm-call-insight.ts +160 -0
  75. package/corpus/templates/crm/actions/record-crm-smart-signal.ts +150 -0
  76. package/corpus/templates/crm/actions/remove-crm-list-entry.ts +79 -0
  77. package/corpus/templates/crm/actions/restore-crm-dashboard-revision.ts +23 -0
  78. package/corpus/templates/crm/actions/review-crm-signal.ts +31 -0
  79. package/corpus/templates/crm/actions/run-crm-attribute-fill.ts +443 -0
  80. package/corpus/templates/crm/actions/run-crm-enrichment.ts +647 -0
  81. package/corpus/templates/crm/actions/run-crm-saved-view-program.ts +103 -0
  82. package/corpus/templates/crm/actions/run-crm-signal-trackers.ts +265 -0
  83. package/corpus/templates/crm/actions/run.ts +7 -0
  84. package/corpus/templates/crm/actions/save-crm-dashboard.ts +51 -0
  85. package/corpus/templates/crm/actions/save-crm-saved-view.ts +296 -0
  86. package/corpus/templates/crm/actions/sync-crm.ts +146 -0
  87. package/corpus/templates/crm/actions/update-crm-attribute.ts +111 -0
  88. package/corpus/templates/crm/actions/update-crm-list-entry.ts +102 -0
  89. package/corpus/templates/crm/actions/update-crm-list.ts +97 -0
  90. package/corpus/templates/crm/actions/update-crm-record.ts +543 -0
  91. package/corpus/templates/crm/actions/view-screen.ts +324 -0
  92. package/corpus/templates/crm/agent-native.app-skill.json +69 -0
  93. package/corpus/templates/crm/app/components/crm/CreateCrmRecordDialog.tsx +158 -0
  94. package/corpus/templates/crm/app/components/crm/CrmDashboardPanel.tsx +78 -0
  95. package/corpus/templates/crm/app/components/crm/CrmSignalsPanel.tsx +220 -0
  96. package/corpus/templates/crm/app/components/crm/IntelligenceSettings.tsx +414 -0
  97. package/corpus/templates/crm/app/components/crm/RecordActions.tsx +648 -0
  98. package/corpus/templates/crm/app/components/crm/RecordGrid.tsx +488 -0
  99. package/corpus/templates/crm/app/components/crm/RecordWorkspace.tsx +232 -0
  100. package/corpus/templates/crm/app/components/crm/SavedViewDataProgram.tsx +93 -0
  101. package/corpus/templates/crm/app/components/crm/Surface.tsx +97 -0
  102. package/corpus/templates/crm/app/components/crm/WorkOverview.tsx +180 -0
  103. package/corpus/templates/crm/app/components/crm/board/CrmBoard.tsx +988 -0
  104. package/corpus/templates/crm/app/components/crm/board/board-model.ts +399 -0
  105. package/corpus/templates/crm/app/components/crm/board/entry-filter.ts +150 -0
  106. package/corpus/templates/crm/app/components/crm/board/view-draft.ts +227 -0
  107. package/corpus/templates/crm/app/components/crm/grid/CrmGrid.tsx +1033 -0
  108. package/corpus/templates/crm/app/components/crm/grid/GridCell.tsx +620 -0
  109. package/corpus/templates/crm/app/components/crm/grid/clipboard.ts +84 -0
  110. package/corpus/templates/crm/app/components/crm/grid/model.ts +304 -0
  111. package/corpus/templates/crm/app/components/crm/grid/navigation.ts +220 -0
  112. package/corpus/templates/crm/app/components/crm/grid/query.ts +214 -0
  113. package/corpus/templates/crm/app/components/crm/record/AttributePanel.tsx +192 -0
  114. package/corpus/templates/crm/app/components/crm/record/FieldHistory.tsx +179 -0
  115. package/corpus/templates/crm/app/components/crm/record/RecordHeader.tsx +103 -0
  116. package/corpus/templates/crm/app/components/crm/record/RecordLists.tsx +235 -0
  117. package/corpus/templates/crm/app/components/crm/record/RecordTabs.tsx +206 -0
  118. package/corpus/templates/crm/app/components/crm/record/field-editors.tsx +245 -0
  119. package/corpus/templates/crm/app/components/crm/record/record-data.ts +603 -0
  120. package/corpus/templates/crm/app/components/crm/settings/AdvancedSettings.tsx +49 -0
  121. package/corpus/templates/crm/app/components/crm/settings/AttributeOptionsEditor.tsx +441 -0
  122. package/corpus/templates/crm/app/components/crm/settings/ConnectionSettings.tsx +169 -0
  123. package/corpus/templates/crm/app/components/crm/settings/FieldsSettings.tsx +935 -0
  124. package/corpus/templates/crm/app/components/crm/settings/ListsSettings.tsx +339 -0
  125. package/corpus/templates/crm/app/components/crm/settings/settings-admin.ts +377 -0
  126. package/corpus/templates/crm/app/components/crm/shared/AttributeValueParts.tsx +69 -0
  127. package/corpus/templates/crm/app/components/crm/shared/attribute-value.ts +591 -0
  128. package/corpus/templates/crm/app/components/layout/CrmCommandMenu.tsx +556 -0
  129. package/corpus/templates/crm/app/components/layout/CrmLayout.tsx +130 -0
  130. package/corpus/templates/crm/app/components/layout/CrmSidebar.tsx +407 -0
  131. package/corpus/templates/crm/app/components/layout/command-palette-search.ts +126 -0
  132. package/corpus/templates/crm/app/components/layout/crm-ui-intents.ts +41 -0
  133. package/corpus/templates/crm/app/components/layout/sidebar-collapse.ts +40 -0
  134. package/corpus/templates/crm/app/components/layout/sidebar-lists.ts +109 -0
  135. package/corpus/templates/crm/app/components/ui/alert-dialog.tsx +1 -0
  136. package/corpus/templates/crm/app/components/ui/badge.tsx +1 -0
  137. package/corpus/templates/crm/app/components/ui/button.tsx +1 -0
  138. package/corpus/templates/crm/app/components/ui/card.tsx +1 -0
  139. package/corpus/templates/crm/app/components/ui/checkbox.tsx +1 -0
  140. package/corpus/templates/crm/app/components/ui/dialog.tsx +1 -0
  141. package/corpus/templates/crm/app/components/ui/dropdown-menu.tsx +1 -0
  142. package/corpus/templates/crm/app/components/ui/hover-card.tsx +1 -0
  143. package/corpus/templates/crm/app/components/ui/input.tsx +1 -0
  144. package/corpus/templates/crm/app/components/ui/label.tsx +1 -0
  145. package/corpus/templates/crm/app/components/ui/popover.tsx +1 -0
  146. package/corpus/templates/crm/app/components/ui/select.tsx +1 -0
  147. package/corpus/templates/crm/app/components/ui/sheet.tsx +1 -0
  148. package/corpus/templates/crm/app/components/ui/skeleton.tsx +1 -0
  149. package/corpus/templates/crm/app/components/ui/switch.tsx +1 -0
  150. package/corpus/templates/crm/app/components/ui/table.tsx +1 -0
  151. package/corpus/templates/crm/app/components/ui/tabs.tsx +1 -0
  152. package/corpus/templates/crm/app/components/ui/textarea.tsx +1 -0
  153. package/corpus/templates/crm/app/components/ui/toolkit-provider.tsx +17 -0
  154. package/corpus/templates/crm/app/components/ui/tooltip.tsx +1 -0
  155. package/corpus/templates/crm/app/design-system.ts +3 -0
  156. package/corpus/templates/crm/app/entry.client.tsx +19 -0
  157. package/corpus/templates/crm/app/entry.server.tsx +10 -0
  158. package/corpus/templates/crm/app/global.css +167 -0
  159. package/corpus/templates/crm/app/hooks/use-keyboard-shortcuts.ts +206 -0
  160. package/corpus/templates/crm/app/hooks/use-navigation-state.ts +48 -0
  161. package/corpus/templates/crm/app/i18n/en-US.ts +684 -0
  162. package/corpus/templates/crm/app/i18n/index.ts +34 -0
  163. package/corpus/templates/crm/app/lib/dashboard.ts +13 -0
  164. package/corpus/templates/crm/app/lib/navigation.ts +17 -0
  165. package/corpus/templates/crm/app/lib/tab-id.ts +1 -0
  166. package/corpus/templates/crm/app/lib/types.ts +172 -0
  167. package/corpus/templates/crm/app/lib/utils.ts +1 -0
  168. package/corpus/templates/crm/app/root.tsx +93 -0
  169. package/corpus/templates/crm/app/routes/_index.tsx +26 -0
  170. package/corpus/templates/crm/app/routes/accounts.tsx +41 -0
  171. package/corpus/templates/crm/app/routes/agent.tsx +5 -0
  172. package/corpus/templates/crm/app/routes/ask.tsx +53 -0
  173. package/corpus/templates/crm/app/routes/dashboard.tsx +118 -0
  174. package/corpus/templates/crm/app/routes/lists.tsx +228 -0
  175. package/corpus/templates/crm/app/routes/opportunities.tsx +41 -0
  176. package/corpus/templates/crm/app/routes/people.tsx +41 -0
  177. package/corpus/templates/crm/app/routes/proposals.tsx +388 -0
  178. package/corpus/templates/crm/app/routes/records.$recordId.tsx +53 -0
  179. package/corpus/templates/crm/app/routes/records.tsx +64 -0
  180. package/corpus/templates/crm/app/routes/settings.tsx +118 -0
  181. package/corpus/templates/crm/app/routes/setup.tsx +308 -0
  182. package/corpus/templates/crm/app/routes/tasks.tsx +252 -0
  183. package/corpus/templates/crm/app/routes/views.tsx +683 -0
  184. package/corpus/templates/crm/app/routes.ts +19 -0
  185. package/corpus/templates/crm/app/vite-env.d.ts +6 -0
  186. package/corpus/templates/crm/changelog/2026-07-26-any-list-or-view-can-be-shown-as-a-board-grouped-by-stage-so.md +6 -0
  187. package/corpus/templates/crm/changelog/2026-07-26-crm-now-finds-likely-duplicate-records-and-tells-you-exactly.md +6 -0
  188. package/corpus/templates/crm/changelog/2026-07-26-enrichment-now-shows-what-a-run-will-cost-before-it-starts-g.md +6 -0
  189. package/corpus/templates/crm/changelog/2026-07-26-lists-turn-any-set-of-records-into-a-pipeline-with-its-own-s.md +6 -0
  190. package/corpus/templates/crm/changelog/2026-07-26-moving-a-record-or-list-entry-into-a-stage-that-was-retired-.md +6 -0
  191. package/corpus/templates/crm/changelog/2026-07-26-opening-a-record-now-shows-one-page-with-its-fields-the-list.md +6 -0
  192. package/corpus/templates/crm/changelog/2026-07-26-press-cmd-k-to-jump-to-any-record-list-view-or-setting-and-u.md +6 -0
  193. package/corpus/templates/crm/changelog/2026-07-26-provider-record-edits-now-show-an-exact-before-after-diff-an.md +6 -0
  194. package/corpus/templates/crm/changelog/2026-07-26-record-page-attribute-labels-now-read-in-full-instead-of-bei.md +6 -0
  195. package/corpus/templates/crm/changelog/2026-07-26-records-now-use-typed-fields-currency-dates-ratings-status-s.md +6 -0
  196. package/corpus/templates/crm/changelog/2026-07-26-retyping-a-value-on-the-record-page-now-replaces-it-instead-.md +6 -0
  197. package/corpus/templates/crm/changelog/2026-07-26-settings-now-has-fields-and-lists-tabs-for-adding-fields-edi.md +6 -0
  198. package/corpus/templates/crm/changelog/2026-07-26-the-proposal-review-now-shows-the-field-that-actually-change.md +6 -0
  199. package/corpus/templates/crm/changelog/2026-07-26-the-record-grid-is-now-a-real-spreadsheet-filter-sort-and-pa.md +6 -0
  200. package/corpus/templates/crm/components.json +20 -0
  201. package/corpus/templates/crm/docs/architecture/crm-contract.md +214 -0
  202. package/corpus/templates/crm/docs/features/README.md +55 -0
  203. package/corpus/templates/crm/docs/features/f1-records-and-fields.mdx +52 -0
  204. package/corpus/templates/crm/docs/features/f2-lists-pipelines-and-views.mdx +48 -0
  205. package/corpus/templates/crm/docs/features/f3-work-and-record-quality.mdx +40 -0
  206. package/corpus/templates/crm/docs/features/f4-evidence-and-chat.mdx +47 -0
  207. package/corpus/templates/crm/learnings.defaults.md +5 -0
  208. package/corpus/templates/crm/netlify.toml +11 -0
  209. package/corpus/templates/crm/package.json +61 -0
  210. package/corpus/templates/crm/public/favicon.svg +1 -0
  211. package/corpus/templates/crm/public/icon-180.svg +1 -0
  212. package/corpus/templates/crm/public/manifest.json +11 -0
  213. package/corpus/templates/crm/react-router.config.ts +7 -0
  214. package/corpus/templates/crm/server/crm/adapter.ts +32 -0
  215. package/corpus/templates/crm/server/crm/crm-field-firewall.ts +64 -0
  216. package/corpus/templates/crm/server/crm/crm-mirror.ts +721 -0
  217. package/corpus/templates/crm/server/crm/field-policy-attributes.ts +48 -0
  218. package/corpus/templates/crm/server/crm/hubspot-adapter.ts +1146 -0
  219. package/corpus/templates/crm/server/crm/native-adapter.ts +1810 -0
  220. package/corpus/templates/crm/server/crm/provider-record-link.ts +264 -0
  221. package/corpus/templates/crm/server/crm/read-through.ts +207 -0
  222. package/corpus/templates/crm/server/crm/salesforce-adapter.ts +1255 -0
  223. package/corpus/templates/crm/server/db/crm-store.ts +1112 -0
  224. package/corpus/templates/crm/server/db/index.ts +191 -0
  225. package/corpus/templates/crm/server/db/schema.ts +647 -0
  226. package/corpus/templates/crm/server/lib/crm-query.ts +1558 -0
  227. package/corpus/templates/crm/server/lib/dedupe.ts +714 -0
  228. package/corpus/templates/crm/server/lib/enrichment-cost.ts +381 -0
  229. package/corpus/templates/crm/server/lib/enrichment-slots.ts +519 -0
  230. package/corpus/templates/crm/server/lib/intelligence/default-detectors.ts +65 -0
  231. package/corpus/templates/crm/server/lib/intelligence/evidence.ts +105 -0
  232. package/corpus/templates/crm/server/lib/intelligence/keyword-detector.ts +94 -0
  233. package/corpus/templates/crm/server/lib/intelligence/smart-detector.ts +39 -0
  234. package/corpus/templates/crm/server/lib/intelligence/summary.ts +19 -0
  235. package/corpus/templates/crm/server/lib/lifecycle.ts +634 -0
  236. package/corpus/templates/crm/server/lib/provider-api.ts +64 -0
  237. package/corpus/templates/crm/server/middleware/auth.ts +4 -0
  238. package/corpus/templates/crm/server/plugins/agent-chat.ts +65 -0
  239. package/corpus/templates/crm/server/plugins/auth.ts +14 -0
  240. package/corpus/templates/crm/server/plugins/core-routes.ts +17 -0
  241. package/corpus/templates/crm/server/plugins/db.ts +738 -0
  242. package/corpus/templates/crm/server/routes/[...page].get.ts +5 -0
  243. package/corpus/templates/crm/shared/crm-attributes.ts +469 -0
  244. package/corpus/templates/crm/shared/crm-automation-recipes.ts +132 -0
  245. package/corpus/templates/crm/shared/crm-contract.ts +417 -0
  246. package/corpus/templates/crm/shared/crm-navigation.ts +190 -0
  247. package/corpus/templates/crm/shared/crm-sales-config.ts +63 -0
  248. package/corpus/templates/crm/ssr-entry.ts +11 -0
  249. package/corpus/templates/crm/tsconfig.json +24 -0
  250. package/corpus/templates/crm/vite.config.ts +15 -0
  251. package/corpus/templates/crm/vitest.config.ts +5 -0
  252. package/corpus/templates/design/app/i18n-data.ts +12 -12
  253. package/corpus/templates/design/changelog/2026-07-26-apply-design-updates-from-a-centered-top-toolbar-without-cov.md +6 -0
  254. package/dist/cli/templates-meta.d.ts.map +1 -1
  255. package/dist/cli/templates-meta.js +12 -0
  256. package/dist/cli/templates-meta.js.map +1 -1
  257. package/dist/collab/routes.d.ts +1 -1
  258. package/dist/observability/routes.d.ts +3 -3
  259. package/dist/resources/handlers.d.ts +1 -1
  260. package/dist/server/realtime-token.d.ts +1 -1
  261. package/dist/server/transcribe-voice.d.ts +1 -1
  262. package/package.json +1 -1
  263. package/src/cli/templates-meta.ts +12 -0
@@ -0,0 +1,232 @@
1
+ /**
2
+ * The record page: attribute panel on the left, tabs in the middle, list
3
+ * memberships and signals on the right.
4
+ *
5
+ * The page reads two actions on purpose. `get-crm-record` is the one that
6
+ * verifies provider read-through permission for a mirrored record and carries
7
+ * evidence, tasks, and relationships; `get-crm-record-page` carries the typed
8
+ * attribute schema, the current bitemporal values, and list memberships. The
9
+ * second does not replace the first.
10
+ */
11
+
12
+ import { setClientAppState } from "@agent-native/core/client/application-state";
13
+ import {
14
+ useActionMutation,
15
+ useActionQuery,
16
+ } from "@agent-native/core/client/hooks";
17
+ import { useT } from "@agent-native/core/client/i18n";
18
+ import { useQueryClient } from "@tanstack/react-query";
19
+ import { useEffect, useState } from "react";
20
+ import { toast } from "sonner";
21
+
22
+ import { CrmSignalsPanel } from "@/components/crm/CrmSignalsPanel";
23
+ import { AttributePanel } from "@/components/crm/record/AttributePanel";
24
+ import {
25
+ applyEntryValue,
26
+ applyFieldValue,
27
+ rollbackEntryValue,
28
+ rollbackFieldValue,
29
+ type CrmRecordPage,
30
+ type CrmRecordPageListAttribute,
31
+ type RecordTab,
32
+ } from "@/components/crm/record/record-data";
33
+ import { RecordHeader } from "@/components/crm/record/RecordHeader";
34
+ import { RecordLists } from "@/components/crm/record/RecordLists";
35
+ import { RecordTabs } from "@/components/crm/record/RecordTabs";
36
+ import { LoadingRows, SetupEmptyState } from "@/components/crm/Surface";
37
+ import { TAB_ID } from "@/lib/tab-id";
38
+ import type { CrmRecordDetail } from "@/lib/types";
39
+
40
+ import type {
41
+ CrmAttributeDefinition,
42
+ CrmValue,
43
+ } from "../../../shared/crm-contract";
44
+
45
+ const RECORD_APP_STATE_KEY = "crm-record-workspace";
46
+
47
+ export function RecordWorkspace({
48
+ record,
49
+ isLoading,
50
+ onCompleteTask,
51
+ isCompletingTask,
52
+ actions,
53
+ }: {
54
+ record: CrmRecordDetail | undefined;
55
+ isLoading: boolean;
56
+ onCompleteTask: (taskId: string) => void;
57
+ isCompletingTask: boolean;
58
+ actions?: React.ReactNode;
59
+ }) {
60
+ const t = useT();
61
+ const [tab, setTab] = useState<RecordTab>("activity");
62
+ const queryClient = useQueryClient();
63
+ const recordId = record?.id;
64
+
65
+ const pageParams = { recordId };
66
+ const pageQuery = useActionQuery<CrmRecordPage>(
67
+ "get-crm-record-page" as never,
68
+ pageParams as never,
69
+ { enabled: Boolean(recordId) },
70
+ );
71
+ const pageKey = ["action", "get-crm-record-page", pageParams] as const;
72
+
73
+ const updateRecord = useActionMutation<
74
+ { mutationId: string; status?: string },
75
+ {
76
+ recordId: string;
77
+ target: "local";
78
+ fields: Record<string, unknown>;
79
+ expectedRemoteRevision?: string;
80
+ }
81
+ >("update-crm-record" as never);
82
+ const updateEntry = useActionMutation<
83
+ unknown,
84
+ { entryId: string; values: Record<string, unknown> }
85
+ >("update-crm-list-entry" as never);
86
+
87
+ // The agent needs to know which record is open and which tab is showing.
88
+ useEffect(() => {
89
+ if (!recordId) return;
90
+ void setClientAppState(
91
+ `${RECORD_APP_STATE_KEY}:${TAB_ID}`,
92
+ { recordId, tab },
93
+ { requestSource: TAB_ID },
94
+ );
95
+ }, [recordId, tab]);
96
+
97
+ async function commitField(
98
+ attribute: CrmAttributeDefinition,
99
+ value: CrmValue,
100
+ ) {
101
+ const current = queryClient.getQueryData<CrmRecordPage>(pageKey);
102
+ if (!current) return;
103
+ const { page: optimistic, edit } = applyFieldValue(
104
+ current,
105
+ attribute.apiSlug,
106
+ value,
107
+ { since: new Date().toISOString(), actorType: "user", actorId: null },
108
+ );
109
+ queryClient.setQueryData(pageKey, optimistic);
110
+ try {
111
+ await updateRecord.mutateAsync({
112
+ recordId: current.record.id,
113
+ target: "local",
114
+ fields: { [attribute.apiSlug]: value },
115
+ ...(current.record.remoteRevision
116
+ ? { expectedRemoteRevision: current.record.remoteRevision }
117
+ : {}),
118
+ });
119
+ } catch (error) {
120
+ queryClient.setQueryData<CrmRecordPage>(pageKey, (previous) =>
121
+ rollbackFieldValue(previous ?? optimistic, edit),
122
+ );
123
+ toast.error(
124
+ error instanceof Error ? error.message : t("record.saveFailed"),
125
+ );
126
+ }
127
+ }
128
+
129
+ async function commitEntryValue(
130
+ entryId: string,
131
+ attribute: CrmRecordPageListAttribute,
132
+ value: CrmValue,
133
+ ) {
134
+ const current = queryClient.getQueryData<CrmRecordPage>(pageKey);
135
+ if (!current) return;
136
+ const { page: optimistic, previousValue } = applyEntryValue(
137
+ current,
138
+ entryId,
139
+ attribute.apiSlug,
140
+ value,
141
+ );
142
+ queryClient.setQueryData(pageKey, optimistic);
143
+ try {
144
+ await updateEntry.mutateAsync({
145
+ entryId,
146
+ values: { [attribute.apiSlug]: value },
147
+ });
148
+ } catch (error) {
149
+ queryClient.setQueryData<CrmRecordPage>(pageKey, (previous) =>
150
+ rollbackEntryValue(
151
+ previous ?? optimistic,
152
+ entryId,
153
+ attribute.apiSlug,
154
+ previousValue,
155
+ ),
156
+ );
157
+ toast.error(
158
+ error instanceof Error ? error.message : t("record.saveFailed"),
159
+ );
160
+ }
161
+ }
162
+
163
+ if (isLoading) return <LoadingRows rows={8} />;
164
+ if (!record)
165
+ return (
166
+ <SetupEmptyState
167
+ title={t("record.unavailableTitle")}
168
+ description={t("record.unavailableDescription")}
169
+ />
170
+ );
171
+
172
+ const page = pageQuery.data;
173
+
174
+ return (
175
+ <div className="flex min-h-full flex-col">
176
+ <RecordHeader
177
+ record={record}
178
+ recordUrl={page?.recordUrl ?? null}
179
+ recordUrlUnavailableReason={page?.recordUrlUnavailableReason ?? null}
180
+ actions={actions}
181
+ />
182
+ <div className="flex min-h-0 flex-1 flex-col lg:flex-row">
183
+ {/* A failed panel load is a visible error, not an empty attribute list:
184
+ "this record has no attributes" and "we could not read them" are
185
+ different answers and must not render the same. */}
186
+ {pageQuery.error ? (
187
+ <aside className="w-full border-border/70 p-5 lg:w-[22rem] lg:shrink-0 lg:border-r">
188
+ <p className="text-sm text-destructive">
189
+ {pageQuery.error instanceof Error
190
+ ? pageQuery.error.message
191
+ : t("record.panelLoadFailed")}
192
+ </p>
193
+ </aside>
194
+ ) : page ? (
195
+ <AttributePanel page={page} onCommit={commitField} />
196
+ ) : (
197
+ <aside className="w-full border-border/70 p-5 lg:w-[22rem] lg:shrink-0 lg:border-r">
198
+ <LoadingRows rows={6} />
199
+ </aside>
200
+ )}
201
+
202
+ <div className="min-w-0 flex-1">
203
+ <RecordTabs
204
+ record={record}
205
+ tab={tab}
206
+ onTabChange={setTab}
207
+ onCompleteTask={onCompleteTask}
208
+ isCompletingTask={isCompletingTask}
209
+ />
210
+ </div>
211
+
212
+ <div className="grid w-full content-start gap-6 border-border/70 p-5 lg:w-[22rem] lg:shrink-0 lg:border-l">
213
+ {page ? (
214
+ <RecordLists page={page} onEntryCommit={commitEntryValue} />
215
+ ) : null}
216
+ <section aria-labelledby="crm-record-signals">
217
+ <h2
218
+ id="crm-record-signals"
219
+ className="mb-2 text-xs font-medium uppercase tracking-[0.12em] text-muted-foreground"
220
+ >
221
+ {t("record.signals")}
222
+ </h2>
223
+ <CrmSignalsPanel
224
+ recordId={record.id}
225
+ evidence={record.evidence ?? []}
226
+ />
227
+ </section>
228
+ </div>
229
+ </div>
230
+ </div>
231
+ );
232
+ }
@@ -0,0 +1,93 @@
1
+ import { useActionMutation } from "@agent-native/core/client/hooks";
2
+ import { DataTable } from "@agent-native/toolkit/dashboard";
3
+ import { IconChartDots } from "@tabler/icons-react";
4
+ import { useEffect, useRef } from "react";
5
+
6
+ import { Badge } from "@/components/ui/badge";
7
+
8
+ interface ProgramPreview {
9
+ ok: boolean;
10
+ rowCount?: number;
11
+ columns?: Array<{ name: string; type: string }>;
12
+ sampleRows?: Array<Record<string, unknown>>;
13
+ asOfMs?: number;
14
+ cacheHit?: boolean;
15
+ stale?: boolean;
16
+ truncated?: boolean;
17
+ message?: string;
18
+ lastGoodRun?: {
19
+ rowCount: number;
20
+ columns: Array<{ name: string; type: string }>;
21
+ sampleRows: Array<Record<string, unknown>>;
22
+ truncated: boolean;
23
+ asOfMs: number;
24
+ };
25
+ }
26
+
27
+ export function SavedViewDataProgram({ data }: { data: unknown }) {
28
+ const viewId = linkedProgramViewId(data);
29
+ const lastRunViewId = useRef<string | undefined>(undefined);
30
+ const run = useActionMutation<ProgramPreview, { viewId: string }>(
31
+ "run-crm-saved-view-program" as never,
32
+ );
33
+
34
+ useEffect(() => {
35
+ if (!viewId || lastRunViewId.current === viewId) return;
36
+ lastRunViewId.current = viewId;
37
+ run.mutate({ viewId });
38
+ }, [run, viewId]);
39
+
40
+ if (!viewId) return null;
41
+ const preview = run.data?.ok ? run.data : run.data?.lastGoodRun;
42
+ return (
43
+ <section className="mx-5 mt-5 rounded-lg border border-border/70 bg-card p-4 sm:mx-7">
44
+ <div className="flex flex-wrap items-start justify-between gap-3">
45
+ <div className="flex items-start gap-3">
46
+ <IconChartDots className="mt-0.5 size-4 text-muted-foreground" />
47
+ <div>
48
+ <h2 className="text-sm font-semibold">Cross-source context</h2>
49
+ <p className="mt-1 text-sm text-muted-foreground">
50
+ {run.isPending
51
+ ? "Running the saved data program…"
52
+ : run.isError
53
+ ? "The saved data program is unavailable."
54
+ : run.data?.ok
55
+ ? `${run.data.rowCount ?? 0} rows from the linked data program.`
56
+ : run.data?.message ||
57
+ "Showing the last good program result."}
58
+ </p>
59
+ </div>
60
+ </div>
61
+ {preview ? (
62
+ <div className="flex gap-2">
63
+ {preview.truncated ? (
64
+ <Badge variant="outline">Truncated</Badge>
65
+ ) : null}
66
+ {run.data?.ok && run.data.cacheHit ? (
67
+ <Badge variant="secondary">Cached</Badge>
68
+ ) : null}
69
+ </div>
70
+ ) : null}
71
+ </div>
72
+ {preview?.sampleRows?.length ? (
73
+ <div className="mt-4 overflow-hidden rounded-md border border-border/70">
74
+ <DataTable
75
+ data={preview.sampleRows.slice(0, 5)}
76
+ columns={preview.columns?.slice(0, 8).map((column) => column.name)}
77
+ maxRows={5}
78
+ />
79
+ </div>
80
+ ) : null}
81
+ </section>
82
+ );
83
+ }
84
+
85
+ function linkedProgramViewId(data: unknown) {
86
+ if (!data || typeof data !== "object") return undefined;
87
+ const appliedView = (data as { appliedView?: unknown }).appliedView;
88
+ if (!appliedView || typeof appliedView !== "object") return undefined;
89
+ const view = appliedView as Record<string, unknown>;
90
+ return typeof view.id === "string" && typeof view.dataProgramId === "string"
91
+ ? view.id
92
+ : undefined;
93
+ }
@@ -0,0 +1,97 @@
1
+ import {
2
+ IconArrowUpRight,
3
+ IconDatabase,
4
+ IconDatabaseOff,
5
+ } from "@tabler/icons-react";
6
+ import { Link } from "react-router";
7
+
8
+ import { Button } from "@/components/ui/button";
9
+ import { Skeleton } from "@/components/ui/skeleton";
10
+
11
+ export function PageHeader({
12
+ eyebrow,
13
+ title,
14
+ description,
15
+ actions,
16
+ }: {
17
+ eyebrow?: string;
18
+ title: string;
19
+ description?: string;
20
+ actions?: React.ReactNode;
21
+ }) {
22
+ return (
23
+ <header className="flex flex-col gap-4 border-b border-border/70 px-5 py-5 sm:flex-row sm:items-end sm:justify-between sm:px-7">
24
+ <div className="min-w-0">
25
+ {eyebrow ? (
26
+ <p className="mb-1 text-xs font-medium uppercase tracking-[0.12em] text-muted-foreground">
27
+ {eyebrow}
28
+ </p>
29
+ ) : null}
30
+ <h1 className="text-xl font-semibold tracking-tight">{title}</h1>
31
+ {description ? (
32
+ <p className="mt-1 max-w-2xl text-sm leading-6 text-muted-foreground">
33
+ {description}
34
+ </p>
35
+ ) : null}
36
+ </div>
37
+ {actions ? (
38
+ <div className="flex shrink-0 items-center gap-2">{actions}</div>
39
+ ) : null}
40
+ </header>
41
+ );
42
+ }
43
+
44
+ export function LoadingRows({ rows = 5 }: { rows?: number }) {
45
+ return (
46
+ <div className="grid gap-2 p-5 sm:p-7">
47
+ {Array.from({ length: rows }, (_, index) => (
48
+ <Skeleton key={index} className="h-12 w-full" />
49
+ ))}
50
+ </div>
51
+ );
52
+ }
53
+
54
+ export function SetupEmptyState({
55
+ title = "Start a CRM to begin",
56
+ description = "Start with the built-in SQL CRM, or connect a scoped workspace CRM. No provider credentials are stored in this app.",
57
+ onSync,
58
+ isSyncing = false,
59
+ actionLabel,
60
+ }: {
61
+ title?: string;
62
+ description?: string;
63
+ onSync?: () => void;
64
+ isSyncing?: boolean;
65
+ actionLabel?: string;
66
+ }) {
67
+ return (
68
+ <div className="grid min-h-[360px] place-items-center p-6">
69
+ <div className="max-w-sm text-center">
70
+ <div className="mx-auto grid size-10 place-items-center rounded-full bg-muted text-muted-foreground">
71
+ <IconDatabaseOff className="size-5" />
72
+ </div>
73
+ <h2 className="mt-4 text-base font-semibold">{title}</h2>
74
+ <p className="mt-2 text-sm leading-6 text-muted-foreground">
75
+ {description}
76
+ </p>
77
+ <div className="mt-5 flex flex-wrap justify-center gap-2">
78
+ <Button asChild className="gap-2">
79
+ <Link to="/setup">
80
+ <IconDatabase className="size-4" /> Start with Native SQL
81
+ </Link>
82
+ </Button>
83
+ <Button asChild variant="outline" className="gap-2">
84
+ <Link to="/settings/connections">
85
+ Open shared connections <IconArrowUpRight className="size-4" />
86
+ </Link>
87
+ </Button>
88
+ {onSync ? (
89
+ <Button onClick={onSync} disabled={isSyncing}>
90
+ {isSyncing ? "Working…" : (actionLabel ?? "Sync recent records")}
91
+ </Button>
92
+ ) : null}
93
+ </div>
94
+ </div>
95
+ </div>
96
+ );
97
+ }
@@ -0,0 +1,180 @@
1
+ import { IconArrowRight, IconCheck, IconListCheck } from "@tabler/icons-react";
2
+ import { Link } from "react-router";
3
+
4
+ import { LoadingRows, SetupEmptyState } from "@/components/crm/Surface";
5
+ import { Badge } from "@/components/ui/badge";
6
+ import { Button } from "@/components/ui/button";
7
+ import {
8
+ normalizeRecords,
9
+ normalizeTasks,
10
+ type CrmOverview,
11
+ } from "@/lib/types";
12
+
13
+ export function WorkOverview({
14
+ overview,
15
+ isLoading,
16
+ }: {
17
+ overview: CrmOverview | undefined;
18
+ isLoading: boolean;
19
+ }) {
20
+ const tasks = normalizeTasks(overview?.tasks);
21
+ const records = normalizeRecords(overview?.records, "account");
22
+ const focus = overview?.focus ?? [];
23
+
24
+ if (isLoading) return <LoadingRows rows={7} />;
25
+ if (!overview) return <SetupEmptyState />;
26
+
27
+ return (
28
+ <div className="mx-auto grid max-w-6xl gap-8 p-5 sm:p-7 xl:grid-cols-[minmax(0,1fr)_320px]">
29
+ <div className="min-w-0">
30
+ <section>
31
+ <div className="flex items-center justify-between gap-3">
32
+ <div>
33
+ <h2 className="text-sm font-semibold">Today’s work</h2>
34
+ <p className="mt-1 text-sm text-muted-foreground">
35
+ A focused queue across your CRM workspace.
36
+ </p>
37
+ </div>
38
+ <Button
39
+ asChild
40
+ variant="ghost"
41
+ size="sm"
42
+ className="gap-1.5 text-muted-foreground"
43
+ >
44
+ <Link to="/views">
45
+ Saved views <IconArrowRight className="size-3.5" />
46
+ </Link>
47
+ </Button>
48
+ </div>
49
+ <div className="mt-3 divide-y divide-border rounded-lg border border-border/70 bg-card">
50
+ {tasks.length ? (
51
+ tasks.map((task) => (
52
+ <div
53
+ key={task.id}
54
+ className="flex min-h-14 items-center gap-3 px-3.5 py-2.5"
55
+ >
56
+ <span className="grid size-5 shrink-0 place-items-center rounded-full border border-border text-muted-foreground">
57
+ <IconCheck className="size-3" />
58
+ </span>
59
+ <div className="min-w-0 flex-1">
60
+ <p className="truncate text-sm font-medium">{task.title}</p>
61
+ <p className="mt-0.5 text-xs text-muted-foreground">
62
+ {task.dueAt
63
+ ? `Due ${formatDate(task.dueAt)}`
64
+ : "No due date"}
65
+ </p>
66
+ </div>
67
+ {task.recordId ? (
68
+ <Button asChild variant="ghost" size="sm">
69
+ <Link
70
+ to={`/records/${encodeURIComponent(task.recordId)}`}
71
+ >
72
+ Open
73
+ </Link>
74
+ </Button>
75
+ ) : null}
76
+ </div>
77
+ ))
78
+ ) : (
79
+ <QuietEmpty
80
+ icon={<IconListCheck className="size-4" />}
81
+ text="No open CRM tasks right now."
82
+ />
83
+ )}
84
+ </div>
85
+ </section>
86
+
87
+ <section className="mt-8">
88
+ <div className="flex items-center justify-between">
89
+ <h2 className="text-sm font-semibold">Recently active</h2>
90
+ <Button
91
+ asChild
92
+ variant="ghost"
93
+ size="sm"
94
+ className="gap-1.5 text-muted-foreground"
95
+ >
96
+ <Link to="/accounts">
97
+ Accounts <IconArrowRight className="size-3.5" />
98
+ </Link>
99
+ </Button>
100
+ </div>
101
+ <div className="mt-3 divide-y divide-border rounded-lg border border-border/70 bg-card">
102
+ {records.length ? (
103
+ records.slice(0, 5).map((record) => (
104
+ <Link
105
+ key={record.id}
106
+ to={`/records/${encodeURIComponent(record.id)}`}
107
+ className="flex items-center gap-3 px-3.5 py-3 transition-colors hover:bg-muted/50"
108
+ >
109
+ <div className="min-w-0 flex-1">
110
+ <p className="truncate text-sm font-medium">
111
+ {record.displayName}
112
+ </p>
113
+ <p className="mt-0.5 truncate text-xs text-muted-foreground">
114
+ {record.subtitle ?? record.owner ?? "CRM record"}
115
+ </p>
116
+ </div>
117
+ {record.stage ? (
118
+ <Badge variant="secondary" className="font-normal">
119
+ {record.stage}
120
+ </Badge>
121
+ ) : null}
122
+ </Link>
123
+ ))
124
+ ) : (
125
+ <QuietEmpty text="Recent activity will appear as CRM records are added or synced." />
126
+ )}
127
+ </div>
128
+ </section>
129
+ </div>
130
+
131
+ <aside className="self-start border-t border-border/70 pt-5 xl:border-l xl:border-t-0 xl:pl-7 xl:pt-0">
132
+ <h2 className="text-sm font-semibold">Focus</h2>
133
+ <p className="mt-1 text-sm text-muted-foreground">
134
+ Signals worth deciding on.
135
+ </p>
136
+ <div className="mt-4 grid gap-3">
137
+ {focus.length ? (
138
+ focus.map((item) => (
139
+ <div
140
+ key={item.label}
141
+ className="border-b border-border/70 pb-3 last:border-0"
142
+ >
143
+ <p className="text-xs font-medium uppercase tracking-[0.1em] text-muted-foreground">
144
+ {item.label}
145
+ </p>
146
+ <p className="mt-1 text-sm font-medium">{item.value}</p>
147
+ {item.detail ? (
148
+ <p className="mt-1 text-xs leading-5 text-muted-foreground">
149
+ {item.detail}
150
+ </p>
151
+ ) : null}
152
+ </div>
153
+ ))
154
+ ) : (
155
+ <p className="text-sm leading-6 text-muted-foreground">
156
+ CRM will surface follow-up signals as records and interactions
157
+ become available.
158
+ </p>
159
+ )}
160
+ </div>
161
+ </aside>
162
+ </div>
163
+ );
164
+ }
165
+
166
+ function QuietEmpty({ icon, text }: { icon?: React.ReactNode; text: string }) {
167
+ return (
168
+ <div className="flex min-h-20 items-center justify-center gap-2 px-4 text-sm text-muted-foreground">
169
+ {icon}
170
+ {text}
171
+ </div>
172
+ );
173
+ }
174
+
175
+ function formatDate(value: string) {
176
+ const date = new Date(value);
177
+ return Number.isNaN(date.valueOf())
178
+ ? value
179
+ : date.toLocaleDateString(undefined, { month: "short", day: "numeric" });
180
+ }