@graph8/sdk 0.362.0 → 0.370.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 (349) hide show
  1. package/README.md +14 -10
  2. package/blocks/_foundation/README.md +64 -1
  3. package/blocks/_foundation/block.json +79 -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/confirmed-action.tsx +90 -0
  7. package/blocks/_foundation/components/connect-graph8.tsx +280 -0
  8. package/blocks/_foundation/components/screen/index.ts +1 -0
  9. package/blocks/_foundation/components/screen/record-link.tsx +76 -0
  10. package/blocks/_foundation/components/screen/time-series-chart-recharts.tsx +65 -0
  11. package/blocks/_foundation/components/screen/time-series-chart.tsx +15 -97
  12. package/blocks/_foundation/components/screen/time-series-format.ts +57 -0
  13. package/blocks/_foundation/hooks/use-brand.ts +52 -0
  14. package/blocks/_foundation/hooks/use-g8-mutation.ts +23 -0
  15. package/blocks/_foundation/hooks/use-g8-query.ts +127 -1
  16. package/blocks/_foundation/hooks/use-paginated.ts +120 -0
  17. package/blocks/_foundation/hooks/use-row-activation.ts +57 -0
  18. package/blocks/_foundation/lib/g8-cache.ts +454 -0
  19. package/blocks/_foundation/lib/g8-client.ts +1 -1
  20. package/blocks/_foundation/lib/plural.ts +23 -0
  21. package/blocks/_foundation/server/connect-route.ts +15 -0
  22. package/blocks/_foundation/server/connect.ts +127 -0
  23. package/blocks/_foundation/server/local-key.ts +70 -0
  24. package/blocks/_foundation/server/proxy.ts +103 -3
  25. package/blocks/_foundation/server/route.ts +16 -4
  26. package/blocks/agents/README.md +14 -0
  27. package/blocks/agents/block.json +33 -1
  28. package/blocks/agents/components/agent-builder.tsx +13 -8
  29. package/blocks/agents/components/agent-knowledge-panel.tsx +2 -1
  30. package/blocks/agents/components/agent-run-detail.tsx +102 -0
  31. package/blocks/agents/components/agent-runs.tsx +16 -7
  32. package/blocks/agents/components/agents-screen.tsx +184 -0
  33. package/blocks/analytics/README.md +13 -0
  34. package/blocks/analytics/block.json +45 -1
  35. package/blocks/analytics/components/analytics-screen.tsx +209 -0
  36. package/blocks/analytics/components/leaderboard.tsx +14 -7
  37. package/blocks/analytics/components/report-builder.tsx +23 -9
  38. package/blocks/analytics/components/report-detail.tsx +84 -0
  39. package/blocks/analytics/components/trend-chart.tsx +62 -0
  40. package/blocks/appointments/block.json +6 -2
  41. package/blocks/appointments/components/availability-editor.tsx +17 -5
  42. package/blocks/appointments/components/event-type-editor.tsx +2 -1
  43. package/blocks/appointments/components/event-type-related.tsx +4 -2
  44. package/blocks/appointments/components/meeting-list.tsx +9 -5
  45. package/blocks/appointments/components/meetings-screen.tsx +7 -4
  46. package/blocks/appointments/components/scheduling-screen.tsx +2 -1
  47. package/blocks/block.schema.json +18 -0
  48. package/blocks/block.types.ts +17 -0
  49. package/blocks/campaigns/README.md +41 -11
  50. package/blocks/campaigns/block.json +105 -42
  51. package/blocks/campaigns/components/campaign-assets.tsx +16 -5
  52. package/blocks/campaigns/components/campaign-launch-step.tsx +102 -19
  53. package/blocks/campaigns/components/campaign-list.tsx +7 -3
  54. package/blocks/campaigns/components/campaign-wizard.tsx +43 -10
  55. package/blocks/campaigns/components/campaigns-screen.tsx +267 -0
  56. package/blocks/campaigns/components/icp-detail.tsx +154 -0
  57. package/blocks/campaigns/components/icp-editor.tsx +15 -15
  58. package/blocks/campaigns/components/persona-detail.tsx +172 -0
  59. package/blocks/campaigns/components/persona-editor.tsx +14 -11
  60. package/blocks/campaigns/lib/campaigns-utils.ts +134 -1
  61. package/blocks/campaigns/recipes/attach-audience-and-launch.ts +16 -3
  62. package/blocks/campaigns/recipes/generate-ideas-and-create-campaign.ts +8 -4
  63. package/blocks/coach/README.md +10 -0
  64. package/blocks/coach/block.json +16 -1
  65. package/blocks/coach/components/coach-screen.tsx +222 -0
  66. package/blocks/copilot/README.md +13 -0
  67. package/blocks/copilot/block.json +14 -1
  68. package/blocks/copilot/components/copilot-panel.tsx +14 -5
  69. package/blocks/copilot/components/copilot-screen.tsx +148 -0
  70. package/blocks/copilot/components/copilot-team-chat.tsx +7 -5
  71. package/blocks/crm/README.md +28 -0
  72. package/blocks/crm/block.json +58 -2
  73. package/blocks/crm/components/companies-screen.tsx +345 -0
  74. package/blocks/crm/components/company-detail.tsx +8 -6
  75. package/blocks/crm/components/company-table.tsx +11 -4
  76. package/blocks/crm/components/contact-detail.tsx +6 -3
  77. package/blocks/crm/components/contact-table.tsx +10 -3
  78. package/blocks/crm/components/contacts-screen.tsx +359 -0
  79. package/blocks/crm/components/dedupe-review.tsx +7 -5
  80. package/blocks/crm/components/import-wizard.tsx +4 -2
  81. package/blocks/crm/components/notes-panel.tsx +4 -4
  82. package/blocks/crm/components/record-form.tsx +5 -3
  83. package/blocks/crm/components/task-list.tsx +4 -4
  84. package/blocks/crm/components/tasks-screen.tsx +379 -0
  85. package/blocks/crm/hooks/use-custom-fields.ts +6 -1
  86. package/blocks/crm/hooks/use-page-query.ts +7 -1
  87. package/blocks/crm/lib/crm-types.ts +12 -2
  88. package/blocks/crm/lib/screen-parts.tsx +105 -0
  89. package/blocks/deals/block.json +9 -3
  90. package/blocks/deals/components/deal-detail.tsx +23 -11
  91. package/blocks/deals/components/deal-related.tsx +1 -1
  92. package/blocks/deals/components/deals-screen.tsx +10 -5
  93. package/blocks/deals/components/stage-editor.tsx +4 -4
  94. package/blocks/deals/hooks/use-pipeline-deals.ts +5 -1
  95. package/blocks/deals/hooks/use-pipelines.ts +1 -1
  96. package/blocks/dialer/README.md +34 -2
  97. package/blocks/dialer/block.json +67 -1
  98. package/blocks/dialer/components/call-contact-button.tsx +252 -0
  99. package/blocks/dialer/components/call-log-screen.tsx +205 -0
  100. package/blocks/dialer/components/call-log.tsx +7 -3
  101. package/blocks/dialer/components/call-queue.tsx +1 -1
  102. package/blocks/dialer/components/dialer-screen.tsx +319 -0
  103. package/blocks/dialer/components/dialer-session-detail.tsx +153 -0
  104. package/blocks/dialer/components/live-calls-table.tsx +7 -3
  105. package/blocks/dialer/components/session-start-form.tsx +4 -2
  106. package/blocks/dialer/components/softphone.tsx +3 -1
  107. package/blocks/enrichment/README.md +14 -2
  108. package/blocks/enrichment/block.json +32 -4
  109. package/blocks/enrichment/components/bulk-enrich-panel.tsx +10 -5
  110. package/blocks/enrichment/components/email-verify-badge.tsx +28 -8
  111. package/blocks/enrichment/components/enrich-button.tsx +32 -9
  112. package/blocks/enrichment/components/enrichment-job-detail.tsx +78 -0
  113. package/blocks/enrichment/components/enrichment-screen.tsx +288 -0
  114. package/blocks/enrichment/components/waterfall-config.tsx +39 -10
  115. package/blocks/enrichment/recipes/waterfall-pipeline.ts +3 -2
  116. package/blocks/forms/block.json +32 -18
  117. package/blocks/forms/components/form-builder.tsx +3 -1
  118. package/blocks/forms/components/form-embed.tsx +3 -1
  119. package/blocks/forms/components/forms-screen.tsx +5 -2
  120. package/blocks/forms/components/submissions-table.tsx +3 -1
  121. package/blocks/inbox/README.md +27 -3
  122. package/blocks/inbox/block.json +14 -2
  123. package/blocks/inbox/components/inbox-list.tsx +7 -3
  124. package/blocks/inbox/components/inbox-screen.tsx +294 -27
  125. package/blocks/inbox/components/reply-composer.tsx +65 -9
  126. package/blocks/inbox/components/tag-and-assign.tsx +4 -2
  127. package/blocks/inbox/hooks/use-inbox-thread.ts +1 -1
  128. package/blocks/inbox/hooks/use-inbox-threads.ts +1 -0
  129. package/blocks/inbox/lib/inbox-types.ts +47 -1
  130. package/blocks/inbox/recipes/01-triage-and-reply.ts +12 -2
  131. package/blocks/integrations/README.md +9 -0
  132. package/blocks/integrations/block.json +46 -1
  133. package/blocks/integrations/components/crm-detail.tsx +100 -0
  134. package/blocks/integrations/components/integration-list.tsx +9 -4
  135. package/blocks/integrations/components/integrations-screen.tsx +135 -0
  136. package/blocks/integrations/components/sync-status.tsx +5 -3
  137. package/blocks/integrations/components/webhook-detail.tsx +160 -0
  138. package/blocks/integrations/components/webhook-manager.tsx +13 -8
  139. package/blocks/integrations/lib/integrations-utils.ts +6 -1
  140. package/blocks/intent/README.md +14 -0
  141. package/blocks/intent/block.json +27 -1
  142. package/blocks/intent/components/intent-feed.tsx +5 -2
  143. package/blocks/intent/components/intent-keyword-manager.tsx +8 -4
  144. package/blocks/intent/components/intent-screen.tsx +265 -0
  145. package/blocks/intent/components/keyword-detail.tsx +124 -0
  146. package/blocks/lifecycle/block.json +13 -8
  147. package/blocks/lifecycle/components/lifecycle-board.tsx +3 -1
  148. package/blocks/lifecycle/components/lifecycle-screen.tsx +3 -1
  149. package/blocks/lifecycle/lib/lifecycle.ts +1 -1
  150. package/blocks/lists/README.md +13 -0
  151. package/blocks/lists/block.json +39 -1
  152. package/blocks/lists/components/add-to-list-button.tsx +3 -2
  153. package/blocks/lists/components/list-picker.tsx +5 -1
  154. package/blocks/lists/components/list-table.tsx +11 -5
  155. package/blocks/lists/components/lists-screen.tsx +344 -0
  156. package/blocks/lists/components/suppression-manager.tsx +6 -2
  157. package/blocks/lists/components/suppressions-screen.tsx +255 -0
  158. package/blocks/mailboxes/README.md +17 -0
  159. package/blocks/mailboxes/block.json +65 -17
  160. package/blocks/mailboxes/components/connect-mailbox.tsx +23 -13
  161. package/blocks/mailboxes/components/domain-detail.tsx +142 -0
  162. package/blocks/mailboxes/components/domain-purchase.tsx +6 -3
  163. package/blocks/mailboxes/components/mailbox-detail.tsx +156 -0
  164. package/blocks/mailboxes/components/mailboxes-screen.tsx +223 -0
  165. package/blocks/mailboxes/components/warmup-health.tsx +4 -2
  166. package/blocks/mailboxes/lib/mailboxes-utils.ts +22 -1
  167. package/blocks/newsletter/block.json +40 -24
  168. package/blocks/newsletter/components/issue-scheduler.tsx +2 -1
  169. package/blocks/newsletter/components/newsletter-screen.tsx +3 -1
  170. package/blocks/nurture/block.json +28 -14
  171. package/blocks/nurture/components/nurture-builder.tsx +3 -1
  172. package/blocks/nurture/components/nurture-enroll.tsx +6 -3
  173. package/blocks/nurture/components/nurture-form.tsx +23 -3
  174. package/blocks/nurture/components/nurture-screen.tsx +10 -6
  175. package/blocks/nurture/lib/nurture-types.ts +1 -1
  176. package/blocks/objects/README.md +17 -0
  177. package/blocks/objects/block.json +29 -16
  178. package/blocks/objects/components/object-not-found.tsx +3 -1
  179. package/blocks/objects/components/object-record-detail.tsx +2 -1
  180. package/blocks/objects/components/object-schema-editor.tsx +3 -1
  181. package/blocks/objects/components/object-table.tsx +13 -3
  182. package/blocks/objects/components/objects-screen.tsx +147 -0
  183. package/blocks/objects/lib/object-types.ts +1 -1
  184. package/blocks/pages/block.json +28 -23
  185. package/blocks/pages/components/brand-kit-editor.tsx +5 -2
  186. package/blocks/pages/components/content-library.tsx +9 -4
  187. package/blocks/pages/components/page-list.tsx +2 -1
  188. package/blocks/quotes/block.json +17 -12
  189. package/blocks/quotes/components/product-picker.tsx +3 -1
  190. package/blocks/quotes/components/quote-related.tsx +5 -2
  191. package/blocks/radar/README.md +10 -0
  192. package/blocks/radar/block.json +41 -1
  193. package/blocks/radar/components/competitor-detail.tsx +117 -0
  194. package/blocks/radar/components/competitor-tracker.tsx +3 -1
  195. package/blocks/radar/components/radar-report.tsx +4 -2
  196. package/blocks/radar/components/radar-screen.tsx +214 -0
  197. package/blocks/radar/components/report-detail.tsx +104 -0
  198. package/blocks/search/README.md +10 -0
  199. package/blocks/search/block.json +16 -1
  200. package/blocks/search/components/filter-builder.tsx +3 -3
  201. package/blocks/search/components/prospect-results.tsx +5 -3
  202. package/blocks/search/components/prospect-search-screen.tsx +235 -0
  203. package/blocks/search/components/saved-searches.tsx +4 -1
  204. package/blocks/search/hooks/use-filter-options.ts +20 -6
  205. package/blocks/search/lib/search-types.ts +2 -0
  206. package/blocks/sequencer/README.md +49 -9
  207. package/blocks/sequencer/block.json +99 -19
  208. package/blocks/sequencer/components/enroll-dialog.tsx +9 -3
  209. package/blocks/sequencer/components/sequence-builder.tsx +211 -46
  210. package/blocks/sequencer/components/sequence-detail.tsx +259 -0
  211. package/blocks/sequencer/components/sequence-enrollments.tsx +114 -0
  212. package/blocks/sequencer/components/sequence-list.tsx +33 -8
  213. package/blocks/sequencer/components/sequence-stats.tsx +3 -2
  214. package/blocks/sequencer/components/sequence-steps-panel.tsx +103 -0
  215. package/blocks/sequencer/components/sequences-screen.tsx +159 -0
  216. package/blocks/sequencer/components/step-editor.tsx +3 -1
  217. package/blocks/sequencer/lib/sequencer-utils.ts +128 -5
  218. package/blocks/sequencer/recipes/create-multichannel-sequence.ts +33 -5
  219. package/blocks/sequencer/recipes/launch-and-enroll.ts +19 -2
  220. package/blocks/social-ads/README.md +11 -0
  221. package/blocks/social-ads/block.json +101 -2
  222. package/blocks/social-ads/components/ad-campaign-list.tsx +9 -5
  223. package/blocks/social-ads/components/ads-screen.tsx +152 -0
  224. package/blocks/social-ads/components/audience-sync-detail.tsx +188 -0
  225. package/blocks/social-ads/components/audience-sync.tsx +11 -7
  226. package/blocks/social-ads/components/campaign-detail.tsx +133 -0
  227. package/blocks/social-ads/components/creative-detail.tsx +150 -0
  228. package/blocks/social-ads/components/creative-gallery.tsx +15 -5
  229. package/blocks/social-ads/components/post-composer.tsx +3 -1
  230. package/blocks/social-ads/components/post-detail.tsx +160 -0
  231. package/blocks/social-ads/components/social-post-list.tsx +11 -3
  232. package/blocks/social-ads/components/social-screen.tsx +160 -0
  233. package/blocks/visitors/README.md +10 -0
  234. package/blocks/visitors/block.json +25 -1
  235. package/blocks/visitors/components/snippet-install.tsx +3 -1
  236. package/blocks/visitors/components/visitor-company-card.tsx +2 -2
  237. package/blocks/visitors/components/visitor-detail.tsx +105 -0
  238. package/blocks/visitors/components/visitor-feed.tsx +11 -2
  239. package/blocks/visitors/components/visitors-screen.tsx +166 -0
  240. package/blocks/webchat/README.md +10 -0
  241. package/blocks/webchat/block.json +21 -7
  242. package/blocks/webchat/components/chat-widget-config.tsx +7 -4
  243. package/blocks/webchat/components/conversation-list.tsx +7 -3
  244. package/blocks/webchat/components/webchat-screen.tsx +245 -0
  245. package/blocks/workbench/README.md +11 -0
  246. package/blocks/workbench/block.json +48 -1
  247. package/blocks/workbench/components/column-recipe-editor.tsx +4 -2
  248. package/blocks/workbench/components/workbench-grid.tsx +9 -3
  249. package/blocks/workbench/components/workbench-row-detail.tsx +104 -0
  250. package/blocks/workbench/components/workbench-screen.tsx +192 -0
  251. package/blocks/workbench/components/workbench-sources.tsx +194 -0
  252. package/blocks/workflows/README.md +13 -0
  253. package/blocks/workflows/block.json +28 -9
  254. package/blocks/workflows/components/run-history.tsx +31 -9
  255. package/blocks/workflows/components/trigger-config.tsx +3 -1
  256. package/blocks/workflows/components/workflow-editor.tsx +3 -1
  257. package/blocks/workflows/components/workflow-list.tsx +13 -6
  258. package/blocks/workflows/components/workflows-screen.tsx +193 -0
  259. package/dist/cli.js +2709 -1721
  260. package/dist/errors.d.mts +34 -0
  261. package/dist/errors.d.ts +34 -0
  262. package/dist/errors.js +46 -0
  263. package/dist/errors.js.map +1 -0
  264. package/dist/errors.mjs +21 -0
  265. package/dist/errors.mjs.map +1 -0
  266. package/dist/index.browser.js +11 -5
  267. package/dist/index.browser.js.map +1 -1
  268. package/dist/index.browser.mjs +11 -5
  269. package/dist/index.browser.mjs.map +1 -1
  270. package/dist/index.d.mts +164 -25
  271. package/dist/index.d.ts +164 -25
  272. package/dist/index.js +11 -5
  273. package/dist/index.js.map +1 -1
  274. package/dist/index.mjs +11 -5
  275. package/dist/index.mjs.map +1 -1
  276. package/dist/react.browser.js +10 -4
  277. package/dist/react.browser.js.map +1 -1
  278. package/dist/react.browser.mjs +10 -4
  279. package/dist/react.browser.mjs.map +1 -1
  280. package/dist/react.d.mts +142 -5
  281. package/dist/react.d.ts +142 -5
  282. package/dist/react.js +10 -4
  283. package/dist/react.js.map +1 -1
  284. package/dist/react.mjs +10 -4
  285. package/dist/react.mjs.map +1 -1
  286. package/dist/types/blocks/add.d.ts +11 -4
  287. package/dist/types/blocks/brand.d.ts +113 -0
  288. package/dist/types/blocks/compose.d.ts +53 -0
  289. package/dist/types/blocks/create.d.ts +22 -19
  290. package/dist/types/blocks/doctor.d.ts +5 -1
  291. package/dist/types/blocks/navfile.d.ts +35 -0
  292. package/dist/types/blocks/paths.d.ts +4 -0
  293. package/dist/types/blocks/prefixes.d.ts +37 -0
  294. package/dist/types/blocks/registry.d.ts +17 -0
  295. package/dist/types/blocks/screens.d.ts +53 -0
  296. package/dist/types/cli.d.ts +2 -0
  297. package/dist/types/errors.d.ts +33 -0
  298. package/dist/types/http.d.ts +2 -18
  299. package/dist/types/version.d.ts +2 -2
  300. package/docs/agents.md +15 -4
  301. package/docs/api/README.md +2 -2
  302. package/docs/api/by-path.md +1 -0
  303. package/docs/api/desk.d.ts +98 -2
  304. package/docs/api/desk.md +123 -1
  305. package/docs/api/inbox.d.ts +10 -2
  306. package/docs/api/inbox.md +10 -2
  307. package/docs/api/seo.d.ts +19 -1
  308. package/docs/api/seo.md +24 -1
  309. package/package.json +16 -2
  310. package/stacks/6sense.json +41 -16
  311. package/stacks/apollo/pages/companies.tsx.tmpl +50 -0
  312. package/stacks/apollo/pages/contacts.tsx.tmpl +117 -0
  313. package/stacks/apollo.json +118 -36
  314. package/stacks/calendly.json +60 -0
  315. package/stacks/clay/pages/companies.tsx.tmpl +15 -0
  316. package/stacks/clay/pages/contacts.tsx.tmpl +19 -0
  317. package/stacks/clay.json +59 -26
  318. package/stacks/gong.json +81 -35
  319. package/stacks/hootsuite.json +99 -0
  320. package/stacks/hubspot/pages/companies.tsx.tmpl +47 -0
  321. package/stacks/hubspot/pages/contacts.tsx.tmpl +111 -0
  322. package/stacks/hubspot/pages/deals.tsx.tmpl +11 -0
  323. package/stacks/hubspot/pages/inbox.tsx.tmpl +92 -0
  324. package/stacks/hubspot/pages/quotes.tsx.tmpl +11 -0
  325. package/stacks/hubspot.json +167 -66
  326. package/stacks/intercom.json +48 -20
  327. package/stacks/mailchimp.json +52 -33
  328. package/stacks/outreach/pages/contacts.tsx.tmpl +85 -0
  329. package/stacks/outreach.json +88 -25
  330. package/stacks/pipedrive/pages/companies.tsx.tmpl +47 -0
  331. package/stacks/pipedrive/pages/contacts.tsx.tmpl +47 -0
  332. package/stacks/pipedrive/pages/deals.tsx.tmpl +11 -0
  333. package/stacks/pipedrive/pages/inbox.tsx.tmpl +92 -0
  334. package/stacks/pipedrive/pages/quotes.tsx.tmpl +11 -0
  335. package/stacks/pipedrive.json +115 -0
  336. package/stacks/salesforce/pages/companies.tsx.tmpl +47 -0
  337. package/stacks/salesforce/pages/contacts.tsx.tmpl +47 -0
  338. package/stacks/salesforce/pages/deals.tsx.tmpl +11 -0
  339. package/stacks/salesforce/pages/quotes.tsx.tmpl +11 -0
  340. package/stacks/salesforce.json +81 -31
  341. package/stacks/zoominfo/pages/companies.tsx.tmpl +15 -0
  342. package/stacks/zoominfo/pages/contacts.tsx.tmpl +19 -0
  343. package/stacks/zoominfo.json +102 -0
  344. package/styles/styles.css +1 -1
  345. package/templates/next-app/README.md.tmpl +40 -9
  346. package/templates/next-app/app/layout.tsx.tmpl +7 -2
  347. package/templates/next-app/components/app-shell.tsx.tmpl +99 -89
  348. package/templates/next-app/dot-gitignore.tmpl +2 -0
  349. package/templates/next-app/package.json.tmpl +1 -1
@@ -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
+ }