@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
@@ -28,6 +28,8 @@ import { EmptyState } from "@/g8/components/empty-state";
28
28
  import { ConfirmSpendDialog } from "@/g8/components/confirm-spend-dialog";
29
29
  import { TERMINAL_STATUSES, WAITING_STATUSES, payload, str } from "../lib/graph";
30
30
  import type { ExecutionRow, Rec } from "../lib/graph";
31
+ import { rowActivationProps } from "@/g8/hooks/use-row-activation";
32
+ import { pluralWord } from "@/g8/lib/plural";
31
33
 
32
34
  export interface RunHistoryProps {
33
35
  /** Only this workflow's runs. Omit for every workflow. */
@@ -35,6 +37,15 @@ export interface RunHistoryProps {
35
37
  /** Open this run's detail first (for example the id returned by WorkflowList's onRunStarted). */
36
38
  initialExecutionId?: string;
37
39
  pageSize?: number;
40
+ /** Controlled selection (a run id). With it, the host owns which run is open (for example from the URL). */
41
+ selectedId?: string | null;
42
+ onSelectRun?: (executionId: string | null) => void;
43
+ /** Hide the built-in detail column when the host shows the run elsewhere (see RunDetail). */
44
+ hideDetail?: boolean;
45
+ /** Change this number to reload the list (for example after RunDetail paused or stopped a run). */
46
+ refreshKey?: number;
47
+ /** Show the workflow's name next to each run when the list spans several workflows. */
48
+ nameFor?: (actionId: string) => string | undefined;
38
49
  className?: string;
39
50
  }
40
51
 
@@ -50,7 +61,8 @@ const VERB_COPY: Record<Verb, { title: string; description: string; label: strin
50
61
  const allowed = (status: string): Verb[] =>
51
62
  status === "running" ? ["pause", "stop"] : status === "paused" || status === "pending_approval" ? ["resume", "stop"] : TERMINAL_STATUSES.includes(status) ? ["restart"] : [];
52
63
 
53
- function Detail({ executionId, onChanged }: { executionId: string; onChanged: () => void }) {
64
+ /** One run in full: status, timing, who started it, error, input and output, and the controls its status allows. */
65
+ export function RunDetail({ executionId, onChanged }: { executionId: string; onChanged: () => void }) {
54
66
  const [verb, setVerb] = useState<Verb | null>(null);
55
67
  const poll = useJobPoll<Rec>(async (signal) => payload(await g8Get(`/workflows/executions/${encodeURIComponent(executionId)}`, undefined, { signal })), {
56
68
  isTerminal: (e) => TERMINAL_STATUSES.includes(str(e.status)) || WAITING_STATUSES.includes(str(e.status)),
@@ -67,6 +79,13 @@ function Detail({ executionId, onChanged }: { executionId: string; onChanged: ()
67
79
  {poll.error ? <ApiError error={poll.error} title="Could not read this run" onRetry={poll.start} /> : null}
68
80
  {act.error ? <ApiError error={act.error} title="That action failed" /> : null}
69
81
  <p>Status: <strong>{status.replace(/_/g, " ")}</strong>{e.duration_ms != null ? ` in ${Math.round(Number(e.duration_ms) / 100) / 10}s` : ""}</p>
82
+ <dl className="grid grid-cols-2 gap-x-3 gap-y-1 text-xs">
83
+ <dt className="text-muted-foreground">Run id</dt><dd className="font-mono break-all">{executionId}</dd>
84
+ <dt className="text-muted-foreground">Workflow</dt><dd className="font-mono break-all">{str(e.action_id)}</dd>
85
+ <dt className="text-muted-foreground">Started by</dt><dd>{str(e.triggered_by ?? e.trigger_type)}</dd>
86
+ <dt className="text-muted-foreground">Started</dt><dd>{e.started_at || e.created_at ? new Date(str(e.started_at ?? e.created_at)).toLocaleString() : ""}</dd>
87
+ <dt className="text-muted-foreground">Finished</dt><dd>{e.completed_at ? new Date(str(e.completed_at)).toLocaleString() : ""}</dd>
88
+ </dl>
70
89
  {e.error_message ? <p className="text-destructive">{str(e.error_message)}</p> : null}
71
90
  {e.input_data ? <details><summary>Input</summary><pre className="overflow-auto text-xs">{JSON.stringify(e.input_data, null, 2)}</pre></details> : null}
72
91
  {e.output_data ? <details open><summary>Output</summary><pre className="overflow-auto text-xs">{JSON.stringify(e.output_data, null, 2)}</pre></details> : null}
@@ -82,13 +101,15 @@ function Detail({ executionId, onChanged }: { executionId: string; onChanged: ()
82
101
  );
83
102
  }
84
103
 
85
- export function RunHistory({ actionId, initialExecutionId, pageSize = 25, className }: RunHistoryProps) {
104
+ export function RunHistory({ actionId, initialExecutionId, pageSize = 25, selectedId, onSelectRun, hideDetail, refreshKey, nameFor, className }: RunHistoryProps) {
86
105
  const [status, setStatus] = useState("");
87
106
  const [offset, setOffset] = useState(0);
88
- const [selected, setSelected] = useState<string | null>(initialExecutionId ?? null);
107
+ const [innerSel, setInnerSel] = useState<string | null>(initialExecutionId ?? null);
108
+ const selected = selectedId !== undefined ? selectedId : innerSel;
109
+ const setSelected = (id: string | null) => { setInnerSel(id); onSelectRun?.(id); };
89
110
  const list = useG8Query(
90
111
  (signal) => g8Get("/workflows/executions", { action_id: actionId, status_filter: status || undefined, limit: pageSize, offset }, { signal }),
91
- [actionId, status, offset, pageSize],
112
+ [actionId, status, offset, pageSize, refreshKey],
92
113
  );
93
114
  const body = payload(list.data);
94
115
  const rows = (Array.isArray(body.executions) ? body.executions : []) as ExecutionRow[];
@@ -103,17 +124,18 @@ export function RunHistory({ actionId, initialExecutionId, pageSize = 25, classN
103
124
  <Button size="sm" variant="outline" onClick={() => void list.refetch()}>Refresh</Button>
104
125
  </div>
105
126
  {list.error ? <ApiError error={list.error} onRetry={() => void list.refetch()} /> : null}
106
- <div className="grid gap-4 lg:grid-cols-2">
127
+ <div className={hideDetail ? "" : "grid gap-4 lg:grid-cols-2"}>
107
128
  <div>
108
129
  {list.loading && !list.data ? <div className="space-y-2" aria-busy="true">{[0, 1, 2].map((i) => <Skeleton key={i} className="h-10 w-full" />)}</div> : !list.error && rows.length === 0 ? (
109
130
  <EmptyState title="No runs yet" description="Runs appear here when a trigger fires or someone runs the workflow." />
110
131
  ) : (
111
132
  <Table>
112
- <TableHeader><TableRow><TableHead>Started</TableHead><TableHead>Status</TableHead><TableHead>By</TableHead></TableRow></TableHeader>
133
+ <TableHeader><TableRow><TableHead>Started</TableHead>{nameFor ? <TableHead>Workflow</TableHead> : null}<TableHead>Status</TableHead><TableHead>By</TableHead></TableRow></TableHeader>
113
134
  <TableBody>
114
135
  {rows.map((r) => (
115
- <TableRow key={r.execution_id} data-state={selected === r.execution_id ? "selected" : undefined}>
136
+ <TableRow key={r.execution_id} data-state={selected === r.execution_id ? "selected" : undefined} {...rowActivationProps(() => setSelected(r.execution_id))}>
116
137
  <TableCell><button type="button" className="hover:underline" onClick={() => setSelected(r.execution_id)}>{new Date(str(r.started_at ?? r.created_at)).toLocaleString()}</button></TableCell>
138
+ {nameFor ? <TableCell>{nameFor(r.action_id) ?? ""}</TableCell> : null}
117
139
  <TableCell className="capitalize">{r.status.replace(/_/g, " ")}</TableCell>
118
140
  <TableCell>{r.triggered_by ?? r.trigger_type ?? ""}</TableCell>
119
141
  </TableRow>
@@ -122,14 +144,14 @@ export function RunHistory({ actionId, initialExecutionId, pageSize = 25, classN
122
144
  </Table>
123
145
  )}
124
146
  <div className="mt-3 flex items-center justify-between text-sm">
125
- <span className="text-muted-foreground">{total} {total === 1 ? "run" : "runs"}</span>
147
+ <span className="text-muted-foreground">{total} {pluralWord(total, "run")}</span>
126
148
  <div className="flex gap-2">
127
149
  <Button size="sm" variant="outline" disabled={offset === 0} onClick={() => setOffset(Math.max(0, offset - pageSize))}>Previous</Button>
128
150
  <Button size="sm" variant="outline" disabled={offset + pageSize >= total} onClick={() => setOffset(offset + pageSize)}>Next</Button>
129
151
  </div>
130
152
  </div>
131
153
  </div>
132
- <div>{selected ? <Detail key={selected} executionId={selected} onChanged={() => void list.refetch()} /> : <p className="text-muted-foreground text-sm">Select a run to see its input, output and errors.</p>}</div>
154
+ {hideDetail ? null : <div>{selected ? <RunDetail key={selected} executionId={selected} onChanged={() => void list.refetch()} /> : <p className="text-muted-foreground text-sm">Select a run to see its input, output and errors.</p>}</div>}
133
155
  </div>
134
156
  </div>
135
157
  );
@@ -26,6 +26,7 @@ import { ConfirmSpendDialog } from "@/g8/components/confirm-spend-dialog";
26
26
  import { useWorkflow } from "../hooks/use-workflow";
27
27
  import { asRec, nodeIdOf, nodesOf, payload, setStep, str, triggerOf } from "../lib/graph";
28
28
  import type { Rec } from "../lib/graph";
29
+ import { useBrand } from "@/g8/hooks/use-brand";
29
30
 
30
31
  export interface TriggerConfigProps {
31
32
  actionId: string;
@@ -100,6 +101,7 @@ function Form({ actionId, config, save, saveAndRefresh, trigger, types, onSaved
100
101
  }
101
102
 
102
103
  export function TriggerConfig({ actionId, onSaved, className }: TriggerConfigProps) {
104
+ const brand = useBrand();
103
105
  const wf = useWorkflow(actionId);
104
106
  const status = useG8Query((signal) => g8Get(`/workflows/${encodeURIComponent(actionId)}/trigger-status`, undefined, { signal }), [actionId]);
105
107
  const catalog = useG8Query((signal) => g8Get("/workflows/node-types/schema", { node_type: "trigger", detail: "slim" }, { signal }), []);
@@ -127,7 +129,7 @@ export function TriggerConfig({ actionId, onSaved, className }: TriggerConfigPro
127
129
  </Card>
128
130
  {catalog.error ? <p className="mb-2 text-xs">The trigger catalog could not be loaded; the current type is still editable.</p> : null}
129
131
  {trigger ? <Form key={JSON.stringify(trigger.config)} actionId={actionId} config={wf.config} save={wf.save} saveAndRefresh={wf.saveAndRefresh} trigger={trigger} types={types} onSaved={refresh} /> : (
130
- <p className="text-muted-foreground text-sm">There is no trigger node to configure ({nodesOf(wf.config).length} steps). Add a trigger step in the graph8 app.</p>
132
+ <p className="text-muted-foreground text-sm">There is no trigger node to configure ({nodesOf(wf.config).length} steps). Add a trigger step in the {brand.linkLabel} app.</p>
131
133
  )}
132
134
  </div>
133
135
  );
@@ -26,6 +26,7 @@ import { ConfirmSpendDialog } from "@/g8/components/confirm-spend-dialog";
26
26
  import { useWorkflow } from "../hooks/use-workflow";
27
27
  import { addStep, asRec, canMove, isLinear, moveStep, newStep, nodeIdOf, nodeNameOf, nodeTypeOf, orderedSteps, payload, removeStep, setStep, str } from "../lib/graph";
28
28
  import type { Rec, WfNode } from "../lib/graph";
29
+ import { useBrand } from "@/g8/hooks/use-brand";
29
30
 
30
31
  export interface WorkflowEditorProps {
31
32
  actionId: string;
@@ -85,6 +86,7 @@ function AddStep({ onAdd }: { onAdd: (type: string) => void }) {
85
86
  }
86
87
 
87
88
  export function WorkflowEditor({ actionId, onSaved, className }: WorkflowEditorProps) {
89
+ const brand = useBrand();
88
90
  const wf = useWorkflow(actionId);
89
91
  // The draft remembers which workflow it belongs to, so switching `actionId` can never show or save the old one.
90
92
  const [draftState, setDraftState] = useState<{ id: string; config: Rec } | null>(null);
@@ -105,7 +107,7 @@ export function WorkflowEditor({ actionId, onSaved, className }: WorkflowEditorP
105
107
  return (
106
108
  <div className={className}>
107
109
  <div className="mb-4 flex flex-wrap items-center gap-2">
108
- <h2 className="text-lg font-semibold">{str(wf.workflow.name, "Workflow")}</h2>
110
+ <h2 className="text-lg font-medium">{str(wf.workflow.name, "Workflow")}</h2>
109
111
  <span className="text-muted-foreground text-sm">{wf.workflow.enabled === false ? "Disabled" : "Enabled"}</span>
110
112
  <div className="ml-auto flex items-center gap-2">
111
113
  {draft ? <span className="text-sm">Unsaved changes</span> : null}
@@ -113,7 +115,7 @@ export function WorkflowEditor({ actionId, onSaved, className }: WorkflowEditorP
113
115
  <Button disabled={!draft} onClick={() => setConfirm(true)}>Save workflow</Button>
114
116
  </div>
115
117
  </div>
116
- {!linear ? <p className="text-muted-foreground mb-3 text-sm">This workflow branches, so steps can be renamed and reconfigured here but not added, moved or removed. Use the graph8 app to change its shape.</p> : null}
118
+ {!linear ? <p className="text-muted-foreground mb-3 text-sm">This workflow branches, so steps can be renamed and reconfigured here but not added, moved or removed. Use the {brand.linkLabel} app to change its shape.</p> : null}
117
119
  {wf.save.error ? <ApiError error={wf.save.error} title="Could not save the workflow" /> : null}
118
120
  <div className="mb-4 space-y-3">
119
121
  {steps.map((s, i) => (
@@ -26,8 +26,14 @@ import { EmptyState } from "@/g8/components/empty-state";
26
26
  import { ConfirmSpendDialog } from "@/g8/components/confirm-spend-dialog";
27
27
  import { blankConfig, payload, str } from "../lib/graph";
28
28
  import type { Rec, WorkflowRow } from "../lib/graph";
29
+ import { rowActivationProps } from "@/g8/hooks/use-row-activation";
30
+ import { RecordLink } from "@/g8/components/screen/record-link";
31
+ import { pluralWord } from "@/g8/lib/plural";
32
+ import { useBrand } from "@/g8/hooks/use-brand";
29
33
 
30
34
  export interface WorkflowListProps {
35
+ /** URL param the host screen reads for this record type. With it the name is a real link (Cmd-click opens it in a new tab); without it a plain button. */
36
+ linkParam?: string;
31
37
  /** Open a workflow in your WorkflowEditor route. */
32
38
  onOpen?: (actionId: string) => void;
33
39
  /** Called with `(actionId, executionId)` after a run starts, so you can show RunHistory. */
@@ -55,7 +61,8 @@ function planFacts(plan: Rec): { credits?: number; audience?: number; steps: num
55
61
  };
56
62
  }
57
63
 
58
- function RunPlan({ actionId, input, setInput }: { actionId: string; input: string; setInput: (v: string) => void }) {
64
+ export function RunPlan({ actionId, input, setInput }: { actionId: string; input: string; setInput: (v: string) => void }) {
65
+ const brand = useBrand();
59
66
  const plan = useG8Query((signal) => g8Post(`/workflows/${encodeURIComponent(actionId)}/plan`, {}, { signal }), [actionId]);
60
67
  const facts = planFacts(payload(plan.data));
61
68
  let valid = true;
@@ -64,9 +71,9 @@ function RunPlan({ actionId, input, setInput }: { actionId: string; input: strin
64
71
  <div className="space-y-2 text-left">
65
72
  {plan.loading ? <Skeleton className="h-4 w-48" /> : plan.error ? <p className="text-xs">The preview is unavailable; the run still works.</p> : (
66
73
  <p className="text-xs" data-testid="run-plan">
67
- {facts.steps} steps{facts.audience != null ? `, ${facts.audience.toLocaleString()} contacts in scope` : ""}
74
+ {facts.steps} {pluralWord(facts.steps, "step")}{facts.audience != null ? `, ${facts.audience.toLocaleString()} ${pluralWord(facts.audience, "contact")} in scope` : ""}
68
75
  {facts.credits != null ? `, about ${Math.round(facts.credits).toLocaleString()} credits (advisory)` : ""}
69
- {facts.approval ? ", waits for a human approval" : ""}{facts.external ? ", contacts people or systems outside graph8" : ""}.
76
+ {facts.approval ? ", waits for a human approval" : ""}{facts.external ? `, contacts people or systems outside ${brand.linkLabel}` : ""}.
70
77
  </p>
71
78
  )}
72
79
  <label className="block text-xs font-medium" htmlFor="run-input">Run input (JSON)</label>
@@ -86,7 +93,7 @@ function confirmCopy(p: Pending): { tier: "external" | "destructive"; title: str
86
93
  return { tier: "destructive", title: `Delete "${p.workflow.name}"`, label: "Delete", description: "The workflow and its trigger are removed. Past runs stay in history." };
87
94
  }
88
95
 
89
- export function WorkflowList({ onOpen, onRunStarted, className }: WorkflowListProps) {
96
+ export function WorkflowList({ linkParam, onOpen, onRunStarted, className }: WorkflowListProps) {
90
97
  const list = useG8Query((signal) => g8Get("/workflows", undefined, { signal }), []);
91
98
  const [name, setName] = useState("");
92
99
  const [pending, setPending] = useState<Pending | null>(null);
@@ -128,8 +135,8 @@ export function WorkflowList({ onOpen, onRunStarted, className }: WorkflowListPr
128
135
  <TableHeader><TableRow><TableHead>Name</TableHead><TableHead>Status</TableHead><TableHead>Category</TableHead><TableHead className="text-right">Actions</TableHead></TableRow></TableHeader>
129
136
  <TableBody>
130
137
  {rows.map((w) => (
131
- <TableRow key={w.action_id}>
132
- <TableCell><button type="button" className="font-medium hover:underline" onClick={() => onOpen?.(w.action_id)}>{w.name}</button>{w.description ? <div className="text-muted-foreground text-xs">{w.description}</div> : null}</TableCell>
138
+ <TableRow key={w.action_id} {...(onOpen ? rowActivationProps(() => onOpen(w.action_id)) : {})}>
139
+ <TableCell>{onOpen ? <RecordLink param={linkParam} id={w.action_id} onOpen={() => onOpen(w.action_id)}>{w.name}</RecordLink> : <span className="font-medium">{w.name}</span>}{w.description ? <div className="text-muted-foreground text-xs">{w.description}</div> : null}</TableCell>
133
140
  <TableCell>{w.enabled === false ? "Disabled" : "Enabled"}</TableCell>
134
141
  <TableCell>{w.category ?? ""}</TableCell>
135
142
  <TableCell className="space-x-1 text-right">
@@ -0,0 +1,193 @@
1
+ "use client";
2
+
3
+ /**
4
+ * WorkflowsScreen: the finished automations workspace. Two tabs.
5
+ * Workflows: stat strip (workflows, enabled, disabled, failed runs), search and status filter, the list with
6
+ * Run / Enable-Disable / Delete per row, and for the open workflow (deep link ?id=) a detail panel with
7
+ * Steps (WorkflowEditor), Trigger (TriggerConfig) and Runs (RunHistory for this workflow).
8
+ * Runs: every run across all workflows as a list; opening one (deep link ?run=) shows the full run (status, timing,
9
+ * who started it, error, input, output) with pause / resume / stop / restart.
10
+ * "New workflow" asks for a name, creates a manual-trigger workflow and opens it so steps can be added. Starting a
11
+ * run shows the plan preview (steps, audience, advisory credits) and the run input first, inside the confirm.
12
+ *
13
+ * Composes: WorkflowEditor, TriggerConfig, RunHistory, RunPlan (from WorkflowList).
14
+ *
15
+ * SDK operations of its own, in the order used:
16
+ * 1. GET /workflows (workflows.listWorkflows) read `{ actions, total }`
17
+ * 2. GET /workflows/executions?status_filter=failed&limit=1 (workflows.listWorkflowExecutions) read failed-run count
18
+ * 3. POST /workflows (workflows.createWorkflow) external -> ConfirmSpendDialog
19
+ * 4. POST /workflows/{id}/plan, then POST /workflows/{id}/execute { input_data } (plan read; execute external -> ConfirmSpendDialog)
20
+ * 5. PUT /workflows/{id} { enabled } (workflows.updateWorkflow) external -> ConfirmSpendDialog
21
+ * 6. DELETE /workflows/{id} (workflows.deleteWorkflow) destructive -> ConfirmSpendDialog
22
+ * Slot props: renderRowActions, renderDetailActions, extraDetailTabs, toolbarActions.
23
+ */
24
+ import { useEffect, useState, type ReactNode } from "react";
25
+ import { Plus, Workflow } from "lucide-react";
26
+ import { Button, Input, Skeleton } from "@graph8/sdk/ui";
27
+ import { ApiError } from "@/g8/components/api-error";
28
+ import { ConfirmSpendDialog } from "@/g8/components/confirm-spend-dialog";
29
+ import { EmptyState } from "@/g8/components/empty-state";
30
+ import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
31
+ import { useG8Query } from "@/g8/hooks/use-g8-query";
32
+ import { g8Delete, g8Get, g8Post, g8Put } from "@/g8/lib/g8-client";
33
+ import { DetailPanel } from "@/g8/components/screen/detail-panel";
34
+ import { FormSheet } from "@/g8/components/screen/form-sheet";
35
+ import { ScreenLayout } from "@/g8/components/screen/screen-layout";
36
+ import { SplitView } from "@/g8/components/screen/split-view";
37
+ import { StatusPill } from "@/g8/components/screen/status-pill";
38
+ import { Toolbar } from "@/g8/components/screen/toolbar";
39
+ import { useUrlState } from "@/g8/hooks/use-url-state";
40
+ import { blankConfig, payload, str } from "../lib/graph";
41
+ import type { WorkflowRow } from "../lib/graph";
42
+ import { RunDetail, RunHistory } from "./run-history";
43
+ import { RunPlan } from "./workflow-list";
44
+ import { TriggerConfig } from "./trigger-config";
45
+ import { WorkflowEditor } from "./workflow-editor";
46
+
47
+ export interface WorkflowsScreenProps {
48
+ renderRowActions?: (workflow: WorkflowRow) => ReactNode;
49
+ renderDetailActions?: (workflow: WorkflowRow) => ReactNode;
50
+ extraDetailTabs?: Array<{ id: string; label: string; render: (workflow: WorkflowRow) => ReactNode }>;
51
+ toolbarActions?: ReactNode;
52
+ className?: string;
53
+ }
54
+
55
+ type Pending =
56
+ | { kind: "create"; name: string }
57
+ | { kind: "run"; workflow: WorkflowRow }
58
+ | { kind: "toggle"; workflow: WorkflowRow }
59
+ | { kind: "delete"; workflow: WorkflowRow };
60
+
61
+ const SCREEN_TABS = [{ id: "workflows", label: "Workflows" }, { id: "runs", label: "Runs" }];
62
+ const FILTERS = [{ id: "", label: "All" }, { id: "enabled", label: "Enabled" }, { id: "disabled", label: "Disabled" }];
63
+ const isOn = (w: WorkflowRow) => w.enabled !== false;
64
+
65
+ function copyFor(p: Pending): { tier: "external" | "destructive"; title: string; label: string; description?: string } {
66
+ if (p.kind === "create") return { tier: "external", title: `Create workflow "${p.name || "Untitled workflow"}"`, label: "Create", description: "A manual-trigger workflow with no steps yet. Add steps in the editor." };
67
+ if (p.kind === "run") return { tier: "external", title: `Run "${p.workflow.name}"`, label: "Run now" };
68
+ if (p.kind === "toggle") {
69
+ const on = isOn(p.workflow);
70
+ return { tier: "external", title: `${on ? "Disable" : "Enable"} "${p.workflow.name}"`, label: on ? "Disable" : "Enable", description: on ? "Its trigger stops firing." : "Its trigger starts firing and the workflow can act on real contacts." };
71
+ }
72
+ return { tier: "destructive", title: `Delete "${p.workflow.name}"`, label: "Delete", description: "The workflow and its trigger are removed. Past runs stay in history." };
73
+ }
74
+
75
+ export function WorkflowsScreen({ renderRowActions, renderDetailActions, extraDetailTabs, toolbarActions, className }: WorkflowsScreenProps) {
76
+ const [tab, setTab] = useUrlState("tab", "workflows");
77
+ const [selected, setSelected] = useUrlState("id", "");
78
+ const [search, setSearch] = useState("");
79
+ const [filter, setFilter] = useState("");
80
+ const [newName, setNewName] = useState("");
81
+ const [sheet, setSheet] = useState(false);
82
+ const [pending, setPending] = useState<Pending | null>(null);
83
+ const [input, setInput] = useState("{}");
84
+ const [run, setRun] = useUrlState("run", "");
85
+ const [view, setView] = useUrlState("view", "");
86
+ const closeWorkflow = () => { setSelected(""); setView(""); };
87
+ const closeRun = () => { setRun(""); setView(""); };
88
+ const expand = { expanded: view === "full", onExpandedChange: (e: boolean) => setView(e ? "full" : "") };
89
+ const [runsKey, setRunsKey] = useState(0);
90
+ const q = search.trim().toLowerCase();
91
+ useEffect(() => setInput("{}"), [pending]);
92
+
93
+ const list = useG8Query((signal) => g8Get("/workflows", undefined, { signal }), []);
94
+ const failed = useG8Query((signal) => g8Get("/workflows/executions", { status_filter: "failed", limit: 1 }, { signal }), []);
95
+ const all = (Array.isArray(payload(list.data).actions) ? payload(list.data).actions : []) as WorkflowRow[];
96
+ const failedTotal = payload(failed.data).total_count;
97
+ const rows = all.filter((w) => (!q || `${w.name} ${w.description ?? ""}`.toLowerCase().includes(q)) && (!filter || (filter === "enabled") === isOn(w)));
98
+ const open = all.find((w) => w.action_id === selected);
99
+
100
+ const act = useG8Mutation(async (p: Pending) => {
101
+ if (p.kind === "create") return g8Post("/workflows", { name: p.name || "Untitled workflow", description: "", config: blankConfig(p.name || "Untitled workflow") });
102
+ const base = `/workflows/${encodeURIComponent(p.workflow.action_id)}`;
103
+ if (p.kind === "run") return g8Post(`${base}/execute`, { input_data: JSON.parse(input || "{}") });
104
+ if (p.kind === "toggle") return g8Put(base, { enabled: !isOn(p.workflow) });
105
+ return g8Delete(base);
106
+ }, {
107
+ onSuccess: (res, p) => {
108
+ setPending(null);
109
+ if (p.kind === "create") { setNewName(""); setSheet(false); const id = str(payload(res).action_id); if (id) { setSelected(id); setTab("workflows"); } }
110
+ if (p.kind === "run") { setTab("runs"); setRun(str(payload(res).execution_id)); }
111
+ if (p.kind === "delete" && selected === p.workflow.action_id) setSelected("");
112
+ void list.refetch();
113
+ void failed.refetch();
114
+ },
115
+ });
116
+ let inputOk = true;
117
+ try { JSON.parse(input || "{}"); } catch { inputOk = false; }
118
+ const copy = pending ? copyFor(pending) : null;
119
+
120
+ const detail = open ? (
121
+ <DetailPanel title={open.name} subtitle={open.description ?? open.category ?? undefined} onClose={closeWorkflow} onExpand={() => setView("full")}
122
+ badges={<StatusPill tone={isOn(open) ? "positive" : "neutral"} dot>{isOn(open) ? "Enabled" : "Disabled"}</StatusPill>}
123
+ actions={<>
124
+ <Button size="sm" onClick={() => setPending({ kind: "run", workflow: open })}>Run</Button>
125
+ {renderDetailActions?.(open)}
126
+ </>}
127
+ tabs={[
128
+ { id: "steps", label: "Steps", content: <WorkflowEditor key={open.action_id} actionId={open.action_id} onSaved={() => void list.refetch()} /> },
129
+ { id: "trigger", label: "Trigger", content: <TriggerConfig key={open.action_id} actionId={open.action_id} onSaved={() => void list.refetch()} /> },
130
+ { id: "runs", label: "Runs", content: <RunHistory key={open.action_id} actionId={open.action_id} /> },
131
+ ...(extraDetailTabs ?? []).map((t) => ({ id: t.id, label: t.label, content: t.render(open) })),
132
+ ]} />
133
+ ) : selected && !list.loading ? <EmptyState title="Workflow not found" description="It may have been deleted." /> : null;
134
+
135
+ const listView = (
136
+ <div className="flex flex-col gap-3">
137
+ <Toolbar search={{ value: search, onChange: setSearch, placeholder: "Search workflows" }} actions={toolbarActions}
138
+ filters={<div role="group" aria-label="Status" className="flex gap-1">{FILTERS.map((f) => <Button key={f.id || "all"} size="sm" variant={filter === f.id ? "default" : "outline"} onClick={() => setFilter(f.id)}>{f.label}</Button>)}</div>} />
139
+ {list.error ? <ApiError error={list.error} title="Could not load workflows" onRetry={() => void list.refetch()} /> : null}
140
+ {act.error ? <ApiError error={act.error} title="That action failed" /> : null}
141
+ {list.loading && !list.data ? <div className="space-y-2" aria-busy="true">{[0, 1, 2].map((i) => <Skeleton key={i} className="h-14 w-full" />)}</div> : null}
142
+ <ul className="rounded-md border">
143
+ {rows.map((w) => (
144
+ <li key={w.action_id} className={`flex items-center gap-2 border-b px-3 py-2 ${w.action_id === selected ? "bg-accent" : ""}`}>
145
+ <button type="button" data-testid={`workflow-${w.action_id}`} onClick={() => setSelected(w.action_id)} className="min-w-0 flex-1 text-left">
146
+ <span className="block truncate text-sm font-medium">{w.name}</span>
147
+ <span className="block truncate text-xs text-muted-foreground">{w.description || w.category || "No description"}</span>
148
+ </button>
149
+ <StatusPill tone={isOn(w) ? "positive" : "neutral"} dot>{isOn(w) ? "Enabled" : "Disabled"}</StatusPill>
150
+ <Button size="sm" variant="outline" onClick={() => setPending({ kind: "run", workflow: w })}>Run</Button>
151
+ <Button size="sm" variant="ghost" onClick={() => setPending({ kind: "toggle", workflow: w })}>{isOn(w) ? "Disable" : "Enable"}</Button>
152
+ <Button size="sm" variant="ghost" aria-label={`Delete ${w.name}`} onClick={() => setPending({ kind: "delete", workflow: w })}>Delete</Button>
153
+ {renderRowActions?.(w)}
154
+ </li>
155
+ ))}
156
+ </ul>
157
+ {!list.loading && !list.error && rows.length === 0 ? (
158
+ <EmptyState title={all.length ? "No workflows match" : "No workflows yet"} description="A workflow runs steps automatically when its trigger fires or when you run it."
159
+ action={all.length ? undefined : <Button onClick={() => setSheet(true)}>New workflow</Button>} />
160
+ ) : null}
161
+ </div>
162
+ );
163
+
164
+ const current = tab === "runs" ? "runs" : "workflows";
165
+ return (
166
+ <ScreenLayout className={className} title="Workflows" description="Automations that run when something happens, or when you press Run."
167
+ icon={<Workflow className="size-5" />} tabs={SCREEN_TABS} activeTab={current} onTabChange={setTab}
168
+ actions={<Button onClick={() => setSheet(true)}><Plus className="mr-1 size-4" />New workflow</Button>}
169
+ stats={current === "workflows" ? [
170
+ { label: "Workflows", value: String(all.length) },
171
+ { label: "Enabled", value: String(all.filter(isOn).length), tone: "positive" },
172
+ { label: "Disabled", value: String(all.filter((w) => !isOn(w)).length) },
173
+ { label: "Failed runs", value: typeof failedTotal === "number" ? failedTotal.toLocaleString() : "-", tone: failedTotal ? "negative" : "neutral" },
174
+ ] : undefined}>
175
+ {current === "runs" ? (
176
+ <SplitView list={<RunHistory hideDetail selectedId={run || null} onSelectRun={(id) => setRun(id ?? "")} refreshKey={runsKey} nameFor={(id) => all.find((w) => w.action_id === id)?.name} />}
177
+ detailOpen={Boolean(run)} onCloseDetail={closeRun} {...expand} detailWidth="lg"
178
+ detail={run ? <DetailPanel title="Run" subtitle={run} onClose={closeRun} onExpand={() => setView("full")}
179
+ tabs={[{ id: "run", label: "Run", content: <RunDetail key={run} executionId={run} onChanged={() => setRunsKey((k) => k + 1)} /> }]} /> : null} />
180
+ ) : <SplitView list={listView} detail={detail} detailOpen={Boolean(selected)} onCloseDetail={closeWorkflow} {...expand} detailWidth="xl" />}
181
+
182
+ <FormSheet open={sheet} onOpenChange={setSheet} title="New workflow" description="Starts with a manual trigger and an end step. You add the steps next."
183
+ footer={<><Button variant="ghost" onClick={() => setSheet(false)}>Cancel</Button><Button onClick={() => setPending({ kind: "create", name: newName.trim() })}>Create</Button></>}>
184
+ <div className="flex flex-col gap-1"><label className="text-sm font-medium" htmlFor="wf-name">Name</label><Input id="wf-name" value={newName} onChange={(e) => setNewName(e.target.value)} placeholder="Workflow name" /></div>
185
+ </FormSheet>
186
+ {pending && copy ? (
187
+ <ConfirmSpendDialog open tier={copy.tier} title={copy.title} confirmLabel={copy.label} confirming={act.loading} affected={pending.kind === "delete" ? 1 : undefined}
188
+ description={pending.kind === "run" ? <RunPlan actionId={pending.workflow.action_id} input={input} setInput={setInput} /> : copy.description}
189
+ onConfirm={() => { if (pending.kind !== "run" || inputOk) void act.mutate(pending); }} onCancel={() => setPending(null)} />
190
+ ) : null}
191
+ </ScreenLayout>
192
+ );
193
+ }