@graph8/sdk 0.362.0 → 0.373.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (615) hide show
  1. package/README.md +33 -10
  2. package/blocks/_foundation/README.md +78 -1
  3. package/blocks/_foundation/block.json +123 -6
  4. package/blocks/_foundation/components/api-error.tsx +9 -7
  5. package/blocks/_foundation/components/brand-provider.tsx +23 -0
  6. package/blocks/_foundation/components/confirm-spend-dialog.tsx +15 -8
  7. package/blocks/_foundation/components/confirmed-action.tsx +90 -0
  8. package/blocks/_foundation/components/connect-graph8.tsx +280 -0
  9. package/blocks/_foundation/components/empty-state.tsx +5 -2
  10. package/blocks/_foundation/components/list-select.tsx +112 -0
  11. package/blocks/_foundation/components/paging-notices.tsx +55 -0
  12. package/blocks/_foundation/components/screen/detail-panel.tsx +2 -1
  13. package/blocks/_foundation/components/screen/filter-controls.tsx +92 -0
  14. package/blocks/_foundation/components/screen/index.ts +2 -0
  15. package/blocks/_foundation/components/screen/record-link.tsx +76 -0
  16. package/blocks/_foundation/components/screen/split-view.tsx +1 -1
  17. package/blocks/_foundation/components/screen/time-series-chart-recharts.tsx +65 -0
  18. package/blocks/_foundation/components/screen/time-series-chart.tsx +15 -97
  19. package/blocks/_foundation/components/screen/time-series-format.ts +57 -0
  20. package/blocks/_foundation/hooks/use-all-pages.ts +54 -0
  21. package/blocks/_foundation/hooks/use-brand.ts +52 -0
  22. package/blocks/_foundation/hooks/use-g8-mutation.ts +23 -0
  23. package/blocks/_foundation/hooks/use-g8-query.ts +127 -1
  24. package/blocks/_foundation/hooks/use-load-more-list.ts +61 -0
  25. package/blocks/_foundation/hooks/use-paginated.ts +120 -0
  26. package/blocks/_foundation/hooks/use-row-activation.ts +57 -0
  27. package/blocks/_foundation/hooks/use-url-filters.ts +93 -0
  28. package/blocks/_foundation/lib/g8-cache.ts +454 -0
  29. package/blocks/_foundation/lib/g8-client.ts +1 -1
  30. package/blocks/_foundation/lib/paging.ts +57 -0
  31. package/blocks/_foundation/lib/plural.ts +23 -0
  32. package/blocks/_foundation/lib/use-debounced-value.ts +13 -0
  33. package/blocks/_foundation/server/connect-route.ts +15 -0
  34. package/blocks/_foundation/server/connect.ts +127 -0
  35. package/blocks/_foundation/server/local-key.ts +70 -0
  36. package/blocks/_foundation/server/proxy.ts +103 -3
  37. package/blocks/_foundation/server/route.ts +16 -4
  38. package/blocks/agents/README.md +93 -2
  39. package/blocks/agents/block.json +343 -2
  40. package/blocks/agents/components/agent-activity.tsx +112 -0
  41. package/blocks/agents/components/agent-approvals.tsx +135 -0
  42. package/blocks/agents/components/agent-builder.tsx +29 -10
  43. package/blocks/agents/components/agent-conversations.tsx +143 -0
  44. package/blocks/agents/components/agent-knowledge-panel.tsx +4 -3
  45. package/blocks/agents/components/agent-operators.tsx +164 -0
  46. package/blocks/agents/components/agent-run-detail.tsx +119 -0
  47. package/blocks/agents/components/agent-runs.tsx +19 -10
  48. package/blocks/agents/components/agent-skills-panel.tsx +7 -7
  49. package/blocks/agents/components/agent-test-history.tsx +121 -0
  50. package/blocks/agents/components/agents-screen.tsx +226 -0
  51. package/blocks/agents/components/assistant-sessions.tsx +150 -0
  52. package/blocks/agents/components/duplicate-agent-button.tsx +42 -0
  53. package/blocks/agents/components/from-template-button.tsx +65 -0
  54. package/blocks/agents/components/knowledge-browser.tsx +107 -0
  55. package/blocks/agents/components/knowledge-detail.tsx +156 -0
  56. package/blocks/agents/components/knowledge-form.tsx +69 -0
  57. package/blocks/agents/components/name-dialog.tsx +32 -0
  58. package/blocks/agents/components/receptionist-button.tsx +46 -0
  59. package/blocks/agents/components/save-as-template-button.tsx +32 -0
  60. package/blocks/agents/components/skill-editor.tsx +147 -0
  61. package/blocks/agents/components/skill-library.tsx +111 -0
  62. package/blocks/agents/lib/agent-types.ts +7 -1
  63. package/blocks/agents/lib/facts.tsx +13 -0
  64. package/blocks/agents/lib/knowledge-utils.ts +49 -0
  65. package/blocks/agents/lib/load-more.tsx +141 -0
  66. package/blocks/agents/lib/skill-utils.ts +91 -0
  67. package/blocks/agents/recipes/create-agent-and-attach-skill.ts +1 -1
  68. package/blocks/analytics/README.md +13 -0
  69. package/blocks/analytics/block.json +45 -1
  70. package/blocks/analytics/components/analytics-screen.tsx +222 -0
  71. package/blocks/analytics/components/kpi-tiles.tsx +1 -1
  72. package/blocks/analytics/components/leaderboard.tsx +14 -7
  73. package/blocks/analytics/components/report-builder.tsx +38 -11
  74. package/blocks/analytics/components/report-detail.tsx +84 -0
  75. package/blocks/analytics/components/trend-chart.tsx +62 -0
  76. package/blocks/appointments/README.md +6 -0
  77. package/blocks/appointments/block.json +33 -2
  78. package/blocks/appointments/components/availability-editor.tsx +17 -5
  79. package/blocks/appointments/components/booking-page.tsx +2 -2
  80. package/blocks/appointments/components/event-type-editor.tsx +2 -1
  81. package/blocks/appointments/components/event-type-related.tsx +4 -2
  82. package/blocks/appointments/components/meeting-contact-links.tsx +49 -0
  83. package/blocks/appointments/components/meeting-detail.tsx +2 -2
  84. package/blocks/appointments/components/meeting-list.tsx +38 -18
  85. package/blocks/appointments/components/meetings-screen.tsx +7 -4
  86. package/blocks/appointments/components/record-meetings-tab.tsx +92 -0
  87. package/blocks/appointments/components/scheduling-screen.tsx +2 -1
  88. package/blocks/block.schema.json +18 -0
  89. package/blocks/block.types.ts +17 -0
  90. package/blocks/campaigns/README.md +41 -11
  91. package/blocks/campaigns/block.json +105 -42
  92. package/blocks/campaigns/components/campaign-assets.tsx +17 -6
  93. package/blocks/campaigns/components/campaign-launch-step.tsx +114 -26
  94. package/blocks/campaigns/components/campaign-list.tsx +7 -3
  95. package/blocks/campaigns/components/campaign-metrics.tsx +1 -1
  96. package/blocks/campaigns/components/campaign-wizard.tsx +52 -14
  97. package/blocks/campaigns/components/campaigns-screen.tsx +267 -0
  98. package/blocks/campaigns/components/icp-detail.tsx +163 -0
  99. package/blocks/campaigns/components/icp-editor.tsx +22 -17
  100. package/blocks/campaigns/components/persona-detail.tsx +181 -0
  101. package/blocks/campaigns/components/persona-editor.tsx +19 -11
  102. package/blocks/campaigns/lib/campaigns-utils.ts +138 -5
  103. package/blocks/campaigns/recipes/attach-audience-and-launch.ts +16 -3
  104. package/blocks/campaigns/recipes/generate-ideas-and-create-campaign.ts +8 -4
  105. package/blocks/coach/README.md +10 -0
  106. package/blocks/coach/block.json +16 -1
  107. package/blocks/coach/components/call-scorecard.tsx +7 -7
  108. package/blocks/coach/components/coach-screen.tsx +222 -0
  109. package/blocks/company-intelligence/README.md +132 -0
  110. package/blocks/company-intelligence/block.json +318 -0
  111. package/blocks/company-intelligence/components/company-intelligence-screen.tsx +114 -0
  112. package/blocks/company-intelligence/components/company-intelligence-tab.tsx +88 -0
  113. package/blocks/company-intelligence/components/company-profile.tsx +68 -0
  114. package/blocks/company-intelligence/components/intelligence-sources.tsx +63 -0
  115. package/blocks/company-intelligence/components/report-detail.tsx +189 -0
  116. package/blocks/company-intelligence/components/research-reports.tsx +136 -0
  117. package/blocks/company-intelligence/components/research-run.tsx +161 -0
  118. package/blocks/company-intelligence/components/source-detail.tsx +117 -0
  119. package/blocks/company-intelligence/components/source-view.tsx +274 -0
  120. package/blocks/company-intelligence/components/wiki-document-detail.tsx +116 -0
  121. package/blocks/company-intelligence/components/wiki-documents.tsx +66 -0
  122. package/blocks/company-intelligence/hooks/use-intelligence.ts +173 -0
  123. package/blocks/company-intelligence/lib/intelligence-types.ts +230 -0
  124. package/blocks/company-intelligence/lib/safe-markdown.tsx +150 -0
  125. package/blocks/company-intelligence/recipes/read-competitors-and-news.ts +33 -0
  126. package/blocks/company-intelligence/recipes/research-your-website.ts +43 -0
  127. package/blocks/company-intelligence/recipes/write-the-research-reports.ts +54 -0
  128. package/blocks/copilot/README.md +13 -0
  129. package/blocks/copilot/block.json +14 -1
  130. package/blocks/copilot/components/copilot-memory.tsx +6 -2
  131. package/blocks/copilot/components/copilot-panel.tsx +14 -5
  132. package/blocks/copilot/components/copilot-screen.tsx +148 -0
  133. package/blocks/copilot/components/copilot-team-chat.tsx +13 -6
  134. package/blocks/crm/README.md +34 -0
  135. package/blocks/crm/block.json +107 -2
  136. package/blocks/crm/components/companies-screen.tsx +359 -0
  137. package/blocks/crm/components/company-detail.tsx +10 -8
  138. package/blocks/crm/components/company-table.tsx +11 -4
  139. package/blocks/crm/components/contact-detail.tsx +10 -7
  140. package/blocks/crm/components/contact-lookup-link.tsx +44 -0
  141. package/blocks/crm/components/contact-table.tsx +10 -3
  142. package/blocks/crm/components/contacts-screen.tsx +377 -0
  143. package/blocks/crm/components/dedupe-review.tsx +13 -6
  144. package/blocks/crm/components/import-wizard.tsx +7 -9
  145. package/blocks/crm/components/list-select.tsx +20 -0
  146. package/blocks/crm/components/notes-panel.tsx +4 -4
  147. package/blocks/crm/components/record-engagement-tab.tsx +103 -0
  148. package/blocks/crm/components/record-form.tsx +6 -4
  149. package/blocks/crm/components/task-list.tsx +4 -4
  150. package/blocks/crm/components/tasks-screen.tsx +399 -0
  151. package/blocks/crm/hooks/use-all-lists.ts +18 -0
  152. package/blocks/crm/hooks/use-custom-fields.ts +6 -1
  153. package/blocks/crm/hooks/use-page-query.ts +9 -1
  154. package/blocks/crm/lib/crm-types.ts +26 -2
  155. package/blocks/crm/lib/read-lists.ts +16 -0
  156. package/blocks/crm/lib/screen-parts.tsx +105 -0
  157. package/blocks/deals/README.md +24 -6
  158. package/blocks/deals/block.json +160 -5
  159. package/blocks/deals/components/close-deal-dialog.tsx +169 -0
  160. package/blocks/deals/components/deal-contacts-editor.tsx +134 -0
  161. package/blocks/deals/components/deal-detail.tsx +25 -13
  162. package/blocks/deals/components/deal-filters.tsx +65 -0
  163. package/blocks/deals/components/deal-line-items.tsx +120 -0
  164. package/blocks/deals/components/deal-notes-tab.tsx +97 -0
  165. package/blocks/deals/components/deal-related.tsx +31 -10
  166. package/blocks/deals/components/deal-tasks-tab.tsx +88 -0
  167. package/blocks/deals/components/deals-screen.tsx +66 -26
  168. package/blocks/deals/components/forecast-view.tsx +4 -4
  169. package/blocks/deals/components/pipeline-board.tsx +8 -6
  170. package/blocks/deals/components/pipeline-editor.tsx +132 -0
  171. package/blocks/deals/components/record-deals-tab.tsx +62 -0
  172. package/blocks/deals/components/stage-editor.tsx +5 -5
  173. package/blocks/deals/hooks/use-pipeline-deals.ts +21 -4
  174. package/blocks/deals/hooks/use-pipelines.ts +1 -1
  175. package/blocks/deals/lib/deal-extras.ts +156 -0
  176. package/blocks/dialer/README.md +45 -4
  177. package/blocks/dialer/block.json +84 -3
  178. package/blocks/dialer/components/call-contact-button.tsx +269 -0
  179. package/blocks/dialer/components/call-log-screen.tsx +208 -0
  180. package/blocks/dialer/components/call-log.tsx +23 -7
  181. package/blocks/dialer/components/call-queue.tsx +4 -4
  182. package/blocks/dialer/components/dialer-screen.tsx +361 -0
  183. package/blocks/dialer/components/dialer-session-detail.tsx +153 -0
  184. package/blocks/dialer/components/disposition-panel.tsx +1 -1
  185. package/blocks/dialer/components/live-calls-table.tsx +7 -3
  186. package/blocks/dialer/components/record-calls-tab.tsx +101 -0
  187. package/blocks/dialer/components/session-start-form.tsx +61 -12
  188. package/blocks/dialer/components/softphone.tsx +4 -2
  189. package/blocks/enrichment/README.md +29 -7
  190. package/blocks/enrichment/block.json +49 -9
  191. package/blocks/enrichment/components/bulk-enrich-panel.tsx +83 -29
  192. package/blocks/enrichment/components/email-verify-badge.tsx +31 -11
  193. package/blocks/enrichment/components/enrich-button.tsx +32 -9
  194. package/blocks/enrichment/components/enrichment-job-detail.tsx +78 -0
  195. package/blocks/enrichment/components/enrichment-screen.tsx +341 -0
  196. package/blocks/enrichment/components/waterfall-config.tsx +66 -17
  197. package/blocks/enrichment/lib/enrichment-types.ts +39 -0
  198. package/blocks/enrichment/recipes/bulk-enrich-and-poll.ts +15 -4
  199. package/blocks/enrichment/recipes/waterfall-pipeline.ts +3 -2
  200. package/blocks/forms/README.md +8 -0
  201. package/blocks/forms/block.json +139 -18
  202. package/blocks/forms/components/form-activity.tsx +71 -0
  203. package/blocks/forms/components/form-backfill.tsx +108 -0
  204. package/blocks/forms/components/form-builder.tsx +4 -2
  205. package/blocks/forms/components/form-embed.tsx +4 -2
  206. package/blocks/forms/components/form-groups.tsx +151 -0
  207. package/blocks/forms/components/form-mapping-editor.tsx +6 -9
  208. package/blocks/forms/components/form-route-history.tsx +72 -0
  209. package/blocks/forms/components/form-saved-views.tsx +81 -0
  210. package/blocks/forms/components/forms-screen.tsx +56 -19
  211. package/blocks/forms/components/more-rows.tsx +30 -0
  212. package/blocks/forms/components/submissions-export.tsx +67 -0
  213. package/blocks/forms/components/submissions-table.tsx +17 -6
  214. package/blocks/forms/lib/forms-extra.ts +67 -0
  215. package/blocks/inbox/README.md +32 -3
  216. package/blocks/inbox/block.json +29 -2
  217. package/blocks/inbox/components/inbox-list.tsx +7 -3
  218. package/blocks/inbox/components/inbox-screen.tsx +294 -27
  219. package/blocks/inbox/components/record-conversations-tab.tsx +154 -0
  220. package/blocks/inbox/components/reply-composer.tsx +65 -9
  221. package/blocks/inbox/components/tag-and-assign.tsx +4 -2
  222. package/blocks/inbox/components/thread-badge.tsx +2 -2
  223. package/blocks/inbox/components/thread-view.tsx +1 -1
  224. package/blocks/inbox/hooks/use-inbox-thread.ts +1 -1
  225. package/blocks/inbox/hooks/use-inbox-threads.ts +1 -0
  226. package/blocks/inbox/lib/inbox-types.ts +47 -1
  227. package/blocks/inbox/recipes/01-triage-and-reply.ts +12 -2
  228. package/blocks/integrations/README.md +63 -1
  229. package/blocks/integrations/block.json +234 -15
  230. package/blocks/integrations/components/ad-accounts.tsx +82 -0
  231. package/blocks/integrations/components/connection-health.tsx +59 -0
  232. package/blocks/integrations/components/crm-detail.tsx +119 -0
  233. package/blocks/integrations/components/crm-records.tsx +103 -0
  234. package/blocks/integrations/components/field-mappings.tsx +61 -0
  235. package/blocks/integrations/components/integration-list.tsx +13 -8
  236. package/blocks/integrations/components/integrations-screen.tsx +152 -0
  237. package/blocks/integrations/components/migration-panel.tsx +133 -0
  238. package/blocks/integrations/components/sugarcrm-connect.tsx +95 -0
  239. package/blocks/integrations/components/sync-status.tsx +11 -9
  240. package/blocks/integrations/components/sync-triggers.tsx +134 -0
  241. package/blocks/integrations/components/webhook-detail.tsx +179 -0
  242. package/blocks/integrations/components/webhook-manager.tsx +43 -25
  243. package/blocks/integrations/components/workspace-tools.tsx +127 -0
  244. package/blocks/integrations/lib/crm-utils.ts +39 -0
  245. package/blocks/integrations/lib/facts.tsx +13 -0
  246. package/blocks/integrations/lib/integrations-utils.ts +11 -1
  247. package/blocks/integrations/lib/load-more.tsx +141 -0
  248. package/blocks/intent/README.md +14 -0
  249. package/blocks/intent/block.json +27 -1
  250. package/blocks/intent/components/account-intent-score.tsx +5 -5
  251. package/blocks/intent/components/intent-feed.tsx +13 -10
  252. package/blocks/intent/components/intent-keyword-manager.tsx +16 -12
  253. package/blocks/intent/components/intent-screen.tsx +265 -0
  254. package/blocks/intent/components/keyword-detail.tsx +124 -0
  255. package/blocks/intent/lib/intent-utils.ts +4 -4
  256. package/blocks/lifecycle/block.json +13 -8
  257. package/blocks/lifecycle/components/account-related.tsx +8 -1
  258. package/blocks/lifecycle/components/churn-list.tsx +5 -2
  259. package/blocks/lifecycle/components/customer-table.tsx +1 -1
  260. package/blocks/lifecycle/components/lifecycle-board.tsx +3 -1
  261. package/blocks/lifecycle/components/lifecycle-screen.tsx +3 -1
  262. package/blocks/lifecycle/lib/lifecycle.ts +1 -1
  263. package/blocks/lists/README.md +20 -1
  264. package/blocks/lists/block.json +69 -3
  265. package/blocks/lists/components/add-company-people-to-list-button.tsx +73 -0
  266. package/blocks/lists/components/add-to-list-button.tsx +20 -9
  267. package/blocks/lists/components/list-picker.tsx +17 -3
  268. package/blocks/lists/components/list-table.tsx +12 -6
  269. package/blocks/lists/components/lists-screen.tsx +346 -0
  270. package/blocks/lists/components/record-lists-tab.tsx +99 -0
  271. package/blocks/lists/components/suppression-manager.tsx +33 -8
  272. package/blocks/lists/components/suppressions-screen.tsx +264 -0
  273. package/blocks/mailboxes/README.md +17 -0
  274. package/blocks/mailboxes/block.json +69 -17
  275. package/blocks/mailboxes/components/connect-mailbox.tsx +32 -21
  276. package/blocks/mailboxes/components/domain-detail.tsx +148 -0
  277. package/blocks/mailboxes/components/domain-purchase.tsx +20 -14
  278. package/blocks/mailboxes/components/mailbox-detail.tsx +157 -0
  279. package/blocks/mailboxes/components/mailbox-list.tsx +8 -3
  280. package/blocks/mailboxes/components/mailboxes-screen.tsx +228 -0
  281. package/blocks/mailboxes/components/warmup-health.tsx +24 -22
  282. package/blocks/mailboxes/lib/mailboxes-utils.ts +26 -5
  283. package/blocks/mailboxes/lib/use-provider-domains.ts +23 -0
  284. package/blocks/newsletter/README.md +12 -0
  285. package/blocks/newsletter/block.json +157 -24
  286. package/blocks/newsletter/components/issue-scheduler.tsx +4 -3
  287. package/blocks/newsletter/components/issue-versions.tsx +93 -0
  288. package/blocks/newsletter/components/more-rows.tsx +30 -0
  289. package/blocks/newsletter/components/newsletter-editor.tsx +1 -1
  290. package/blocks/newsletter/components/newsletter-list-syncs.tsx +121 -0
  291. package/blocks/newsletter/components/newsletter-screen.tsx +39 -14
  292. package/blocks/newsletter/components/newsletter-settings.tsx +211 -0
  293. package/blocks/newsletter/components/newsletter-stats.tsx +2 -2
  294. package/blocks/newsletter/components/newsletter-templates.tsx +133 -0
  295. package/blocks/newsletter/components/subscriber-table.tsx +5 -7
  296. package/blocks/newsletter/components/subscriber-timeline.tsx +65 -0
  297. package/blocks/newsletter/lib/newsletter-extra.ts +60 -0
  298. package/blocks/nurture/block.json +28 -14
  299. package/blocks/nurture/components/nurture-builder.tsx +13 -3
  300. package/blocks/nurture/components/nurture-enroll.tsx +12 -10
  301. package/blocks/nurture/components/nurture-form.tsx +27 -10
  302. package/blocks/nurture/components/nurture-screen.tsx +26 -11
  303. package/blocks/nurture/components/nurture-stats.tsx +5 -4
  304. package/blocks/nurture/lib/nurture-types.ts +1 -1
  305. package/blocks/objects/README.md +18 -1
  306. package/blocks/objects/block.json +42 -21
  307. package/blocks/objects/components/attribute-field-input.tsx +14 -1
  308. package/blocks/objects/components/object-not-found.tsx +3 -1
  309. package/blocks/objects/components/object-record-detail.tsx +10 -2
  310. package/blocks/objects/components/object-schema-editor.tsx +4 -2
  311. package/blocks/objects/components/object-table.tsx +13 -3
  312. package/blocks/objects/components/objects-screen.tsx +147 -0
  313. package/blocks/objects/components/record-roles-panel.tsx +15 -5
  314. package/blocks/objects/lib/object-types.ts +1 -1
  315. package/blocks/pages/README.md +14 -0
  316. package/blocks/pages/block.json +140 -23
  317. package/blocks/pages/components/brand-kit-editor.tsx +5 -2
  318. package/blocks/pages/components/content-library.tsx +9 -4
  319. package/blocks/pages/components/more-rows.tsx +30 -0
  320. package/blocks/pages/components/page-ai-tools.tsx +196 -0
  321. package/blocks/pages/components/page-analytics.tsx +176 -0
  322. package/blocks/pages/components/page-clone.tsx +99 -0
  323. package/blocks/pages/components/page-domains.tsx +167 -0
  324. package/blocks/pages/components/page-editor.tsx +1 -1
  325. package/blocks/pages/components/page-generation-status.tsx +48 -0
  326. package/blocks/pages/components/page-list.tsx +2 -1
  327. package/blocks/pages/components/page-tracking.tsx +137 -0
  328. package/blocks/pages/components/pages-screen.tsx +57 -16
  329. package/blocks/pages/components/seo-report.tsx +6 -1
  330. package/blocks/pages/lib/studio.ts +7 -0
  331. package/blocks/phone-numbers/README.md +74 -0
  332. package/blocks/phone-numbers/block.json +262 -0
  333. package/blocks/phone-numbers/components/a2p-registration.tsx +230 -0
  334. package/blocks/phone-numbers/components/assign-number.tsx +104 -0
  335. package/blocks/phone-numbers/components/number-search.tsx +239 -0
  336. package/blocks/phone-numbers/components/number-settings.tsx +134 -0
  337. package/blocks/phone-numbers/components/number-usage.tsx +97 -0
  338. package/blocks/phone-numbers/components/phone-number-detail.tsx +84 -0
  339. package/blocks/phone-numbers/components/phone-number-list.tsx +110 -0
  340. package/blocks/phone-numbers/components/phone-numbers-screen.tsx +201 -0
  341. package/blocks/phone-numbers/components/register-owned-number.tsx +98 -0
  342. package/blocks/phone-numbers/components/release-number.tsx +126 -0
  343. package/blocks/phone-numbers/hooks/use-phone-numbers.ts +58 -0
  344. package/blocks/phone-numbers/lib/phone-numbers-utils.ts +202 -0
  345. package/blocks/phone-numbers/recipes/01-search-and-buy-number.ts +49 -0
  346. package/blocks/phone-numbers/recipes/02-assign-number-to-rep.ts +25 -0
  347. package/blocks/phone-numbers/recipes/03-audit-numbers.ts +36 -0
  348. package/blocks/phone-numbers/recipes/04-register-text-messaging.ts +39 -0
  349. package/blocks/pickers/README.md +23 -0
  350. package/blocks/pickers/block.json +103 -0
  351. package/blocks/pickers/components/company-picker.tsx +90 -0
  352. package/blocks/pickers/components/contact-picker.tsx +131 -0
  353. package/blocks/pickers/components/member-picker.tsx +99 -0
  354. package/blocks/pickers/components/search-picker.tsx +98 -0
  355. package/blocks/pickers/hooks/use-current-member.ts +104 -0
  356. package/blocks/pickers/hooks/use-picker-pages.ts +73 -0
  357. package/blocks/pickers/lib/use-debounced.ts +13 -0
  358. package/blocks/quotes/README.md +13 -0
  359. package/blocks/quotes/block.json +65 -13
  360. package/blocks/quotes/components/create-quote-from-deal.tsx +48 -0
  361. package/blocks/quotes/components/product-picker.tsx +3 -1
  362. package/blocks/quotes/components/quote-builder.tsx +1 -1
  363. package/blocks/quotes/components/quote-lifecycle-actions.tsx +146 -0
  364. package/blocks/quotes/components/quote-list.tsx +5 -3
  365. package/blocks/quotes/components/quote-related.tsx +8 -4
  366. package/blocks/quotes/components/quote-viewer.tsx +7 -2
  367. package/blocks/quotes/components/record-quotes-tab.tsx +90 -0
  368. package/blocks/radar/README.md +10 -0
  369. package/blocks/radar/block.json +41 -1
  370. package/blocks/radar/components/competitor-battle-card.tsx +1 -1
  371. package/blocks/radar/components/competitor-detail.tsx +117 -0
  372. package/blocks/radar/components/competitor-tracker.tsx +3 -1
  373. package/blocks/radar/components/radar-overview.tsx +4 -4
  374. package/blocks/radar/components/radar-report.tsx +4 -2
  375. package/blocks/radar/components/radar-screen.tsx +214 -0
  376. package/blocks/radar/components/report-detail.tsx +104 -0
  377. package/blocks/search/README.md +10 -0
  378. package/blocks/search/block.json +16 -1
  379. package/blocks/search/components/filter-builder.tsx +3 -3
  380. package/blocks/search/components/prospect-results.tsx +5 -3
  381. package/blocks/search/components/prospect-search-screen.tsx +235 -0
  382. package/blocks/search/components/saved-searches.tsx +4 -1
  383. package/blocks/search/hooks/use-filter-options.ts +20 -6
  384. package/blocks/search/lib/search-types.ts +2 -0
  385. package/blocks/sequencer/README.md +55 -9
  386. package/blocks/sequencer/block.json +126 -19
  387. package/blocks/sequencer/components/add-to-sequence-button.tsx +82 -0
  388. package/blocks/sequencer/components/enroll-dialog.tsx +11 -5
  389. package/blocks/sequencer/components/record-sequences-tab.tsx +126 -0
  390. package/blocks/sequencer/components/sequence-builder.tsx +224 -60
  391. package/blocks/sequencer/components/sequence-detail.tsx +261 -0
  392. package/blocks/sequencer/components/sequence-enrollments.tsx +116 -0
  393. package/blocks/sequencer/components/sequence-list.tsx +34 -9
  394. package/blocks/sequencer/components/sequence-stats.tsx +11 -10
  395. package/blocks/sequencer/components/sequence-steps-panel.tsx +103 -0
  396. package/blocks/sequencer/components/sequences-screen.tsx +162 -0
  397. package/blocks/sequencer/components/step-editor.tsx +14 -12
  398. package/blocks/sequencer/lib/sequencer-utils.ts +133 -10
  399. package/blocks/sequencer/recipes/create-multichannel-sequence.ts +33 -5
  400. package/blocks/sequencer/recipes/launch-and-enroll.ts +19 -2
  401. package/blocks/social-ads/README.md +19 -0
  402. package/blocks/social-ads/block.json +233 -1
  403. package/blocks/social-ads/components/ad-campaign-list.tsx +9 -5
  404. package/blocks/social-ads/components/ads-screen.tsx +152 -0
  405. package/blocks/social-ads/components/audience-sync-detail.tsx +194 -0
  406. package/blocks/social-ads/components/audience-sync.tsx +17 -9
  407. package/blocks/social-ads/components/campaign-ads.tsx +166 -0
  408. package/blocks/social-ads/components/campaign-detail.tsx +138 -0
  409. package/blocks/social-ads/components/campaign-editor.tsx +134 -0
  410. package/blocks/social-ads/components/creative-concepts.tsx +74 -0
  411. package/blocks/social-ads/components/creative-detail.tsx +155 -0
  412. package/blocks/social-ads/components/creative-gallery.tsx +35 -7
  413. package/blocks/social-ads/components/creative-revisions.tsx +57 -0
  414. package/blocks/social-ads/components/creative-tools.tsx +176 -0
  415. package/blocks/social-ads/components/post-composer.tsx +7 -3
  416. package/blocks/social-ads/components/post-detail.tsx +163 -0
  417. package/blocks/social-ads/components/social-ideas.tsx +64 -0
  418. package/blocks/social-ads/components/social-performance.tsx +80 -0
  419. package/blocks/social-ads/components/social-post-list.tsx +11 -3
  420. package/blocks/social-ads/components/social-screen.tsx +187 -0
  421. package/blocks/social-ads/lib/ads-extra.ts +67 -0
  422. package/blocks/surveys/README.md +91 -0
  423. package/blocks/surveys/block.json +299 -0
  424. package/blocks/surveys/components/account-nps.tsx +69 -0
  425. package/blocks/surveys/components/contact-survey-history.tsx +52 -0
  426. package/blocks/surveys/components/org-summary.tsx +82 -0
  427. package/blocks/surveys/components/sending-domains.tsx +113 -0
  428. package/blocks/surveys/components/survey-form.tsx +126 -0
  429. package/blocks/surveys/components/survey-responses.tsx +145 -0
  430. package/blocks/surveys/components/survey-results.tsx +72 -0
  431. package/blocks/surveys/components/survey-send-history.tsx +58 -0
  432. package/blocks/surveys/components/survey-send.tsx +162 -0
  433. package/blocks/surveys/components/survey-triggers.tsx +166 -0
  434. package/blocks/surveys/components/surveys-screen.tsx +234 -0
  435. package/blocks/surveys/lib/surveys-types.ts +228 -0
  436. package/blocks/surveys/recipes/create-an-nps-survey.ts +22 -0
  437. package/blocks/surveys/recipes/send-a-survey-and-follow-it.ts +27 -0
  438. package/blocks/surveys/recipes/survey-score-report.ts +27 -0
  439. package/blocks/visitors/README.md +10 -0
  440. package/blocks/visitors/block.json +25 -1
  441. package/blocks/visitors/components/snippet-install.tsx +21 -10
  442. package/blocks/visitors/components/traffic-chart.tsx +4 -4
  443. package/blocks/visitors/components/visitor-company-card.tsx +26 -16
  444. package/blocks/visitors/components/visitor-detail.tsx +105 -0
  445. package/blocks/visitors/components/visitor-feed.tsx +16 -7
  446. package/blocks/visitors/components/visitors-screen.tsx +166 -0
  447. package/blocks/webchat/README.md +10 -0
  448. package/blocks/webchat/block.json +21 -7
  449. package/blocks/webchat/components/chat-widget-config.tsx +8 -5
  450. package/blocks/webchat/components/conversation-list.tsx +8 -4
  451. package/blocks/webchat/components/webchat-screen.tsx +245 -0
  452. package/blocks/workbench/README.md +11 -0
  453. package/blocks/workbench/block.json +48 -1
  454. package/blocks/workbench/components/column-recipe-editor.tsx +5 -3
  455. package/blocks/workbench/components/run-progress.tsx +1 -1
  456. package/blocks/workbench/components/workbench-grid.tsx +10 -4
  457. package/blocks/workbench/components/workbench-row-detail.tsx +104 -0
  458. package/blocks/workbench/components/workbench-screen.tsx +202 -0
  459. package/blocks/workbench/components/workbench-sources.tsx +194 -0
  460. package/blocks/workbench/components/workbench-table-list.tsx +14 -2
  461. package/blocks/workflows/README.md +13 -0
  462. package/blocks/workflows/block.json +28 -9
  463. package/blocks/workflows/components/run-history.tsx +31 -9
  464. package/blocks/workflows/components/trigger-config.tsx +3 -1
  465. package/blocks/workflows/components/workflow-editor.tsx +4 -2
  466. package/blocks/workflows/components/workflow-list.tsx +13 -6
  467. package/blocks/workflows/components/workflows-screen.tsx +193 -0
  468. package/dist/cli.js +3109 -1977
  469. package/dist/errors.d.mts +34 -0
  470. package/dist/errors.d.ts +34 -0
  471. package/dist/errors.js +46 -0
  472. package/dist/errors.js.map +1 -0
  473. package/dist/errors.mjs +21 -0
  474. package/dist/errors.mjs.map +1 -0
  475. package/dist/index.browser.js +11 -5
  476. package/dist/index.browser.js.map +1 -1
  477. package/dist/index.browser.mjs +11 -5
  478. package/dist/index.browser.mjs.map +1 -1
  479. package/dist/index.d.mts +164 -25
  480. package/dist/index.d.ts +164 -25
  481. package/dist/index.js +11 -5
  482. package/dist/index.js.map +1 -1
  483. package/dist/index.mjs +11 -5
  484. package/dist/index.mjs.map +1 -1
  485. package/dist/react.browser.js +10 -4
  486. package/dist/react.browser.js.map +1 -1
  487. package/dist/react.browser.mjs +10 -4
  488. package/dist/react.browser.mjs.map +1 -1
  489. package/dist/react.d.mts +142 -5
  490. package/dist/react.d.ts +142 -5
  491. package/dist/react.js +10 -4
  492. package/dist/react.js.map +1 -1
  493. package/dist/react.mjs +10 -4
  494. package/dist/react.mjs.map +1 -1
  495. package/dist/types/blocks/add.d.ts +11 -4
  496. package/dist/types/blocks/brand.d.ts +122 -0
  497. package/dist/types/blocks/compose.d.ts +53 -0
  498. package/dist/types/blocks/create.d.ts +27 -20
  499. package/dist/types/blocks/doctor.d.ts +6 -1
  500. package/dist/types/blocks/flows.d.ts +7 -1
  501. package/dist/types/blocks/navfile.d.ts +35 -0
  502. package/dist/types/blocks/orgBrand.d.ts +34 -0
  503. package/dist/types/blocks/paths.d.ts +4 -0
  504. package/dist/types/blocks/prefixes.d.ts +37 -0
  505. package/dist/types/blocks/registry.d.ts +17 -0
  506. package/dist/types/blocks/screens.d.ts +53 -0
  507. package/dist/types/cli.d.ts +5 -0
  508. package/dist/types/errors.d.ts +33 -0
  509. package/dist/types/http.d.ts +2 -18
  510. package/dist/types/version.d.ts +2 -2
  511. package/docs/agents.md +15 -4
  512. package/docs/api/README.md +2 -2
  513. package/docs/api/by-path.md +1 -0
  514. package/docs/api/desk.d.ts +98 -2
  515. package/docs/api/desk.md +123 -1
  516. package/docs/api/inbox.d.ts +10 -2
  517. package/docs/api/inbox.md +10 -2
  518. package/docs/api/seo.d.ts +19 -1
  519. package/docs/api/seo.md +24 -1
  520. package/package.json +16 -2
  521. package/stacks/6sense/pages/companies.tsx.tmpl +21 -0
  522. package/stacks/6sense/pages/contacts.tsx.tmpl +19 -0
  523. package/stacks/6sense/pages/tasks.tsx.tmpl +19 -0
  524. package/stacks/6sense.json +55 -17
  525. package/stacks/apollo/pages/calls.tsx.tmpl +23 -0
  526. package/stacks/apollo/pages/companies.tsx.tmpl +33 -0
  527. package/stacks/apollo/pages/contacts.tsx.tmpl +51 -0
  528. package/stacks/apollo/pages/deals.tsx.tmpl +17 -0
  529. package/stacks/apollo/pages/inbox-contact.tsx.tmpl +13 -0
  530. package/stacks/apollo/pages/lists.tsx.tmpl +12 -0
  531. package/stacks/apollo/pages/meetings.tsx.tmpl +13 -0
  532. package/stacks/apollo/pages/sequences.tsx.tmpl +12 -0
  533. package/stacks/apollo/pages/tasks.tsx.tmpl +20 -0
  534. package/stacks/apollo.json +147 -37
  535. package/stacks/calendly/pages/companies.tsx.tmpl +19 -0
  536. package/stacks/calendly/pages/contacts.tsx.tmpl +21 -0
  537. package/stacks/calendly/pages/meetings.tsx.tmpl +13 -0
  538. package/stacks/calendly/pages/tasks.tsx.tmpl +19 -0
  539. package/stacks/calendly.json +64 -0
  540. package/stacks/clay/pages/companies.tsx.tmpl +31 -0
  541. package/stacks/clay/pages/contacts.tsx.tmpl +31 -0
  542. package/stacks/clay/pages/lists.tsx.tmpl +12 -0
  543. package/stacks/clay/pages/tasks.tsx.tmpl +19 -0
  544. package/stacks/clay.json +73 -27
  545. package/stacks/gong/pages/calls.tsx.tmpl +23 -0
  546. package/stacks/gong/pages/companies.tsx.tmpl +22 -0
  547. package/stacks/gong/pages/contacts.tsx.tmpl +32 -0
  548. package/stacks/gong/pages/lists.tsx.tmpl +12 -0
  549. package/stacks/gong/pages/meetings.tsx.tmpl +13 -0
  550. package/stacks/gong/pages/tasks.tsx.tmpl +19 -0
  551. package/stacks/gong.json +98 -35
  552. package/stacks/hootsuite/pages/companies.tsx.tmpl +22 -0
  553. package/stacks/hootsuite/pages/contacts.tsx.tmpl +28 -0
  554. package/stacks/hootsuite/pages/lists.tsx.tmpl +12 -0
  555. package/stacks/hootsuite/pages/tasks.tsx.tmpl +19 -0
  556. package/stacks/hootsuite.json +104 -0
  557. package/stacks/hubspot/pages/companies.tsx.tmpl +26 -0
  558. package/stacks/hubspot/pages/contacts.tsx.tmpl +45 -0
  559. package/stacks/hubspot/pages/deals.tsx.tmpl +20 -0
  560. package/stacks/hubspot/pages/inbox.tsx.tmpl +103 -0
  561. package/stacks/hubspot/pages/lifecycle.tsx.tmpl +23 -0
  562. package/stacks/hubspot/pages/lists.tsx.tmpl +12 -0
  563. package/stacks/hubspot/pages/meetings.tsx.tmpl +13 -0
  564. package/stacks/hubspot/pages/quotes.tsx.tmpl +11 -0
  565. package/stacks/hubspot/pages/sequences.tsx.tmpl +12 -0
  566. package/stacks/hubspot/pages/tasks.tsx.tmpl +20 -0
  567. package/stacks/hubspot.json +181 -64
  568. package/stacks/intercom/pages/companies.tsx.tmpl +19 -0
  569. package/stacks/intercom/pages/contacts.tsx.tmpl +21 -0
  570. package/stacks/intercom/pages/inbox-contact.tsx.tmpl +13 -0
  571. package/stacks/intercom/pages/tasks.tsx.tmpl +19 -0
  572. package/stacks/intercom.json +52 -20
  573. package/stacks/mailchimp/pages/companies.tsx.tmpl +22 -0
  574. package/stacks/mailchimp/pages/contacts.tsx.tmpl +28 -0
  575. package/stacks/mailchimp/pages/lists.tsx.tmpl +12 -0
  576. package/stacks/mailchimp/pages/tasks.tsx.tmpl +19 -0
  577. package/stacks/mailchimp.json +68 -34
  578. package/stacks/outreach/pages/calls.tsx.tmpl +23 -0
  579. package/stacks/outreach/pages/companies.tsx.tmpl +22 -0
  580. package/stacks/outreach/pages/contacts.tsx.tmpl +46 -0
  581. package/stacks/outreach/pages/inbox-contact.tsx.tmpl +13 -0
  582. package/stacks/outreach/pages/lists.tsx.tmpl +12 -0
  583. package/stacks/outreach/pages/meetings.tsx.tmpl +13 -0
  584. package/stacks/outreach/pages/sequences.tsx.tmpl +12 -0
  585. package/stacks/outreach/pages/tasks.tsx.tmpl +19 -0
  586. package/stacks/outreach.json +106 -25
  587. package/stacks/pipedrive/pages/companies.tsx.tmpl +23 -0
  588. package/stacks/pipedrive/pages/contacts.tsx.tmpl +27 -0
  589. package/stacks/pipedrive/pages/deals.tsx.tmpl +20 -0
  590. package/stacks/pipedrive/pages/inbox.tsx.tmpl +103 -0
  591. package/stacks/pipedrive/pages/meetings.tsx.tmpl +13 -0
  592. package/stacks/pipedrive/pages/quotes.tsx.tmpl +11 -0
  593. package/stacks/pipedrive/pages/tasks.tsx.tmpl +20 -0
  594. package/stacks/pipedrive.json +117 -0
  595. package/stacks/salesforce/pages/companies.tsx.tmpl +26 -0
  596. package/stacks/salesforce/pages/contacts.tsx.tmpl +32 -0
  597. package/stacks/salesforce/pages/deals.tsx.tmpl +20 -0
  598. package/stacks/salesforce/pages/lifecycle.tsx.tmpl +23 -0
  599. package/stacks/salesforce/pages/lists.tsx.tmpl +12 -0
  600. package/stacks/salesforce/pages/quotes.tsx.tmpl +11 -0
  601. package/stacks/salesforce/pages/tasks.tsx.tmpl +20 -0
  602. package/stacks/salesforce.json +85 -31
  603. package/stacks/zoominfo/pages/companies.tsx.tmpl +31 -0
  604. package/stacks/zoominfo/pages/contacts.tsx.tmpl +31 -0
  605. package/stacks/zoominfo/pages/lists.tsx.tmpl +12 -0
  606. package/stacks/zoominfo/pages/tasks.tsx.tmpl +19 -0
  607. package/stacks/zoominfo.json +115 -0
  608. package/styles/styles.css +1 -1
  609. package/templates/next-app/README.md.tmpl +40 -9
  610. package/templates/next-app/app/globals.css.tmpl +349 -64
  611. package/templates/next-app/app/layout.tsx.tmpl +7 -2
  612. package/templates/next-app/components/app-shell.tsx.tmpl +104 -88
  613. package/templates/next-app/components/shell/quick-jump.tsx.tmpl +4 -0
  614. package/templates/next-app/dot-gitignore.tmpl +2 -0
  615. package/templates/next-app/package.json.tmpl +1 -1
