@graph8/sdk 0.362.0 → 0.373.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (615) hide show
  1. package/README.md +33 -10
  2. package/blocks/_foundation/README.md +78 -1
  3. package/blocks/_foundation/block.json +123 -6
  4. package/blocks/_foundation/components/api-error.tsx +9 -7
  5. package/blocks/_foundation/components/brand-provider.tsx +23 -0
  6. package/blocks/_foundation/components/confirm-spend-dialog.tsx +15 -8
  7. package/blocks/_foundation/components/confirmed-action.tsx +90 -0
  8. package/blocks/_foundation/components/connect-graph8.tsx +280 -0
  9. package/blocks/_foundation/components/empty-state.tsx +5 -2
  10. package/blocks/_foundation/components/list-select.tsx +112 -0
  11. package/blocks/_foundation/components/paging-notices.tsx +55 -0
  12. package/blocks/_foundation/components/screen/detail-panel.tsx +2 -1
  13. package/blocks/_foundation/components/screen/filter-controls.tsx +92 -0
  14. package/blocks/_foundation/components/screen/index.ts +2 -0
  15. package/blocks/_foundation/components/screen/record-link.tsx +76 -0
  16. package/blocks/_foundation/components/screen/split-view.tsx +1 -1
  17. package/blocks/_foundation/components/screen/time-series-chart-recharts.tsx +65 -0
  18. package/blocks/_foundation/components/screen/time-series-chart.tsx +15 -97
  19. package/blocks/_foundation/components/screen/time-series-format.ts +57 -0
  20. package/blocks/_foundation/hooks/use-all-pages.ts +54 -0
  21. package/blocks/_foundation/hooks/use-brand.ts +52 -0
  22. package/blocks/_foundation/hooks/use-g8-mutation.ts +23 -0
  23. package/blocks/_foundation/hooks/use-g8-query.ts +127 -1
  24. package/blocks/_foundation/hooks/use-load-more-list.ts +61 -0
  25. package/blocks/_foundation/hooks/use-paginated.ts +120 -0
  26. package/blocks/_foundation/hooks/use-row-activation.ts +57 -0
  27. package/blocks/_foundation/hooks/use-url-filters.ts +93 -0
  28. package/blocks/_foundation/lib/g8-cache.ts +454 -0
  29. package/blocks/_foundation/lib/g8-client.ts +1 -1
  30. package/blocks/_foundation/lib/paging.ts +57 -0
  31. package/blocks/_foundation/lib/plural.ts +23 -0
  32. package/blocks/_foundation/lib/use-debounced-value.ts +13 -0
  33. package/blocks/_foundation/server/connect-route.ts +15 -0
  34. package/blocks/_foundation/server/connect.ts +127 -0
  35. package/blocks/_foundation/server/local-key.ts +70 -0
  36. package/blocks/_foundation/server/proxy.ts +103 -3
  37. package/blocks/_foundation/server/route.ts +16 -4
  38. package/blocks/agents/README.md +93 -2
  39. package/blocks/agents/block.json +343 -2
  40. package/blocks/agents/components/agent-activity.tsx +112 -0
  41. package/blocks/agents/components/agent-approvals.tsx +135 -0
  42. package/blocks/agents/components/agent-builder.tsx +29 -10
  43. package/blocks/agents/components/agent-conversations.tsx +143 -0
  44. package/blocks/agents/components/agent-knowledge-panel.tsx +4 -3
  45. package/blocks/agents/components/agent-operators.tsx +164 -0
  46. package/blocks/agents/components/agent-run-detail.tsx +119 -0
  47. package/blocks/agents/components/agent-runs.tsx +19 -10
  48. package/blocks/agents/components/agent-skills-panel.tsx +7 -7
  49. package/blocks/agents/components/agent-test-history.tsx +121 -0
  50. package/blocks/agents/components/agents-screen.tsx +226 -0
  51. package/blocks/agents/components/assistant-sessions.tsx +150 -0
  52. package/blocks/agents/components/duplicate-agent-button.tsx +42 -0
  53. package/blocks/agents/components/from-template-button.tsx +65 -0
  54. package/blocks/agents/components/knowledge-browser.tsx +107 -0
  55. package/blocks/agents/components/knowledge-detail.tsx +156 -0
  56. package/blocks/agents/components/knowledge-form.tsx +69 -0
  57. package/blocks/agents/components/name-dialog.tsx +32 -0
  58. package/blocks/agents/components/receptionist-button.tsx +46 -0
  59. package/blocks/agents/components/save-as-template-button.tsx +32 -0
  60. package/blocks/agents/components/skill-editor.tsx +147 -0
  61. package/blocks/agents/components/skill-library.tsx +111 -0
  62. package/blocks/agents/lib/agent-types.ts +7 -1
  63. package/blocks/agents/lib/facts.tsx +13 -0
  64. package/blocks/agents/lib/knowledge-utils.ts +49 -0
  65. package/blocks/agents/lib/load-more.tsx +141 -0
  66. package/blocks/agents/lib/skill-utils.ts +91 -0
  67. package/blocks/agents/recipes/create-agent-and-attach-skill.ts +1 -1
  68. package/blocks/analytics/README.md +13 -0
  69. package/blocks/analytics/block.json +45 -1
  70. package/blocks/analytics/components/analytics-screen.tsx +222 -0
  71. package/blocks/analytics/components/kpi-tiles.tsx +1 -1
  72. package/blocks/analytics/components/leaderboard.tsx +14 -7
  73. package/blocks/analytics/components/report-builder.tsx +38 -11
  74. package/blocks/analytics/components/report-detail.tsx +84 -0
  75. package/blocks/analytics/components/trend-chart.tsx +62 -0
  76. package/blocks/appointments/README.md +6 -0
  77. package/blocks/appointments/block.json +33 -2
  78. package/blocks/appointments/components/availability-editor.tsx +17 -5
  79. package/blocks/appointments/components/booking-page.tsx +2 -2
  80. package/blocks/appointments/components/event-type-editor.tsx +2 -1
  81. package/blocks/appointments/components/event-type-related.tsx +4 -2
  82. package/blocks/appointments/components/meeting-contact-links.tsx +49 -0
  83. package/blocks/appointments/components/meeting-detail.tsx +2 -2
  84. package/blocks/appointments/components/meeting-list.tsx +38 -18
  85. package/blocks/appointments/components/meetings-screen.tsx +7 -4
  86. package/blocks/appointments/components/record-meetings-tab.tsx +92 -0
  87. package/blocks/appointments/components/scheduling-screen.tsx +2 -1
  88. package/blocks/block.schema.json +18 -0
  89. package/blocks/block.types.ts +17 -0
  90. package/blocks/campaigns/README.md +41 -11
  91. package/blocks/campaigns/block.json +105 -42
  92. package/blocks/campaigns/components/campaign-assets.tsx +17 -6
  93. package/blocks/campaigns/components/campaign-launch-step.tsx +114 -26
  94. package/blocks/campaigns/components/campaign-list.tsx +7 -3
  95. package/blocks/campaigns/components/campaign-metrics.tsx +1 -1
  96. package/blocks/campaigns/components/campaign-wizard.tsx +52 -14
  97. package/blocks/campaigns/components/campaigns-screen.tsx +267 -0
  98. package/blocks/campaigns/components/icp-detail.tsx +163 -0
  99. package/blocks/campaigns/components/icp-editor.tsx +22 -17
  100. package/blocks/campaigns/components/persona-detail.tsx +181 -0
  101. package/blocks/campaigns/components/persona-editor.tsx +19 -11
  102. package/blocks/campaigns/lib/campaigns-utils.ts +138 -5
  103. package/blocks/campaigns/recipes/attach-audience-and-launch.ts +16 -3
  104. package/blocks/campaigns/recipes/generate-ideas-and-create-campaign.ts +8 -4
  105. package/blocks/coach/README.md +10 -0
  106. package/blocks/coach/block.json +16 -1
  107. package/blocks/coach/components/call-scorecard.tsx +7 -7
  108. package/blocks/coach/components/coach-screen.tsx +222 -0
  109. package/blocks/company-intelligence/README.md +132 -0
  110. package/blocks/company-intelligence/block.json +318 -0
  111. package/blocks/company-intelligence/components/company-intelligence-screen.tsx +114 -0
  112. package/blocks/company-intelligence/components/company-intelligence-tab.tsx +88 -0
  113. package/blocks/company-intelligence/components/company-profile.tsx +68 -0
  114. package/blocks/company-intelligence/components/intelligence-sources.tsx +63 -0
  115. package/blocks/company-intelligence/components/report-detail.tsx +189 -0
  116. package/blocks/company-intelligence/components/research-reports.tsx +136 -0
  117. package/blocks/company-intelligence/components/research-run.tsx +161 -0
  118. package/blocks/company-intelligence/components/source-detail.tsx +117 -0
  119. package/blocks/company-intelligence/components/source-view.tsx +274 -0
  120. package/blocks/company-intelligence/components/wiki-document-detail.tsx +116 -0
  121. package/blocks/company-intelligence/components/wiki-documents.tsx +66 -0
  122. package/blocks/company-intelligence/hooks/use-intelligence.ts +173 -0
  123. package/blocks/company-intelligence/lib/intelligence-types.ts +230 -0
  124. package/blocks/company-intelligence/lib/safe-markdown.tsx +150 -0
  125. package/blocks/company-intelligence/recipes/read-competitors-and-news.ts +33 -0
  126. package/blocks/company-intelligence/recipes/research-your-website.ts +43 -0
  127. package/blocks/company-intelligence/recipes/write-the-research-reports.ts +54 -0
  128. package/blocks/copilot/README.md +13 -0
  129. package/blocks/copilot/block.json +14 -1
  130. package/blocks/copilot/components/copilot-memory.tsx +6 -2
  131. package/blocks/copilot/components/copilot-panel.tsx +14 -5
  132. package/blocks/copilot/components/copilot-screen.tsx +148 -0
  133. package/blocks/copilot/components/copilot-team-chat.tsx +13 -6
  134. package/blocks/crm/README.md +34 -0
  135. package/blocks/crm/block.json +107 -2
  136. package/blocks/crm/components/companies-screen.tsx +359 -0
  137. package/blocks/crm/components/company-detail.tsx +10 -8
  138. package/blocks/crm/components/company-table.tsx +11 -4
  139. package/blocks/crm/components/contact-detail.tsx +10 -7
  140. package/blocks/crm/components/contact-lookup-link.tsx +44 -0
  141. package/blocks/crm/components/contact-table.tsx +10 -3
  142. package/blocks/crm/components/contacts-screen.tsx +377 -0
  143. package/blocks/crm/components/dedupe-review.tsx +13 -6
  144. package/blocks/crm/components/import-wizard.tsx +7 -9
  145. package/blocks/crm/components/list-select.tsx +20 -0
  146. package/blocks/crm/components/notes-panel.tsx +4 -4
  147. package/blocks/crm/components/record-engagement-tab.tsx +103 -0
  148. package/blocks/crm/components/record-form.tsx +6 -4
  149. package/blocks/crm/components/task-list.tsx +4 -4
  150. package/blocks/crm/components/tasks-screen.tsx +399 -0
  151. package/blocks/crm/hooks/use-all-lists.ts +18 -0
  152. package/blocks/crm/hooks/use-custom-fields.ts +6 -1
  153. package/blocks/crm/hooks/use-page-query.ts +9 -1
  154. package/blocks/crm/lib/crm-types.ts +26 -2
  155. package/blocks/crm/lib/read-lists.ts +16 -0
  156. package/blocks/crm/lib/screen-parts.tsx +105 -0
  157. package/blocks/deals/README.md +24 -6
  158. package/blocks/deals/block.json +160 -5
  159. package/blocks/deals/components/close-deal-dialog.tsx +169 -0
  160. package/blocks/deals/components/deal-contacts-editor.tsx +134 -0
  161. package/blocks/deals/components/deal-detail.tsx +25 -13
  162. package/blocks/deals/components/deal-filters.tsx +65 -0
  163. package/blocks/deals/components/deal-line-items.tsx +120 -0
  164. package/blocks/deals/components/deal-notes-tab.tsx +97 -0
  165. package/blocks/deals/components/deal-related.tsx +31 -10
  166. package/blocks/deals/components/deal-tasks-tab.tsx +88 -0
  167. package/blocks/deals/components/deals-screen.tsx +66 -26
  168. package/blocks/deals/components/forecast-view.tsx +4 -4
  169. package/blocks/deals/components/pipeline-board.tsx +8 -6
  170. package/blocks/deals/components/pipeline-editor.tsx +132 -0
  171. package/blocks/deals/components/record-deals-tab.tsx +62 -0
  172. package/blocks/deals/components/stage-editor.tsx +5 -5
  173. package/blocks/deals/hooks/use-pipeline-deals.ts +21 -4
  174. package/blocks/deals/hooks/use-pipelines.ts +1 -1
  175. package/blocks/deals/lib/deal-extras.ts +156 -0
  176. package/blocks/dialer/README.md +45 -4
  177. package/blocks/dialer/block.json +84 -3
  178. package/blocks/dialer/components/call-contact-button.tsx +269 -0
  179. package/blocks/dialer/components/call-log-screen.tsx +208 -0
  180. package/blocks/dialer/components/call-log.tsx +23 -7
  181. package/blocks/dialer/components/call-queue.tsx +4 -4
  182. package/blocks/dialer/components/dialer-screen.tsx +361 -0
  183. package/blocks/dialer/components/dialer-session-detail.tsx +153 -0
  184. package/blocks/dialer/components/disposition-panel.tsx +1 -1
  185. package/blocks/dialer/components/live-calls-table.tsx +7 -3
  186. package/blocks/dialer/components/record-calls-tab.tsx +101 -0
  187. package/blocks/dialer/components/session-start-form.tsx +61 -12
  188. package/blocks/dialer/components/softphone.tsx +4 -2
  189. package/blocks/enrichment/README.md +29 -7
  190. package/blocks/enrichment/block.json +49 -9
  191. package/blocks/enrichment/components/bulk-enrich-panel.tsx +83 -29
  192. package/blocks/enrichment/components/email-verify-badge.tsx +31 -11
  193. package/blocks/enrichment/components/enrich-button.tsx +32 -9
  194. package/blocks/enrichment/components/enrichment-job-detail.tsx +78 -0
  195. package/blocks/enrichment/components/enrichment-screen.tsx +341 -0
  196. package/blocks/enrichment/components/waterfall-config.tsx +66 -17
  197. package/blocks/enrichment/lib/enrichment-types.ts +39 -0
  198. package/blocks/enrichment/recipes/bulk-enrich-and-poll.ts +15 -4
  199. package/blocks/enrichment/recipes/waterfall-pipeline.ts +3 -2
  200. package/blocks/forms/README.md +8 -0
  201. package/blocks/forms/block.json +139 -18
  202. package/blocks/forms/components/form-activity.tsx +71 -0
  203. package/blocks/forms/components/form-backfill.tsx +108 -0
  204. package/blocks/forms/components/form-builder.tsx +4 -2
  205. package/blocks/forms/components/form-embed.tsx +4 -2
  206. package/blocks/forms/components/form-groups.tsx +151 -0
  207. package/blocks/forms/components/form-mapping-editor.tsx +6 -9
  208. package/blocks/forms/components/form-route-history.tsx +72 -0
  209. package/blocks/forms/components/form-saved-views.tsx +81 -0
  210. package/blocks/forms/components/forms-screen.tsx +56 -19
  211. package/blocks/forms/components/more-rows.tsx +30 -0
  212. package/blocks/forms/components/submissions-export.tsx +67 -0
  213. package/blocks/forms/components/submissions-table.tsx +17 -6
  214. package/blocks/forms/lib/forms-extra.ts +67 -0
  215. package/blocks/inbox/README.md +32 -3
  216. package/blocks/inbox/block.json +29 -2
  217. package/blocks/inbox/components/inbox-list.tsx +7 -3
  218. package/blocks/inbox/components/inbox-screen.tsx +294 -27
  219. package/blocks/inbox/components/record-conversations-tab.tsx +154 -0
  220. package/blocks/inbox/components/reply-composer.tsx +65 -9
  221. package/blocks/inbox/components/tag-and-assign.tsx +4 -2
  222. package/blocks/inbox/components/thread-badge.tsx +2 -2
  223. package/blocks/inbox/components/thread-view.tsx +1 -1
  224. package/blocks/inbox/hooks/use-inbox-thread.ts +1 -1
  225. package/blocks/inbox/hooks/use-inbox-threads.ts +1 -0
  226. package/blocks/inbox/lib/inbox-types.ts +47 -1
  227. package/blocks/inbox/recipes/01-triage-and-reply.ts +12 -2
  228. package/blocks/integrations/README.md +63 -1
  229. package/blocks/integrations/block.json +234 -15
  230. package/blocks/integrations/components/ad-accounts.tsx +82 -0
  231. package/blocks/integrations/components/connection-health.tsx +59 -0
  232. package/blocks/integrations/components/crm-detail.tsx +119 -0
  233. package/blocks/integrations/components/crm-records.tsx +103 -0
  234. package/blocks/integrations/components/field-mappings.tsx +61 -0
  235. package/blocks/integrations/components/integration-list.tsx +13 -8
  236. package/blocks/integrations/components/integrations-screen.tsx +152 -0
  237. package/blocks/integrations/components/migration-panel.tsx +133 -0
  238. package/blocks/integrations/components/sugarcrm-connect.tsx +95 -0
  239. package/blocks/integrations/components/sync-status.tsx +11 -9
  240. package/blocks/integrations/components/sync-triggers.tsx +134 -0
  241. package/blocks/integrations/components/webhook-detail.tsx +179 -0
  242. package/blocks/integrations/components/webhook-manager.tsx +43 -25
  243. package/blocks/integrations/components/workspace-tools.tsx +127 -0
  244. package/blocks/integrations/lib/crm-utils.ts +39 -0
  245. package/blocks/integrations/lib/facts.tsx +13 -0
  246. package/blocks/integrations/lib/integrations-utils.ts +11 -1
  247. package/blocks/integrations/lib/load-more.tsx +141 -0
  248. package/blocks/intent/README.md +14 -0
  249. package/blocks/intent/block.json +27 -1
  250. package/blocks/intent/components/account-intent-score.tsx +5 -5
  251. package/blocks/intent/components/intent-feed.tsx +13 -10
  252. package/blocks/intent/components/intent-keyword-manager.tsx +16 -12
  253. package/blocks/intent/components/intent-screen.tsx +265 -0
  254. package/blocks/intent/components/keyword-detail.tsx +124 -0
  255. package/blocks/intent/lib/intent-utils.ts +4 -4
  256. package/blocks/lifecycle/block.json +13 -8
  257. package/blocks/lifecycle/components/account-related.tsx +8 -1
  258. package/blocks/lifecycle/components/churn-list.tsx +5 -2
  259. package/blocks/lifecycle/components/customer-table.tsx +1 -1
  260. package/blocks/lifecycle/components/lifecycle-board.tsx +3 -1
  261. package/blocks/lifecycle/components/lifecycle-screen.tsx +3 -1
  262. package/blocks/lifecycle/lib/lifecycle.ts +1 -1
  263. package/blocks/lists/README.md +20 -1
  264. package/blocks/lists/block.json +69 -3
  265. package/blocks/lists/components/add-company-people-to-list-button.tsx +73 -0
  266. package/blocks/lists/components/add-to-list-button.tsx +20 -9
  267. package/blocks/lists/components/list-picker.tsx +17 -3
  268. package/blocks/lists/components/list-table.tsx +12 -6
  269. package/blocks/lists/components/lists-screen.tsx +346 -0
  270. package/blocks/lists/components/record-lists-tab.tsx +99 -0
  271. package/blocks/lists/components/suppression-manager.tsx +33 -8
  272. package/blocks/lists/components/suppressions-screen.tsx +264 -0
  273. package/blocks/mailboxes/README.md +17 -0
  274. package/blocks/mailboxes/block.json +69 -17
  275. package/blocks/mailboxes/components/connect-mailbox.tsx +32 -21
  276. package/blocks/mailboxes/components/domain-detail.tsx +148 -0
  277. package/blocks/mailboxes/components/domain-purchase.tsx +20 -14
  278. package/blocks/mailboxes/components/mailbox-detail.tsx +157 -0
  279. package/blocks/mailboxes/components/mailbox-list.tsx +8 -3
  280. package/blocks/mailboxes/components/mailboxes-screen.tsx +228 -0
  281. package/blocks/mailboxes/components/warmup-health.tsx +24 -22
  282. package/blocks/mailboxes/lib/mailboxes-utils.ts +26 -5
  283. package/blocks/mailboxes/lib/use-provider-domains.ts +23 -0
  284. package/blocks/newsletter/README.md +12 -0
  285. package/blocks/newsletter/block.json +157 -24
  286. package/blocks/newsletter/components/issue-scheduler.tsx +4 -3
  287. package/blocks/newsletter/components/issue-versions.tsx +93 -0
  288. package/blocks/newsletter/components/more-rows.tsx +30 -0
  289. package/blocks/newsletter/components/newsletter-editor.tsx +1 -1
  290. package/blocks/newsletter/components/newsletter-list-syncs.tsx +121 -0
  291. package/blocks/newsletter/components/newsletter-screen.tsx +39 -14
  292. package/blocks/newsletter/components/newsletter-settings.tsx +211 -0
  293. package/blocks/newsletter/components/newsletter-stats.tsx +2 -2
  294. package/blocks/newsletter/components/newsletter-templates.tsx +133 -0
  295. package/blocks/newsletter/components/subscriber-table.tsx +5 -7
  296. package/blocks/newsletter/components/subscriber-timeline.tsx +65 -0
  297. package/blocks/newsletter/lib/newsletter-extra.ts +60 -0
  298. package/blocks/nurture/block.json +28 -14
  299. package/blocks/nurture/components/nurture-builder.tsx +13 -3
  300. package/blocks/nurture/components/nurture-enroll.tsx +12 -10
  301. package/blocks/nurture/components/nurture-form.tsx +27 -10
  302. package/blocks/nurture/components/nurture-screen.tsx +26 -11
  303. package/blocks/nurture/components/nurture-stats.tsx +5 -4
  304. package/blocks/nurture/lib/nurture-types.ts +1 -1
  305. package/blocks/objects/README.md +18 -1
  306. package/blocks/objects/block.json +42 -21
  307. package/blocks/objects/components/attribute-field-input.tsx +14 -1
  308. package/blocks/objects/components/object-not-found.tsx +3 -1
  309. package/blocks/objects/components/object-record-detail.tsx +10 -2
  310. package/blocks/objects/components/object-schema-editor.tsx +4 -2
  311. package/blocks/objects/components/object-table.tsx +13 -3
  312. package/blocks/objects/components/objects-screen.tsx +147 -0
  313. package/blocks/objects/components/record-roles-panel.tsx +15 -5
  314. package/blocks/objects/lib/object-types.ts +1 -1
  315. package/blocks/pages/README.md +14 -0
  316. package/blocks/pages/block.json +140 -23
  317. package/blocks/pages/components/brand-kit-editor.tsx +5 -2
  318. package/blocks/pages/components/content-library.tsx +9 -4
  319. package/blocks/pages/components/more-rows.tsx +30 -0
  320. package/blocks/pages/components/page-ai-tools.tsx +196 -0
  321. package/blocks/pages/components/page-analytics.tsx +176 -0
  322. package/blocks/pages/components/page-clone.tsx +99 -0
  323. package/blocks/pages/components/page-domains.tsx +167 -0
  324. package/blocks/pages/components/page-editor.tsx +1 -1
  325. package/blocks/pages/components/page-generation-status.tsx +48 -0
  326. package/blocks/pages/components/page-list.tsx +2 -1
  327. package/blocks/pages/components/page-tracking.tsx +137 -0
  328. package/blocks/pages/components/pages-screen.tsx +57 -16
  329. package/blocks/pages/components/seo-report.tsx +6 -1
  330. package/blocks/pages/lib/studio.ts +7 -0
  331. package/blocks/phone-numbers/README.md +74 -0
  332. package/blocks/phone-numbers/block.json +262 -0
  333. package/blocks/phone-numbers/components/a2p-registration.tsx +230 -0
  334. package/blocks/phone-numbers/components/assign-number.tsx +104 -0
  335. package/blocks/phone-numbers/components/number-search.tsx +239 -0
  336. package/blocks/phone-numbers/components/number-settings.tsx +134 -0
  337. package/blocks/phone-numbers/components/number-usage.tsx +97 -0
  338. package/blocks/phone-numbers/components/phone-number-detail.tsx +84 -0
  339. package/blocks/phone-numbers/components/phone-number-list.tsx +110 -0
  340. package/blocks/phone-numbers/components/phone-numbers-screen.tsx +201 -0
  341. package/blocks/phone-numbers/components/register-owned-number.tsx +98 -0
  342. package/blocks/phone-numbers/components/release-number.tsx +126 -0
  343. package/blocks/phone-numbers/hooks/use-phone-numbers.ts +58 -0
  344. package/blocks/phone-numbers/lib/phone-numbers-utils.ts +202 -0
  345. package/blocks/phone-numbers/recipes/01-search-and-buy-number.ts +49 -0
  346. package/blocks/phone-numbers/recipes/02-assign-number-to-rep.ts +25 -0
  347. package/blocks/phone-numbers/recipes/03-audit-numbers.ts +36 -0
  348. package/blocks/phone-numbers/recipes/04-register-text-messaging.ts +39 -0
  349. package/blocks/pickers/README.md +23 -0
  350. package/blocks/pickers/block.json +103 -0
  351. package/blocks/pickers/components/company-picker.tsx +90 -0
  352. package/blocks/pickers/components/contact-picker.tsx +131 -0
  353. package/blocks/pickers/components/member-picker.tsx +99 -0
  354. package/blocks/pickers/components/search-picker.tsx +98 -0
  355. package/blocks/pickers/hooks/use-current-member.ts +104 -0
  356. package/blocks/pickers/hooks/use-picker-pages.ts +73 -0
  357. package/blocks/pickers/lib/use-debounced.ts +13 -0
  358. package/blocks/quotes/README.md +13 -0
  359. package/blocks/quotes/block.json +65 -13
  360. package/blocks/quotes/components/create-quote-from-deal.tsx +48 -0
  361. package/blocks/quotes/components/product-picker.tsx +3 -1
  362. package/blocks/quotes/components/quote-builder.tsx +1 -1
  363. package/blocks/quotes/components/quote-lifecycle-actions.tsx +146 -0
  364. package/blocks/quotes/components/quote-list.tsx +5 -3
  365. package/blocks/quotes/components/quote-related.tsx +8 -4
  366. package/blocks/quotes/components/quote-viewer.tsx +7 -2
  367. package/blocks/quotes/components/record-quotes-tab.tsx +90 -0
  368. package/blocks/radar/README.md +10 -0
  369. package/blocks/radar/block.json +41 -1
  370. package/blocks/radar/components/competitor-battle-card.tsx +1 -1
  371. package/blocks/radar/components/competitor-detail.tsx +117 -0
  372. package/blocks/radar/components/competitor-tracker.tsx +3 -1
  373. package/blocks/radar/components/radar-overview.tsx +4 -4
  374. package/blocks/radar/components/radar-report.tsx +4 -2
  375. package/blocks/radar/components/radar-screen.tsx +214 -0
  376. package/blocks/radar/components/report-detail.tsx +104 -0
  377. package/blocks/search/README.md +10 -0
  378. package/blocks/search/block.json +16 -1
  379. package/blocks/search/components/filter-builder.tsx +3 -3
  380. package/blocks/search/components/prospect-results.tsx +5 -3
  381. package/blocks/search/components/prospect-search-screen.tsx +235 -0
  382. package/blocks/search/components/saved-searches.tsx +4 -1
  383. package/blocks/search/hooks/use-filter-options.ts +20 -6
  384. package/blocks/search/lib/search-types.ts +2 -0
  385. package/blocks/sequencer/README.md +55 -9
  386. package/blocks/sequencer/block.json +126 -19
  387. package/blocks/sequencer/components/add-to-sequence-button.tsx +82 -0
  388. package/blocks/sequencer/components/enroll-dialog.tsx +11 -5
  389. package/blocks/sequencer/components/record-sequences-tab.tsx +126 -0
  390. package/blocks/sequencer/components/sequence-builder.tsx +224 -60
  391. package/blocks/sequencer/components/sequence-detail.tsx +261 -0
  392. package/blocks/sequencer/components/sequence-enrollments.tsx +116 -0
  393. package/blocks/sequencer/components/sequence-list.tsx +34 -9
  394. package/blocks/sequencer/components/sequence-stats.tsx +11 -10
  395. package/blocks/sequencer/components/sequence-steps-panel.tsx +103 -0
  396. package/blocks/sequencer/components/sequences-screen.tsx +162 -0
  397. package/blocks/sequencer/components/step-editor.tsx +14 -12
  398. package/blocks/sequencer/lib/sequencer-utils.ts +133 -10
  399. package/blocks/sequencer/recipes/create-multichannel-sequence.ts +33 -5
  400. package/blocks/sequencer/recipes/launch-and-enroll.ts +19 -2
  401. package/blocks/social-ads/README.md +19 -0
  402. package/blocks/social-ads/block.json +233 -1
  403. package/blocks/social-ads/components/ad-campaign-list.tsx +9 -5
  404. package/blocks/social-ads/components/ads-screen.tsx +152 -0
  405. package/blocks/social-ads/components/audience-sync-detail.tsx +194 -0
  406. package/blocks/social-ads/components/audience-sync.tsx +17 -9
  407. package/blocks/social-ads/components/campaign-ads.tsx +166 -0
  408. package/blocks/social-ads/components/campaign-detail.tsx +138 -0
  409. package/blocks/social-ads/components/campaign-editor.tsx +134 -0
  410. package/blocks/social-ads/components/creative-concepts.tsx +74 -0
  411. package/blocks/social-ads/components/creative-detail.tsx +155 -0
  412. package/blocks/social-ads/components/creative-gallery.tsx +35 -7
  413. package/blocks/social-ads/components/creative-revisions.tsx +57 -0
  414. package/blocks/social-ads/components/creative-tools.tsx +176 -0
  415. package/blocks/social-ads/components/post-composer.tsx +7 -3
  416. package/blocks/social-ads/components/post-detail.tsx +163 -0
  417. package/blocks/social-ads/components/social-ideas.tsx +64 -0
  418. package/blocks/social-ads/components/social-performance.tsx +80 -0
  419. package/blocks/social-ads/components/social-post-list.tsx +11 -3
  420. package/blocks/social-ads/components/social-screen.tsx +187 -0
  421. package/blocks/social-ads/lib/ads-extra.ts +67 -0
  422. package/blocks/surveys/README.md +91 -0
  423. package/blocks/surveys/block.json +299 -0
  424. package/blocks/surveys/components/account-nps.tsx +69 -0
  425. package/blocks/surveys/components/contact-survey-history.tsx +52 -0
  426. package/blocks/surveys/components/org-summary.tsx +82 -0
  427. package/blocks/surveys/components/sending-domains.tsx +113 -0
  428. package/blocks/surveys/components/survey-form.tsx +126 -0
  429. package/blocks/surveys/components/survey-responses.tsx +145 -0
  430. package/blocks/surveys/components/survey-results.tsx +72 -0
  431. package/blocks/surveys/components/survey-send-history.tsx +58 -0
  432. package/blocks/surveys/components/survey-send.tsx +162 -0
  433. package/blocks/surveys/components/survey-triggers.tsx +166 -0
  434. package/blocks/surveys/components/surveys-screen.tsx +234 -0
  435. package/blocks/surveys/lib/surveys-types.ts +228 -0
  436. package/blocks/surveys/recipes/create-an-nps-survey.ts +22 -0
  437. package/blocks/surveys/recipes/send-a-survey-and-follow-it.ts +27 -0
  438. package/blocks/surveys/recipes/survey-score-report.ts +27 -0
  439. package/blocks/visitors/README.md +10 -0
  440. package/blocks/visitors/block.json +25 -1
  441. package/blocks/visitors/components/snippet-install.tsx +21 -10
  442. package/blocks/visitors/components/traffic-chart.tsx +4 -4
  443. package/blocks/visitors/components/visitor-company-card.tsx +26 -16
  444. package/blocks/visitors/components/visitor-detail.tsx +105 -0
  445. package/blocks/visitors/components/visitor-feed.tsx +16 -7
  446. package/blocks/visitors/components/visitors-screen.tsx +166 -0
  447. package/blocks/webchat/README.md +10 -0
  448. package/blocks/webchat/block.json +21 -7
  449. package/blocks/webchat/components/chat-widget-config.tsx +8 -5
  450. package/blocks/webchat/components/conversation-list.tsx +8 -4
  451. package/blocks/webchat/components/webchat-screen.tsx +245 -0
  452. package/blocks/workbench/README.md +11 -0
  453. package/blocks/workbench/block.json +48 -1
  454. package/blocks/workbench/components/column-recipe-editor.tsx +5 -3
  455. package/blocks/workbench/components/run-progress.tsx +1 -1
  456. package/blocks/workbench/components/workbench-grid.tsx +10 -4
  457. package/blocks/workbench/components/workbench-row-detail.tsx +104 -0
  458. package/blocks/workbench/components/workbench-screen.tsx +202 -0
  459. package/blocks/workbench/components/workbench-sources.tsx +194 -0
  460. package/blocks/workbench/components/workbench-table-list.tsx +14 -2
  461. package/blocks/workflows/README.md +13 -0
  462. package/blocks/workflows/block.json +28 -9
  463. package/blocks/workflows/components/run-history.tsx +31 -9
  464. package/blocks/workflows/components/trigger-config.tsx +3 -1
  465. package/blocks/workflows/components/workflow-editor.tsx +4 -2
  466. package/blocks/workflows/components/workflow-list.tsx +13 -6
  467. package/blocks/workflows/components/workflows-screen.tsx +193 -0
  468. package/dist/cli.js +3109 -1977
  469. package/dist/errors.d.mts +34 -0
  470. package/dist/errors.d.ts +34 -0
  471. package/dist/errors.js +46 -0
  472. package/dist/errors.js.map +1 -0
  473. package/dist/errors.mjs +21 -0
  474. package/dist/errors.mjs.map +1 -0
  475. package/dist/index.browser.js +11 -5
  476. package/dist/index.browser.js.map +1 -1
  477. package/dist/index.browser.mjs +11 -5
  478. package/dist/index.browser.mjs.map +1 -1
  479. package/dist/index.d.mts +164 -25
  480. package/dist/index.d.ts +164 -25
  481. package/dist/index.js +11 -5
  482. package/dist/index.js.map +1 -1
  483. package/dist/index.mjs +11 -5
  484. package/dist/index.mjs.map +1 -1
  485. package/dist/react.browser.js +10 -4
  486. package/dist/react.browser.js.map +1 -1
  487. package/dist/react.browser.mjs +10 -4
  488. package/dist/react.browser.mjs.map +1 -1
  489. package/dist/react.d.mts +142 -5
  490. package/dist/react.d.ts +142 -5
  491. package/dist/react.js +10 -4
  492. package/dist/react.js.map +1 -1
  493. package/dist/react.mjs +10 -4
  494. package/dist/react.mjs.map +1 -1
  495. package/dist/types/blocks/add.d.ts +11 -4
  496. package/dist/types/blocks/brand.d.ts +122 -0
  497. package/dist/types/blocks/compose.d.ts +53 -0
  498. package/dist/types/blocks/create.d.ts +27 -20
  499. package/dist/types/blocks/doctor.d.ts +6 -1
  500. package/dist/types/blocks/flows.d.ts +7 -1
  501. package/dist/types/blocks/navfile.d.ts +35 -0
  502. package/dist/types/blocks/orgBrand.d.ts +34 -0
  503. package/dist/types/blocks/paths.d.ts +4 -0
  504. package/dist/types/blocks/prefixes.d.ts +37 -0
  505. package/dist/types/blocks/registry.d.ts +17 -0
  506. package/dist/types/blocks/screens.d.ts +53 -0
  507. package/dist/types/cli.d.ts +5 -0
  508. package/dist/types/errors.d.ts +33 -0
  509. package/dist/types/http.d.ts +2 -18
  510. package/dist/types/version.d.ts +2 -2
  511. package/docs/agents.md +15 -4
  512. package/docs/api/README.md +2 -2
  513. package/docs/api/by-path.md +1 -0
  514. package/docs/api/desk.d.ts +98 -2
  515. package/docs/api/desk.md +123 -1
  516. package/docs/api/inbox.d.ts +10 -2
  517. package/docs/api/inbox.md +10 -2
  518. package/docs/api/seo.d.ts +19 -1
  519. package/docs/api/seo.md +24 -1
  520. package/package.json +16 -2
  521. package/stacks/6sense/pages/companies.tsx.tmpl +21 -0
  522. package/stacks/6sense/pages/contacts.tsx.tmpl +19 -0
  523. package/stacks/6sense/pages/tasks.tsx.tmpl +19 -0
  524. package/stacks/6sense.json +55 -17
  525. package/stacks/apollo/pages/calls.tsx.tmpl +23 -0
  526. package/stacks/apollo/pages/companies.tsx.tmpl +33 -0
  527. package/stacks/apollo/pages/contacts.tsx.tmpl +51 -0
  528. package/stacks/apollo/pages/deals.tsx.tmpl +17 -0
  529. package/stacks/apollo/pages/inbox-contact.tsx.tmpl +13 -0
  530. package/stacks/apollo/pages/lists.tsx.tmpl +12 -0
  531. package/stacks/apollo/pages/meetings.tsx.tmpl +13 -0
  532. package/stacks/apollo/pages/sequences.tsx.tmpl +12 -0
  533. package/stacks/apollo/pages/tasks.tsx.tmpl +20 -0
  534. package/stacks/apollo.json +147 -37
  535. package/stacks/calendly/pages/companies.tsx.tmpl +19 -0
  536. package/stacks/calendly/pages/contacts.tsx.tmpl +21 -0
  537. package/stacks/calendly/pages/meetings.tsx.tmpl +13 -0
  538. package/stacks/calendly/pages/tasks.tsx.tmpl +19 -0
  539. package/stacks/calendly.json +64 -0
  540. package/stacks/clay/pages/companies.tsx.tmpl +31 -0
  541. package/stacks/clay/pages/contacts.tsx.tmpl +31 -0
  542. package/stacks/clay/pages/lists.tsx.tmpl +12 -0
  543. package/stacks/clay/pages/tasks.tsx.tmpl +19 -0
  544. package/stacks/clay.json +73 -27
  545. package/stacks/gong/pages/calls.tsx.tmpl +23 -0
  546. package/stacks/gong/pages/companies.tsx.tmpl +22 -0
  547. package/stacks/gong/pages/contacts.tsx.tmpl +32 -0
  548. package/stacks/gong/pages/lists.tsx.tmpl +12 -0
  549. package/stacks/gong/pages/meetings.tsx.tmpl +13 -0
  550. package/stacks/gong/pages/tasks.tsx.tmpl +19 -0
  551. package/stacks/gong.json +98 -35
  552. package/stacks/hootsuite/pages/companies.tsx.tmpl +22 -0
  553. package/stacks/hootsuite/pages/contacts.tsx.tmpl +28 -0
  554. package/stacks/hootsuite/pages/lists.tsx.tmpl +12 -0
  555. package/stacks/hootsuite/pages/tasks.tsx.tmpl +19 -0
  556. package/stacks/hootsuite.json +104 -0
  557. package/stacks/hubspot/pages/companies.tsx.tmpl +26 -0
  558. package/stacks/hubspot/pages/contacts.tsx.tmpl +45 -0
  559. package/stacks/hubspot/pages/deals.tsx.tmpl +20 -0
  560. package/stacks/hubspot/pages/inbox.tsx.tmpl +103 -0
  561. package/stacks/hubspot/pages/lifecycle.tsx.tmpl +23 -0
  562. package/stacks/hubspot/pages/lists.tsx.tmpl +12 -0
  563. package/stacks/hubspot/pages/meetings.tsx.tmpl +13 -0
  564. package/stacks/hubspot/pages/quotes.tsx.tmpl +11 -0
  565. package/stacks/hubspot/pages/sequences.tsx.tmpl +12 -0
  566. package/stacks/hubspot/pages/tasks.tsx.tmpl +20 -0
  567. package/stacks/hubspot.json +181 -64
  568. package/stacks/intercom/pages/companies.tsx.tmpl +19 -0
  569. package/stacks/intercom/pages/contacts.tsx.tmpl +21 -0
  570. package/stacks/intercom/pages/inbox-contact.tsx.tmpl +13 -0
  571. package/stacks/intercom/pages/tasks.tsx.tmpl +19 -0
  572. package/stacks/intercom.json +52 -20
  573. package/stacks/mailchimp/pages/companies.tsx.tmpl +22 -0
  574. package/stacks/mailchimp/pages/contacts.tsx.tmpl +28 -0
  575. package/stacks/mailchimp/pages/lists.tsx.tmpl +12 -0
  576. package/stacks/mailchimp/pages/tasks.tsx.tmpl +19 -0
  577. package/stacks/mailchimp.json +68 -34
  578. package/stacks/outreach/pages/calls.tsx.tmpl +23 -0
  579. package/stacks/outreach/pages/companies.tsx.tmpl +22 -0
  580. package/stacks/outreach/pages/contacts.tsx.tmpl +46 -0
  581. package/stacks/outreach/pages/inbox-contact.tsx.tmpl +13 -0
  582. package/stacks/outreach/pages/lists.tsx.tmpl +12 -0
  583. package/stacks/outreach/pages/meetings.tsx.tmpl +13 -0
  584. package/stacks/outreach/pages/sequences.tsx.tmpl +12 -0
  585. package/stacks/outreach/pages/tasks.tsx.tmpl +19 -0
  586. package/stacks/outreach.json +106 -25
  587. package/stacks/pipedrive/pages/companies.tsx.tmpl +23 -0
  588. package/stacks/pipedrive/pages/contacts.tsx.tmpl +27 -0
  589. package/stacks/pipedrive/pages/deals.tsx.tmpl +20 -0
  590. package/stacks/pipedrive/pages/inbox.tsx.tmpl +103 -0
  591. package/stacks/pipedrive/pages/meetings.tsx.tmpl +13 -0
  592. package/stacks/pipedrive/pages/quotes.tsx.tmpl +11 -0
  593. package/stacks/pipedrive/pages/tasks.tsx.tmpl +20 -0
  594. package/stacks/pipedrive.json +117 -0
  595. package/stacks/salesforce/pages/companies.tsx.tmpl +26 -0
  596. package/stacks/salesforce/pages/contacts.tsx.tmpl +32 -0
  597. package/stacks/salesforce/pages/deals.tsx.tmpl +20 -0
  598. package/stacks/salesforce/pages/lifecycle.tsx.tmpl +23 -0
  599. package/stacks/salesforce/pages/lists.tsx.tmpl +12 -0
  600. package/stacks/salesforce/pages/quotes.tsx.tmpl +11 -0
  601. package/stacks/salesforce/pages/tasks.tsx.tmpl +20 -0
  602. package/stacks/salesforce.json +85 -31
  603. package/stacks/zoominfo/pages/companies.tsx.tmpl +31 -0
  604. package/stacks/zoominfo/pages/contacts.tsx.tmpl +31 -0
  605. package/stacks/zoominfo/pages/lists.tsx.tmpl +12 -0
  606. package/stacks/zoominfo/pages/tasks.tsx.tmpl +19 -0
  607. package/stacks/zoominfo.json +115 -0
  608. package/styles/styles.css +1 -1
  609. package/templates/next-app/README.md.tmpl +40 -9
  610. package/templates/next-app/app/globals.css.tmpl +349 -64
  611. package/templates/next-app/app/layout.tsx.tmpl +7 -2
  612. package/templates/next-app/components/app-shell.tsx.tmpl +104 -88
  613. package/templates/next-app/components/shell/quick-jump.tsx.tmpl +4 -0
  614. package/templates/next-app/dot-gitignore.tmpl +2 -0
  615. package/templates/next-app/package.json.tmpl +1 -1
