@graph8/sdk 0.362.0 → 0.373.0

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 (615) hide show
  1. package/README.md +33 -10
  2. package/blocks/_foundation/README.md +78 -1
  3. package/blocks/_foundation/block.json +123 -6
  4. package/blocks/_foundation/components/api-error.tsx +9 -7
  5. package/blocks/_foundation/components/brand-provider.tsx +23 -0
  6. package/blocks/_foundation/components/confirm-spend-dialog.tsx +15 -8
  7. package/blocks/_foundation/components/confirmed-action.tsx +90 -0
  8. package/blocks/_foundation/components/connect-graph8.tsx +280 -0
  9. package/blocks/_foundation/components/empty-state.tsx +5 -2
  10. package/blocks/_foundation/components/list-select.tsx +112 -0
  11. package/blocks/_foundation/components/paging-notices.tsx +55 -0
  12. package/blocks/_foundation/components/screen/detail-panel.tsx +2 -1
  13. package/blocks/_foundation/components/screen/filter-controls.tsx +92 -0
  14. package/blocks/_foundation/components/screen/index.ts +2 -0
  15. package/blocks/_foundation/components/screen/record-link.tsx +76 -0
  16. package/blocks/_foundation/components/screen/split-view.tsx +1 -1
  17. package/blocks/_foundation/components/screen/time-series-chart-recharts.tsx +65 -0
  18. package/blocks/_foundation/components/screen/time-series-chart.tsx +15 -97
  19. package/blocks/_foundation/components/screen/time-series-format.ts +57 -0
  20. package/blocks/_foundation/hooks/use-all-pages.ts +54 -0
  21. package/blocks/_foundation/hooks/use-brand.ts +52 -0
  22. package/blocks/_foundation/hooks/use-g8-mutation.ts +23 -0
  23. package/blocks/_foundation/hooks/use-g8-query.ts +127 -1
  24. package/blocks/_foundation/hooks/use-load-more-list.ts +61 -0
  25. package/blocks/_foundation/hooks/use-paginated.ts +120 -0
  26. package/blocks/_foundation/hooks/use-row-activation.ts +57 -0
  27. package/blocks/_foundation/hooks/use-url-filters.ts +93 -0
  28. package/blocks/_foundation/lib/g8-cache.ts +454 -0
  29. package/blocks/_foundation/lib/g8-client.ts +1 -1
  30. package/blocks/_foundation/lib/paging.ts +57 -0
  31. package/blocks/_foundation/lib/plural.ts +23 -0
  32. package/blocks/_foundation/lib/use-debounced-value.ts +13 -0
  33. package/blocks/_foundation/server/connect-route.ts +15 -0
  34. package/blocks/_foundation/server/connect.ts +127 -0
  35. package/blocks/_foundation/server/local-key.ts +70 -0
  36. package/blocks/_foundation/server/proxy.ts +103 -3
  37. package/blocks/_foundation/server/route.ts +16 -4
  38. package/blocks/agents/README.md +93 -2
  39. package/blocks/agents/block.json +343 -2
  40. package/blocks/agents/components/agent-activity.tsx +112 -0
  41. package/blocks/agents/components/agent-approvals.tsx +135 -0
  42. package/blocks/agents/components/agent-builder.tsx +29 -10
  43. package/blocks/agents/components/agent-conversations.tsx +143 -0
  44. package/blocks/agents/components/agent-knowledge-panel.tsx +4 -3
  45. package/blocks/agents/components/agent-operators.tsx +164 -0
  46. package/blocks/agents/components/agent-run-detail.tsx +119 -0
  47. package/blocks/agents/components/agent-runs.tsx +19 -10
  48. package/blocks/agents/components/agent-skills-panel.tsx +7 -7
  49. package/blocks/agents/components/agent-test-history.tsx +121 -0
  50. package/blocks/agents/components/agents-screen.tsx +226 -0
  51. package/blocks/agents/components/assistant-sessions.tsx +150 -0
  52. package/blocks/agents/components/duplicate-agent-button.tsx +42 -0
  53. package/blocks/agents/components/from-template-button.tsx +65 -0
  54. package/blocks/agents/components/knowledge-browser.tsx +107 -0
  55. package/blocks/agents/components/knowledge-detail.tsx +156 -0
  56. package/blocks/agents/components/knowledge-form.tsx +69 -0
  57. package/blocks/agents/components/name-dialog.tsx +32 -0
  58. package/blocks/agents/components/receptionist-button.tsx +46 -0
  59. package/blocks/agents/components/save-as-template-button.tsx +32 -0
  60. package/blocks/agents/components/skill-editor.tsx +147 -0
  61. package/blocks/agents/components/skill-library.tsx +111 -0
  62. package/blocks/agents/lib/agent-types.ts +7 -1
  63. package/blocks/agents/lib/facts.tsx +13 -0
  64. package/blocks/agents/lib/knowledge-utils.ts +49 -0
  65. package/blocks/agents/lib/load-more.tsx +141 -0
  66. package/blocks/agents/lib/skill-utils.ts +91 -0
  67. package/blocks/agents/recipes/create-agent-and-attach-skill.ts +1 -1
  68. package/blocks/analytics/README.md +13 -0
  69. package/blocks/analytics/block.json +45 -1
  70. package/blocks/analytics/components/analytics-screen.tsx +222 -0
  71. package/blocks/analytics/components/kpi-tiles.tsx +1 -1
  72. package/blocks/analytics/components/leaderboard.tsx +14 -7
  73. package/blocks/analytics/components/report-builder.tsx +38 -11
  74. package/blocks/analytics/components/report-detail.tsx +84 -0
  75. package/blocks/analytics/components/trend-chart.tsx +62 -0
  76. package/blocks/appointments/README.md +6 -0
  77. package/blocks/appointments/block.json +33 -2
  78. package/blocks/appointments/components/availability-editor.tsx +17 -5
  79. package/blocks/appointments/components/booking-page.tsx +2 -2
  80. package/blocks/appointments/components/event-type-editor.tsx +2 -1
  81. package/blocks/appointments/components/event-type-related.tsx +4 -2
  82. package/blocks/appointments/components/meeting-contact-links.tsx +49 -0
  83. package/blocks/appointments/components/meeting-detail.tsx +2 -2
  84. package/blocks/appointments/components/meeting-list.tsx +38 -18
  85. package/blocks/appointments/components/meetings-screen.tsx +7 -4
  86. package/blocks/appointments/components/record-meetings-tab.tsx +92 -0
  87. package/blocks/appointments/components/scheduling-screen.tsx +2 -1
  88. package/blocks/block.schema.json +18 -0
  89. package/blocks/block.types.ts +17 -0
  90. package/blocks/campaigns/README.md +41 -11
  91. package/blocks/campaigns/block.json +105 -42
  92. package/blocks/campaigns/components/campaign-assets.tsx +17 -6
  93. package/blocks/campaigns/components/campaign-launch-step.tsx +114 -26
  94. package/blocks/campaigns/components/campaign-list.tsx +7 -3
  95. package/blocks/campaigns/components/campaign-metrics.tsx +1 -1
  96. package/blocks/campaigns/components/campaign-wizard.tsx +52 -14
  97. package/blocks/campaigns/components/campaigns-screen.tsx +267 -0
  98. package/blocks/campaigns/components/icp-detail.tsx +163 -0
  99. package/blocks/campaigns/components/icp-editor.tsx +22 -17
  100. package/blocks/campaigns/components/persona-detail.tsx +181 -0
  101. package/blocks/campaigns/components/persona-editor.tsx +19 -11
  102. package/blocks/campaigns/lib/campaigns-utils.ts +138 -5
  103. package/blocks/campaigns/recipes/attach-audience-and-launch.ts +16 -3
  104. package/blocks/campaigns/recipes/generate-ideas-and-create-campaign.ts +8 -4
  105. package/blocks/coach/README.md +10 -0
  106. package/blocks/coach/block.json +16 -1
  107. package/blocks/coach/components/call-scorecard.tsx +7 -7
  108. package/blocks/coach/components/coach-screen.tsx +222 -0
  109. package/blocks/company-intelligence/README.md +132 -0
  110. package/blocks/company-intelligence/block.json +318 -0
  111. package/blocks/company-intelligence/components/company-intelligence-screen.tsx +114 -0
  112. package/blocks/company-intelligence/components/company-intelligence-tab.tsx +88 -0
  113. package/blocks/company-intelligence/components/company-profile.tsx +68 -0
  114. package/blocks/company-intelligence/components/intelligence-sources.tsx +63 -0
  115. package/blocks/company-intelligence/components/report-detail.tsx +189 -0
  116. package/blocks/company-intelligence/components/research-reports.tsx +136 -0
  117. package/blocks/company-intelligence/components/research-run.tsx +161 -0
  118. package/blocks/company-intelligence/components/source-detail.tsx +117 -0
  119. package/blocks/company-intelligence/components/source-view.tsx +274 -0
  120. package/blocks/company-intelligence/components/wiki-document-detail.tsx +116 -0
  121. package/blocks/company-intelligence/components/wiki-documents.tsx +66 -0
  122. package/blocks/company-intelligence/hooks/use-intelligence.ts +173 -0
  123. package/blocks/company-intelligence/lib/intelligence-types.ts +230 -0
  124. package/blocks/company-intelligence/lib/safe-markdown.tsx +150 -0
  125. package/blocks/company-intelligence/recipes/read-competitors-and-news.ts +33 -0
  126. package/blocks/company-intelligence/recipes/research-your-website.ts +43 -0
  127. package/blocks/company-intelligence/recipes/write-the-research-reports.ts +54 -0
  128. package/blocks/copilot/README.md +13 -0
  129. package/blocks/copilot/block.json +14 -1
  130. package/blocks/copilot/components/copilot-memory.tsx +6 -2
  131. package/blocks/copilot/components/copilot-panel.tsx +14 -5
  132. package/blocks/copilot/components/copilot-screen.tsx +148 -0
  133. package/blocks/copilot/components/copilot-team-chat.tsx +13 -6
  134. package/blocks/crm/README.md +34 -0
  135. package/blocks/crm/block.json +107 -2
  136. package/blocks/crm/components/companies-screen.tsx +359 -0
  137. package/blocks/crm/components/company-detail.tsx +10 -8
  138. package/blocks/crm/components/company-table.tsx +11 -4
  139. package/blocks/crm/components/contact-detail.tsx +10 -7
  140. package/blocks/crm/components/contact-lookup-link.tsx +44 -0
  141. package/blocks/crm/components/contact-table.tsx +10 -3
  142. package/blocks/crm/components/contacts-screen.tsx +377 -0
  143. package/blocks/crm/components/dedupe-review.tsx +13 -6
  144. package/blocks/crm/components/import-wizard.tsx +7 -9
  145. package/blocks/crm/components/list-select.tsx +20 -0
  146. package/blocks/crm/components/notes-panel.tsx +4 -4
  147. package/blocks/crm/components/record-engagement-tab.tsx +103 -0
  148. package/blocks/crm/components/record-form.tsx +6 -4
  149. package/blocks/crm/components/task-list.tsx +4 -4
  150. package/blocks/crm/components/tasks-screen.tsx +399 -0
  151. package/blocks/crm/hooks/use-all-lists.ts +18 -0
  152. package/blocks/crm/hooks/use-custom-fields.ts +6 -1
  153. package/blocks/crm/hooks/use-page-query.ts +9 -1
  154. package/blocks/crm/lib/crm-types.ts +26 -2
  155. package/blocks/crm/lib/read-lists.ts +16 -0
  156. package/blocks/crm/lib/screen-parts.tsx +105 -0
  157. package/blocks/deals/README.md +24 -6
  158. package/blocks/deals/block.json +160 -5
  159. package/blocks/deals/components/close-deal-dialog.tsx +169 -0
  160. package/blocks/deals/components/deal-contacts-editor.tsx +134 -0
  161. package/blocks/deals/components/deal-detail.tsx +25 -13
  162. package/blocks/deals/components/deal-filters.tsx +65 -0
  163. package/blocks/deals/components/deal-line-items.tsx +120 -0
  164. package/blocks/deals/components/deal-notes-tab.tsx +97 -0
  165. package/blocks/deals/components/deal-related.tsx +31 -10
  166. package/blocks/deals/components/deal-tasks-tab.tsx +88 -0
  167. package/blocks/deals/components/deals-screen.tsx +66 -26
  168. package/blocks/deals/components/forecast-view.tsx +4 -4
  169. package/blocks/deals/components/pipeline-board.tsx +8 -6
  170. package/blocks/deals/components/pipeline-editor.tsx +132 -0
  171. package/blocks/deals/components/record-deals-tab.tsx +62 -0
  172. package/blocks/deals/components/stage-editor.tsx +5 -5
  173. package/blocks/deals/hooks/use-pipeline-deals.ts +21 -4
  174. package/blocks/deals/hooks/use-pipelines.ts +1 -1
  175. package/blocks/deals/lib/deal-extras.ts +156 -0
  176. package/blocks/dialer/README.md +45 -4
  177. package/blocks/dialer/block.json +84 -3
  178. package/blocks/dialer/components/call-contact-button.tsx +269 -0
  179. package/blocks/dialer/components/call-log-screen.tsx +208 -0
  180. package/blocks/dialer/components/call-log.tsx +23 -7
  181. package/blocks/dialer/components/call-queue.tsx +4 -4
  182. package/blocks/dialer/components/dialer-screen.tsx +361 -0
  183. package/blocks/dialer/components/dialer-session-detail.tsx +153 -0
  184. package/blocks/dialer/components/disposition-panel.tsx +1 -1
  185. package/blocks/dialer/components/live-calls-table.tsx +7 -3
  186. package/blocks/dialer/components/record-calls-tab.tsx +101 -0
  187. package/blocks/dialer/components/session-start-form.tsx +61 -12
  188. package/blocks/dialer/components/softphone.tsx +4 -2
  189. package/blocks/enrichment/README.md +29 -7
  190. package/blocks/enrichment/block.json +49 -9
  191. package/blocks/enrichment/components/bulk-enrich-panel.tsx +83 -29
  192. package/blocks/enrichment/components/email-verify-badge.tsx +31 -11
  193. package/blocks/enrichment/components/enrich-button.tsx +32 -9
  194. package/blocks/enrichment/components/enrichment-job-detail.tsx +78 -0
  195. package/blocks/enrichment/components/enrichment-screen.tsx +341 -0
  196. package/blocks/enrichment/components/waterfall-config.tsx +66 -17
  197. package/blocks/enrichment/lib/enrichment-types.ts +39 -0
  198. package/blocks/enrichment/recipes/bulk-enrich-and-poll.ts +15 -4
  199. package/blocks/enrichment/recipes/waterfall-pipeline.ts +3 -2
  200. package/blocks/forms/README.md +8 -0
  201. package/blocks/forms/block.json +139 -18
  202. package/blocks/forms/components/form-activity.tsx +71 -0
  203. package/blocks/forms/components/form-backfill.tsx +108 -0
  204. package/blocks/forms/components/form-builder.tsx +4 -2
  205. package/blocks/forms/components/form-embed.tsx +4 -2
  206. package/blocks/forms/components/form-groups.tsx +151 -0
  207. package/blocks/forms/components/form-mapping-editor.tsx +6 -9
  208. package/blocks/forms/components/form-route-history.tsx +72 -0
  209. package/blocks/forms/components/form-saved-views.tsx +81 -0
  210. package/blocks/forms/components/forms-screen.tsx +56 -19
  211. package/blocks/forms/components/more-rows.tsx +30 -0
  212. package/blocks/forms/components/submissions-export.tsx +67 -0
  213. package/blocks/forms/components/submissions-table.tsx +17 -6
  214. package/blocks/forms/lib/forms-extra.ts +67 -0
  215. package/blocks/inbox/README.md +32 -3
  216. package/blocks/inbox/block.json +29 -2
  217. package/blocks/inbox/components/inbox-list.tsx +7 -3
  218. package/blocks/inbox/components/inbox-screen.tsx +294 -27
  219. package/blocks/inbox/components/record-conversations-tab.tsx +154 -0
  220. package/blocks/inbox/components/reply-composer.tsx +65 -9
  221. package/blocks/inbox/components/tag-and-assign.tsx +4 -2
  222. package/blocks/inbox/components/thread-badge.tsx +2 -2
  223. package/blocks/inbox/components/thread-view.tsx +1 -1
  224. package/blocks/inbox/hooks/use-inbox-thread.ts +1 -1
  225. package/blocks/inbox/hooks/use-inbox-threads.ts +1 -0
  226. package/blocks/inbox/lib/inbox-types.ts +47 -1
  227. package/blocks/inbox/recipes/01-triage-and-reply.ts +12 -2
  228. package/blocks/integrations/README.md +63 -1
  229. package/blocks/integrations/block.json +234 -15
  230. package/blocks/integrations/components/ad-accounts.tsx +82 -0
  231. package/blocks/integrations/components/connection-health.tsx +59 -0
  232. package/blocks/integrations/components/crm-detail.tsx +119 -0
  233. package/blocks/integrations/components/crm-records.tsx +103 -0
  234. package/blocks/integrations/components/field-mappings.tsx +61 -0
  235. package/blocks/integrations/components/integration-list.tsx +13 -8
  236. package/blocks/integrations/components/integrations-screen.tsx +152 -0
  237. package/blocks/integrations/components/migration-panel.tsx +133 -0
  238. package/blocks/integrations/components/sugarcrm-connect.tsx +95 -0
  239. package/blocks/integrations/components/sync-status.tsx +11 -9
  240. package/blocks/integrations/components/sync-triggers.tsx +134 -0
  241. package/blocks/integrations/components/webhook-detail.tsx +179 -0
  242. package/blocks/integrations/components/webhook-manager.tsx +43 -25
  243. package/blocks/integrations/components/workspace-tools.tsx +127 -0
  244. package/blocks/integrations/lib/crm-utils.ts +39 -0
  245. package/blocks/integrations/lib/facts.tsx +13 -0
  246. package/blocks/integrations/lib/integrations-utils.ts +11 -1
  247. package/blocks/integrations/lib/load-more.tsx +141 -0
  248. package/blocks/intent/README.md +14 -0
  249. package/blocks/intent/block.json +27 -1
  250. package/blocks/intent/components/account-intent-score.tsx +5 -5
  251. package/blocks/intent/components/intent-feed.tsx +13 -10
  252. package/blocks/intent/components/intent-keyword-manager.tsx +16 -12
  253. package/blocks/intent/components/intent-screen.tsx +265 -0
  254. package/blocks/intent/components/keyword-detail.tsx +124 -0
  255. package/blocks/intent/lib/intent-utils.ts +4 -4
  256. package/blocks/lifecycle/block.json +13 -8
  257. package/blocks/lifecycle/components/account-related.tsx +8 -1
  258. package/blocks/lifecycle/components/churn-list.tsx +5 -2
  259. package/blocks/lifecycle/components/customer-table.tsx +1 -1
  260. package/blocks/lifecycle/components/lifecycle-board.tsx +3 -1
  261. package/blocks/lifecycle/components/lifecycle-screen.tsx +3 -1
  262. package/blocks/lifecycle/lib/lifecycle.ts +1 -1
  263. package/blocks/lists/README.md +20 -1
  264. package/blocks/lists/block.json +69 -3
  265. package/blocks/lists/components/add-company-people-to-list-button.tsx +73 -0
  266. package/blocks/lists/components/add-to-list-button.tsx +20 -9
  267. package/blocks/lists/components/list-picker.tsx +17 -3
  268. package/blocks/lists/components/list-table.tsx +12 -6
  269. package/blocks/lists/components/lists-screen.tsx +346 -0
  270. package/blocks/lists/components/record-lists-tab.tsx +99 -0
  271. package/blocks/lists/components/suppression-manager.tsx +33 -8
  272. package/blocks/lists/components/suppressions-screen.tsx +264 -0
  273. package/blocks/mailboxes/README.md +17 -0
  274. package/blocks/mailboxes/block.json +69 -17
  275. package/blocks/mailboxes/components/connect-mailbox.tsx +32 -21
  276. package/blocks/mailboxes/components/domain-detail.tsx +148 -0
  277. package/blocks/mailboxes/components/domain-purchase.tsx +20 -14
  278. package/blocks/mailboxes/components/mailbox-detail.tsx +157 -0
  279. package/blocks/mailboxes/components/mailbox-list.tsx +8 -3
  280. package/blocks/mailboxes/components/mailboxes-screen.tsx +228 -0
  281. package/blocks/mailboxes/components/warmup-health.tsx +24 -22
  282. package/blocks/mailboxes/lib/mailboxes-utils.ts +26 -5
  283. package/blocks/mailboxes/lib/use-provider-domains.ts +23 -0
  284. package/blocks/newsletter/README.md +12 -0
  285. package/blocks/newsletter/block.json +157 -24
  286. package/blocks/newsletter/components/issue-scheduler.tsx +4 -3
  287. package/blocks/newsletter/components/issue-versions.tsx +93 -0
  288. package/blocks/newsletter/components/more-rows.tsx +30 -0
  289. package/blocks/newsletter/components/newsletter-editor.tsx +1 -1
  290. package/blocks/newsletter/components/newsletter-list-syncs.tsx +121 -0
  291. package/blocks/newsletter/components/newsletter-screen.tsx +39 -14
  292. package/blocks/newsletter/components/newsletter-settings.tsx +211 -0
  293. package/blocks/newsletter/components/newsletter-stats.tsx +2 -2
  294. package/blocks/newsletter/components/newsletter-templates.tsx +133 -0
  295. package/blocks/newsletter/components/subscriber-table.tsx +5 -7
  296. package/blocks/newsletter/components/subscriber-timeline.tsx +65 -0
  297. package/blocks/newsletter/lib/newsletter-extra.ts +60 -0
  298. package/blocks/nurture/block.json +28 -14
  299. package/blocks/nurture/components/nurture-builder.tsx +13 -3
  300. package/blocks/nurture/components/nurture-enroll.tsx +12 -10
  301. package/blocks/nurture/components/nurture-form.tsx +27 -10
  302. package/blocks/nurture/components/nurture-screen.tsx +26 -11
  303. package/blocks/nurture/components/nurture-stats.tsx +5 -4
  304. package/blocks/nurture/lib/nurture-types.ts +1 -1
  305. package/blocks/objects/README.md +18 -1
  306. package/blocks/objects/block.json +42 -21
  307. package/blocks/objects/components/attribute-field-input.tsx +14 -1
  308. package/blocks/objects/components/object-not-found.tsx +3 -1
  309. package/blocks/objects/components/object-record-detail.tsx +10 -2
  310. package/blocks/objects/components/object-schema-editor.tsx +4 -2
  311. package/blocks/objects/components/object-table.tsx +13 -3
  312. package/blocks/objects/components/objects-screen.tsx +147 -0
  313. package/blocks/objects/components/record-roles-panel.tsx +15 -5
  314. package/blocks/objects/lib/object-types.ts +1 -1
  315. package/blocks/pages/README.md +14 -0
  316. package/blocks/pages/block.json +140 -23
  317. package/blocks/pages/components/brand-kit-editor.tsx +5 -2
  318. package/blocks/pages/components/content-library.tsx +9 -4
  319. package/blocks/pages/components/more-rows.tsx +30 -0
  320. package/blocks/pages/components/page-ai-tools.tsx +196 -0
  321. package/blocks/pages/components/page-analytics.tsx +176 -0
  322. package/blocks/pages/components/page-clone.tsx +99 -0
  323. package/blocks/pages/components/page-domains.tsx +167 -0
  324. package/blocks/pages/components/page-editor.tsx +1 -1
  325. package/blocks/pages/components/page-generation-status.tsx +48 -0
  326. package/blocks/pages/components/page-list.tsx +2 -1
  327. package/blocks/pages/components/page-tracking.tsx +137 -0
  328. package/blocks/pages/components/pages-screen.tsx +57 -16
  329. package/blocks/pages/components/seo-report.tsx +6 -1
  330. package/blocks/pages/lib/studio.ts +7 -0
  331. package/blocks/phone-numbers/README.md +74 -0
  332. package/blocks/phone-numbers/block.json +262 -0
  333. package/blocks/phone-numbers/components/a2p-registration.tsx +230 -0
  334. package/blocks/phone-numbers/components/assign-number.tsx +104 -0
  335. package/blocks/phone-numbers/components/number-search.tsx +239 -0
  336. package/blocks/phone-numbers/components/number-settings.tsx +134 -0
  337. package/blocks/phone-numbers/components/number-usage.tsx +97 -0
  338. package/blocks/phone-numbers/components/phone-number-detail.tsx +84 -0
  339. package/blocks/phone-numbers/components/phone-number-list.tsx +110 -0
  340. package/blocks/phone-numbers/components/phone-numbers-screen.tsx +201 -0
  341. package/blocks/phone-numbers/components/register-owned-number.tsx +98 -0
  342. package/blocks/phone-numbers/components/release-number.tsx +126 -0
  343. package/blocks/phone-numbers/hooks/use-phone-numbers.ts +58 -0
  344. package/blocks/phone-numbers/lib/phone-numbers-utils.ts +202 -0
  345. package/blocks/phone-numbers/recipes/01-search-and-buy-number.ts +49 -0
  346. package/blocks/phone-numbers/recipes/02-assign-number-to-rep.ts +25 -0
  347. package/blocks/phone-numbers/recipes/03-audit-numbers.ts +36 -0
  348. package/blocks/phone-numbers/recipes/04-register-text-messaging.ts +39 -0
  349. package/blocks/pickers/README.md +23 -0
  350. package/blocks/pickers/block.json +103 -0
  351. package/blocks/pickers/components/company-picker.tsx +90 -0
  352. package/blocks/pickers/components/contact-picker.tsx +131 -0
  353. package/blocks/pickers/components/member-picker.tsx +99 -0
  354. package/blocks/pickers/components/search-picker.tsx +98 -0
  355. package/blocks/pickers/hooks/use-current-member.ts +104 -0
  356. package/blocks/pickers/hooks/use-picker-pages.ts +73 -0
  357. package/blocks/pickers/lib/use-debounced.ts +13 -0
  358. package/blocks/quotes/README.md +13 -0
  359. package/blocks/quotes/block.json +65 -13
  360. package/blocks/quotes/components/create-quote-from-deal.tsx +48 -0
  361. package/blocks/quotes/components/product-picker.tsx +3 -1
  362. package/blocks/quotes/components/quote-builder.tsx +1 -1
  363. package/blocks/quotes/components/quote-lifecycle-actions.tsx +146 -0
  364. package/blocks/quotes/components/quote-list.tsx +5 -3
  365. package/blocks/quotes/components/quote-related.tsx +8 -4
  366. package/blocks/quotes/components/quote-viewer.tsx +7 -2
  367. package/blocks/quotes/components/record-quotes-tab.tsx +90 -0
  368. package/blocks/radar/README.md +10 -0
  369. package/blocks/radar/block.json +41 -1
  370. package/blocks/radar/components/competitor-battle-card.tsx +1 -1
  371. package/blocks/radar/components/competitor-detail.tsx +117 -0
  372. package/blocks/radar/components/competitor-tracker.tsx +3 -1
  373. package/blocks/radar/components/radar-overview.tsx +4 -4
  374. package/blocks/radar/components/radar-report.tsx +4 -2
  375. package/blocks/radar/components/radar-screen.tsx +214 -0
  376. package/blocks/radar/components/report-detail.tsx +104 -0
  377. package/blocks/search/README.md +10 -0
  378. package/blocks/search/block.json +16 -1
  379. package/blocks/search/components/filter-builder.tsx +3 -3
  380. package/blocks/search/components/prospect-results.tsx +5 -3
  381. package/blocks/search/components/prospect-search-screen.tsx +235 -0
  382. package/blocks/search/components/saved-searches.tsx +4 -1
  383. package/blocks/search/hooks/use-filter-options.ts +20 -6
  384. package/blocks/search/lib/search-types.ts +2 -0
  385. package/blocks/sequencer/README.md +55 -9
  386. package/blocks/sequencer/block.json +126 -19
  387. package/blocks/sequencer/components/add-to-sequence-button.tsx +82 -0
  388. package/blocks/sequencer/components/enroll-dialog.tsx +11 -5
  389. package/blocks/sequencer/components/record-sequences-tab.tsx +126 -0
  390. package/blocks/sequencer/components/sequence-builder.tsx +224 -60
  391. package/blocks/sequencer/components/sequence-detail.tsx +261 -0
  392. package/blocks/sequencer/components/sequence-enrollments.tsx +116 -0
  393. package/blocks/sequencer/components/sequence-list.tsx +34 -9
  394. package/blocks/sequencer/components/sequence-stats.tsx +11 -10
  395. package/blocks/sequencer/components/sequence-steps-panel.tsx +103 -0
  396. package/blocks/sequencer/components/sequences-screen.tsx +162 -0
  397. package/blocks/sequencer/components/step-editor.tsx +14 -12
  398. package/blocks/sequencer/lib/sequencer-utils.ts +133 -10
  399. package/blocks/sequencer/recipes/create-multichannel-sequence.ts +33 -5
  400. package/blocks/sequencer/recipes/launch-and-enroll.ts +19 -2
  401. package/blocks/social-ads/README.md +19 -0
  402. package/blocks/social-ads/block.json +233 -1
  403. package/blocks/social-ads/components/ad-campaign-list.tsx +9 -5
  404. package/blocks/social-ads/components/ads-screen.tsx +152 -0
  405. package/blocks/social-ads/components/audience-sync-detail.tsx +194 -0
  406. package/blocks/social-ads/components/audience-sync.tsx +17 -9
  407. package/blocks/social-ads/components/campaign-ads.tsx +166 -0
  408. package/blocks/social-ads/components/campaign-detail.tsx +138 -0
  409. package/blocks/social-ads/components/campaign-editor.tsx +134 -0
  410. package/blocks/social-ads/components/creative-concepts.tsx +74 -0
  411. package/blocks/social-ads/components/creative-detail.tsx +155 -0
  412. package/blocks/social-ads/components/creative-gallery.tsx +35 -7
  413. package/blocks/social-ads/components/creative-revisions.tsx +57 -0
  414. package/blocks/social-ads/components/creative-tools.tsx +176 -0
  415. package/blocks/social-ads/components/post-composer.tsx +7 -3
  416. package/blocks/social-ads/components/post-detail.tsx +163 -0
  417. package/blocks/social-ads/components/social-ideas.tsx +64 -0
  418. package/blocks/social-ads/components/social-performance.tsx +80 -0
  419. package/blocks/social-ads/components/social-post-list.tsx +11 -3
  420. package/blocks/social-ads/components/social-screen.tsx +187 -0
  421. package/blocks/social-ads/lib/ads-extra.ts +67 -0
  422. package/blocks/surveys/README.md +91 -0
  423. package/blocks/surveys/block.json +299 -0
  424. package/blocks/surveys/components/account-nps.tsx +69 -0
  425. package/blocks/surveys/components/contact-survey-history.tsx +52 -0
  426. package/blocks/surveys/components/org-summary.tsx +82 -0
  427. package/blocks/surveys/components/sending-domains.tsx +113 -0
  428. package/blocks/surveys/components/survey-form.tsx +126 -0
  429. package/blocks/surveys/components/survey-responses.tsx +145 -0
  430. package/blocks/surveys/components/survey-results.tsx +72 -0
  431. package/blocks/surveys/components/survey-send-history.tsx +58 -0
  432. package/blocks/surveys/components/survey-send.tsx +162 -0
  433. package/blocks/surveys/components/survey-triggers.tsx +166 -0
  434. package/blocks/surveys/components/surveys-screen.tsx +234 -0
  435. package/blocks/surveys/lib/surveys-types.ts +228 -0
  436. package/blocks/surveys/recipes/create-an-nps-survey.ts +22 -0
  437. package/blocks/surveys/recipes/send-a-survey-and-follow-it.ts +27 -0
  438. package/blocks/surveys/recipes/survey-score-report.ts +27 -0
  439. package/blocks/visitors/README.md +10 -0
  440. package/blocks/visitors/block.json +25 -1
  441. package/blocks/visitors/components/snippet-install.tsx +21 -10
  442. package/blocks/visitors/components/traffic-chart.tsx +4 -4
  443. package/blocks/visitors/components/visitor-company-card.tsx +26 -16
  444. package/blocks/visitors/components/visitor-detail.tsx +105 -0
  445. package/blocks/visitors/components/visitor-feed.tsx +16 -7
  446. package/blocks/visitors/components/visitors-screen.tsx +166 -0
  447. package/blocks/webchat/README.md +10 -0
  448. package/blocks/webchat/block.json +21 -7
  449. package/blocks/webchat/components/chat-widget-config.tsx +8 -5
  450. package/blocks/webchat/components/conversation-list.tsx +8 -4
  451. package/blocks/webchat/components/webchat-screen.tsx +245 -0
  452. package/blocks/workbench/README.md +11 -0
  453. package/blocks/workbench/block.json +48 -1
  454. package/blocks/workbench/components/column-recipe-editor.tsx +5 -3
  455. package/blocks/workbench/components/run-progress.tsx +1 -1
  456. package/blocks/workbench/components/workbench-grid.tsx +10 -4
  457. package/blocks/workbench/components/workbench-row-detail.tsx +104 -0
  458. package/blocks/workbench/components/workbench-screen.tsx +202 -0
  459. package/blocks/workbench/components/workbench-sources.tsx +194 -0
  460. package/blocks/workbench/components/workbench-table-list.tsx +14 -2
  461. package/blocks/workflows/README.md +13 -0
  462. package/blocks/workflows/block.json +28 -9
  463. package/blocks/workflows/components/run-history.tsx +31 -9
  464. package/blocks/workflows/components/trigger-config.tsx +3 -1
  465. package/blocks/workflows/components/workflow-editor.tsx +4 -2
  466. package/blocks/workflows/components/workflow-list.tsx +13 -6
  467. package/blocks/workflows/components/workflows-screen.tsx +193 -0
  468. package/dist/cli.js +3109 -1977
  469. package/dist/errors.d.mts +34 -0
  470. package/dist/errors.d.ts +34 -0
  471. package/dist/errors.js +46 -0
  472. package/dist/errors.js.map +1 -0
  473. package/dist/errors.mjs +21 -0
  474. package/dist/errors.mjs.map +1 -0
  475. package/dist/index.browser.js +11 -5
  476. package/dist/index.browser.js.map +1 -1
  477. package/dist/index.browser.mjs +11 -5
  478. package/dist/index.browser.mjs.map +1 -1
  479. package/dist/index.d.mts +164 -25
  480. package/dist/index.d.ts +164 -25
  481. package/dist/index.js +11 -5
  482. package/dist/index.js.map +1 -1
  483. package/dist/index.mjs +11 -5
  484. package/dist/index.mjs.map +1 -1
  485. package/dist/react.browser.js +10 -4
  486. package/dist/react.browser.js.map +1 -1
  487. package/dist/react.browser.mjs +10 -4
  488. package/dist/react.browser.mjs.map +1 -1
  489. package/dist/react.d.mts +142 -5
  490. package/dist/react.d.ts +142 -5
  491. package/dist/react.js +10 -4
  492. package/dist/react.js.map +1 -1
  493. package/dist/react.mjs +10 -4
  494. package/dist/react.mjs.map +1 -1
  495. package/dist/types/blocks/add.d.ts +11 -4
  496. package/dist/types/blocks/brand.d.ts +122 -0
  497. package/dist/types/blocks/compose.d.ts +53 -0
  498. package/dist/types/blocks/create.d.ts +27 -20
  499. package/dist/types/blocks/doctor.d.ts +6 -1
  500. package/dist/types/blocks/flows.d.ts +7 -1
  501. package/dist/types/blocks/navfile.d.ts +35 -0
  502. package/dist/types/blocks/orgBrand.d.ts +34 -0
  503. package/dist/types/blocks/paths.d.ts +4 -0
  504. package/dist/types/blocks/prefixes.d.ts +37 -0
  505. package/dist/types/blocks/registry.d.ts +17 -0
  506. package/dist/types/blocks/screens.d.ts +53 -0
  507. package/dist/types/cli.d.ts +5 -0
  508. package/dist/types/errors.d.ts +33 -0
  509. package/dist/types/http.d.ts +2 -18
  510. package/dist/types/version.d.ts +2 -2
  511. package/docs/agents.md +15 -4
  512. package/docs/api/README.md +2 -2
  513. package/docs/api/by-path.md +1 -0
  514. package/docs/api/desk.d.ts +98 -2
  515. package/docs/api/desk.md +123 -1
  516. package/docs/api/inbox.d.ts +10 -2
  517. package/docs/api/inbox.md +10 -2
  518. package/docs/api/seo.d.ts +19 -1
  519. package/docs/api/seo.md +24 -1
  520. package/package.json +16 -2
  521. package/stacks/6sense/pages/companies.tsx.tmpl +21 -0
  522. package/stacks/6sense/pages/contacts.tsx.tmpl +19 -0
  523. package/stacks/6sense/pages/tasks.tsx.tmpl +19 -0
  524. package/stacks/6sense.json +55 -17
  525. package/stacks/apollo/pages/calls.tsx.tmpl +23 -0
  526. package/stacks/apollo/pages/companies.tsx.tmpl +33 -0
  527. package/stacks/apollo/pages/contacts.tsx.tmpl +51 -0
  528. package/stacks/apollo/pages/deals.tsx.tmpl +17 -0
  529. package/stacks/apollo/pages/inbox-contact.tsx.tmpl +13 -0
  530. package/stacks/apollo/pages/lists.tsx.tmpl +12 -0
  531. package/stacks/apollo/pages/meetings.tsx.tmpl +13 -0
  532. package/stacks/apollo/pages/sequences.tsx.tmpl +12 -0
  533. package/stacks/apollo/pages/tasks.tsx.tmpl +20 -0
  534. package/stacks/apollo.json +147 -37
  535. package/stacks/calendly/pages/companies.tsx.tmpl +19 -0
  536. package/stacks/calendly/pages/contacts.tsx.tmpl +21 -0
  537. package/stacks/calendly/pages/meetings.tsx.tmpl +13 -0
  538. package/stacks/calendly/pages/tasks.tsx.tmpl +19 -0
  539. package/stacks/calendly.json +64 -0
  540. package/stacks/clay/pages/companies.tsx.tmpl +31 -0
  541. package/stacks/clay/pages/contacts.tsx.tmpl +31 -0
  542. package/stacks/clay/pages/lists.tsx.tmpl +12 -0
  543. package/stacks/clay/pages/tasks.tsx.tmpl +19 -0
  544. package/stacks/clay.json +73 -27
  545. package/stacks/gong/pages/calls.tsx.tmpl +23 -0
  546. package/stacks/gong/pages/companies.tsx.tmpl +22 -0
  547. package/stacks/gong/pages/contacts.tsx.tmpl +32 -0
  548. package/stacks/gong/pages/lists.tsx.tmpl +12 -0
  549. package/stacks/gong/pages/meetings.tsx.tmpl +13 -0
  550. package/stacks/gong/pages/tasks.tsx.tmpl +19 -0
  551. package/stacks/gong.json +98 -35
  552. package/stacks/hootsuite/pages/companies.tsx.tmpl +22 -0
  553. package/stacks/hootsuite/pages/contacts.tsx.tmpl +28 -0
  554. package/stacks/hootsuite/pages/lists.tsx.tmpl +12 -0
  555. package/stacks/hootsuite/pages/tasks.tsx.tmpl +19 -0
  556. package/stacks/hootsuite.json +104 -0
  557. package/stacks/hubspot/pages/companies.tsx.tmpl +26 -0
  558. package/stacks/hubspot/pages/contacts.tsx.tmpl +45 -0
  559. package/stacks/hubspot/pages/deals.tsx.tmpl +20 -0
  560. package/stacks/hubspot/pages/inbox.tsx.tmpl +103 -0
  561. package/stacks/hubspot/pages/lifecycle.tsx.tmpl +23 -0
  562. package/stacks/hubspot/pages/lists.tsx.tmpl +12 -0
  563. package/stacks/hubspot/pages/meetings.tsx.tmpl +13 -0
  564. package/stacks/hubspot/pages/quotes.tsx.tmpl +11 -0
  565. package/stacks/hubspot/pages/sequences.tsx.tmpl +12 -0
  566. package/stacks/hubspot/pages/tasks.tsx.tmpl +20 -0
  567. package/stacks/hubspot.json +181 -64
  568. package/stacks/intercom/pages/companies.tsx.tmpl +19 -0
  569. package/stacks/intercom/pages/contacts.tsx.tmpl +21 -0
  570. package/stacks/intercom/pages/inbox-contact.tsx.tmpl +13 -0
  571. package/stacks/intercom/pages/tasks.tsx.tmpl +19 -0
  572. package/stacks/intercom.json +52 -20
  573. package/stacks/mailchimp/pages/companies.tsx.tmpl +22 -0
  574. package/stacks/mailchimp/pages/contacts.tsx.tmpl +28 -0
  575. package/stacks/mailchimp/pages/lists.tsx.tmpl +12 -0
  576. package/stacks/mailchimp/pages/tasks.tsx.tmpl +19 -0
  577. package/stacks/mailchimp.json +68 -34
  578. package/stacks/outreach/pages/calls.tsx.tmpl +23 -0
  579. package/stacks/outreach/pages/companies.tsx.tmpl +22 -0
  580. package/stacks/outreach/pages/contacts.tsx.tmpl +46 -0
  581. package/stacks/outreach/pages/inbox-contact.tsx.tmpl +13 -0
  582. package/stacks/outreach/pages/lists.tsx.tmpl +12 -0
  583. package/stacks/outreach/pages/meetings.tsx.tmpl +13 -0
  584. package/stacks/outreach/pages/sequences.tsx.tmpl +12 -0
  585. package/stacks/outreach/pages/tasks.tsx.tmpl +19 -0
  586. package/stacks/outreach.json +106 -25
  587. package/stacks/pipedrive/pages/companies.tsx.tmpl +23 -0
  588. package/stacks/pipedrive/pages/contacts.tsx.tmpl +27 -0
  589. package/stacks/pipedrive/pages/deals.tsx.tmpl +20 -0
  590. package/stacks/pipedrive/pages/inbox.tsx.tmpl +103 -0
  591. package/stacks/pipedrive/pages/meetings.tsx.tmpl +13 -0
  592. package/stacks/pipedrive/pages/quotes.tsx.tmpl +11 -0
  593. package/stacks/pipedrive/pages/tasks.tsx.tmpl +20 -0
  594. package/stacks/pipedrive.json +117 -0
  595. package/stacks/salesforce/pages/companies.tsx.tmpl +26 -0
  596. package/stacks/salesforce/pages/contacts.tsx.tmpl +32 -0
  597. package/stacks/salesforce/pages/deals.tsx.tmpl +20 -0
  598. package/stacks/salesforce/pages/lifecycle.tsx.tmpl +23 -0
  599. package/stacks/salesforce/pages/lists.tsx.tmpl +12 -0
  600. package/stacks/salesforce/pages/quotes.tsx.tmpl +11 -0
  601. package/stacks/salesforce/pages/tasks.tsx.tmpl +20 -0
  602. package/stacks/salesforce.json +85 -31
  603. package/stacks/zoominfo/pages/companies.tsx.tmpl +31 -0
  604. package/stacks/zoominfo/pages/contacts.tsx.tmpl +31 -0
  605. package/stacks/zoominfo/pages/lists.tsx.tmpl +12 -0
  606. package/stacks/zoominfo/pages/tasks.tsx.tmpl +19 -0
  607. package/stacks/zoominfo.json +115 -0
  608. package/styles/styles.css +1 -1
  609. package/templates/next-app/README.md.tmpl +40 -9
  610. package/templates/next-app/app/globals.css.tmpl +349 -64
  611. package/templates/next-app/app/layout.tsx.tmpl +7 -2
  612. package/templates/next-app/components/app-shell.tsx.tmpl +104 -88
  613. package/templates/next-app/components/shell/quick-jump.tsx.tmpl +4 -0
  614. package/templates/next-app/dot-gitignore.tmpl +2 -0
  615. package/templates/next-app/package.json.tmpl +1 -1