@@ -0,0 +1,454 @@
1
+ "use client";
2
+
3
+ /**
4
+ * The shared query cache behind `useG8Query(..., { key })`, `usePaginated(..., { key })` and
5
+ * `useG8Mutation(..., { invalidates })`. No dependencies, framework-agnostic, SSR-safe.
6
+ *
7
+ * What a `key` buys you (all opt-in: hooks called WITHOUT a key never touch this file's state):
8
+ * - Dedupe: any number of components asking for the same key share ONE in-flight request.
9
+ * - Stale-while-revalidate: cached data renders instantly on mount, then refreshes in the background
10
+ * once it is older than `staleTime` (default 0: always revalidate, but never show a skeleton twice).
11
+ * - Revalidate on window focus / reconnect, throttled per key (default 5 s between refreshes).
12
+ * - Smart invalidation: `invalidate("contacts")` marks every key that starts with "contacts" stale and
13
+ * refetches the ones on screen. Mutations pass `invalidates: ["contacts"]` to do this on success.
14
+ * - Bounded memory: at most `maxEntries` keys (LRU, default 200); a key nobody watches is dropped
15
+ * after `gcTime` (default 5 min). Only unwatched, idle entries are ever evicted.
16
+ * - Abort: a request is aborted when its LAST subscriber unmounts (or it is superseded), never while
17
+ * another component still waits for it.
18
+ *
19
+ * Keys are a string or an array whose first item names the area: `["contacts", "list", { page, filters }]`.
20
+ * Object parts are compared by value (key order does not matter). A key must contain every input the
21
+ * fetcher reads; prefixes match whole parts: "contacts" matches ["contacts", ...] but not ["contacts-x"].
22
+ *
23
+ * On the server nothing is cached: hooks render their initial state and fetch after hydration.
24
+ */
25
+
26
+ export type G8QueryKey = string | readonly unknown[];
27
+
28
+ export interface G8QueryState<T = unknown> {
29
+ data: T | undefined;
30
+ error: unknown;
31
+ /** Epoch ms of the last successful fetch or setQueryData (0 = never). */
32
+ updatedAt: number;
33
+ /** A request for this key is in flight. */
34
+ fetching: boolean;
35
+ /** Marked stale by `invalidate` (refetched on next use even inside `staleTime`). */
36
+ invalidated: boolean;
37
+ }
38
+
39
+ export type G8Fetcher<T> = (signal: AbortSignal) => Promise<T>;
40
+
41
+ /** What a mounted hook tells the cache about itself. Read lazily, so the latest render's values win. */
42
+ export interface G8QueryObserver {
43
+ fetcher: () => G8Fetcher<unknown>;
44
+ /** A disabled hook (for example waiting for an id) is never refetched by invalidation. Default true. */
45
+ enabled?: () => boolean;
46
+ staleTime: () => number;
47
+ refetchOnFocus: () => boolean;
48
+ }
49
+
50
+ export interface G8QueryCacheOptions {
51
+ /** Most keys kept at once (LRU). Default 200. */
52
+ maxEntries?: number;
53
+ /** How long an unwatched key is kept, ms. Default 5 minutes. */
54
+ gcTime?: number;
55
+ /** Least time between two focus/reconnect refreshes of one key, ms. Default 5 s. */
56
+ focusThrottleMs?: number;
57
+ }
58
+
59
+ interface Entry {
60
+ key: readonly unknown[];
61
+ hash: string;
62
+ parts: string[];
63
+ state: G8QueryState;
64
+ listeners: Set<() => void>;
65
+ observers: Set<G8QueryObserver>;
66
+ promise?: Promise<unknown>;
67
+ controller?: AbortController;
68
+ /** Bumped by every fetch start and every cancel: only the latest fetch may write. */
69
+ gen: number;
70
+ lastFetchAt: number;
71
+ /** The synchronous run (tick) the request in flight started in. */
72
+ startedTick: number;
73
+ gcTimer?: ReturnType<typeof setTimeout>;
74
+ }
75
+
76
+ const EMPTY_STATE: G8QueryState = Object.freeze({
77
+ data: undefined,
78
+ error: undefined,
79
+ updatedAt: 0,
80
+ fetching: false,
81
+ invalidated: false,
82
+ }) as G8QueryState;
83
+
84
+ const isServer = (): boolean => typeof window === "undefined";
85
+
86
+ function stable(value: unknown): string {
87
+ if (value === undefined) return "u";
88
+ if (value === null || typeof value !== "object") return JSON.stringify(value) ?? String(value);
89
+ if (Array.isArray(value)) return `[${value.map(stable).join(",")}]`;
90
+ if (value instanceof Date) return `D${value.toISOString()}`;
91
+ const proto = Object.getPrototypeOf(value);
92
+ if (proto !== Object.prototype && proto !== null) {
93
+ // A Map, Set, URLSearchParams or class instance has no own keys to hash: two different ones would
94
+ // collide as {} and share data. Fail loudly instead.
95
+ throw new TypeError("g8 query keys must be JSON-like: strings, numbers, booleans, null, arrays, plain objects or Dates");
96
+ }
97
+ const obj = value as Record<string, unknown>;
98
+ const keys = Object.keys(obj).filter((k) => obj[k] !== undefined).sort();
99
+ return `{${keys.map((k) => `${JSON.stringify(k)}:${stable(obj[k])}`).join(",")}}`;
100
+ }
101
+
102
+ /** A key as an array (a string key is a one-part key). */
103
+ export function normalizeKey(key: G8QueryKey): readonly unknown[] {
104
+ return typeof key === "string" ? [key] : key;
105
+ }
106
+
107
+ /** The identity of a key: equal for keys equal by value. */
108
+ export function hashKey(key: G8QueryKey): string {
109
+ return stable(normalizeKey(key));
110
+ }
111
+
112
+ function isAbort(err: unknown, signal: AbortSignal): boolean {
113
+ return signal.aborted || (err as { name?: string } | null)?.name === "AbortError";
114
+ }
115
+
116
+ /** Same JSON as before = keep the old object, so memoised rows and effects do not churn. */
117
+ function share<T>(prev: unknown, next: T): T {
118
+ if (prev === undefined || prev === next) return next;
119
+ try {
120
+ return JSON.stringify(prev) === JSON.stringify(next) ? (prev as T) : next;
121
+ } catch {
122
+ return next;
123
+ }
124
+ }
125
+
126
+ export class G8QueryCache {
127
+ readonly maxEntries: number;
128
+ readonly gcTime: number;
129
+ readonly focusThrottleMs: number;
130
+ private entries = new Map<string, Entry>();
131
+ private windowBound = false;
132
+ private tick = 0;
133
+ private tickScheduled = false;
134
+
135
+ constructor(options: G8QueryCacheOptions = {}) {
136
+ this.maxEntries = Math.max(1, options.maxEntries ?? 200);
137
+ this.gcTime = options.gcTime ?? 5 * 60_000;
138
+ this.focusThrottleMs = options.focusThrottleMs ?? 5_000;
139
+ }
140
+
141
+ /** Number of keys held (for tests and debugging). */
142
+ get size(): number {
143
+ return this.entries.size;
144
+ }
145
+
146
+ /** The current state of a key, or an empty state. Never creates an entry. */
147
+ getState<T = unknown>(key: G8QueryKey): G8QueryState<T> {
148
+ return (this.entries.get(hashKey(key))?.state ?? EMPTY_STATE) as G8QueryState<T>;
149
+ }
150
+
151
+ getQueryData<T = unknown>(key: G8QueryKey): T | undefined {
152
+ return this.getState<T>(key).data;
153
+ }
154
+
155
+ /** Watch a key: `listener` runs on every state change. Returns the unsubscribe function. */
156
+ subscribe(key: G8QueryKey, listener: () => void, observer?: G8QueryObserver): () => void {
157
+ const entry = this.ensure(key);
158
+ clearTimeout(entry.gcTimer);
159
+ entry.gcTimer = undefined;
160
+ entry.listeners.add(listener);
161
+ if (observer) entry.observers.add(observer);
162
+ this.bindWindow();
163
+ return () => {
164
+ entry.listeners.delete(listener);
165
+ if (observer) entry.observers.delete(observer);
166
+ if (entry.listeners.size > 0) return;
167
+ // Last one out: nobody needs the answer any more.
168
+ if (entry.promise) this.cancelEntry(entry);
169
+ this.scheduleGc(entry);
170
+ };
171
+ }
172
+
173
+ /**
174
+ * Fetch a key. Joins the request already in flight unless `force` (which supersedes it: the old
175
+ * request is aborted and only the new answer is kept). Resolves with the data, or undefined when the
176
+ * request failed (the error is in the state) or was superseded/aborted.
177
+ */
178
+ fetch<T>(
179
+ key: G8QueryKey,
180
+ fetcher: G8Fetcher<T>,
181
+ options: { force?: boolean; share?: boolean } = {},
182
+ ): Promise<T | undefined> {
183
+ const entry = this.ensure(key);
184
+ // A forced fetch still joins a request started in this same synchronous run: it cannot predate the
185
+ // write that asked for it (a mutation's `invalidates` plus the component's own refetch = one request).
186
+ if (entry.promise && (!options.force || entry.startedTick === this.tick)) return entry.promise as Promise<T | undefined>;
187
+ if (entry.promise) this.cancelEntry(entry);
188
+ const gen = ++entry.gen;
189
+ entry.startedTick = this.currentTick();
190
+ const controller = new AbortController();
191
+ entry.controller = controller;
192
+ entry.lastFetchAt = Date.now();
193
+ this.write(entry, { fetching: true });
194
+ const promise = (async (): Promise<T | undefined> => {
195
+ try {
196
+ const data = await fetcher(controller.signal);
197
+ if (gen !== entry.gen) return undefined;
198
+ this.write(entry, {
199
+ // Background refreshes keep the old object when nothing changed (no churn); an explicit refetch always
200
+ // hands over a new one, as the uncached hook did, so effects keyed on `data` run again.
201
+ data: options.share === false ? data : share(entry.state.data, data),
202
+ error: undefined,
203
+ updatedAt: Date.now(),
204
+ fetching: false,
205
+ invalidated: false,
206
+ });
207
+ return data;
208
+ } catch (err) {
209
+ if (gen !== entry.gen || isAbort(err, controller.signal)) return undefined;
210
+ this.write(entry, { error: err, fetching: false });
211
+ return undefined;
212
+ } finally {
213
+ if (gen === entry.gen) {
214
+ entry.promise = undefined;
215
+ entry.controller = undefined;
216
+ }
217
+ }
218
+ })();
219
+ entry.promise = promise;
220
+ return promise;
221
+ }
222
+
223
+ /** Is this key due a refresh for a reader that tolerates `staleTime` ms of age? */
224
+ isStale(key: G8QueryKey, staleTime: number): boolean {
225
+ const state = this.getState(key);
226
+ return state.invalidated || state.updatedAt === 0 || Date.now() - state.updatedAt >= staleTime;
227
+ }
228
+
229
+ /** Fetch only if there is no fresh data and no request in flight. */
230
+ ensureFresh<T>(key: G8QueryKey, fetcher: G8Fetcher<T>, staleTime: number): Promise<T | undefined> | undefined {
231
+ const entry = this.ensure(key);
232
+ if (entry.promise) return entry.promise as Promise<T | undefined>;
233
+ if (!this.isStale(key, staleTime)) return undefined;
234
+ return this.fetch(key, fetcher);
235
+ }
236
+
237
+ /**
238
+ * Mark every key under `prefix` (all keys when omitted) stale. Keys on screen refetch now
239
+ * (superseding a request that started before the write); the rest refetch when next used.
240
+ */
241
+ async invalidate(prefix?: G8QueryKey, options: { refetch?: boolean } = {}): Promise<void> {
242
+ const runs: Array<Promise<unknown>> = [];
243
+ for (const entry of this.match(prefix)) {
244
+ this.write(entry, { invalidated: true });
245
+ const observer = [...entry.observers].find((o) => o.enabled?.() ?? true);
246
+ if (options.refetch === false || entry.listeners.size === 0 || !observer) {
247
+ if (entry.promise) this.cancelEntry(entry);
248
+ continue;
249
+ }
250
+ runs.push(this.fetch(entry.key, observer.fetcher(), { force: true }));
251
+ }
252
+ await Promise.all(runs);
253
+ }
254
+
255
+ /** Replace one key's data (a value, or a function of the old data). Cancels a request in flight. */
256
+ setQueryData<T>(key: G8QueryKey, updater: T | ((old: T | undefined) => T | undefined)): T | undefined {
257
+ const entry = this.ensure(key);
258
+ if (entry.promise) this.cancelEntry(entry);
259
+ const old = entry.state.data as T | undefined;
260
+ const next =
261
+ typeof updater === "function" ? (updater as (old: T | undefined) => T | undefined)(old) : updater;
262
+ this.write(entry, { data: next, error: undefined, updatedAt: Date.now(), fetching: false, invalidated: false });
263
+ if (entry.listeners.size === 0) this.scheduleGc(entry);
264
+ return next;
265
+ }
266
+
267
+ /**
268
+ * Update the data of every key under `prefix` that has data. Returns a snapshot to pass to
269
+ * `restore` (an optimistic update that failed puts the old data back).
270
+ */
271
+ setQueriesData<T>(
272
+ prefix: G8QueryKey,
273
+ updater: (old: T, key: readonly unknown[]) => T,
274
+ ): Array<{ key: readonly unknown[]; data: unknown }> {
275
+ const snapshot: Array<{ key: readonly unknown[]; data: unknown }> = [];
276
+ for (const entry of this.match(prefix)) {
277
+ if (entry.state.data === undefined) continue;
278
+ if (entry.promise) this.cancelEntry(entry);
279
+ snapshot.push({ key: entry.key, data: entry.state.data });
280
+ this.write(entry, { data: updater(entry.state.data as T, entry.key) });
281
+ }
282
+ return snapshot;
283
+ }
284
+
285
+ /** Put back the data captured by `setQueriesData`. */
286
+ restore(snapshot: Array<{ key: readonly unknown[]; data: unknown }>): void {
287
+ for (const item of snapshot) {
288
+ const entry = this.entries.get(hashKey(item.key));
289
+ if (entry) this.write(entry, { data: item.data });
290
+ }
291
+ }
292
+
293
+ /** Abort the requests in flight under `prefix` (their answers are dropped). */
294
+ cancel(prefix?: G8QueryKey): void {
295
+ for (const entry of this.match(prefix)) if (entry.promise) this.cancelEntry(entry);
296
+ }
297
+
298
+ /** Forget keys under `prefix` that nobody is watching (all of them when omitted). */
299
+ remove(prefix?: G8QueryKey): void {
300
+ for (const entry of this.match(prefix)) {
301
+ if (entry.listeners.size > 0) continue;
302
+ this.drop(entry);
303
+ }
304
+ }
305
+
306
+ /** Forget everything, watched or not (tests, sign-out). Subscribers see an empty state. */
307
+ clear(): void {
308
+ for (const entry of [...this.entries.values()]) {
309
+ if (entry.promise) this.cancelEntry(entry);
310
+ clearTimeout(entry.gcTimer);
311
+ if (entry.listeners.size > 0) {
312
+ this.write(entry, { ...EMPTY_STATE });
313
+ } else {
314
+ this.entries.delete(entry.hash);
315
+ }
316
+ }
317
+ }
318
+
319
+ /** Refresh watched keys after the window regains focus or the network returns (throttled per key). */
320
+ revalidateOnFocus(): void {
321
+ const now = Date.now();
322
+ for (const entry of this.entries.values()) {
323
+ if (entry.listeners.size === 0 || entry.promise) continue;
324
+ const observers = [...entry.observers].filter((o) => o.enabled?.() ?? true);
325
+ // An opt-out is a veto: a form seeded from this key must not be refreshed because another view wants it.
326
+ if (observers.length === 0 || observers.some((o) => !o.refetchOnFocus())) continue;
327
+ if (now - entry.lastFetchAt < this.focusThrottleMs) continue;
328
+ const staleTime = Math.min(...observers.map((o) => o.staleTime()));
329
+ if (!this.isStale(entry.key, staleTime)) continue;
330
+ void this.fetch(entry.key, observers[0].fetcher());
331
+ }
332
+ }
333
+
334
+ private ensure(key: G8QueryKey): Entry {
335
+ const norm = normalizeKey(key);
336
+ const hash = hashKey(norm);
337
+ let entry = this.entries.get(hash);
338
+ if (entry) {
339
+ // LRU: most recently used goes to the end.
340
+ this.entries.delete(hash);
341
+ this.entries.set(hash, entry);
342
+ return entry;
343
+ }
344
+ entry = {
345
+ key: norm,
346
+ hash,
347
+ parts: norm.map(stable),
348
+ state: EMPTY_STATE,
349
+ listeners: new Set(),
350
+ observers: new Set(),
351
+ gen: 0,
352
+ lastFetchAt: 0,
353
+ startedTick: -1,
354
+ };
355
+ this.entries.set(hash, entry);
356
+ this.evict();
357
+ return entry;
358
+ }
359
+
360
+ private evict(): void {
361
+ if (this.entries.size <= this.maxEntries) return;
362
+ for (const entry of [...this.entries.values()]) {
363
+ if (this.entries.size <= this.maxEntries) return;
364
+ if (entry.listeners.size > 0 || entry.promise) continue;
365
+ this.drop(entry);
366
+ }
367
+ }
368
+
369
+ private drop(entry: Entry): void {
370
+ clearTimeout(entry.gcTimer);
371
+ if (entry.promise) this.cancelEntry(entry);
372
+ this.entries.delete(entry.hash);
373
+ }
374
+
375
+ private scheduleGc(entry: Entry): void {
376
+ clearTimeout(entry.gcTimer);
377
+ if (isServer()) {
378
+ this.drop(entry);
379
+ return;
380
+ }
381
+ entry.gcTimer = setTimeout(() => {
382
+ if (entry.listeners.size === 0 && !entry.promise) this.drop(entry);
383
+ }, this.gcTime);
384
+ }
385
+
386
+ private match(prefix?: G8QueryKey): Entry[] {
387
+ const parts = prefix === undefined ? [] : normalizeKey(prefix).map(stable);
388
+ return [...this.entries.values()].filter(
389
+ (entry) => parts.length <= entry.parts.length && parts.every((part, i) => entry.parts[i] === part),
390
+ );
391
+ }
392
+
393
+ private cancelEntry(entry: Entry): void {
394
+ entry.gen++;
395
+ entry.controller?.abort();
396
+ entry.controller = undefined;
397
+ entry.promise = undefined;
398
+ if (entry.state.fetching) this.write(entry, { fetching: false });
399
+ }
400
+
401
+ private write(entry: Entry, patch: Partial<G8QueryState>): void {
402
+ entry.state = { ...entry.state, ...patch };
403
+ for (const listener of [...entry.listeners]) listener();
404
+ }
405
+
406
+ /** Ticks advance once per macrotask/microtask boundary after a fetch starts. */
407
+ private currentTick(): number {
408
+ if (!this.tickScheduled) {
409
+ this.tickScheduled = true;
410
+ void Promise.resolve().then(() => {
411
+ this.tick++;
412
+ this.tickScheduled = false;
413
+ });
414
+ }
415
+ return this.tick;
416
+ }
417
+
418
+ private bindWindow(): void {
419
+ if (this.windowBound || isServer() || typeof window.addEventListener !== "function") return;
420
+ this.windowBound = true;
421
+ const onFocus = (): void => this.revalidateOnFocus();
422
+ window.addEventListener("focus", onFocus);
423
+ window.addEventListener("online", onFocus);
424
+ if (typeof document !== "undefined") {
425
+ document.addEventListener("visibilitychange", () => {
426
+ if (document.visibilityState === "visible") onFocus();
427
+ });
428
+ }
429
+ }
430
+ }
431
+
432
+ const SINGLETON = Symbol.for("@graph8/blocks/query-cache");
433
+
434
+ /** The app-wide cache every keyed hook uses (one per page, shared by every copy of this module). */
435
+ export const g8QueryCache: G8QueryCache = ((globalThis as Record<symbol, unknown>)[SINGLETON] as
436
+ | G8QueryCache
437
+ | undefined) ?? ((globalThis as Record<symbol, unknown>)[SINGLETON] = new G8QueryCache()) as G8QueryCache;
438
+
439
+ /** Mark keys under `prefix` stale; the ones on screen refetch now. `invalidate("contacts")`. */
440
+ export const invalidate = (prefix?: G8QueryKey, options?: { refetch?: boolean }): Promise<void> =>
441
+ g8QueryCache.invalidate(prefix, options);
442
+
443
+ /** Write one key's data (after a create/update response, or optimistically). */
444
+ export const setQueryData = <T>(key: G8QueryKey, updater: T | ((old: T | undefined) => T | undefined)) =>
445
+ g8QueryCache.setQueryData<T>(key, updater);
446
+
447
+ export const getQueryData = <T>(key: G8QueryKey): T | undefined => g8QueryCache.getQueryData<T>(key);
448
+
449
+ /** Warm a key before it is shown (hover, next page). Joins a request already in flight. */
450
+ export const prefetchQuery = <T>(key: G8QueryKey, fetcher: G8Fetcher<T>, staleTime = 0) =>
451
+ g8QueryCache.ensureFresh(key, fetcher, staleTime) ?? Promise.resolve(g8QueryCache.getQueryData<T>(key));
452
+
453
+ /** The empty state hooks render before a key has data (and on the server). */
454
+ export const EMPTY_QUERY_STATE: G8QueryState = EMPTY_STATE;
@@ -14,7 +14,7 @@
14
14
  * thing in the browser.