package/README.md CHANGED
@@ -8,7 +8,7 @@ The most comprehensive GTM SDK. One `npm install`. Every graph8 capability.
8
8
  > operation in [`docs/api/`](docs/api/README.md). It ships with the package, so it matches this version.
9
9
  > Run `npx @graph8/sdk init-agent` to point Claude Code, Codex and Cursor in your project at it automatically.
10
10
  >
11
- > Blocks (copy-in components and hooks per feature): `npx @graph8/sdk list`, `search <text>`, `add <block>[/<Component>]`, `create <stack> <dir>` (a whole Next.js app: `npx @graph8/sdk create starter my-app`), and `doctor` to check your key, scopes and subscription. Each takes `--help`.
11
+ > Blocks (copy-in components and hooks per feature): `npx @graph8/sdk list`, `search <text>`, `add <block>[/<Component>]` (`--routes` also wires its screens as routes and nav entries in a created app), `create <stack> <dir>` (a whole Next.js app: `npx @graph8/sdk create starter my-app`), and `doctor` to check your key, scopes and subscription. Each takes `--help`.
12
12
 
13
13
  ## Install
14
14
 
@@ -496,18 +496,41 @@ npx @graph8/sdk create mailchimp my-app # then: cd my-app && cp .env.local.exa
496
496
  | Stack | Screens |
