@graph8/sdk 0.361.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 (388) hide show
  1. package/README.md +14 -10
  2. package/blocks/_foundation/README.md +172 -1
  3. package/blocks/_foundation/block.json +234 -13
  4. package/blocks/_foundation/components/api-error.tsx +71 -25
  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/empty-state.tsx +39 -7
  9. package/blocks/_foundation/components/screen/bar-list.tsx +71 -0
  10. package/blocks/_foundation/components/screen/detail-panel.tsx +114 -0
  11. package/blocks/_foundation/components/screen/entity-avatar.tsx +56 -0
  12. package/blocks/_foundation/components/screen/form-sheet.tsx +73 -0
  13. package/blocks/_foundation/components/screen/index.ts +22 -0
  14. package/blocks/_foundation/components/screen/key-value-list.tsx +65 -0
  15. package/blocks/_foundation/components/screen/record-link.tsx +76 -0
  16. package/blocks/_foundation/components/screen/screen-layout.tsx +124 -0
  17. package/blocks/_foundation/components/screen/section-card.tsx +47 -0
  18. package/blocks/_foundation/components/screen/sparkline.tsx +77 -0
  19. package/blocks/_foundation/components/screen/split-view.tsx +125 -0
  20. package/blocks/_foundation/components/screen/stat-strip.tsx +121 -0
  21. package/blocks/_foundation/components/screen/status-pill.tsx +104 -0
  22. package/blocks/_foundation/components/screen/time-series-chart-recharts.tsx +65 -0
  23. package/blocks/_foundation/components/screen/time-series-chart.tsx +32 -0
  24. package/blocks/_foundation/components/screen/time-series-format.ts +57 -0
  25. package/blocks/_foundation/components/screen/toolbar.tsx +144 -0
  26. package/blocks/_foundation/hooks/use-brand.ts +52 -0
  27. package/blocks/_foundation/hooks/use-g8-mutation.ts +23 -0
  28. package/blocks/_foundation/hooks/use-g8-query.ts +127 -1
  29. package/blocks/_foundation/hooks/use-paginated.ts +120 -0
  30. package/blocks/_foundation/hooks/use-row-activation.ts +57 -0
  31. package/blocks/_foundation/hooks/use-url-state.ts +74 -0
  32. package/blocks/_foundation/lib/g8-cache.ts +454 -0
  33. package/blocks/_foundation/lib/g8-client.ts +1 -1
  34. package/blocks/_foundation/lib/plural.ts +23 -0
  35. package/blocks/_foundation/server/connect-route.ts +15 -0
  36. package/blocks/_foundation/server/connect.ts +127 -0
  37. package/blocks/_foundation/server/local-key.ts +70 -0
  38. package/blocks/_foundation/server/proxy.ts +103 -3
  39. package/blocks/_foundation/server/route.ts +16 -4
  40. package/blocks/agents/README.md +14 -0
  41. package/blocks/agents/block.json +33 -1
  42. package/blocks/agents/components/agent-builder.tsx +13 -8
  43. package/blocks/agents/components/agent-knowledge-panel.tsx +2 -1
  44. package/blocks/agents/components/agent-run-detail.tsx +102 -0
  45. package/blocks/agents/components/agent-runs.tsx +16 -7
  46. package/blocks/agents/components/agents-screen.tsx +184 -0
  47. package/blocks/analytics/README.md +13 -0
  48. package/blocks/analytics/block.json +45 -1
  49. package/blocks/analytics/components/analytics-screen.tsx +209 -0
  50. package/blocks/analytics/components/leaderboard.tsx +14 -7
  51. package/blocks/analytics/components/report-builder.tsx +23 -9
  52. package/blocks/analytics/components/report-detail.tsx +84 -0
  53. package/blocks/analytics/components/trend-chart.tsx +62 -0
  54. package/blocks/appointments/README.md +21 -1
  55. package/blocks/appointments/block.json +43 -2
  56. package/blocks/appointments/components/availability-editor.tsx +17 -5
  57. package/blocks/appointments/components/event-type-editor.tsx +2 -1
  58. package/blocks/appointments/components/event-type-related.tsx +77 -0
  59. package/blocks/appointments/components/meeting-list.tsx +14 -6
  60. package/blocks/appointments/components/meetings-screen.tsx +157 -0
  61. package/blocks/appointments/components/scheduling-screen.tsx +122 -0
  62. package/blocks/appointments/hooks/use-event-types.ts +17 -0
  63. package/blocks/block.schema.json +18 -0
  64. package/blocks/block.types.ts +26 -0
  65. package/blocks/campaigns/README.md +41 -11
  66. package/blocks/campaigns/block.json +105 -42
  67. package/blocks/campaigns/components/campaign-assets.tsx +16 -5
  68. package/blocks/campaigns/components/campaign-launch-step.tsx +102 -19
  69. package/blocks/campaigns/components/campaign-list.tsx +7 -3
  70. package/blocks/campaigns/components/campaign-wizard.tsx +43 -10
  71. package/blocks/campaigns/components/campaigns-screen.tsx +267 -0
  72. package/blocks/campaigns/components/icp-detail.tsx +154 -0
  73. package/blocks/campaigns/components/icp-editor.tsx +15 -15
  74. package/blocks/campaigns/components/persona-detail.tsx +172 -0
  75. package/blocks/campaigns/components/persona-editor.tsx +14 -11
  76. package/blocks/campaigns/lib/campaigns-utils.ts +134 -1
  77. package/blocks/campaigns/recipes/attach-audience-and-launch.ts +16 -3
  78. package/blocks/campaigns/recipes/generate-ideas-and-create-campaign.ts +8 -4
  79. package/blocks/coach/README.md +10 -0
  80. package/blocks/coach/block.json +16 -1
  81. package/blocks/coach/components/coach-screen.tsx +222 -0
  82. package/blocks/copilot/README.md +13 -0
  83. package/blocks/copilot/block.json +14 -1
  84. package/blocks/copilot/components/copilot-panel.tsx +14 -5
  85. package/blocks/copilot/components/copilot-screen.tsx +148 -0
  86. package/blocks/copilot/components/copilot-team-chat.tsx +7 -5
  87. package/blocks/crm/README.md +28 -0
  88. package/blocks/crm/block.json +58 -2
  89. package/blocks/crm/components/companies-screen.tsx +345 -0
  90. package/blocks/crm/components/company-detail.tsx +8 -6
  91. package/blocks/crm/components/company-table.tsx +11 -4
  92. package/blocks/crm/components/contact-detail.tsx +6 -3
  93. package/blocks/crm/components/contact-table.tsx +10 -3
  94. package/blocks/crm/components/contacts-screen.tsx +359 -0
  95. package/blocks/crm/components/dedupe-review.tsx +7 -5
  96. package/blocks/crm/components/import-wizard.tsx +4 -2
  97. package/blocks/crm/components/notes-panel.tsx +4 -4
  98. package/blocks/crm/components/record-form.tsx +5 -3
  99. package/blocks/crm/components/task-list.tsx +4 -4
  100. package/blocks/crm/components/tasks-screen.tsx +379 -0
  101. package/blocks/crm/hooks/use-custom-fields.ts +6 -1
  102. package/blocks/crm/hooks/use-page-query.ts +7 -1
  103. package/blocks/crm/lib/crm-types.ts +12 -2
  104. package/blocks/crm/lib/screen-parts.tsx +105 -0
  105. package/blocks/deals/README.md +34 -3
  106. package/blocks/deals/block.json +45 -4
  107. package/blocks/deals/components/deal-detail.tsx +27 -13
  108. package/blocks/deals/components/deal-related.tsx +87 -0
  109. package/blocks/deals/components/deals-screen.tsx +298 -0
  110. package/blocks/deals/components/pipeline-board.tsx +4 -2
  111. package/blocks/deals/components/stage-editor.tsx +4 -4
  112. package/blocks/deals/hooks/use-default-owner.ts +60 -0
  113. package/blocks/deals/hooks/use-pipeline-deals.ts +5 -1
  114. package/blocks/deals/hooks/use-pipelines.ts +1 -1
  115. package/blocks/deals/lib/deals-types.ts +14 -0
  116. package/blocks/dialer/README.md +34 -2
  117. package/blocks/dialer/block.json +67 -1
  118. package/blocks/dialer/components/call-contact-button.tsx +252 -0
  119. package/blocks/dialer/components/call-log-screen.tsx +205 -0
  120. package/blocks/dialer/components/call-log.tsx +7 -3
  121. package/blocks/dialer/components/call-queue.tsx +1 -1
  122. package/blocks/dialer/components/dialer-screen.tsx +319 -0
  123. package/blocks/dialer/components/dialer-session-detail.tsx +153 -0
  124. package/blocks/dialer/components/live-calls-table.tsx +7 -3
  125. package/blocks/dialer/components/session-start-form.tsx +4 -2
  126. package/blocks/dialer/components/softphone.tsx +3 -1
  127. package/blocks/enrichment/README.md +14 -2
  128. package/blocks/enrichment/block.json +32 -4
  129. package/blocks/enrichment/components/bulk-enrich-panel.tsx +10 -5
  130. package/blocks/enrichment/components/email-verify-badge.tsx +28 -8
  131. package/blocks/enrichment/components/enrich-button.tsx +32 -9
  132. package/blocks/enrichment/components/enrichment-job-detail.tsx +78 -0
  133. package/blocks/enrichment/components/enrichment-screen.tsx +288 -0
  134. package/blocks/enrichment/components/waterfall-config.tsx +39 -10
  135. package/blocks/enrichment/recipes/waterfall-pipeline.ts +3 -2
  136. package/blocks/forms/README.md +12 -0
  137. package/blocks/forms/block.json +39 -17
  138. package/blocks/forms/components/form-builder.tsx +3 -1
  139. package/blocks/forms/components/form-embed.tsx +3 -1
  140. package/blocks/forms/components/forms-screen.tsx +353 -0
  141. package/blocks/forms/components/submissions-table.tsx +16 -6
  142. package/blocks/inbox/README.md +27 -3
  143. package/blocks/inbox/block.json +14 -2
  144. package/blocks/inbox/components/inbox-list.tsx +7 -3
  145. package/blocks/inbox/components/inbox-screen.tsx +294 -27
  146. package/blocks/inbox/components/reply-composer.tsx +65 -9
  147. package/blocks/inbox/components/tag-and-assign.tsx +4 -2
  148. package/blocks/inbox/hooks/use-inbox-thread.ts +1 -1
  149. package/blocks/inbox/hooks/use-inbox-threads.ts +1 -0
  150. package/blocks/inbox/lib/inbox-types.ts +47 -1
  151. package/blocks/inbox/recipes/01-triage-and-reply.ts +12 -2
  152. package/blocks/integrations/README.md +9 -0
  153. package/blocks/integrations/block.json +46 -1
  154. package/blocks/integrations/components/crm-detail.tsx +100 -0
  155. package/blocks/integrations/components/integration-list.tsx +9 -4
  156. package/blocks/integrations/components/integrations-screen.tsx +135 -0
  157. package/blocks/integrations/components/sync-status.tsx +5 -3
  158. package/blocks/integrations/components/webhook-detail.tsx +160 -0
  159. package/blocks/integrations/components/webhook-manager.tsx +13 -8
  160. package/blocks/integrations/lib/integrations-utils.ts +6 -1
  161. package/blocks/intent/README.md +14 -0
  162. package/blocks/intent/block.json +27 -1
  163. package/blocks/intent/components/intent-feed.tsx +5 -2
  164. package/blocks/intent/components/intent-keyword-manager.tsx +8 -4
  165. package/blocks/intent/components/intent-screen.tsx +265 -0
  166. package/blocks/intent/components/keyword-detail.tsx +124 -0
  167. package/blocks/lifecycle/README.md +16 -0
  168. package/blocks/lifecycle/block.json +34 -7
  169. package/blocks/lifecycle/components/account-related.tsx +66 -0
  170. package/blocks/lifecycle/components/lifecycle-board.tsx +3 -1
  171. package/blocks/lifecycle/components/lifecycle-screen.tsx +149 -0
  172. package/blocks/lifecycle/lib/lifecycle.ts +1 -1
  173. package/blocks/lists/README.md +13 -0
  174. package/blocks/lists/block.json +39 -1
  175. package/blocks/lists/components/add-to-list-button.tsx +3 -2
  176. package/blocks/lists/components/list-picker.tsx +5 -1
  177. package/blocks/lists/components/list-table.tsx +11 -5
  178. package/blocks/lists/components/lists-screen.tsx +344 -0
  179. package/blocks/lists/components/suppression-manager.tsx +6 -2
  180. package/blocks/lists/components/suppressions-screen.tsx +255 -0
  181. package/blocks/mailboxes/README.md +17 -0
  182. package/blocks/mailboxes/block.json +65 -17
  183. package/blocks/mailboxes/components/connect-mailbox.tsx +23 -13
  184. package/blocks/mailboxes/components/domain-detail.tsx +142 -0
  185. package/blocks/mailboxes/components/domain-purchase.tsx +6 -3
  186. package/blocks/mailboxes/components/mailbox-detail.tsx +156 -0
  187. package/blocks/mailboxes/components/mailboxes-screen.tsx +223 -0
  188. package/blocks/mailboxes/components/warmup-health.tsx +4 -2
  189. package/blocks/mailboxes/lib/mailboxes-utils.ts +22 -1
  190. package/blocks/newsletter/README.md +13 -0
  191. package/blocks/newsletter/block.json +47 -23
  192. package/blocks/newsletter/components/issue-scheduler.tsx +2 -1
  193. package/blocks/newsletter/components/newsletter-screen.tsx +404 -0
  194. package/blocks/newsletter/components/subscriber-table.tsx +12 -6
  195. package/blocks/nurture/README.md +12 -0
  196. package/blocks/nurture/block.json +35 -13
  197. package/blocks/nurture/components/nurture-builder.tsx +3 -1
  198. package/blocks/nurture/components/nurture-enroll.tsx +6 -3
  199. package/blocks/nurture/components/nurture-form.tsx +23 -3
  200. package/blocks/nurture/components/nurture-screen.tsx +283 -0
  201. package/blocks/nurture/lib/nurture-types.ts +1 -1
  202. package/blocks/objects/README.md +17 -0
  203. package/blocks/objects/block.json +29 -16
  204. package/blocks/objects/components/object-not-found.tsx +3 -1
  205. package/blocks/objects/components/object-record-detail.tsx +2 -1
  206. package/blocks/objects/components/object-schema-editor.tsx +3 -1
  207. package/blocks/objects/components/object-table.tsx +13 -3
  208. package/blocks/objects/components/objects-screen.tsx +147 -0
  209. package/blocks/objects/lib/object-types.ts +1 -1
  210. package/blocks/pages/README.md +12 -0
  211. package/blocks/pages/block.json +35 -22
  212. package/blocks/pages/components/brand-kit-editor.tsx +5 -2
  213. package/blocks/pages/components/content-library.tsx +46 -11
  214. package/blocks/pages/components/page-list.tsx +6 -3
  215. package/blocks/pages/components/pages-screen.tsx +154 -0
  216. package/blocks/quotes/README.md +17 -0
  217. package/blocks/quotes/block.json +38 -9
  218. package/blocks/quotes/components/product-picker.tsx +3 -1
  219. package/blocks/quotes/components/quote-list.tsx +7 -3
  220. package/blocks/quotes/components/quote-related.tsx +123 -0
  221. package/blocks/quotes/components/quotes-screen.tsx +169 -0
  222. package/blocks/radar/README.md +10 -0
  223. package/blocks/radar/block.json +41 -1
  224. package/blocks/radar/components/competitor-detail.tsx +117 -0
  225. package/blocks/radar/components/competitor-tracker.tsx +3 -1
  226. package/blocks/radar/components/radar-report.tsx +4 -2
  227. package/blocks/radar/components/radar-screen.tsx +214 -0
  228. package/blocks/radar/components/report-detail.tsx +104 -0
  229. package/blocks/search/README.md +10 -0
  230. package/blocks/search/block.json +16 -1
  231. package/blocks/search/components/filter-builder.tsx +3 -3
  232. package/blocks/search/components/prospect-results.tsx +5 -3
  233. package/blocks/search/components/prospect-search-screen.tsx +235 -0
  234. package/blocks/search/components/saved-searches.tsx +4 -1
  235. package/blocks/search/hooks/use-filter-options.ts +20 -6
  236. package/blocks/search/lib/search-types.ts +2 -0
  237. package/blocks/sequencer/README.md +49 -9
  238. package/blocks/sequencer/block.json +99 -19
  239. package/blocks/sequencer/components/enroll-dialog.tsx +9 -3
  240. package/blocks/sequencer/components/sequence-builder.tsx +211 -46
  241. package/blocks/sequencer/components/sequence-detail.tsx +259 -0
  242. package/blocks/sequencer/components/sequence-enrollments.tsx +114 -0
  243. package/blocks/sequencer/components/sequence-list.tsx +33 -8
  244. package/blocks/sequencer/components/sequence-stats.tsx +3 -2
  245. package/blocks/sequencer/components/sequence-steps-panel.tsx +103 -0
  246. package/blocks/sequencer/components/sequences-screen.tsx +159 -0
  247. package/blocks/sequencer/components/step-editor.tsx +3 -1
  248. package/blocks/sequencer/lib/sequencer-utils.ts +128 -5
  249. package/blocks/sequencer/recipes/create-multichannel-sequence.ts +33 -5
  250. package/blocks/sequencer/recipes/launch-and-enroll.ts +19 -2
  251. package/blocks/social-ads/README.md +11 -0
  252. package/blocks/social-ads/block.json +101 -2
  253. package/blocks/social-ads/components/ad-campaign-list.tsx +9 -5
  254. package/blocks/social-ads/components/ads-screen.tsx +152 -0
  255. package/blocks/social-ads/components/audience-sync-detail.tsx +188 -0
  256. package/blocks/social-ads/components/audience-sync.tsx +11 -7
  257. package/blocks/social-ads/components/campaign-detail.tsx +133 -0
  258. package/blocks/social-ads/components/creative-detail.tsx +150 -0
  259. package/blocks/social-ads/components/creative-gallery.tsx +15 -5
  260. package/blocks/social-ads/components/post-composer.tsx +3 -1
  261. package/blocks/social-ads/components/post-detail.tsx +160 -0
  262. package/blocks/social-ads/components/social-post-list.tsx +11 -3
  263. package/blocks/social-ads/components/social-screen.tsx +160 -0
  264. package/blocks/visitors/README.md +10 -0
  265. package/blocks/visitors/block.json +25 -1
  266. package/blocks/visitors/components/snippet-install.tsx +3 -1
  267. package/blocks/visitors/components/visitor-company-card.tsx +2 -2
  268. package/blocks/visitors/components/visitor-detail.tsx +105 -0
  269. package/blocks/visitors/components/visitor-feed.tsx +11 -2
  270. package/blocks/visitors/components/visitors-screen.tsx +166 -0
  271. package/blocks/webchat/README.md +10 -0
  272. package/blocks/webchat/block.json +21 -7
  273. package/blocks/webchat/components/chat-widget-config.tsx +7 -4
  274. package/blocks/webchat/components/conversation-list.tsx +7 -3
  275. package/blocks/webchat/components/webchat-screen.tsx +245 -0
  276. package/blocks/workbench/README.md +11 -0
  277. package/blocks/workbench/block.json +48 -1
  278. package/blocks/workbench/components/column-recipe-editor.tsx +4 -2
  279. package/blocks/workbench/components/workbench-grid.tsx +9 -3
  280. package/blocks/workbench/components/workbench-row-detail.tsx +104 -0
  281. package/blocks/workbench/components/workbench-screen.tsx +192 -0
  282. package/blocks/workbench/components/workbench-sources.tsx +194 -0
  283. package/blocks/workflows/README.md +13 -0
  284. package/blocks/workflows/block.json +28 -9
  285. package/blocks/workflows/components/run-history.tsx +31 -9
  286. package/blocks/workflows/components/trigger-config.tsx +3 -1
  287. package/blocks/workflows/components/workflow-editor.tsx +3 -1
  288. package/blocks/workflows/components/workflow-list.tsx +13 -6
  289. package/blocks/workflows/components/workflows-screen.tsx +193 -0
  290. package/dist/cli.js +2785 -1637
  291. package/dist/errors.d.mts +34 -0
  292. package/dist/errors.d.ts +34 -0
  293. package/dist/errors.js +46 -0
  294. package/dist/errors.js.map +1 -0
  295. package/dist/errors.mjs +21 -0
  296. package/dist/errors.mjs.map +1 -0
  297. package/dist/index.browser.js +11 -5
  298. package/dist/index.browser.js.map +1 -1
  299. package/dist/index.browser.mjs +11 -5
  300. package/dist/index.browser.mjs.map +1 -1
  301. package/dist/index.d.mts +164 -25
  302. package/dist/index.d.ts +164 -25
  303. package/dist/index.js +11 -5
  304. package/dist/index.js.map +1 -1
  305. package/dist/index.mjs +11 -5
  306. package/dist/index.mjs.map +1 -1
  307. package/dist/react.browser.js +10 -4
  308. package/dist/react.browser.js.map +1 -1
  309. package/dist/react.browser.mjs +10 -4
  310. package/dist/react.browser.mjs.map +1 -1
  311. package/dist/react.d.mts +142 -5
  312. package/dist/react.d.ts +142 -5
  313. package/dist/react.js +10 -4
  314. package/dist/react.js.map +1 -1
  315. package/dist/react.mjs +10 -4
  316. package/dist/react.mjs.map +1 -1
  317. package/dist/types/blocks/add.d.ts +11 -4
  318. package/dist/types/blocks/brand.d.ts +113 -0
  319. package/dist/types/blocks/compose.d.ts +53 -0
  320. package/dist/types/blocks/create.d.ts +25 -8
  321. package/dist/types/blocks/doctor.d.ts +6 -1
  322. package/dist/types/blocks/flows.d.ts +20 -0
  323. package/dist/types/blocks/navfile.d.ts +35 -0
  324. package/dist/types/blocks/paths.d.ts +4 -0
  325. package/dist/types/blocks/prefixes.d.ts +37 -0
  326. package/dist/types/blocks/registry.d.ts +32 -0
  327. package/dist/types/blocks/screens.d.ts +53 -0
  328. package/dist/types/cli.d.ts +2 -0
  329. package/dist/types/errors.d.ts +33 -0
  330. package/dist/types/http.d.ts +2 -18
  331. package/dist/types/version.d.ts +2 -2
  332. package/docs/agents.md +15 -4
  333. package/docs/api/README.md +2 -2
  334. package/docs/api/by-path.md +1 -0
  335. package/docs/api/desk.d.ts +98 -2
  336. package/docs/api/desk.md +123 -1
  337. package/docs/api/inbox.d.ts +10 -2
  338. package/docs/api/inbox.md +10 -2
  339. package/docs/api/seo.d.ts +19 -1
  340. package/docs/api/seo.md +24 -1
  341. package/package.json +16 -2
  342. package/stacks/6sense.json +41 -16
  343. package/stacks/apollo/pages/companies.tsx.tmpl +50 -0
  344. package/stacks/apollo/pages/contacts.tsx.tmpl +117 -0
  345. package/stacks/apollo.json +118 -36
  346. package/stacks/calendly.json +60 -0
  347. package/stacks/clay/pages/companies.tsx.tmpl +15 -0
  348. package/stacks/clay/pages/contacts.tsx.tmpl +19 -0
  349. package/stacks/clay.json +59 -26
  350. package/stacks/gong.json +81 -35
  351. package/stacks/hootsuite.json +99 -0
  352. package/stacks/hubspot/pages/companies.tsx.tmpl +47 -0
  353. package/stacks/hubspot/pages/contacts.tsx.tmpl +111 -0
  354. package/stacks/hubspot/pages/deals.tsx.tmpl +11 -0
  355. package/stacks/hubspot/pages/inbox.tsx.tmpl +92 -0
  356. package/stacks/hubspot/pages/quotes.tsx.tmpl +11 -0
  357. package/stacks/hubspot.json +167 -66
  358. package/stacks/intercom.json +48 -20
  359. package/stacks/mailchimp.json +52 -33
  360. package/stacks/outreach/pages/contacts.tsx.tmpl +85 -0
  361. package/stacks/outreach.json +88 -25
  362. package/stacks/pipedrive/pages/companies.tsx.tmpl +47 -0
  363. package/stacks/pipedrive/pages/contacts.tsx.tmpl +47 -0
  364. package/stacks/pipedrive/pages/deals.tsx.tmpl +11 -0
  365. package/stacks/pipedrive/pages/inbox.tsx.tmpl +92 -0
  366. package/stacks/pipedrive/pages/quotes.tsx.tmpl +11 -0
  367. package/stacks/pipedrive.json +115 -0
  368. package/stacks/salesforce/pages/companies.tsx.tmpl +47 -0
  369. package/stacks/salesforce/pages/contacts.tsx.tmpl +47 -0
  370. package/stacks/salesforce/pages/deals.tsx.tmpl +11 -0
  371. package/stacks/salesforce/pages/quotes.tsx.tmpl +11 -0
  372. package/stacks/salesforce.json +81 -31
  373. package/stacks/stack.schema.json +6 -1
  374. package/stacks/zoominfo/pages/companies.tsx.tmpl +15 -0
  375. package/stacks/zoominfo/pages/contacts.tsx.tmpl +19 -0
  376. package/stacks/zoominfo.json +102 -0
  377. package/styles/styles.css +1 -1
  378. package/templates/next-app/README.md.tmpl +55 -8
  379. package/templates/next-app/app/globals.css.tmpl +105 -0
  380. package/templates/next-app/app/layout.tsx.tmpl +18 -4
  381. package/templates/next-app/components/app-shell.tsx.tmpl +248 -23
  382. package/templates/next-app/components/shell/connection-status.tsx.tmpl +109 -0
  383. package/templates/next-app/components/shell/nav-model.ts.tmpl +65 -0
  384. package/templates/next-app/components/shell/quick-jump.tsx.tmpl +131 -0
  385. package/templates/next-app/components/shell/theme-toggle.tsx.tmpl +40 -0
  386. package/templates/next-app/dot-gitignore.tmpl +2 -0
  387. package/templates/next-app/lib/nav.ts.tmpl +1 -1
  388. package/templates/next-app/package.json.tmpl +1 -1