15
15
  */
16
16
 
17
- import { G8Error } from "@graph8/sdk";
17
+ import { G8Error } from "@graph8/sdk/errors";
18
18
  import type { PaginatedResponse } from "@graph8/sdk";
19
19
 
20
20
  /** The envelope every graph8 list/read call returns. */
@@ -0,0 +1,57 @@
1
+ "use client";
2
+
3
+ /**
4
+ * Paging helpers: adapt the API's several paging dialects (`page`, `offset`, `cursor`) to the one shape
5
+ * `usePaginated` loads ("load more" appends the next page). No operation is called here; the caller's component
6
+ * performs and declares the read.
7
+ *
8
+ * const list = usePaginated(byOffset(PAGE, async (offset, signal) => {
9
+ * const env = await g8Get("/landing-pages", { limit: PAGE, offset }, { signal });
10
+ * return { rows: listOf(env.data), hasNext: hasNextOf(env, PAGE) };
11
+ * }), [status]);
12
+ */
13
+ import type { PaginatedResponse } from "@graph8/sdk";
14
+
15
+ /** One page of rows and whether another follows (omit `hasNext` and a full page is taken to mean "maybe more"). */
16
+ export interface RowsPage<T> {
17
+ rows: T[];
18
+ hasNext?: boolean;
19
+ }
20
+
21
+ type Fetcher<T> = (cursor: string | undefined, signal: AbortSignal) => Promise<PaginatedResponse<T>>;
22
+
23
+ /**
24
+ * `trustHasNext`: when the page says `hasNext: true` believe it even if it carries no rows (a page-number list whose rows
25
+ * were filtered client-side can be empty while the server still has more). Offset lists cannot: an empty page does not
26
+ * advance the offset, so they stop on an empty page.
27
+ */
28
+ function toResponse<T>(page: RowsPage<T>, size: number, next: number, trustHasNext = false): PaginatedResponse<T> {
29
+ const more = page.hasNext ?? page.rows.length >= size;
30
+ const go = more && (page.rows.length > 0 || (trustHasNext && page.hasNext === true));
31
+ return { data: page.rows, pagination: { next_cursor: go ? String(next) : null } } as unknown as PaginatedResponse<T>;
32
+ }
33
+
34
+ /** `?page=N` lists: the cursor carries the next page number. */
35
+ export function byPage<T>(size: number, read: (page: number, signal: AbortSignal) => Promise<RowsPage<T>>): Fetcher<T> {
36
+ return async (cursor, signal) => {
37
+ const page = cursor ? Number(cursor) || 1 : 1;
38
+ return toResponse(await read(page, signal), size, page + 1, true);
39
+ };
40
+ }
41
+
42
+ /** `?offset=N` (or skip) lists: the cursor carries the next offset. */
43
+ export function byOffset<T>(size: number, read: (offset: number, signal: AbortSignal) => Promise<RowsPage<T>>): Fetcher<T> {
44
+ return async (cursor, signal) => {
45
+ const offset = cursor ? Number(cursor) || 0 : 0;
46
+ const page = await read(offset, signal);
47
+ return toResponse(page, size, offset + page.rows.length);
48
+ };
49
+ }
50
+
51
+ /** Whether an API envelope says another page follows: `pagination.has_next`, then `next_cursor`, then a full page. */
52
+ export function hasNextOf(env: unknown, size: number, rows?: number): boolean | undefined {
53
+ const p = (env as { pagination?: { has_next?: boolean; next_cursor?: string | null; total?: number; page?: number } } | null)?.pagination;
54
+ if (typeof p?.has_next === "boolean") return p.has_next;
55
+ if (p && "next_cursor" in p && p.next_cursor !== undefined) return Boolean(p.next_cursor);
56
+ return rows === undefined ? undefined : rows >= size;
57
+ }
@@ -0,0 +1,23 @@
1
+ "use client";
2
+
3
+ /**
4
+ * Plurals done once, correctly: `plural(1, "contact")` -> "1 contact", `plural(2, "contact")` -> "2 contacts",
5
+ * `plural(3, "company", "companies")` -> "3 companies". The count is formatted with thousands separators.
6
+ * Use it instead of a hand-rolled `n === 1 ? "" : "s"`.
7
+ *
8
+ * SDK operations: none.
9
+ */
10
+
11
+ /** The word alone, singular for exactly 1: `pluralWord(2, "city", "cities")` -> "cities". */
12
+ export function pluralWord(count: number, singular: string, pluralForm?: string): string {
13
+ if (count === 1) return singular;
14
+ if (pluralForm) return pluralForm;
15
+ if (/[^aeiou]y$/i.test(singular)) return `${singular.slice(0, -1)}ies`;
16
+ if (/(s|x|z|ch|sh)$/i.test(singular)) return `${singular}es`;
17
+ return `${singular}s`;
18
+ }
19
+
20
+ /** The count and the word: "1 contact", "1,200 contacts". */
21
+ export function plural(count: number, singular: string, pluralForm?: string): string {
22
+ return `${count.toLocaleString("en-US")} ${pluralWord(count, singular, pluralForm)}`;
23
+ }
@@ -0,0 +1,13 @@
1
+ "use client";
2
+
3
+ import { useEffect, useState } from "react";
4
+
5
+ /** `value`, but only after it has stopped changing for `ms` (foundation-local: the pickers block has its own copy). */
6
+ export function useDebounced<T>(value: T, ms = 250): T {
7
+ const [v, setV] = useState(value);
8
+ useEffect(() => {
9
+ const t = setTimeout(() => setV(value), ms);
10
+ return () => clearTimeout(t);
11
+ }, [value, ms]);
12
+ return v;
13
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Install as `app/api/g8-connect/route.ts` (the CLI copies it there). It backs the Connect graph8 screen that
3
+ * a created app shows in local preview when the server has no graph8 API key yet: the pasted key is checked
4
+ * against graph8 and stored server-side in `.g8/local-key` (never in the browser). See g8/server/connect.ts.
5
+ *
6
+ * It only answers `next dev` requests from this machine; in a production build every method is 404. Delete
7
+ * this file (and the gate in app/layout.tsx) if you never want the screen.
8
+ */
9
+
10
+ import { createG8ConnectRoute } from "@/g8/server/connect";
11
+
12
+ export const { GET, POST, DELETE } = createG8ConnectRoute();
13
+
14
+ // The answer depends on the key on disk: never cache it.
15
+ export const dynamic = "force-dynamic";