497
497
  |---|---|
498
498
  | `starter` | An empty app: proxy, shell and one landing page |
499
- | `mailchimp` | Contacts, companies, forms, submissions, lists, suppressions, newsletter, subscribers, nurture |
500
- | `clay` | Contacts, companies, lists, suppressions, prospect search, bulk enrichment, workbench |
501
- | `6sense` | Contacts, companies, intent feed, website visitors, competitor tracker |
502
- | `intercom` | Contacts, companies, web chat conversations, mailboxes, AI inbox, AI agents |
503
- | `outreach` | Contacts, companies, lists, mailboxes, sequences, inbox, dialer, call log, meetings, analytics |
504
- | `apollo` | Outreach's screens plus prospect search, enrichment, intent, visitors, deals and integrations |
505
- | `gong` | Contacts, companies, lists, dialer, call log, meetings, coaching feed, analytics |
506
- | `salesforce` | Contacts, companies, custom objects, lists, lifecycle, deals, quotes, analytics, workflows, copilot, integrations |
507
- | `hubspot` | The full CRM and marketing suite: forms, newsletter, nurture, web chat, sequences, inbox, pages, lifecycle, deals, quotes, workflows |
499
+ | `mailchimp` | Newsletter, nurture, forms, contacts, companies, tasks, lists, suppressions |
500
+ | `clay` | Prospect search, workbench, enrichment, lists, contacts, companies, tasks, suppressions |
501
+ | `6sense` | Intent, visitors, competitors, companies, contacts, tasks |
502
+ | `intercom` | Inbox, web chat conversations, contacts, companies, tasks, AI agents, mailboxes |
503
+ | `calendly` | Meetings and scheduling (event types, availability, booking pages) over contacts, companies and tasks |
504
+ | `pipedrive` | Analytics, deals, quotes, contacts, companies, inbox, meetings, scheduling, tasks and mailboxes; deals and quotes open each other, replies can start a deal |
505
+ | `outreach` | Analytics, contacts, companies, lists, sequences, campaigns, inbox, dialer, call log, meetings, scheduling, tasks, mailboxes, suppressions |
506
+ | `apollo` | Outreach's screens plus prospect search, enrichment, intent, visitors, deals and integrations; contacts get Enrich, Add to sequence and a Deals tab |
507
+ | `gong` | Analytics, call log, coaching, dialer, meetings, scheduling, contacts, companies, tasks, lists, suppressions |
508
+ | `salesforce` | Analytics, contacts, companies, lists, deals, quotes, lifecycle, tasks, workflows, copilot, custom objects, suppressions, integrations |
509
+ | `hubspot` | The full CRM and marketing suite: inbox, web chat, sequences, campaigns, newsletter, nurture, forms, pages, social, ads, deals, quotes, lifecycle, workflows |
510
+ | `zoominfo` | Prospect search, enrichment, intent and visitors over contacts, companies and lists |
511
+ | `hootsuite` | Social posts, ads, landing pages and analytics over your contacts and lists |
508
512
 