@@ -0,0 +1,157 @@
1
+ "use client";
2
+
3
+ /**
4
+ * MeetingsScreen: the finished meetings product. Upcoming, past, cancelled and recorded meetings, a detail panel
5
+ * for the open one, and a "Schedule a meeting" flow. No props are required.
6
+ *
7
+ * It composes: MeetingList (views, search, paging), MeetingDetail (booking or recorded meeting) and BookingPage.
8
+ *
9
+ * Flows:
10
+ * - Headline numbers: meetings coming up and bookings waiting for your approval.
11
+ * - Open a booking (deep link ?sel=booking:<uid>, full page with &view=full) -> accept or decline a pending one, add an internal note,
12
+ * cancel (all confirmed because they email attendees). Open a recorded meeting -> summary, topics, action
13
+ * items and transcript.
14
+ * - Schedule a meeting: pick an event type, a day and time, enter the attendee, confirm, and the invite is
15
+ * emailed (external, confirmed inside BookingPage).
16
+ * - Calendar connection and working hours live in SchedulingScreen; Google or Microsoft sign-in is only possible
17
+ * in the graph8 app, so this screen links out instead of showing a dead button.
18
+ *
19
+ * SDK operations (through the composed parts): listBookings, getBooking, confirmBooking (external), cancelBooking
20
+ * (destructive), addBookingNote (external), listMeetings, getMeeting, listEventTypes, getEventType,
21
+ * getAvailableSlots, createBooking (external). Every confirmation uses ConfirmSpendDialog.
22
+ *
23
+ * Slots (all optional): renderDetailActions, extraDetailTabs, toolbarActions.
24
+ */
25
+ import { useMemo, useState, type ReactNode } from "react";
26
+ import { CalendarClock, CalendarPlus, ExternalLink } from "lucide-react";
27
+ import { Button, Skeleton } from "@graph8/sdk/ui";
28
+ import { ApiError } from "@/g8/components/api-error";
29
+ import { EmptyState } from "@/g8/components/empty-state";
30
+ import { DetailPanel } from "@/g8/components/screen/detail-panel";
31
+ import { FormSheet } from "@/g8/components/screen/form-sheet";
32
+ import { ScreenLayout } from "@/g8/components/screen/screen-layout";
33
+ import { SectionCard } from "@/g8/components/screen/section-card";
34
+ import { SplitView } from "@/g8/components/screen/split-view";
35
+ import { useUrlState } from "@/g8/hooks/use-url-state";
36
+ import { g8Get } from "@/g8/lib/g8-client";
37
+ import { useG8Query } from "@/g8/hooks/use-g8-query";
38
+ import { useEventTypes } from "../hooks/use-event-types";
39
+ import { listOf, type Booking } from "../lib/appointments-types";
40
+ import { BookingPage } from "./booking-page";
41
+ import { MeetingDetail } from "./meeting-detail";
42
+ import { MeetingList, type MeetingSelection } from "./meeting-list";
43
+ import { useBrand } from "@/g8/hooks/use-brand";
44
+
45
+ export interface MeetingsScreenProps {
46
+ /** Where a person connects a calendar (graph8 app). */
47
+ connectCalendarUrl?: string;
48
+ /** Extra buttons in the detail header. */
49
+ renderDetailActions?: (selection: MeetingSelection) => ReactNode;
50
+ /** Extra tabs in the detail panel (for example the contact's CRM record). */
51
+ extraDetailTabs?: Array<{ id: string; label: string; render: (selection: MeetingSelection) => ReactNode }>;
52
+ /** Extra controls next to "Schedule a meeting". */
53
+ toolbarActions?: ReactNode;
54
+ }
55
+
56
+ const COUNT_CAP = 100;
57
+
58
+ function parseSelection(raw: string | null | undefined): MeetingSelection | null {
59
+ const [kind, ...rest] = (raw ?? "").split(":");
60
+ const id = rest.join(":");
61
+ return id && (kind === "booking" || kind === "meeting") ? { kind, id } : null;
62
+ }
63
+
64
+ function useBookingCounts(refreshKey: number) {
65
+ const now = useMemo(() => new Date().toISOString(), []);
66
+ const q = useG8Query(async (signal) => {
67
+ const [upcoming, pending] = await Promise.all([
68
+ g8Get("/appointments/bookings", { after_start: now, take: COUNT_CAP, skip: 0 }, { signal }),
69
+ g8Get("/appointments/bookings", { status: "pending", take: COUNT_CAP, skip: 0 }, { signal }),
70
+ ]);
71
+ return { upcoming: listOf<Booking>(upcoming, "bookings"), pending: listOf<Booking>(pending, "bookings") };
72
+ }, [now, refreshKey]);
73
+ return q;
74
+ }
75
+
76
+ function countLabel(list: Booking[]): string {
77
+ return list.length >= COUNT_CAP ? `${COUNT_CAP}+` : String(list.length);
78
+ }
79
+
80
+ function ScheduleSheet({ open, onOpenChange, onBooked }: { open: boolean; onOpenChange: (open: boolean) => void; onBooked: () => void }) {
81
+ const types = useEventTypes();
82
+ const [chosen, setChosen] = useState<number | null>(null);
83
+ const eventTypeId = chosen ?? types.eventTypes.find((t) => !t.hidden)?.id ?? types.eventTypes[0]?.id ?? null;
84
+ return (
85
+ <FormSheet open={open} onOpenChange={onOpenChange} title="Schedule a meeting" description="Pick a time that works for the host, then enter who is joining.">
86
+ {types.error ? <ApiError error={types.error} title="Could not load event types" onRetry={() => void types.refetch()} /> : types.loading && !types.data ? <Skeleton className="h-32 w-full" aria-label="Loading event types" /> : eventTypeId == null ? (
87
+ <EmptyState icon={<CalendarClock className="size-8" />} title="No event types yet" description="Create an event type in Scheduling first, then book meetings with it here." />
88
+ ) : (
89
+ <div className="space-y-4">
90
+ <div className="space-y-1">
91
+ <label htmlFor="schedule-event-type" className="text-sm font-medium">Event type</label>
92
+ <select id="schedule-event-type" className="h-9 w-full rounded-md border bg-background px-2 text-sm" value={eventTypeId} onChange={(e) => setChosen(Number(e.target.value))}>
93
+ {types.eventTypes.map((t) => <option key={t.id} value={t.id}>{t.title} ({t.length} min)</option>)}
94
+ </select>
95
+ </div>
96
+ <BookingPage eventTypeId={eventTypeId} onBooked={onBooked} />
97
+ </div>
98
+ )}
99
+ </FormSheet>
100
+ );
101
+ }
102
+
103
+ export function MeetingsScreen({ connectCalendarUrl: connectCalendarUrlProp, renderDetailActions, extraDetailTabs, toolbarActions }: MeetingsScreenProps) {
104
+ const brand = useBrand();
105
+ const connectCalendarUrl = connectCalendarUrlProp ?? brand.appUrl;
106
+ const [raw, setRaw] = useUrlState("sel");
107
+ const [mode, setMode] = useUrlState("view");
108
+ const selection = parseSelection(raw);
109
+ const [version, setVersion] = useState(0);
110
+ const [scheduling, setScheduling] = useState(false);
111
+ const counts = useBookingCounts(version);
112
+ const changed = () => setVersion((v) => v + 1);
113
+ const close = () => { setRaw(null); setMode(null); };
114
+
115
+ const stats = [
116
+ { label: "Upcoming", value: counts.data ? countLabel(counts.data.upcoming) : "-", hint: "Meetings starting from now" },
117
+ { label: "Awaiting approval", value: counts.data ? countLabel(counts.data.pending) : "-", tone: counts.data && counts.data.pending.length > 0 ? ("warning" as const) : undefined, hint: "Pending bookings you can accept or decline" },
118
+ ];
119
+
120
+ const detail = selection ? (
121
+ <DetailPanel
122
+ title={selection.kind === "booking" ? "Booking" : "Recorded meeting"}
123
+ actions={renderDetailActions?.(selection)}
124
+ onClose={close}
125
+ onExpand={mode === "full" ? undefined : () => setMode("full")}
126
+ onCollapse={mode === "full" ? () => setMode(null) : undefined}
127
+ tabs={[
128
+ { id: "overview", label: "Overview", content: <MeetingDetail selection={selection} onChanged={changed} /> },
129
+ ...(extraDetailTabs ?? []).map((t) => ({ id: t.id, label: t.label, content: t.render(selection) })),
130
+ ]}
131
+ />
132
+ ) : null;
133
+
134
+ return (
135
+ <ScreenLayout
136
+ title="Meetings"
137
+ description="Everything booked and recorded, and the next meeting to prepare for."
138
+ icon={<CalendarClock className="size-5" />}
139
+ actions={<><Button onClick={() => setScheduling(true)}><CalendarPlus className="size-4" aria-hidden />Schedule a meeting</Button>{toolbarActions}</>}
140
+ stats={stats}
141
+ >
142
+ {counts.error ? <ApiError error={counts.error} title="Could not load meeting numbers" onRetry={() => void counts.refetch()} /> : null}
143
+ <SplitView
144
+ detailOpen={Boolean(selection)}
145
+ onCloseDetail={close}
146
+ expanded={mode === "full" && Boolean(selection)}
147
+ onExpandedChange={(full) => setMode(full ? "full" : null)}
148
+ detail={detail}
149
+ list={<MeetingList linkParam="sel" refreshKey={version} selectedId={selection?.id ?? null} onSelect={(s) => setRaw(`${s.kind}:${s.id}`)} />}
150
+ />
151
+ <SectionCard title="Calendar sync" description={`Google and Microsoft sign-in happens in the ${brand.linkLabel} app. Once connected, booked and recorded meetings appear here.`}>
152
+ <a className="inline-flex items-center gap-1 text-sm underline" href={connectCalendarUrl} target="_blank" rel="noreferrer">Connect a calendar in {brand.linkLabel}<ExternalLink className="size-3.5" aria-hidden /></a>
153
+ </SectionCard>
154
+ <ScheduleSheet open={scheduling} onOpenChange={setScheduling} onBooked={changed} />
155
+ </ScreenLayout>
156
+ );
157
+ }
@@ -0,0 +1,122 @@
1
+ "use client";
2
+
3
+ /**
4
+ * SchedulingScreen: the finished scheduling product. Your event types (meeting links), their settings, a live
5
+ * preview of each booking page, and your weekly working hours and calendar status. No props are required.
6
+ *
7
+ * It composes: EventTypeEditor (create, edit, delete), BookingPage (the preview), AvailabilityEditor (hours,
8
+ * date overrides, connected calendars) and the useEventTypes list.
9
+ *
10
+ * Flows:
11
+ * - Event types tab (full page with &view=full): see every event type with its length and status, create one, open one (deep link
12
+ * ?id=<event type id>) for Overview (every field), Bookings (who booked it, open one to accept, decline, note or
13
+ * cancel), Settings (edit, delete confirmed) and its Booking page preview. Booking from the preview is a real booking and emails the attendee (confirmed).
14
+ * - Working hours tab: edit the weekly hours and date overrides of a schedule. Connecting a Google or Microsoft
15
+ * calendar is only possible in the graph8 app, so the editor links out.
16
+ *
17
+ * SDK operations (through the composed parts): listEventTypes, getEventType, createEventType, updateEventType,
18
+ * deleteEventType (destructive, confirmed), listSchedules, getAvailability, updateAvailability, listCalendars,
19
+ * getAvailableSlots, createBooking (external, confirmed).
20
+ *
21
+ * Slots (all optional): renderRowActions, renderDetailActions, extraDetailTabs, toolbarActions.
22
+ */
23
+ import { useState, type ReactNode } from "react";
24
+ import { CalendarClock, Plus } from "lucide-react";
25
+ import { Button, Skeleton, Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@graph8/sdk/ui";
26
+ import { ApiError } from "@/g8/components/api-error";
27
+ import { EmptyState } from "@/g8/components/empty-state";
28
+ import { DetailPanel } from "@/g8/components/screen/detail-panel";
29
+ import { FormSheet } from "@/g8/components/screen/form-sheet";
30
+ import { ScreenLayout } from "@/g8/components/screen/screen-layout";
31
+ import { SplitView } from "@/g8/components/screen/split-view";
32
+ import { StatusPill } from "@/g8/components/screen/status-pill";
33
+ import { useUrlState } from "@/g8/hooks/use-url-state";
34
+ import { useEventTypes } from "../hooks/use-event-types";
35
+ import type { EventType } from "../lib/appointments-types";
36
+ import { AvailabilityEditor } from "./availability-editor";
37
+ import { BookingPage } from "./booking-page";
38
+ import { EventTypeEditor } from "./event-type-editor";
39
+ import { EventTypeBookings, EventTypeOverview } from "./event-type-related";
40
+ import { rowActivationProps } from "@/g8/hooks/use-row-activation";
41
+
42
+ export interface SchedulingScreenProps {
43
+ /** Where a person connects a calendar (graph8 app). */
44
+ connectCalendarUrl?: string;
45
+ renderRowActions?: (eventType: EventType) => ReactNode;
46
+ renderDetailActions?: (eventType: EventType) => ReactNode;
47
+ extraDetailTabs?: Array<{ id: string; label: string; render: (eventType: EventType) => ReactNode }>;
48
+ toolbarActions?: ReactNode;
49
+ }
50
+
51
+ const TABS = [{ id: "types", label: "Event types" }, { id: "hours", label: "Working hours" }];
52
+
53
+ export function SchedulingScreen({ connectCalendarUrl, renderRowActions, renderDetailActions, extraDetailTabs, toolbarActions }: SchedulingScreenProps) {
54
+ const [tabParam, setTab] = useUrlState("tab", "types");
55
+ const [idParam, setId] = useUrlState("id");
56
+ const [mode, setMode] = useUrlState("view");
57
+ const [creating, setCreating] = useState(false);
58
+ const types = useEventTypes();
59
+ const tab = tabParam === "hours" ? "hours" : "types";
60
+ const selected = types.eventTypes.find((t) => String(t.id) === idParam);
61
+ const selectedId = idParam && /^\d+$/.test(idParam) ? Number(idParam) : null;
62
+ const close = () => { setId(null); setMode(null); };
63
+
64
+ const detail = selectedId != null ? (
65
+ <DetailPanel
66
+ title={selected?.title ?? "Event type"}
67
+ subtitle={selected ? `${selected.length} minutes` : undefined}
68
+ badges={selected?.hidden ? <StatusPill tone="neutral">Hidden</StatusPill> : undefined}
69
+ actions={selected ? renderDetailActions?.(selected) : undefined}
70
+ onClose={close}
71
+ onExpand={mode === "full" ? undefined : () => setMode("full")}
72
+ onCollapse={mode === "full" ? () => setMode(null) : undefined}
73
+ tabs={[
74
+ { id: "overview", label: "Overview", content: <EventTypeOverview key={selectedId} eventTypeId={selectedId} /> },
75
+ { id: "bookings", label: "Bookings", content: <EventTypeBookings key={selectedId} eventTypeId={selectedId} /> },
76
+ { id: "settings", label: "Settings", content: <EventTypeEditor key={selectedId} eventTypeId={selectedId} onSaved={() => void types.refetch()} onDeleted={() => { close(); void types.refetch(); }} /> },
77
+ { id: "preview", label: "Booking page", content: <BookingPage key={selectedId} eventTypeId={selectedId} /> },
78
+ ...(extraDetailTabs ?? []).map((t) => ({ id: t.id, label: t.label, content: selected ? t.render(selected) : null })),
79
+ ]}
80
+ />
81
+ ) : null;
82
+
83
+ const list = types.error ? <ApiError error={types.error} title="Could not load event types" onRetry={() => void types.refetch()} /> : types.loading && !types.data ? (
84
+ <div className="space-y-2" aria-busy="true" aria-label="Loading event types">{[0, 1, 2].map((i) => <Skeleton key={i} className="h-10 w-full" />)}</div>
85
+ ) : types.eventTypes.length === 0 ? (
86
+ <EmptyState icon={<CalendarClock className="size-8" />} title="No event types yet" description="An event type is a meeting link, like a 30 minute intro call. Create one and share its booking page." action={<Button onClick={() => setCreating(true)}>New event type</Button>} />
87
+ ) : (
88
+ <Table>
89
+ <TableHeader><TableRow><TableHead>Event type</TableHead><TableHead className="text-right">Length</TableHead><TableHead>Rules</TableHead>{renderRowActions ? <TableHead /> : null}</TableRow></TableHeader>
90
+ <TableBody>
91
+ {types.eventTypes.map((t) => (
92
+ <TableRow key={t.id} className="cursor-pointer" data-state={String(t.id) === idParam ? "selected" : undefined} {...rowActivationProps(() => setId(String(t.id)))}>
93
+ <TableCell><button type="button" className="font-medium hover:underline" onClick={(e) => { e.stopPropagation(); setId(String(t.id)); }}>{t.title}</button><div className="text-xs text-muted-foreground">/{t.slug}</div></TableCell>
94
+ <TableCell className="text-right tabular-nums">{t.length} min</TableCell>
95
+ <TableCell className="space-x-1">
96
+ {t.hidden ? <StatusPill tone="neutral">Hidden</StatusPill> : <StatusPill tone="positive" dot>Bookable</StatusPill>}
97
+ {t.requires_confirmation ? <StatusPill tone="info">Needs approval</StatusPill> : null}
98
+ </TableCell>
99
+ {renderRowActions ? <TableCell onClick={(e) => e.stopPropagation()}>{renderRowActions(t)}</TableCell> : null}
100
+ </TableRow>
101
+ ))}
102
+ </TableBody>
103
+ </Table>
104
+ );
105
+
106
+ return (
107
+ <ScreenLayout
108
+ title="Scheduling"
109
+ description="Meeting links people can book, and the hours you are available."
110
+ icon={<CalendarClock className="size-5" />}
111
+ actions={<>{toolbarActions}<Button onClick={() => setCreating(true)}><Plus className="size-4" aria-hidden />New event type</Button></>}
112
+ tabs={TABS.map((t) => (t.id === "types" ? { ...t, count: types.data ? types.eventTypes.length : undefined } : t))}
113
+ activeTab={tab}
114
+ onTabChange={(id) => { setTab(id); close(); }}
115
+ >
116
+ {tab === "hours" ? <AvailabilityEditor connectCalendarUrl={connectCalendarUrl} /> : <SplitView detailOpen={selectedId != null} onCloseDetail={close} expanded={mode === "full" && selectedId != null} onExpandedChange={(full) => setMode(full ? "full" : null)} detail={detail} list={list} />}
117
+ <FormSheet open={creating} onOpenChange={setCreating} title="New event type" description="Name it, set its length and pick the working hours it uses.">
118
+ {creating ? <EventTypeEditor onSaved={(et) => { setCreating(false); void types.refetch(); setTab("types"); setId(String(et.id)); }} /> : null}
119
+ </FormSheet>
120
+ </ScreenLayout>
121
+ );
122
+ }
@@ -0,0 +1,17 @@
1
+ "use client";
2
+
3
+ /**
4
+ * useEventTypes: the org's bookable event types (meeting links), newest data first.
5
+ *
6
+ * SDK operations: GET /event-types?include_hidden= (gtmLaunchHelpers.listEventTypes), read tier, scope campaigns:read.
7
+ * This is the list operation the editor and booking screens were missing: ids no longer have to be remembered.
8
+ * Hidden event types are included so a rep can still edit them.
9
+ */
10
+ import { g8Get } from "@/g8/lib/g8-client";
11
+ import { useG8Query } from "@/g8/hooks/use-g8-query";
12
+ import { listOf, type EventType } from "../lib/appointments-types";
13
+
14
+ export function useEventTypes() {
15
+ const query = useG8Query(async (signal) => listOf<EventType>(await g8Get("/event-types", { include_hidden: true }, { signal }), "event_types", "items"), []);
16
+ return { ...query, eventTypes: query.data ?? [] };
17
+ }
@@ -36,6 +36,24 @@
36
36
  }