@@ -0,0 +1,235 @@
1
+ "use client";
2
+
3
+ /**
4
+ * ProspectSearchScreen: the finished prospecting page. People / Companies tabs, a filter builder with saved searches
5
+ * on the left, the results with paging and the locked-field banner on the right, and, once results are saved, a panel
6
+ * with the NEW list's real (unlocked) contacts.
7
+ *
8
+ * Composes: ScreenLayout, SplitView, DetailPanel, SectionCard (screen kit), FilterBuilder, SavedSearches,
9
+ * ProspectResults (results + SaveToListDialog), ContactTable (crm), AddToListButton (lists).
10
+ *
11
+ * User flows:
12
+ * - choose People or Companies (`?tab=`), add filters (real value suggestions), Search, page through matches
13
+ * - Clear filters; load, name and delete saved searches
14
+ * - Save results to a new list (billable, confirmed, polled until built), then "Open list" shows the saved members:
15
+ * emails and phones are unlocked now, and they can be added to another list from the same panel
16
+ * - hand the saved list to your own next step (enrich, enroll) through `renderSavedListActions`
17
+ * Masked results never appear as values: locked cells read "Locked" and have no contact id until saved.
18
+ *
19
+ * SDK operations (all inside the parts it composes): getFilterOptions, getAutocompleteSuggestions (read),
20
+ * searchContacts / searchCompanies (read in practice), saveContactSearch / saveCompanySearch (billable, ConfirmSpendDialog),
21
+ * listLists (polling), listSavedSearches / saveSearch / deleteSavedSearch (destructive, ConfirmSpendDialog),
22
+ * listContacts + addContactsToList for the saved list's members.
23
+ */
24
+
25
+ import { useState } from "react";
26
+ import type { ReactNode } from "react";
27
+ import { Bookmark, ExternalLink, ListChecks, Play, Search, Trash2 } from "lucide-react";
28
+ import { Button, Skeleton } from "@graph8/sdk/ui";
29
+ import { ApiError } from "@/g8/components/api-error";
30
+ import { ConfirmSpendDialog } from "@/g8/components/confirm-spend-dialog";
31
+ import { KeyValueList } from "@/g8/components/screen/key-value-list";
32
+ import { StatusPill } from "@/g8/components/screen/status-pill";
33
+ import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
34
+ import { useG8Query } from "@/g8/hooks/use-g8-query";
35
+ import { g8Delete, g8Get } from "@/g8/lib/g8-client";
36
+ import { EmptyState } from "@/g8/components/empty-state";
37
+ import { DetailPanel } from "@/g8/components/screen/detail-panel";
38
+ import { EntityAvatar } from "@/g8/components/screen/entity-avatar";
39
+ import { ScreenLayout } from "@/g8/components/screen/screen-layout";
40
+ import { SectionCard } from "@/g8/components/screen/section-card";
41
+ import { SplitView } from "@/g8/components/screen/split-view";
42
+ import { useUrlState } from "@/g8/hooks/use-url-state";
43
+ import { ContactTable } from "@/g8/crm/components/contact-table";
44
+ import { AddToListButton } from "@/g8/lists/components/add-to-list-button";
45
+ import { OPERATOR_LABEL, completeFilters, fieldsFor, isLocked } from "../lib/search-types";
46
+ import type { ProspectCompany, ProspectContact, SearchCondition, SearchResource } from "../lib/search-types";
47
+ import { FilterBuilder } from "./filter-builder";
48
+ import { ProspectResults } from "./prospect-results";
49
+ import { SAVED_FORMAT, SavedSearches } from "./saved-searches";
50
+ import type { SavedListInfo } from "./save-to-list-dialog";
51
+ import { useBrand } from "@/g8/hooks/use-brand";
52
+
53
+ export interface ProspectSearchScreenProps {
54
+ /** Which tab opens first when `?tab=` is absent. Default people. */
55
+ defaultResource?: SearchResource;
56
+ pageSize?: number;
57
+ confirmSearch?: boolean;
58
+ creditsPerRecord?: number;
59
+ onSaved?: (list: SavedListInfo) => void;
60
+ toolbarActions?: ReactNode;
61
+ /** Buttons for the saved list panel, for example enrich or enroll: `(list) => <BulkEnrichPanel listId={list.id} />`. */
62
+ renderSavedListActions?: (list: SavedListInfo) => ReactNode;
63
+ className?: string;
64
+ }
65
+
66
+
67
+ type Row = ProspectContact & ProspectCompany;
68
+
69
+ interface SavedRow { id: number; title: string; scope?: string | null; icp_query?: { format?: string; filters?: SearchCondition[] } & Record<string, unknown>; created_at?: string | null }
70
+
71
+ const CONTACT_FACTS: Array<[string, keyof ProspectContact]> = [
72
+ ["Job title", "job_title"], ["Seniority", "seniority_level"], ["Department", "job_department"], ["Work email", "work_email"], ["Direct phone", "direct_phone"],
73
+ ["Mobile phone", "mobile_phone"], ["Personal email", "personal_emails"], ["LinkedIn", "linkedin_url"], ["City", "city"], ["State", "state"], ["Country", "country"],
74
+ ["Company", "company_name"], ["Company domain", "company_domain"], ["Industry", "company_industry"],
75
+ ];
76
+ const COMPANY_FACTS: Array<[string, keyof ProspectCompany]> = [
77
+ ["Domain", "domain"], ["Industry", "industry"], ["Employees", "employee_count"], ["Revenue", "revenue"], ["City", "city"], ["State", "state"], ["Country", "country"], ["LinkedIn", "linkedin_url"],
78
+ ];
79
+
80
+ /** Every field of a search result. Locked values read "Locked" and are never shown; there is no contact id until it is saved to a list. */
81
+ function ProspectDetail({ row, resource, onClose }: { row: Row; resource: SearchResource; onClose: () => void }) {
82
+ const person = resource === "contacts";
83
+ const name = person ? [row.first_name, row.last_name].filter(Boolean).join(" ") || "Contact" : row.name ?? "Company";
84
+ const facts = (person ? CONTACT_FACTS : COMPANY_FACTS).map(([label, key]) => {
85
+ const locked = person && isLocked(row, key as keyof ProspectContact);
86
+ const raw = (row as Record<string, unknown>)[key as string];
87
+ const text = typeof raw === "string" || typeof raw === "number" ? String(raw) : "";
88
+ const link = text && key === "linkedin_url" && !locked ? <a className="inline-flex items-center gap-1 underline" href={text.startsWith("http") ? text : `https://${text}`} target="_blank" rel="noreferrer">{text}<ExternalLink className="size-3" aria-hidden /></a> : null;
89
+ return { label, value: locked ? <StatusPill tone="neutral">Locked</StatusPill> : (link ?? text) };
90
+ });
91
+ const hasLocked = person && (row.locked_fields?.length ?? 0) > 0;
92
+ return (
93
+ <DetailPanel title={name} subtitle={person ? [row.job_title, row.company_name].filter(Boolean).join(" at ") : row.industry} onClose={onClose}
94
+ avatar={<EntityAvatar name={name} kind={person ? "person" : "company"} size="lg" />}
95
+ badges={hasLocked ? <StatusPill tone="warning" dot>Details locked</StatusPill> : undefined}>
96
+ <div className="flex flex-col gap-4 p-1">
97
+ <KeyValueList columns={2} items={facts} />
98
+ {!person && row.description ? <p className="text-sm">{String(row.description)}</p> : null}
99
+ <p className="text-xs text-muted-foreground">{hasLocked ? "Locked fields unlock when you save these results to a list. " : ""}A search result has no contact id: save the results to a list to work with the real record.</p>
100
+ </div>
101
+ </DetailPanel>
102
+ );
103
+ }
104
+
105
+ /** Saved searches of both kinds in one place, each openable (`?id=`) with its filters, Run and Delete. */
106
+ function SavedSearchesTab({ onRun, toolbarId, setToolbarId }: { onRun: (resource: SearchResource, filters: SearchCondition[]) => void; toolbarId: string; setToolbarId: (v: string | null) => void }) {
107
+ const brand = useBrand();
108
+ const [deleting, setDeleting] = useState<SavedRow | null>(null);
109
+ const [view, setView] = useUrlState("view");
110
+ const full = view === "full";
111
+ const all = useG8Query(async (signal) => {
112
+ const [c, p] = await Promise.all((["contacts", "companies"] as const).map((scope) => g8Get<SavedRow[]>("/search/saved-searches", { scope }, { signal })));
113
+ return [...((c.data ?? []) as SavedRow[]), ...((p.data ?? []) as SavedRow[])];
114
+ }, []);
115
+ const remove = useG8Mutation((id: number) => g8Delete(`/search/saved-searches/${id}`), { onSuccess: (_r, id) => { setDeleting(null); if (String(id) === toolbarId) setToolbarId(null); void all.refetch(); } });
116
+ const rows = all.data ?? [];
117
+ const open = rows.find((r) => String(r.id) === toolbarId);
118
+ const loadable = (r: SavedRow) => r.icp_query?.format === SAVED_FORMAT && Array.isArray(r.icp_query.filters) && r.icp_query.filters.length > 0;
119
+ const resourceOf = (r: SavedRow): SearchResource => (r.scope === "companies" ? "companies" : "contacts");
120
+ const list = (
121
+ <div className="flex flex-col gap-3 p-1">
122
+ {all.error ? <ApiError error={all.error} title="Could not load saved searches" onRetry={() => void all.refetch()} /> : null}
123
+ {remove.error ? <ApiError error={remove.error} title="Could not delete the search" /> : null}
124
+ {all.loading && !all.data ? <Skeleton className="h-24 w-full" /> : null}
125
+ {!all.loading && !all.error && rows.length === 0 ? <EmptyState icon={<Bookmark className="size-6" aria-hidden />} title="No saved searches" description="Build filters on the People or Companies tab and name them to keep them here." /> : null}
126
+ <ul className="divide-y rounded-md border">
127
+ {rows.map((r) => (
128
+ <li key={r.id} className={r.id === open?.id ? "bg-muted/50" : ""}>
129
+ <button type="button" className="flex w-full items-center gap-3 p-3 text-left" onClick={() => setToolbarId(String(r.id))}>
130
+ <Bookmark className="size-4 text-muted-foreground" aria-hidden />
131
+ <span className="min-w-0 flex-1 truncate text-sm font-medium">{r.title}</span>
132
+ <StatusPill tone="neutral">{resourceOf(r) === "companies" ? "Companies" : "People"}</StatusPill>
133
+ </button>
134
+ </li>
135
+ ))}
136
+ </ul>
137
+ </div>
138
+ );
139
+ const detail = open ? (
140
+ <DetailPanel key={open.id} onExpand={full ? undefined : () => setView("full")} onCollapse={full ? () => setView(null) : undefined} title={open.title} subtitle={`${resourceOf(open) === "companies" ? "Companies" : "People"} search`} onClose={() => setToolbarId(null)}
141
+ actions={(<>
142
+ <Button size="sm" disabled={!loadable(open)} onClick={() => onRun(resourceOf(open), open.icp_query!.filters!)}><Play className="mr-1 size-4" aria-hidden />Run search</Button>
143
+ <Button size="sm" variant="outline" onClick={() => setDeleting(open)}><Trash2 className="mr-1 size-4" aria-hidden />Delete</Button>
144
+ </>)}>
145
+ <div className="flex flex-col gap-4 p-1">
146
+ {loadable(open) ? (
147
+ <ul className="flex flex-col gap-1" aria-label="Saved filters">
148
+ {open.icp_query!.filters!.map((f, i) => {
149
+ const meta = fieldsFor(resourceOf(open)).find((m) => m.key === f.field);
150
+ return <li key={i} className="text-sm"><strong>{meta?.label ?? f.field}</strong> {OPERATOR_LABEL[f.operator] ?? f.operator} {(f.value ?? []).map(String).join(", ")}</li>;
151
+ })}
152
+ </ul>
153
+ ) : <p className="text-sm text-muted-foreground">Saved in the {brand.linkLabel} app. It uses the app's own format, so it can be seen here but not run.</p>}
154
+ <KeyValueList items={[{ label: "Saved", value: open.created_at ? new Date(open.created_at).toLocaleDateString() : "" }, { label: "Search id", value: String(open.id), mono: true }]} columns={2} />
155
+ </div>
156
+ </DetailPanel>
157
+ ) : null;
158
+ return (
159
+ <>
160
+ <SplitView expanded={full} onExpandedChange={(e) => setView(e ? "full" : null)} list={list} detail={detail} detailOpen={open != null} onCloseDetail={() => setToolbarId(null)} detailTitle="Saved search" />
161
+ <ConfirmSpendDialog open={deleting != null} tier="destructive" title={`Delete "${deleting?.title ?? ""}"`} description="Lists already built from this search are not affected."
162
+ affected={1} confirming={remove.loading} onConfirm={() => deleting && void remove.mutate(deleting.id)} onCancel={() => setDeleting(null)} />
163
+ </>
164
+ );
165
+ }
166
+
167
+ export function ProspectSearchScreen({ defaultResource = "contacts", pageSize = 25, confirmSearch, creditsPerRecord, onSaved, toolbarActions, renderSavedListActions, className }: ProspectSearchScreenProps) {
168
+ const brand = useBrand();
169
+ const [tab, setTab] = useUrlState("tab", defaultResource === "companies" ? "companies" : "people");
170
+ const resource: SearchResource = tab === "companies" ? "companies" : "contacts";
171
+ const [savedId, setSavedId] = useUrlState("id");
172
+ const [draft, setDraft] = useState<SearchCondition[]>([]);
173
+ const [applied, setApplied] = useState<SearchCondition[]>([]);
174
+ const [saved, setSaved] = useState<SavedListInfo | null>(null);
175
+ const [prospect, setProspect] = useState<{ row: Row; resource: SearchResource } | null>(null);
176
+ const [membersKey, setMembersKey] = useState(0);
177
+
178
+ const switchTab = (id: string) => { setTab(id); setDraft([]); setApplied([]); setSaved(null); setProspect(null); setSavedId(null); };
179
+ const clear = () => { setDraft([]); setApplied([]); setProspect(null); };
180
+ const count = (l: SavedListInfo) => (l.contact_count ?? l.company_count ?? l.total ?? 0).toLocaleString();
181
+ const runSaved = (r: SearchResource, filters: SearchCondition[]) => { setTab(r === "companies" ? "companies" : "people"); setSavedId(null); setDraft(filters); setApplied(completeFilters(filters)); };
182
+
183
+ const left = (
184
+ <div className="flex min-w-0 flex-col gap-4 xl:grid xl:grid-cols-[20rem_minmax(0,1fr)]">
185
+ <div className="flex flex-col gap-4">
186
+ <SectionCard title="Filters" description="All filters must match. Pick values from the suggestions."
187
+ actions={<Button size="sm" variant="ghost" disabled={draft.length === 0 && applied.length === 0} onClick={clear}>Reset search</Button>}>
188
+ <FilterBuilder key={resource} resource={resource} value={draft} onChange={setDraft} onSearch={(f) => { setApplied(completeFilters(f)); setProspect(null); }} />
189
+ </SectionCard>
190
+ <SectionCard title="Saved searches">
191
+ <SavedSearches key={resource} resource={resource} current={draft} onLoad={(f) => { setDraft(f); setApplied(completeFilters(f)); setProspect(null); }} />
192
+ </SectionCard>
193
+ </div>
194
+ <div className="min-w-0">
195
+ <ProspectResults resource={resource} filters={applied} pageSize={pageSize} confirmSearch={confirmSearch} creditsPerRecord={creditsPerRecord}
196
+ onOpenRow={(row, r) => { setSaved(null); setProspect({ row, resource: r }); }}
197
+ onSaved={(list) => { setProspect(null); setSaved(list); setMembersKey((k) => k + 1); onSaved?.(list); }} />
198
+ </div>
199
+ </div>
200
+ );
201
+
202
+ const detail = saved ? (
203
+ <DetailPanel
204
+ key={saved.id}
205
+ title={saved.title}
206
+ subtitle={`${count(saved)} ${resource === "companies" ? "companies" : "contacts"} saved to your workspace`}
207
+ onClose={() => setSaved(null)}
208
+ actions={renderSavedListActions?.(saved)}
209
+ >
210
+ {resource === "companies"
211
+ ? <EmptyState icon={<ListChecks className="size-6" aria-hidden />} title="Companies saved" description={`The API has no read operation for company-list members, so review them in the ${brand.linkLabel} app.`} />
212
+ : (
213
+ <ContactTable listId={saved.id} selectable allowDelete={false} pageSize={10} refreshKey={membersKey}
214
+ renderBulkActions={({ ids, clear: done }) => <AddToListButton contactIds={ids} label="Add to another list" onAdded={() => done()} />} />
215
+ )}
216
+ </DetailPanel>
217
+ ) : prospect ? <ProspectDetail row={prospect.row} resource={prospect.resource} onClose={() => setProspect(null)} /> : null;
218
+
219
+ return (
220
+ <ScreenLayout
221
+ className={className}
222
+ title="Prospect search"
223
+ description="Find new people and companies, preview them, and save the ones you want to a list. Saving unlocks their details."
224
+ icon={<Search className="size-5" aria-hidden />}
225
+ actions={toolbarActions}
226
+ tabs={[{ id: "people", label: "People" }, { id: "companies", label: "Companies" }, { id: "saved", label: "Saved searches" }]}
227
+ activeTab={tab === "saved" ? "saved" : resource === "companies" ? "companies" : "people"}
228
+ onTabChange={switchTab}
229
+ >
230
+ {tab === "saved"
231
+ ? <SavedSearchesTab onRun={runSaved} toolbarId={savedId} setToolbarId={setSavedId} />
232
+ : <SplitView list={left} detail={detail} detailOpen={saved != null || prospect != null} onCloseDetail={() => { setSaved(null); setProspect(null); }} detailWidth="xl" detailTitle="Details" />}
233
+ </ScreenLayout>
234
+ );
235
+ }
@@ -22,8 +22,10 @@ import { EmptyState } from "@/g8/components/empty-state";
22
22
  import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
23
23
  import { useG8Query } from "@/g8/hooks/use-g8-query";
24
24
  import { g8Delete, g8Get, g8Post } from "@/g8/lib/g8-client";
25
+ import { plural } from "@/g8/lib/plural";
25
26
  import { completeFilters } from "../lib/search-types";
26
27
  import type { SearchCondition, SearchResource } from "../lib/search-types";
28
+ import { useBrand } from "@/g8/hooks/use-brand";
27
29
 
28
30
  export const SAVED_FORMAT = "g8-sdk-v1";
29
31
 
@@ -49,6 +51,7 @@ function loadable(s: SavedSearch): boolean {
49
51
  }
50
52
 
51
53
  export function SavedSearches({ resource = "contacts", current, onLoad, className }: SavedSearchesProps) {
54
+ const brand = useBrand();
52
55
  const [title, setTitle] = useState("");
53
56
  const [deleting, setDeleting] = useState<SavedSearch | null>(null);
54
57
  const ready = completeFilters(current);
@@ -78,7 +81,7 @@ export function SavedSearches({ resource = "contacts", current, onLoad, classNam
78
81
  <li key={s.id} className="flex items-center justify-between gap-2 rounded-md border p-3" data-testid="saved-search">
79
82
  <div className="min-w-0">
80
83
  <p className="truncate text-sm font-medium">{s.title}</p>
81
- <p className="text-xs text-muted-foreground">{loadable(s) ? `${s.icp_query.filters!.length} filter${s.icp_query.filters!.length === 1 ? "" : "s"}` : "Saved in the graph8 app"}</p>
84
+ <p className="text-xs text-muted-foreground">{loadable(s) ? plural(s.icp_query.filters!.length, "filter") : `Saved in the ${brand.linkLabel} app`}</p>
82
85
  </div>
83
86
  <div className="flex gap-1">
84
87
  <Button size="sm" variant="outline" disabled={!loadable(s)} onClick={() => onLoad(s.icp_query.filters!, { id: s.id, title: s.title })}>Load</Button>
@@ -1,8 +1,9 @@
1
1
  "use client";
2
2
 
3
3
  import { useG8Query } from "@/g8/hooks/use-g8-query";
4
- import { g8Get } from "@/g8/lib/g8-client";
5
- import type { SearchResource } from "../lib/search-types";
4
+ import { g8Get, g8Post } from "@/g8/lib/g8-client";
5
+ import { completeFilters } from "../lib/search-types";
6
+ import type { SearchCondition, SearchResource } from "../lib/search-types";
6
7
 
7
8
  export interface FilterOption {
8
9
  /** Normalised value: send this back as the filter value. */
@@ -12,21 +13,29 @@ export interface FilterOption {
12
13
  }
13
14
 
14
15
  const cache = new Map<string, FilterOption[]>();
16
+ /** Each filter combination is its own key, so the cache is capped; the least recently used entry goes first. */
17
+ const CACHE_LIMIT = 50;
15
18
 
16
19
  /**
17
20
  * The distinct values a field holds (`GET /search/filter-options`), so a filter can offer real
18
21
  * choices instead of guessed strings (a guessed industry silently returns zero rows). Cached per
19
22
  * resource and field for the life of the page. `enabled: false` makes no request.
20
23
  */
21
- export function useFilterOptions(resource: SearchResource, field: string, enabled = true) {
22
- const key = `${resource}:${field}`;
24
+ export function useFilterOptions(resource: SearchResource, field: string, enabled = true, within: SearchCondition[] = []) {
25
+ // With other filters already set, ask what exists WITHIN them (POST /search/filter-options): industries among
26
+ // companies in Spain, not every industry. An empty context falls back to the cached global list.
27
+ const context = completeFilters(within);
28
+ const key = `${resource}:${field}${context.length ? `:${JSON.stringify(context)}` : ""}`;
23
29
  const query = useG8Query(
24
30
  async (signal) => {
25
31
  const hit = cache.get(key);
26
- if (hit) return hit;
27
- const res = await g8Get<Record<string, FilterOption[]>>("/search/filter-options", { resource, fields: [field] }, { signal });
32
+ if (hit) { cache.delete(key); cache.set(key, hit); return hit; }
33
+ const res = context.length
34
+ ? await g8Post<Record<string, FilterOption[]>>("/search/filter-options", { resource, fields: [field], filters: context }, { signal })
35
+ : await g8Get<Record<string, FilterOption[]>>("/search/filter-options", { resource, fields: [field] }, { signal });
28
36
  const options = res.data?.[field] ?? [];
29
37
  cache.set(key, options);
38
+ while (cache.size > CACHE_LIMIT) cache.delete(cache.keys().next().value as string);
30
39
  return options;
31
40
  },
32
41
  [key],
@@ -39,3 +48,8 @@ export function useFilterOptions(resource: SearchResource, field: string, enable
39
48
  export function clearFilterOptionsCache(): void {
40
49
  cache.clear();
41
50
  }
51
+
52
+ /** Test hook: how many option lists are cached. */
53
+ export function filterOptionsCacheSize(): number {
54
+ return cache.size;
55
+ }
@@ -111,6 +111,7 @@ export interface ProspectContact {
111
111
  direct_phone?: string | null;
112
112
  mobile_phone?: string | null;
113
113
  job_title?: string | null;
114
+ job_department?: string | null;
114
115
  seniority_level?: string | null;
115
116
  linkedin_url?: string | null;
116
117
  city?: string | null;
@@ -132,6 +133,7 @@ export interface ProspectCompany {
132
133
  state?: string | null;
133
134
  country?: string | null;
134
135
  linkedin_url?: string | null;
136
+ description?: string | null;
135
137
  }
136
138
 
137
139
  /** True when a value exists but is locked ("***", or named in `locked_fields`) until the contact is saved to a list. */
@@ -5,12 +5,35 @@ Apollo / Outreach "sequences" screen.
5
5
 
6
6
  Depends on: `lists` (the audience) and `mailboxes` (the sender). Install: `npx @graph8/sdk add sequencer`.
7
7
 
8
+ ## Screens
9
+
10
+ `SequencesScreen` is the finished page: mount it as the route. Every record type has a complete detail view,
11
+ deep-linkable (`?id=` the sequence, `?dtab=` the tab, `?step=` a step, `?enrollment=` a contact's enrollment).
12
+
13
+ - **List** with status filter and a stat strip (live, paused, drafts, contacts in live sequences, from the first
14
+ 100). Launch (after a copy and sender check), pause, resume, duplicate, archive, all confirmed.
15
+ - **New sequence** (header button): the builder in a sheet (audience list, email/LinkedIn/AI call/call task steps
16
+ and a sender for each step type); the new draft opens right away. Nobody is contacted until you launch it.
17
+ - **`SequenceDetail`**: Details (every field, audience), Steps, Contacts, Results, plus the same actions as the list
18
+ and **Enroll contacts** (pick a list, tick contacts or enroll the whole list, confirm the recipient count).
19
+ - **`SequenceStepsPanel`**: each step is a record (channel, wait, every field, per-state counts) and opens the editor.
20
+ - **`SequenceEnrollments`**: each enrolled contact is a record (state, step, the full contact); pause directly,
21
+ resume confirmed.
22
+
23
+ Slot props: `toolbarActions`, `renderDetailActions(sequence)`, `extraDetailTabs`, `ownerEmail`. Needs a connected
24
+ sender mailbox and a contact list (see Prerequisites). `SequenceList` gained an optional `onChanged`; `EnrollDialog`
25
+ an optional `note`.
26
+
27
+ Known limits: "Enroll the whole list" reads at most 5,000 contacts (the enroll endpoint needs explicit
28
+ `contact_ids`); a bigger list is cut there and the confirm says so. `?enrollment=` opens a contact only when it is on
29
+ the enrollments page being shown (the API has no single-enrollment read), so a link to a later page opens nothing.
30
+
8
31
  ## Components
9
32
 
10
33
  | Component | What it does | Tiers |
11
34
  |---|---|---|
12
- | `SequenceList` | Sequences with status. Launch (after a copy preview), pause, resume, duplicate, archive. | read, write, external, destructive |
13
- | `SequenceBuilder` | Build a draft (email, LinkedIn, call, call task + a sender), or edit an existing sequence's steps. | read, write, external, destructive |
35
+ | `SequenceList` | Sequences with status. Launch (after a copy and sender check), pause, resume, duplicate, archive. | read, write, external, destructive |
36
+ | `SequenceBuilder` | Build a draft (email, LinkedIn, AI call, call task, a required list and a sender per step type), or edit an existing sequence's steps. | read, write, external, destructive |
14
37
  | `StepEditor` | One step's form, with the rules the API only enforces at send time. | read |
15
38
  | `EnrollDialog` | Confirm and enroll contacts, with the recipient count and a deliverability warning. | read, external |
16
39
  | `SequenceStats` | Totals, per-step progress, skip reasons, per-contact state with pause and resume. | read, write, external |
@@ -21,12 +44,23 @@ un-archive.
21
44
 
22
45
  ## The flow, in order
23
46
 
24
- 1. **Pick a sender and an audience.** `SequenceBuilder` -> `listMailboxes` (the chosen mailbox `id` and `email`
25
- become the sequence channel) and `listLists` (the list `id` becomes `associated_list_id`).
26
- 2. **Create the draft.** `createSequence` with all steps and the channel in one call (confirmed; nobody is contacted).
27
- It returns the sequence `id`.
28
- 3. **Check the copy.** `SequenceList` -> `previewSequence` before launch: `will_send_as_shown` and `content_issues`
29
- catch broken tokens that would otherwise be deleted silently or block the launch with a 400.
47
+ 1. **Pick the senders and an audience.** Every step type needs its own sender, or `runSequence` answers 400:
48
+ - email steps: a mailbox from `listMailboxes`. The channel type comes from the mailbox `source` first:
49
+ `inboxkit` -> `INBOXKIT`, Google -> `GMAIL`, SMTP -> `SMTP`. A directly connected Microsoft mailbox
50
+ (`manual_microsoft`) cannot send from sequences yet, so the builder shows it disabled with the reason.
51
+ - AI call steps (`PHONE`, an AI voice agent calls): a Voice AI number (`inbound_type: "ai"`) from
52
+ `listSequencerPhoneNumbers` (GET /sequencer/channels/phone_numbers), channel type `PHONE`.
53
+ - call tasks (`MANUAL_DIALER`, a rep calls from the dialer): a dialer number (`inbound_type: "dialer"`), `PHONE`.
54
+ - LinkedIn steps: an account from `listLinkedinAccounts` (GET /sequencer/channels/linkedin_accounts), channel type
55
+ `LINKEDIN` (HeyReach), or `LINKEDIN_NETRION` with `NETRION` steps when the response says `provider: "NETRION"`.
56
+ - the audience: `listLists`; the list `id` becomes `associated_list_id`. **Required**: the API cannot set it later.
57
+ 2. **Create the draft.** `createSequence` with all steps and channels in one call (confirmed; nobody is contacted).
58
+ It returns the sequence `id`. Editing later: steps that need a sender the sequence lacks offer a picker and attach
59
+ it with `addSequenceChannels` (POST /sequences/{id}/channels) before the steps are appended.
60
+ 3. **Check before launch.** `SequenceList` / `SequenceDetail` -> `previewSequence`. The copy verdict is per step:
61
+ `steps[].rendered.will_send_as_shown` and `steps[].rendered.content_issues`. An `error` issue, a step type with no
62
+ sender channel (`channels[]`) or a missing audience list would make `/run` answer 400, so the launch is refused
63
+ with the reasons; other issues are warnings in the confirm dialog.
30
64
  4. **Launch.** `runSequence` (confirmed with the contact count). Draft -> live.
31
65
  5. **Enroll more contacts.** `EnrollDialog` -> `addContactsToSequence` with `contact_ids` AND the `list_id` they
32
66
  came from (both required). Already-enrolled contacts are skipped, so a retry never duplicates.
@@ -39,7 +73,8 @@ un-archive.
39
73
  writes each email from `instructions`).
40
74
  - **LinkedIn** is the `HEYREACH` step type: `linkedin_action_type` is required, and the copy goes in that action's
41
75
  own key (`linkedin_connection_message`, `linkedin_message_text`, `linkedin_inmail_body`), never `body`.
42
- - **Call** is `PHONE`; **call task** is `MANUAL_DIALER` (a person works it in the dialer). Do-not-call numbers are never dialed by default.
76
+ - **AI call** is `PHONE`: an AI voice agent places the call from a Voice AI number. **Call task** is `MANUAL_DIALER`
77
+ (a person works it in the dialer, from a dialer number). Do-not-call numbers are never dialed by default.
43
78
  - `time_interval` is in seconds (the UI works in days). A step's `step_data` is replaced wholesale on update, so the
44
79
  editor always sends the complete object.
45
80
  - Search results are masked until saved to a list: enroll contacts that already live in a list.
@@ -49,6 +84,7 @@ un-archive.
49
84
  - A connected sending mailbox (`where: "api"`). Google and Microsoft are connected in the graph8 app.
50
85
  - An audience list with the contacts (`where: "api"`).
51
86
  - A LinkedIn sender account for LinkedIn steps (`where: "app"`): a sequence with LinkedIn steps and no sender cannot launch.
87
+ - A Voice AI number for AI call steps and a dialer number for call tasks (`where: "app"`).
52
88
 
53
89
  ## Defaults (do not ask the human)
54
90
 
@@ -75,6 +111,10 @@ un-archive.
75
111
  | `add_sequence_steps_sequences__sequence_id__steps_post` | POST /sequences/{sequence_id}/steps | write | SequenceBuilder |
76
112
  | `update_sequence_step_sequences__sequence_id__steps__step_id__patch` | PATCH /sequences/{sequence_id}/steps/{step_id} | external | SequenceBuilder |
77
113
  | `delete_sequence_step_sequences__sequence_id__steps__step_id__delete` | DELETE /sequences/{sequence_id}/steps/{step_id} | destructive | SequenceBuilder |
114
+ | `list_sequencer_phone_numbers_sequencer_channels_phone_numbers_get` | GET /sequencer/channels/phone_numbers | read | SequenceBuilder |
115
+ | `list_linkedin_accounts_sequencer_channels_linkedin_accounts_get` | GET /sequencer/channels/linkedin_accounts | read | SequenceBuilder |
116
+ | `list_sequence_channels_sequences__sequence_id__channels_get` | GET /sequences/{sequence_id}/channels | read | SequenceBuilder |
117
+ | `add_sequence_channels_sequences__sequence_id__channels_post` | POST /sequences/{sequence_id}/channels | write | SequenceBuilder |
78
118
  | `get_outreach_connection_integrations_linkedin_outreach_connection_get` | GET /integrations/linkedin-outreach/connection | read | StepEditor |
79
119
  | `get_safe_to_send_deliverability_safe_to_send_get` | GET /deliverability/safe-to-send | read | EnrollDialog |
80
120
  | `add_contacts_to_sequence_sequences__sequence_id__contacts_post` | POST /sequences/{sequence_id}/contacts | external | EnrollDialog |
@@ -85,3 +125,9 @@ un-archive.
85
125
  | `resume_sequence_contact_sequences__sequence_id__contacts__contact_id__resume_post` | POST /sequences/{sequence_id}/contacts/{contact_id}/resume | external | SequenceStats |
86
126
 
87
127
  Recipes (`recipes/`): `create-multichannel-sequence.ts`, `launch-and-enroll.ts`, `sequence-report.ts`, `pause-when-unsafe.ts`.
128
+
129
+ ## Cross-block glue
130
+
131
+ - `AddToSequenceButton` (`components/add-to-sequence-button.tsx`): "Add to sequence" for any selection of contacts (live sequences only, then the enroll dialog with its preflight and confirmation). `SequencesScreen` gains optional `onOpenContact`: an enrolled person gets an "Open in contacts" button.
132
+
133
+ - `RecordSequencesTab` (`components/record-sequences-tab.tsx`): a contact's enrollments with state and step (`getContactSequences`, the API's 50 most recent, with a notice when full), `AddToSequenceButton`, and a confirmed Withdraw from one sequence (`withdrawContactsFromSequences`, needs `contacts:write`). `extraDetailTabs` entry for ContactsScreen.