509
513
  Every available stack is proven by `npm run build && npm run e2e:create -- <stack>`, which packs the SDK, scaffolds the app with the built CLI, installs it and requires `next build` to pass. More stacks appear here as their blocks land; `create` refuses a stack that is not available yet.
510
514
 
515
+ ### Branding: your Brand Kit, automatically
516
+
517
+ If your graph8 organization already has a Brand Kit (Studio), `create` uses it. With a key in `G8_API_KEY` (environment or `.env.local`), the new app gets your organization's name, primary colour and logo without any flags; the logo is downloaded into `public/brand/` (never hot-linked), and the colour becomes `--brand` / `--primary` with button text checked for WCAG AA.
518
+
519
+ ```bash
520
+ G8_API_KEY=... npx @graph8/sdk create apollo my-app # picks up the Brand Kit
521
+ npx @graph8/sdk create apollo my-app --no-org-brand # skip it
522
+ npx @graph8/sdk create apollo my-app --brand-color "#0f62fe" # your colour wins; the kit still fills the name and logo
523
+ npx @graph8/sdk brand --from-org # brand an existing app from the kit
524
+ ```
525
+
526
+ | Brand Kit field (Developer API) | App brand input |
527
+ |---|---|
528
+ | `GET /me` `org_name` | `--name` (`g8.config.ts` `name`) |
529
+ | `GET /brand-kit` `design_tokens.colors.primary` (else `brand`, `accent`, button background) | `--brand-color` |
530
+ | `GET /brand-kit` `logo_url` (one logo; no light, dark or icon variant) | `--logo` (`public/brand/logo.*`) |
531
+
532
+ Explicit flags always win, field by field. With no key, a missing or empty kit, no `campaigns:read` scope or no network, the app keeps the default look and the CLI prints one info line; it never fails. The key is never printed or written. Fonts, tagline and voice traits are in the kit but have no brand input, so they are not applied. Icons stay Lucide.
533
+
511
534
  ## UI kit (`@graph8/sdk/ui`)