37
37
  }
38
38
  },
39
+ "screens": {
40
+ "type": "array",
41
+ "description": "Optional. How the block's finished screens become routes and sidebar entries in an app made with `create --blocks`. Absent: derived from the components named `*Screen` (ContactsScreen -> /contacts, label Contacts, group = the block title, icon from a built-in per-block map).",
42
+ "items": {
43
+ "type": "object",
44
+ "additionalProperties": false,
45
+ "required": ["component", "path", "label"],
46
+ "properties": {
47
+ "component": { "type": "string", "pattern": "^[A-Z][A-Za-z0-9]*$", "description": "A component of this block." },
48
+ "path": { "type": "string", "pattern": "^/[a-z0-9]+(-[a-z0-9]+)*(/[a-z0-9]+(-[a-z0-9]+)*)*$", "description": "Static route path. On a collision in a generated app it is prefixed with the block id." },
49
+ "label": { "type": "string", "minLength": 1 },
50
+ "icon": { "type": "string", "pattern": "^[A-Z][A-Za-z0-9]*$", "description": "A lucide-react export name." },
51
+ "group": { "type": "string", "minLength": 1, "description": "Sidebar section (default: the block title)." },
52
+ "description": { "type": "string", "minLength": 1 },
53
+ "order": { "type": "number", "description": "Sort position inside the block; lower first." }
54
+ }
55
+ }
56
+ },
39
57
  "hooks": {
40
58
  "type": "array",
41
59
  "items": {
@@ -59,6 +59,21 @@ export interface BlockDefault {
59
59
  askHuman: boolean;
60
60
  }
61
61
 
62
+ /** Optional route metadata for a finished screen (`create --blocks`, `add` inside a created app). */
63
+ export interface BlockScreen {
64
+ /** A component of this block. */
65
+ component: string;
66
+ /** Static route path; prefixed with the block id when it collides in a generated app. */
67
+ path: string;
68
+ label: string;
69
+ /** lucide-react export name. */
70
+ icon?: string;
71
+ /** Sidebar section (default: the block title). */
72
+ group?: string;
73
+ description?: string;
74
+ order?: number;
75
+ }
76
+
62
77
  export interface BlockManifest {
63
78
  $schema?: string;
64
79
  id: string;
@@ -71,6 +86,8 @@ export interface BlockManifest {
71
86
  /** Contract operationIds (keys of API_OPERATIONS). */
72
87
  operations: string[];
73
88
  components: BlockComponent[];
89
+ /** Optional. Absent: derived from the components named `*Screen`. */
90
+ screens?: BlockScreen[];
74
91
  hooks: BlockHook[];
75
92
  lib?: BlockFile[];
76
93
  server?: BlockServerFile[];
@@ -94,6 +111,13 @@ export interface StackNavItem {
94
111
  component: string;
95
112
  /** JSON props passed to the component on its route (for a component that needs input to render anything). */
96
113
  props?: Record<string, unknown>;
114
+ /** lucide-react export name shown beside the label in the sidebar (for example `Users`). */
115
+ icon?: string;
116
+ /** Sidebar section label. */
117
+ group?: string;
118
+ description?: string;
119
+ /** Hand-written page template (`pages/*.tsx.tmpl`, relative to `stacks/<stack-id>/`) used instead of the generic route page. */
120
+ page?: string;
97
121
  }
98
122
 
99
123
  export interface StackManifest {
@@ -106,4 +130,6 @@ export interface StackManifest {
106
130
  /** Install order: each block's `dependsOn` come earlier. */
107
131
  blocks: string[];
108
132
  nav: StackNavItem[];
133
+ /** A nav path that `/` redirects to. */
134
+ home?: string;
109
135
  }
@@ -7,6 +7,26 @@ Apollo / HubSpot "campaigns" screen, with generation behind credit confirmations
7
7
  Depends on: `lists` (the audience picker), `mailboxes` (senders and the connect-a-mailbox link). Install:
8
8
  `npx @graph8/sdk add campaigns`.
9
9
 
10
+ ## Screens
11
+
12
+ `CampaignsScreen` is the finished page: mount it as the route. Every record type has a complete detail view,
13
+ deep-linkable (`?tab=campaigns|profile|personas`, `?id=` the record, `?dtab=` the tab, `?doc=` a document).
14
+
15
+ - **Campaigns tab**: list (delete confirmed) beside a detail panel with Details (every field, audience, edit name,
16
+ goal and brief, confirmed), Metrics, Documents (each document opens with its fields and editor), Audience,
17
+ Sequence and Launch. "New campaign" runs the five-step wizard in a sheet.
18
+ - **Company profile tab**: the profile plus ICPs; `IcpDetail` shows every field and edits, pins or archives.
19
+ - **Personas tab**: `PersonaDetail` shows every field, the campaigns that target it, and edits, pins or archives.
20
+
21
+ Slot props: `toolbarActions`, `renderDetailActions(campaign)`, `extraDetailTabs`, `websiteUrl`. Prerequisites: a
22
+ company profile with the real website, an active mailbox, a contact list, and credits for AI generation.
23
+ `IcpEditor` and `PersonaEditor` gained optional `onOpen` and `refreshKey`; `CampaignAssets` optional
24
+ `documentId` and `onDocumentChange`.
25
+
26
+ Known limits: there is no single-ICP read, so `IcpDetail` finds the ICP in the first 200 (the API maximum).
27
+ `PersonaDetail` lists the campaigns whose `target_persona` matches the persona title among the first 100
28
+ campaigns; the API does not link campaigns to ICPs, so ICPs show none.
29
+
10
30
  ## Components
11
31
 
12
32
  | Component | What it does | Tiers |
@@ -16,7 +36,7 @@ Depends on: `lists` (the audience picker), `mailboxes` (senders and the connect-
16
36
  | `CampaignWizard` | A page. Idea (AI, polled) -> create -> audience -> steps -> launch. Also exports the three step components below. | all |
17
37
  | `CampaignAudienceStep` | Attach or detach a contact list (uses the lists block `ListPicker`; reads `getCampaignFull`, since `getCampaign` omits the audience). | read, write, external |
18
38
  | `CampaignSequenceStep` | Add, edit, remove and AI-write sequence steps. | read, billable, external, destructive |
19
- | `CampaignLaunchStep` | Readiness checklist, sender choice, deliverability warning, confirmed launch. | read, external |
39
+ | `CampaignLaunchStep` | Readiness checklist (audience, steps, written copy, sender), deliverability and warm-up hold warnings, writes missing email copy with AI, confirmed launch. | read, billable, external |
20
40
  | `CampaignList` | A page. All campaigns; open one for metrics and documents; delete. | read, billable, external, destructive |
21
41
  | `CampaignAssets` | Campaign documents: read, edit, rewrite with AI, add, delete. Standalone: shows a campaign picker. | read, billable, external, destructive |
22
42
  | `CampaignMetrics` | A page. Metrics for 7, 30, 90 or 365 days. Standalone: shows a campaign picker. | read |
@@ -40,15 +60,24 @@ read, so credit numbers are estimates: edit `CREDIT_ESTIMATES` in `lib/campaigns
40
60
  3. **Ideas.** `generateCampaignIdeasAsync` (billable) -> `task_id` -> poll `getCampaignIdeasTaskStatus` every
41
61
  2-3 seconds until `completed`; `ideas[]` carry `campaign_name`, `core_concept`, `primary_hook`.
42
62
  4. **Create.** `createCampaign` -> `id`. Store it at once; later steps reuse it and "Create" becomes
43
- "Save changes" (`updateCampaign`). With `auto_generate_documents` the documents generate in the
44
- background: poll `getCampaignGenerationStatus`.
63
+ "Save changes" (`updateCampaign`). With `auto_generate_documents` (on by default in the wizard and the
64
+ recipe) the documents generate in the background: poll `getCampaignGenerationStatus`; a `generation_error`
65
+ there, or `generation_status: "failed_to_dispatch"` on create, means the writing never started.
45
66
  5. **Audience.** `getCampaignFull` shows what is attached (`audience_list_id`, `audience`). `ListPicker` (lists block) -> a list `id` -> `attachAudienceToCampaign` with `list_id` as a
46
67
  string. Build the list in the search/lists blocks first.
47
68
  6. **Steps and copy.** `getCampaignSequence` -> `createCampaignStep` (channel -> mode matrix: email -> email,
48
69
  phone -> call|voicemail, sms -> sms, social -> connect_note|dm|comment|reply) -> `generateCampaignStepContent`
49
- (billable) per step. `CampaignAssets` edits the documents behind them.
50
- 7. **Launch.** `CampaignLaunchStep` -> `getCampaignFull` (audience attached with its size, >= 1 step, `is_launched`),
51
- `listMailboxes` (active ones), `getSafeToSend` (a warning) -> `launchCampaign` with `sender_mailbox_ids`.
70
+ (billable) per step. `CampaignAssets` edits the documents behind them. Writing an EMAIL step needs the
71
+ campaign's `emails` document to exist already (created by `auto_generate_documents`); without it the step
72
+ write answers 404.
73
+ 7. **Launch.** `CampaignLaunchStep` -> `getCampaignFull` (audience attached with its size, >= 1 step, written copy,
74
+ `is_launched`), `listMailboxes` (active ones), `getSafeToSend` (`safe`, a warning) -> `launchCampaign` with
75
+ `sender_mailbox_ids`. The launch is refused (400 "no completed copy documents") unless a document other than
76
+ `sequence` and `step_catalog` has `status: "completed"`, so Launch stays disabled until one does; "Write the
77
+ email copy with AI" creates the `emails` document if missing (`createCampaignDocument`, once) and has AI write it
78
+ (`regenerateCampaignDocument`, billable). `is_launched` is true only for a live launch, so a failed launch can be
79
+ retried. A mailbox added less than 14 days ago is in its warm-up hold: the campaign goes live but sends nothing
80
+ from it until the hold ends (the step warns).
52
81
  The one-step launch executes email only; configure other channels in the sequencer block.
53
82
  8. **Measure.** `CampaignMetrics` -> `getCampaignMetrics` with `days`.
54
83
 
@@ -62,8 +91,9 @@ read, so credit numbers are estimates: edit `CREDIT_ESTIMATES` in `lib/campaigns
62
91
 
63
92
  ## Defaults (do not ask the human)
64
93
 
65
- - 5 ideas, goal `awareness`, channel `email`, no auto-generated documents (generate per step so each spend is
66
- confirmed), sender = the first active mailbox.
94
+ - 5 ideas, goal `awareness`, channel `email`, AI-written documents ON (a campaign without written copy cannot
95
+ launch; the create confirmation shows the spend), personas picked by AI when none are chosen
96
+ (`persona_mode: "ai_pick"`), sender = the first active mailbox.
67
97
  - **Ask the human**: whether to launch and to which list (state the recipient count), and whether to approve
68
98
  (lock) the company profile.
69
99
 
@@ -95,7 +125,7 @@ read, so credit numbers are estimates: edit `CREDIT_ESTIMATES` in `lib/campaigns
95
125
  | `get_campaign_ideas_task_status_campaigns_ideas_status__task_id__get` | GET /campaigns/ideas/status/{task_id} | read | CampaignWizard |
96
126
  | `create_campaign_campaigns_post` | POST /campaigns | external | CampaignWizard |
97
127
  | `update_campaign_campaigns__campaign_id__patch` | PATCH /campaigns/{campaign_id} | external | CampaignWizard |
98
- | `get_campaign_generation_status_campaigns__campaign_id__status_get` | GET /campaigns/{campaign_id}/status | read | CampaignWizard |
128
+ | `get_campaign_generation_status_campaigns__campaign_id__status_get` | GET /campaigns/{campaign_id}/status | read | CampaignWizard, CampaignLaunchStep |
99
129
  | `get_campaign_full_campaigns__campaign_id__full_get` | GET /campaigns/{campaign_id}/full | read | CampaignWizard, CampaignAudienceStep, CampaignLaunchStep |
100
130
  | `attach_audience_to_campaign_campaigns__campaign_id__audience_put` | PUT /campaigns/{campaign_id}/audience | external | CampaignWizard, CampaignAudienceStep |
101
131
  | `get_campaign_sequence_campaigns__campaign_id__sequence_get` | GET /campaigns/{campaign_id}/sequence | read | CampaignWizard, CampaignSequenceStep |
@@ -112,7 +142,7 @@ read, so credit numbers are estimates: edit `CREDIT_ESTIMATES` in `lib/campaigns
112
142
  | `get_campaign_metrics_campaigns__campaign_id__metrics_get` | GET /campaigns/{campaign_id}/metrics | read | CampaignList, CampaignMetrics |
113
143
  | `list_campaign_documents_campaigns__campaign_id__documents_get` | GET /campaigns/{campaign_id}/documents | read | CampaignList, CampaignAssets |
114
144
  | `get_campaign_document_campaigns__campaign_id__documents__document_id__get` | GET /campaigns/{campaign_id}/documents/{document_id} | read | CampaignList, CampaignAssets |
115
- | `create_campaign_document_campaigns__campaign_id__documents_post` | POST /campaigns/{campaign_id}/documents | external | CampaignList, CampaignAssets |
145
+ | `create_campaign_document_campaigns__campaign_id__documents_post` | POST /campaigns/{campaign_id}/documents | external | CampaignWizard, CampaignLaunchStep, CampaignList, CampaignAssets |
116
146
  | `update_campaign_document_campaigns__campaign_id__documents__document_id__put` | PUT /campaigns/{campaign_id}/documents/{document_id} | external | CampaignList, CampaignAssets |
117
147
  | `delete_campaign_document_campaigns__campaign_id__documents__document_id__delete` | DELETE /campaigns/{campaign_id}/documents/{document_id} | destructive | CampaignList, CampaignAssets |
118
- | `regenerate_campaign_document_campaigns__campaign_id__documents__document_id__regenerate_post` | POST /campaigns/{campaign_id}/documents/{document_id}/regenerate | billable | CampaignList, CampaignAssets |
148
+ | `regenerate_campaign_document_campaigns__campaign_id__documents__document_id__regenerate_post` | POST /campaigns/{campaign_id}/documents/{document_id}/regenerate | billable | CampaignWizard, CampaignLaunchStep, CampaignList, CampaignAssets |