512
535
 
513
536
  The React component kit and design tokens behind the graph8 app (primitives, light and dark
@@ -8,7 +8,9 @@ Shared base every other block depends on. Install it first (the CLI does it for
8
8
  |---|---|
9
9
  | `lib/g8-client.ts` | Browser caller: `g8Get/g8Post/g8Put/g8Patch/g8Delete` -> your own `/api/g8/...` proxy. No API key. Returns the API envelope `{ data, pagination? }`, throws `G8Error`. |
10
10
  | `server/proxy.ts` | `createG8Proxy(config)`: the logic of the proxy (forwarding, allowlist, `authorize` hook). |
11
- | `server/route.ts` | Installs as `app/api/g8/[...path]/route.ts` and mounts the proxy. |
11
+ | `server/route.ts` | Installs as `app/api/g8/[...path]/route.ts` and mounts the proxy. `allowedPrefixes` is generated from the installed blocks' operations; local preview is on. |
12
+ | `server/local-key.ts`, `server/connect.ts`, `server/connect-route.ts` | Local preview key store: `app/api/g8-connect/route.ts` checks a pasted key (GET /me) and keeps it in `.g8/local-key` on the server. 404 in production. |
13
+ | `components/connect-graph8.tsx` | `ConnectGraph8Panel` + `G8ConnectGate`: the first-run Connect graph8 panel in `next dev`; a no-op in production. |
12
14
  | `hooks/use-g8-query.ts` | `useG8Query(fetcher, deps)` -> `{ data, error, loading, refetch }`. Latest request wins. |
13
15
  | `hooks/use-g8-mutation.ts` | `useG8Mutation(fn)` -> `{ mutate, mutateAsync, data, error, loading, reset }`. |
14
16
  | `hooks/use-paginated.ts` | `usePaginated(fetchPage, deps)` -> `{ items, hasMore, loadMore, reload, loading, loadingMore, error }`. Follows `pagination.next_cursor`. |
@@ -18,6 +20,7 @@ Shared base every other block depends on. Install it first (the CLI does it for
18
20
  | `components/empty-state.tsx` | `EmptyState`: empty list with a first step. |
19
21
  | `components/screen/*.tsx` | The **screen kit** (below): `ScreenLayout`, `StatStrip`, `SplitView`, `DetailPanel`, `Toolbar`, `StatusPill`, `EntityAvatar`, `SectionCard`, `FormSheet`, `KeyValueList`, `Sparkline`, `BarList`, `TimeSeriesChart`. Barrel: `@/g8/components/screen`. |
20
22
  | `hooks/use-url-state.ts` | `useUrlState(key, default = "")` -> `[value, set]`. One string in the URL query as state (replaceState + popstate, SSR-safe). |
23
+ | `lib/g8-cache.ts` | The shared query cache behind the hooks' optional `key`: dedupe, stale-while-revalidate, focus/reconnect refresh, `invalidate(prefix)`, `setQueryData`, bounded LRU. See "Query cache" below. |
21
24
 
22
25
  ## Flow of every call
23
26
 
@@ -30,6 +33,48 @@ Shared base every other block depends on. Install it first (the CLI does it for
30
33
  Ids that pass between calls always come from a previous response (a list's `id` into the next call's
31
34
  path). Never invent one.
32
35
 
36
+ ## Query cache (opt in with a `key`)
37
+
38
+ Every hook works exactly as before without a key. Pass one and the data lives in one app-wide cache
39
+ (`lib/g8-cache.ts`, no dependencies):
40
+
41
+ ```ts
42
+ const contacts = useG8Query(
43
+ (signal) => g8Get("/contacts", { page, limit: 25, q }, { signal }),
44
+ [page, q],
45
+ { key: ["contacts", "list", { page, q }] }, // name EVERY input the fetcher reads
46
+ );
47
+ const contact = useG8Query((s) => g8Get(`/contacts/${id}`, {}, { signal: s }), [id], { key: ["contacts", "detail", id] });
48
+ const save = useG8Mutation((v: Patch) => g8Patch(`/contacts/${v.id}`, v), { invalidates: ["contacts"] });
49
+ ```
50
+
51
+ - **Dedupe**: components asking for the same key share one request and one answer.
52
+ - **Stale-while-revalidate**: a cached key renders instantly on mount (no skeleton) and refreshes in the
53
+ background once older than `staleTime` (default 0). Use `staleTime: 60_000` for data that rarely changes
54
+ (custom-field schemas, pipelines, dispositions).
55
+ - **Focus/reconnect**: watched keys refresh when the window regains focus or the network returns, at most
56
+ once per 5 s per key (`refetchOnFocus: false` opts out; `usePaginated` defaults to false so a scroll is kept).
57
+ - **Invalidation**: `invalidate("contacts")` (or a mutation's `invalidates: ["contacts"]`) marks every key
58
+ starting with `"contacts"` stale; keys on screen refetch now, a request started before the write is
59
+ superseded, the rest refetch when next shown. Prefixes match whole parts (`"deals"` never matches `"deals-x"`).
60
+ - **Writes into the cache**: `setQueryData(key, value | old => new)`; `useG8Mutation(fn, { optimistic: { prefix, update } })`
61
+ changes cached data before the request and puts it back if the write fails.
62
+ - **Memory**: at most 200 keys (LRU); a key nobody watches is dropped after 5 minutes; requests abort when
63
+ their LAST subscriber unmounts.
64
+ - **`placeholderData`**: show a list row while the full record loads (`isPlaceholderData: true`). Never seed
65
+ an edit form from it (edit-before-load PATCHes blanks over real values): enable Edit only when
66
+ `!isPlaceholderData && !loading`.
67
+ - **SSR**: nothing is cached on the server; hooks render their initial state and fetch after hydration.
68
+ - **Keys are JSON-like**: strings, numbers, booleans, null, arrays, plain objects and Dates. A `Map`, `Set`,
69
+ `URLSearchParams` or class instance throws (it would otherwise collide with every other one).
70
+ - **Sign-out and org switch**: the cache is one per page and is not keyed by user or org. Call
71
+ `g8QueryCache.clear()` on sign-out or org switch, and add the org to your keys if you pass `targetOrgId`,
72
+ so one org's cached rows are never shown to another.
73
+
74
+ Key convention used by the blocks: `[area, "list" | "detail" | <sub-resource>, ...inputs]`, area = the API
75
+ path's first segment (`contacts`, `companies`, `deals`, `sequences`, `lists`, `inbox`, `tasks`...), so a write
76
+ to an area invalidates that area with one prefix.
77
+
33
78
  ## Tiers: when to confirm
34
79
 
35
80
  Every operation has a tier (`read`, `write`, `billable`, `external`, `destructive`); each component's
@@ -62,6 +107,21 @@ The proxy therefore **refuses every request (500) until you configure it**:
62
107
  5. Local prototypes only: `dangerouslyAllowUnauthenticated: true` and `dangerouslyAllowAllPaths: true`
63
108
  switch the two gates off. Never ship them.
64
109
 
110
+ ### Local preview (apps made by `graph8-sdk create`)
111
+
112
+ Created apps run before they have sign-in: the route sets `localPreview: true`. While `authorize` is unset the
113
+ proxy answers a request only if NODE_ENV is not `production` AND it comes from this machine: Host is
114
+ `localhost`, `127.0.0.1` or `[::1]` (blocks DNS rebinding), the browser's `Sec-Fetch-Site`/`Origin` say it is
115
+ the app's own page (blocks other sites in the same browser), and no forwarding proxy names a remote client
116
+ (blocks tunnels). `npm run dev` listens on 127.0.0.1, because a machine on your network could forge those
117
+ headers. Other hosts get 403; a production build gets 500 until `authorize` is set. Once `authorize` is set,
118
+ `localPreview` does nothing.
119
+
120
+ The key comes from `G8_API_KEY` (env, `.env.local`) or, in local preview only, the key pasted in the
121
+ Connect graph8 panel, which the server checks against `GET /me` and stores in `.g8/local-key` (0600, git-ignored).
122
+ `npx @graph8/sdk doctor --production` fails until the route is safe to deploy;
123
+ `npx @graph8/sdk doctor --fix-prefixes` regenerates `allowedPrefixes` after you add or remove blocks.
124
+
65
125
  What the proxy does on its own: rejects `..`, encoded slashes and empty segments (400); forwards only
66
126
  `content-type`, `accept`, `accept-language` and `idempotency-key` (never the browser's cookies or
67
127
  `Authorization`); strips hop-by-hop headers, `set-cookie` and `location` from the response; does not follow
@@ -91,6 +151,9 @@ for meaning, no entrance animations, light and dark through the tokens.
91
151
  | `Sparkline` | `sparkline` | Tiny SVG trend for tiles and cells. `data: number[]`, optional `tone`. |
92
152
  | `BarList` | `bar-list` | Ranked rows with a proportional ink bar; `href` makes a row a link. |
93
153
  | `TimeSeriesChart` | `time-series-chart` | `data`, `xKey`, `series: { key, label?, tone? }[]`, `kind` line, area, bar. Needs the `recharts` peer. `tone: "prior"` is the dashed comparison. |
154
+ | `useRowActivation` | `hooks/use-row-activation` | `<TableRow {...rowActivationProps(() => open(row.id))}>` (inside `.map()`; `useRowActivation` is the hook form): the whole row is focusable, Enter/Space/click open it, nested checkboxes, buttons and links keep their own behaviour. Pass `{ role: "button" }` for a non-table row. |
155
+ | `RecordLink` | `components/screen/record-link` | A record name as a real `<a href="?id=X">`: plain click opens in place (URL state), Cmd/Ctrl/middle-click opens a new tab on the same record. `full` adds `view=full`. |
156
+ | `plural`, `pluralWord` | `lib/plural` | `plural(1, "contact")` -> "1 contact", `plural(3, "company", "companies")` -> "3 companies". Never hand-roll `=== 1 ? "" : "s"`. |
94
157
  | `useUrlState` | `hooks/use-url-state` | `const [id, setId] = useUrlState("id")`; `setId(null)` removes it; `{ push: true }` adds a history entry. |
95
158
 
96
159
  A finished list screen, start to end:
@@ -175,6 +238,20 @@ Rules the kit encodes, so screens stay consistent:
175
238
  - Loading is visible and distinct from empty: `StatStrip loading`, `Skeleton` rows.
176
239
  - Dates show short text; the full timestamp is the `title` of a `<time dateTime>`.
177
240
 
241
+ ## Long lists: paging, notices and filters (no silent truncation)
242
+
243
+ A list read that sends a `limit` must page or say it stopped. The foundation ships the pieces:
244
+
245
+ | Need | Use |
246
+ |---|---|
247
+ | Show rows with a "Load more" button (the API pages by `page` or `offset`) | `useLoadMoreList(byPage(size, read))` or `byOffset(size, read)` (`lib/paging.ts`), then `<LoadMore>` under the rows |
248
+ | The API has only a `limit` (no paging) | `<FirstNotice shown={rows.length} limit={LIMIT} noun="mailboxes" />`: "Showing the first N ..." appears only when the read came back full |
249
+ | A screen needs EVERY row (a lookup by id, a full dropdown) | `useAllPages(read, deps)`: reads page after page, reports `truncated` if the page cap cut it short |
250
+ | Choose one audience list | crm's `ListSelect`: a native select with server search and "Load more lists" |
251
+ | Filters for a list screen | `useUrlFilters(keys, { prefix })` keeps them in the URL (`c_email=...`); `TextFilter`, `SelectFilter`, `RangeFilter` and `MoreFilters` (a "More filters" disclosure) go in the Toolbar `filters` slot |
252
+
253
+ `tests/blocks/list-paging-guard.test.ts` in the SDK repo fails on a new `g8Get(..., { limit })` read that does none of these.
254
+
178
255
  ## Prerequisites
179
256
 
180
257
  - `G8_API_KEY` in the server env; verify with `recipes/verify-proxy-key.ts`.
@@ -7,10 +7,14 @@
7
7
  "browser-safe API calls via a server proxy",
8
8
  "query, mutation, pagination and job-polling hooks",
9
9
  "spend/send/delete confirmation dialog",
10
- "screen kit: layout, stats, split view, detail panel, toolbar, status pills, avatars, cards, form sheet, charts"
10
+ "screen kit: layout, stats, split view, detail panel, toolbar, status pills, avatars, cards, form sheet, charts",
11
+ "local preview: a Connect graph8 screen stores the server's key for `next dev`; generated allowedPrefixes",
12
+ "white-label: BrandProvider + useBrand give every block's copy and link-outs your platform name and app URL (default graph8)"
11
13
  ],
12
14
  "dependsOn": [],
13
- "operations": [],
15
+ "operations": [
16
+ "describe_current_key_me_get"
17
+ ],
14
18
  "components": [
15
19
  {
16
20
  "name": "ConfirmSpendDialog",
@@ -19,6 +23,13 @@
19
23
  "operations": [],
20
24
  "tiers": []
21
25
  },
26
+ {
27
+ "name": "AskButton",
28
+ "file": "components/confirmed-action.tsx",
29
+ "purpose": "useConfirmAction + AskButton: a button asks, one ConfirmSpendDialog per screen confirms, then the write runs (Cancel is ignored while it runs).",
30
+ "operations": [],
31
+ "tiers": []
32
+ },
22
33
  {
23
34
  "name": "ApiError",
24
35
  "file": "components/api-error.tsx",
@@ -123,23 +134,65 @@
123
134
  "purpose": "Line, area or bar chart over time on recharts: ink series, tones only for meaning, dashed prior period, hairline grid.",
124
135
  "operations": [],
125
136
  "tiers": []
137
+ },
138
+ {
139
+ "name": "RecordLink",
140
+ "file": "components/screen/record-link.tsx",
141
+ "purpose": "A record's name as a real <a href=\"?id=X\">: a plain click opens it in place (URL state), Cmd/Ctrl/middle-click opens a new tab on the same record.",
142
+ "operations": [],
143
+ "tiers": []
144
+ },
145
+ {
146
+ "name": "ConnectGraph8Panel",
147
+ "file": "components/connect-graph8.tsx",
148
+ "purpose": "Local preview only: paste a graph8 API key, the server checks it (GET /me) and stores it in .g8/local-key, never in the browser; replace or remove it. G8ConnectGate shows it until a key is connected and is a no-op in production.",
149
+ "operations": [],
150
+ "tiers": []
151
+ },
152
+ {
153
+ "name": "BrandProvider",
154
+ "file": "components/brand-provider.tsx",
155
+ "purpose": "White-label context: the app's name, logo, and what the platform is called and where it lives, for every block's copy and link-outs. Fields left out stay graph8's.",
156
+ "operations": [],
157
+ "tiers": []
158
+ },
159
+ {
160
+ "name": "ListSelect",
161
+ "file": "components/list-select.tsx",
162
+ "purpose": "Audience-list dropdown, searchable and paged (native select + server search + Load more). It owns no API call: the block that uses it passes a `read` (crm ships ListSelect with the /lists read wired in).",
163
+ "operations": [],
164
+ "tiers": []
165
+ },
166
+ {
167
+ "name": "FirstNotice",
168
+ "file": "components/paging-notices.tsx",
169
+ "purpose": "FirstNotice (\"Showing the first N ...\" when a read without paging came back full) and LoadMore (\"Showing N\" + Load more): the two honest endings of a capped list.",
170
+ "operations": [],
171
+ "tiers": []
172
+ },
173
+ {
174
+ "name": "TextFilter",
175
+ "file": "components/screen/filter-controls.tsx",
176
+ "purpose": "Toolbar filter controls: TextFilter, SelectFilter, RangeFilter (from/to) and MoreFilters (a disclosure that opens by itself when a hidden filter is set).",
177
+ "operations": [],
178
+ "tiers": []
126
179
  }
127
180
  ],
128
181
  "hooks": [
129
182
  {
130
183
  "name": "useG8Query",
131
184
  "file": "hooks/use-g8-query.ts",
132
- "purpose": "Load data with loading/error/data, an out-of-order guard and refetch."
185
+ "purpose": "Load data with loading/error/data, an out-of-order guard and refetch. Optional `key` joins the shared cache (dedupe, stale-while-revalidate, focus refresh, invalidation)."
133
186
  },
134
187
  {
135
188
  "name": "useG8Mutation",
136
189
  "file": "hooks/use-g8-mutation.ts",
137
- "purpose": "Run a write with loading/error state and success/error callbacks."
190
+ "purpose": "Run a write with loading/error state and success/error callbacks. Optional `invalidates` refreshes cached keys; optional `optimistic` update with rollback."
138
191
  },
139
192
  {
140
193
  "name": "usePaginated",
141
194
  "file": "hooks/use-paginated.ts",
142
- "purpose": "Cursor pagination over the API envelope (pagination.next_cursor) with load-more and reload."
195
+ "purpose": "Cursor pagination over the API envelope (pagination.next_cursor) with load-more and reload. Optional `key` caches the loaded pages."
143
196
  },
144
197
  {
145
198
  "name": "useJobPoll",
@@ -150,6 +203,31 @@
150
203
  "name": "useUrlState",
151
204
  "file": "hooks/use-url-state.ts",
152
205
  "purpose": "One string in the URL query as state (replaceState + popstate, SSR-safe, no next/navigation): the open record, tab or search survives reload and Back."
206
+ },
207
+ {
208
+ "name": "useRowActivation",
209
+ "file": "hooks/use-row-activation.ts",
210
+ "purpose": "Props that make a whole list row focusable and openable with Enter/Space and click, leaving nested checkboxes, buttons and links alone. rowActivationProps is the same as a plain function for use inside .map()."
211
+ },
212
+ {
213
+ "name": "useBrand",
214
+ "file": "hooks/use-brand.ts",
215
+ "purpose": "The current brand ({ name, appUrl, linkLabel, logo }) from the nearest BrandProvider, else graph8's defaults; brandUrl(brand, path) builds a link-out."
216
+ },
217
+ {
218
+ "name": "useAllPages",
219
+ "file": "hooks/use-all-pages.ts",
220
+ "purpose": "Read every page of a list a screen needs in full (lookups, full dropdowns), up to a page cap, and report when the cap cut it short."
221
+ },
222
+ {
223
+ "name": "useLoadMoreList",
224
+ "file": "hooks/use-load-more-list.ts",
225
+ "purpose": "A list read with Load more: the shape of a useG8Query list (data, loading, error, refetch) plus hasMore / loadMore. Pair with byPage / byOffset and the LoadMore notice."
226
+ },
227
+ {
228
+ "name": "useUrlFilters",
229
+ "file": "hooks/use-url-filters.ts",
230
+ "purpose": "List-screen filters kept in the URL (debounced, prefixed, SSR-safe): draft for the inputs, query for the read, clear()."
153
231
  }
154
232
  ],
155
233
  "lib": [
@@ -157,9 +235,33 @@
157
235
  "file": "lib/g8-client.ts",
158
236
  "purpose": "Browser caller for the app's own /api/g8 proxy. Holds no key, returns the SDK envelope, throws G8Error."
159
237
  },
238
+ {
239
+ "file": "components/screen/time-series-chart-recharts.tsx",
240
+ "purpose": "The recharts implementation of TimeSeriesChart, code-split: TimeSeriesChart loads it on first render, so pages without a chart never download recharts."
241
+ },
242
+ {
243
+ "file": "components/screen/time-series-format.ts",
244
+ "purpose": "TimeSeriesChart props, series type and axis formatters (defaultFormatX/defaultFormatY), with no recharts import."
245
+ },
246
+ {
247
+ "file": "lib/g8-cache.ts",
248
+ "purpose": "Shared query cache behind the hooks' optional `key`: one in-flight request per key, stale-while-revalidate, focus/reconnect refresh, invalidate(prefix), setQueryData, optimistic writes, bounded LRU (200) with GC."
249
+ },
160
250
  {
161
251
  "file": "components/screen/index.ts",
162
252
  "purpose": "Barrel for the screen kit: import every part from @/g8/components/screen."
253
+ },
254
+ {
255
+ "file": "lib/plural.ts",
256
+ "purpose": "plural(n, 'contact') -> '1 contact' / '2 contacts' (and pluralWord): one correct plural helper for titles, counts and bulk dialogs."
257
+ },
258
+ {
259
+ "file": "lib/paging.ts",
260
+ "purpose": "byPage / byOffset adapt page-number and offset lists to usePaginated's cursor shape; hasNextOf reads the API's has_next / next_cursor."
261
+ },
262
+ {
263
+ "file": "lib/use-debounced-value.ts",
264
+ "purpose": "useDebounced: a value after it stopped changing (foundation-local copy)."
163
265
  }
164
266
  ],
165
267
  "server": [
@@ -168,10 +270,25 @@
168
270
  "purpose": "createG8Proxy(): forwards browser calls to the graph8 API with the server-side key, a required path allowlist and authorize hook (it refuses to run without them) and a server-side org hook.",
169
271
  "target": "g8/server/proxy.ts"
170
272
  },
273
+ {
274
+ "file": "server/local-key.ts",
275
+ "purpose": "Node only: the local-preview key file (.g8/local-key, 0600, self-gitignored) and resolveApiKey() (environment first, then the file outside production).",
276
+ "target": "g8/server/local-key.ts"
277
+ },
278
+ {
279
+ "file": "server/connect.ts",
280
+ "purpose": "createG8ConnectRoute(): GET status, POST a key (checked with GET /me), DELETE it; answers only next dev requests from this machine, 404 in production.",
281
+ "target": "g8/server/connect.ts"
282
+ },
171
283
  {
172
284
  "file": "server/route.ts",
173
285
  "purpose": "The Next.js App Router catch-all route that mounts the proxy.",
174
286
  "target": "app/api/g8/[...path]/route.ts"
287
+ },
288
+ {
289
+ "file": "server/connect-route.ts",
290
+ "purpose": "The Next.js route behind the Connect graph8 screen.",
291
+ "target": "app/api/g8-connect/route.ts"
175
292
  }
176
293
  ],
177
294
  "recipes": [
@@ -210,7 +327,7 @@
210
327
  },
211
328
  {
212
329
  "decision": "How the proxy knows the caller is a signed-in user of the app",
213
- "default": "None: the route refuses every request until `authorize` is set",
330
+ "default": "Local preview: until `authorize` is set the route answers only `next dev` requests from this machine; a production build refuses every request until `authorize` is set",
214
331
  "askHuman": true
215
332
  },
216
333
  {
@@ -1,8 +1,9 @@
1
1
  "use client";
2
2
 
3
- import { G8Error } from "@graph8/sdk";
3
+ import { G8Error } from "@graph8/sdk/errors";
4
4
  import { CircleAlert } from "lucide-react";
5
5
  import { Button, cn } from "@graph8/sdk/ui";
6
+ import { DEFAULT_BRAND, useBrand } from "../hooks/use-brand";
6
7
 
7
8
  export interface ApiErrorProps {
8
9
  error: unknown;
@@ -23,22 +24,22 @@ interface FieldError {
23
24
  }
24
25
 
25
26
  /** A readable one-line reason for any thrown value. 422 field errors are listed. */
26
- export function describeError(error: unknown): { message: string; fields: string[]; status?: number; requestId?: string } {
27
+ export function describeError(error: unknown, platform: string = DEFAULT_BRAND.linkLabel): { message: string; fields: string[]; status?: number; requestId?: string } {
27
28
  if (error instanceof G8Error) {
28
29
  const fields = Array.isArray(error.detail)
29
30
  ? (error.detail as FieldError[]).map((f) => `${(f.loc ?? []).filter((p) => p !== "body").join(".")}: ${f.msg ?? ""}`.trim())
30
31
  : [];
31
32
  const message =
32
- typeof error.detail === "string" ? error.detail : fields.length ? "Some fields are invalid." : explain(error);
33
+ typeof error.detail === "string" ? error.detail : fields.length ? "Some fields are invalid." : explain(error, platform);
33
34
  return { message, fields, status: error.status, requestId: error.requestId };
34
35
  }
35
36
  return { message: error instanceof Error ? error.message : "Something went wrong.", fields: [] };
36
37
  }
37
38
 
38
- function explain(error: G8Error): string {
39
- if (error.status === 401) return "Your session with graph8 is not valid. Check the server API key.";
39
+ function explain(error: G8Error, platform: string): string {
40
+ if (error.status === 401) return `Your session with ${platform} is not valid. Check the server API key.`;
40
41
  if (error.status === 403) return "This API key is not allowed to do that (missing scope, or the proxy blocked the path).";
41
- if (error.status === 429) return "graph8 is rate limiting requests. Wait a moment and retry.";
42
+ if (error.status === 429) return `${platform} is rate limiting requests. Wait a moment and retry.`;
42
43
  if (error.status === 0) return "Could not reach the server. Check your connection.";
43
44
  return error.message;
44
45
  }
@@ -48,8 +49,9 @@ function explain(error: G8Error): string {
48
49
  * is an error with Retry, never an empty list. Ember is reserved for this: errors and alerts.
49
50
  */
50
51
  export function ApiError({ error, title = "Something went wrong", onRetry, className, compact, retryLabel = "Retry", actions }: ApiErrorProps) {
52
+ const brand = useBrand();
51
53
  if (error == null) return null;
52
- const info = describeError(error);
54
+ const info = describeError(error, brand.linkLabel);
53
55
 
54
56
  if (compact) {
55
57
  return (
@@ -0,0 +1,23 @@
1
+ "use client";
2
+
3
+ /**
4
+ * Wraps the app so every block shows YOUR brand: the platform's name in copy and link-outs, and where those links
5
+ * go. A created app renders it in components/app-shell.tsx from `g8.config.ts`:
6
+ *
7
+ * <BrandProvider brand={brand}>{children}</BrandProvider>
8
+ *
9
+ * Fields left out keep graph8's defaults (see hooks/use-brand.ts). No API calls.
10
+ */
11
+
12
+ import { useMemo, type ReactNode } from "react";
13
+ import { BrandContext, resolveBrand, type BrandConfig } from "../hooks/use-brand";
14
+
15
+ export interface BrandProviderProps {
16
+ brand?: Partial<BrandConfig> | null;
17
+ children: ReactNode;
18
+ }
19
+
20
+ export function BrandProvider({ brand, children }: BrandProviderProps) {
21
+ const value = useMemo(() => resolveBrand(brand), [brand?.name, brand?.appUrl, brand?.linkLabel, brand?.logo]); // eslint-disable-line react-hooks/exhaustive-deps
22
+ return <BrandContext.Provider value={value}>{children}</BrandContext.Provider>;
23
+ }
@@ -39,6 +39,13 @@ const TIER_COPY: Record<ConfirmTier, { badge: string; confirm: string; note: str
39
39
  };
40
40
 
41
41
  /** True for the tiers an app should confirm before calling. read/write never need it. */
42
+ /** The tier badge: ember for what cannot be undone, ochre for spend and outreach. Token classes only. */
43
+ const TIER_BADGE: Record<ConfirmTier, string> = {
44
+ billable: "bg-warning text-warning-foreground",
45
+ external: "bg-warning text-warning-foreground",
46
+ destructive: "bg-error text-error-foreground",
47
+ };
48
+
42
49
  export function tierRequiresConfirmation(tier: string): tier is ConfirmTier {
43
50
  return tier === "billable" || tier === "external" || tier === "destructive";
44
51
  }
@@ -83,7 +90,7 @@ export function ConfirmSpendDialog(props: ConfirmSpendDialogProps) {
83
90
  showCloseButton={false}
84
91
  data-tier={tier}
85
92
  style={interactive}
86
- className={`max-w-md sm:max-w-md bg-white p-5 shadow-xl ${props.className ?? ""}`}
93
+ className={`max-w-md gap-4 border-border bg-background p-5 shadow-xl sm:max-w-md ${props.className ?? ""}`}
87
94
  onOpenAutoFocus={(event) => {
88
95
  event.preventDefault();
89
96
  openerRef.current = document.activeElement instanceof HTMLElement ? document.activeElement : null;
@@ -98,15 +105,15 @@ export function ConfirmSpendDialog(props: ConfirmSpendDialogProps) {
98
105
  onInteractOutside={(event) => event.preventDefault()}
99
106
  onEscapeKeyDown={(event) => confirming && event.preventDefault()}
100
107
  >
101
- <span className="inline-block w-fit rounded bg-gray-100 px-2 py-0.5 text-xs font-medium text-gray-700">
108
+ <span className={`inline-flex h-5 w-fit items-center rounded-full px-2 text-[11px] font-medium leading-none ${TIER_BADGE[tier]}`}>
102
109
  {copy.badge}
103
110
  </span>
104
- <DialogTitle className="text-base font-semibold text-gray-900">{title}</DialogTitle>
111
+ <DialogTitle className="text-base font-medium leading-6 text-foreground">{title}</DialogTitle>
105
112
  <DialogDescription asChild>
106
- <div className="space-y-2 text-sm text-gray-600">
113
+ <div className="space-y-2 text-sm leading-5 text-muted-foreground">
107
114
  {description ? <div>{description}</div> : null}
108
115
  {summary.length > 0 ? (
109
- <ul className="list-disc pl-5 text-gray-900">
116
+ <ul className="list-disc space-y-0.5 pl-5 tabular-nums text-foreground">
110
117
  {summary.map((line) => (
111
118
  <li key={line}>{line}</li>
112
119
  ))}
@@ -115,14 +122,14 @@ export function ConfirmSpendDialog(props: ConfirmSpendDialogProps) {
115
122
  <p>{copy.note}</p>
116
123
  </div>
117
124
  </DialogDescription>
118
- <div className="flex justify-end gap-2">
125
+ <div className="flex justify-end gap-2 pt-1">
119
126
  <button
120
127
  ref={cancelRef}
121
128
  type="button"
122
129
  disabled={confirming}
123
130
  onClick={onCancel}
124
131
  style={interactive}
125
- className="rounded border border-gray-300 px-3 py-1.5 text-sm text-gray-700 disabled:opacity-50"
132
+ className="inline-flex h-8 items-center rounded-md border border-border bg-background px-3 text-sm font-medium text-foreground transition-colors hover:bg-surface-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-50"
126
133
  >
127
134
  {props.cancelLabel ?? "Cancel"}
128
135
  </button>
@@ -131,7 +138,7 @@ export function ConfirmSpendDialog(props: ConfirmSpendDialogProps) {
131
138
  disabled={confirming}
132
139
  onClick={onConfirm}
133
140
  style={interactive}
134
- className="rounded bg-gray-900 px-3 py-1.5 text-sm font-medium text-white disabled:opacity-50"
141
+ className={`inline-flex h-8 items-center rounded-md px-3 text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:opacity-50 ${tier === "destructive" ? "bg-destructive text-destructive-foreground hover:bg-destructive/90" : "bg-primary text-primary-foreground hover:bg-primary/90"}`}
135
142
  >
136
143
  {confirming ? "Working..." : (props.confirmLabel ?? copy.confirm)}
137
144
  </button>
@@ -0,0 +1,90 @@
1
+ "use client";
2
+
3
+ /**
4
+ * useConfirmAction + AskButton (foundation, shared by every block's record detail): the pattern detail screens use for every write. A button calls `ask(config)`; the screen
5
+ * renders ONE `<ConfirmSpendDialog {...dialog} />` (so the confirmation is visible in the screen's own source), the person confirms,
6
+ * then `config.run()` executes and the failure, if any, is shown in `failure`.
7
+ *
8
+ * SDK operations: none of its own; `run` is whatever the caller passes. `tolerateStatus` lists HTTP statuses that count as done
9
+ * (a delete answering 404); `mapError` can turn a thrown error into a clearer one (return null to treat it as done).
10
+ */
11
+ import { useState } from "react";
12
+ import type { ReactNode } from "react";
13
+ import { Button } from "@graph8/sdk/ui";
14
+ import type { ConfirmTier } from "@/g8/components/confirm-spend-dialog";
15
+
16
+ export interface ConfirmActionConfig {
17
+ tier: ConfirmTier;
18
+ title: string;
19
+ description?: ReactNode;
20
+ confirmLabel?: string;
21
+ estimatedCredits?: number;
22
+ recipients?: number;
23
+ affected?: number;
24
+ tolerateStatus?: number[];
25
+ mapError?: (error: unknown) => unknown;
26
+ run: () => Promise<unknown>;
27
+ onDone?: () => void;
28
+ }
29
+
30
+ export interface AskButtonProps extends ConfirmActionConfig {
31
+ ask: (config: ConfirmActionConfig) => void;
32
+ label: string;
33
+ variant?: "default" | "outline" | "ghost";
34
+ disabled?: boolean;
35
+ }
36
+
37
+ export function useConfirmAction() {
38
+ const [cfg, setCfg] = useState<ConfirmActionConfig | null>(null);
39
+ const [busy, setBusy] = useState(false);
40
+ const [failure, setFailure] = useState<unknown>(null);
41
+
42
+ const go = async () => {
43
+ if (!cfg) return;
44
+ setBusy(true);
45
+ setFailure(null);
46
+ try {
47
+ await cfg.run();
48
+ cfg.onDone?.();
49
+ } catch (err) {
50
+ const status = (err as { status?: number })?.status;
51
+ if (status != null && cfg.tolerateStatus?.includes(status)) cfg.onDone?.();
52
+ else {
53
+ const shown = cfg.mapError ? cfg.mapError(err) : err;
54
+ if (shown == null) cfg.onDone?.();
55
+ else setFailure(shown);
56
+ }
57
+ } finally {
58
+ setBusy(false);
59
+ setCfg(null);
60
+ }
61
+ };
62
+
63
+ return {
64
+ ask: (config: ConfirmActionConfig) => setCfg(config),
65
+ busy,
66
+ failure,
67
+ dialog: {
68
+ open: cfg != null,
69
+ tier: cfg?.tier ?? ("external" as ConfirmTier),
70
+ title: cfg?.title ?? "",
71
+ description: cfg?.description,
72
+ estimatedCredits: cfg?.estimatedCredits,
73
+ recipients: cfg?.recipients,
74
+ affected: cfg?.affected,
75
+ confirmLabel: cfg?.confirmLabel,
76
+ confirming: busy,
77
+ onConfirm: () => void go(),
78
+ // While the write is running, Cancel does nothing: closing then would hide a write that still lands.
79
+ onCancel: () => { if (!busy) setCfg(null); },
80
+ },
81
+ };
82
+ }
83
+
84
+ export function AskButton({ ask, label, variant = "outline", disabled, ...config }: AskButtonProps) {
85
+ return (
86
+ <Button size="sm" variant={variant} disabled={disabled} onClick={() => ask({ confirmLabel: label, ...config })}>
87
+ {label}
88
+ </Button>
89
+ );
90
+ }