@businessdash/sdk 0.9.80 → 0.9.90

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 (264) hide show
  1. package/README.md +133 -949
  2. package/assets/{biab-sw.js → bd-sw.js} +2 -2
  3. package/dist/account-core/index.d.ts +1 -1
  4. package/dist/account-core/index.d.ts.map +1 -1
  5. package/dist/adapters-revalidate.d.ts +2 -2
  6. package/dist/adapters-revalidate.js +2 -2
  7. package/dist/adapters-revalidate.js.map +1 -1
  8. package/dist/analytics-core.d.ts +6 -6
  9. package/dist/analytics-core.d.ts.map +1 -1
  10. package/dist/analytics-core.js +4 -4
  11. package/dist/analytics-core.js.map +1 -1
  12. package/dist/analytics-server.d.ts +6 -6
  13. package/dist/analytics-server.d.ts.map +1 -1
  14. package/dist/analytics-server.js +5 -5
  15. package/dist/auth-handler.d.ts +14 -14
  16. package/dist/auth-handler.d.ts.map +1 -1
  17. package/dist/auth-handler.js +16 -16
  18. package/dist/auth-handler.js.map +1 -1
  19. package/dist/auth-links.d.ts +2 -2
  20. package/dist/auth-links.d.ts.map +1 -1
  21. package/dist/auth-links.js +2 -2
  22. package/dist/auth-links.js.map +1 -1
  23. package/dist/{biab-forms.css → bd-forms.css} +158 -158
  24. package/dist/cli.d.ts +4 -4
  25. package/dist/cli.js +78 -78
  26. package/dist/cli.js.map +1 -1
  27. package/dist/client.d.ts +158 -147
  28. package/dist/client.d.ts.map +1 -1
  29. package/dist/client.js +156 -137
  30. package/dist/client.js.map +1 -1
  31. package/dist/consent-core/index.cjs +2 -2
  32. package/dist/consent-core/index.d.ts +2 -2
  33. package/dist/consent-core/index.d.ts.map +1 -1
  34. package/dist/consent-core/index.js +10 -8
  35. package/dist/consent-core/index.js.map +1 -1
  36. package/dist/contracts.d.ts +411 -52
  37. package/dist/contracts.d.ts.map +1 -1
  38. package/dist/contracts.js +89 -15
  39. package/dist/contracts.js.map +1 -1
  40. package/dist/data-model-schema.d.ts +15 -15
  41. package/dist/data-model-schema.d.ts.map +1 -1
  42. package/dist/data-model-schema.js +19 -17
  43. package/dist/data-model-schema.js.map +1 -1
  44. package/dist/distribution.d.ts +5 -5
  45. package/dist/distribution.d.ts.map +1 -1
  46. package/dist/distribution.js +4 -4
  47. package/dist/element.d.ts +36 -36
  48. package/dist/element.d.ts.map +1 -1
  49. package/dist/element.js +43 -43
  50. package/dist/element.js.map +1 -1
  51. package/dist/embed-protocol.d.ts +11 -11
  52. package/dist/embed-protocol.d.ts.map +1 -1
  53. package/dist/embed-protocol.js +1 -1
  54. package/dist/embed-protocol.js.map +1 -1
  55. package/dist/errors.d.ts +15 -15
  56. package/dist/errors.d.ts.map +1 -1
  57. package/dist/errors.js +18 -18
  58. package/dist/errors.js.map +1 -1
  59. package/dist/forms-core/autocomplete.d.ts +3 -3
  60. package/dist/forms-core/autocomplete.d.ts.map +1 -1
  61. package/dist/forms-core/autocomplete.js +2 -2
  62. package/dist/forms-core/controller.d.ts +10 -10
  63. package/dist/forms-core/controller.d.ts.map +1 -1
  64. package/dist/forms-core/controller.js +4 -4
  65. package/dist/forms-core/controller.js.map +1 -1
  66. package/dist/forms-core/elements.d.ts +4 -4
  67. package/dist/forms-core/elements.d.ts.map +1 -1
  68. package/dist/forms-core/elements.js +7 -7
  69. package/dist/forms-core/elements.js.map +1 -1
  70. package/dist/forms-core/formula.d.ts +1 -1
  71. package/dist/forms-core/formula.js +1 -1
  72. package/dist/forms-core/index.d.ts +6 -6
  73. package/dist/forms-core/index.d.ts.map +1 -1
  74. package/dist/forms-core/index.js +5 -5
  75. package/dist/forms-core/index.js.map +1 -1
  76. package/dist/forms-core/units.d.ts +1 -1
  77. package/dist/forms-core/units.js +1 -1
  78. package/dist/forms-core/us-states.d.ts +1 -1
  79. package/dist/forms-core/us-states.js +1 -1
  80. package/dist/forms-core/validation.d.ts +12 -3
  81. package/dist/forms-core/validation.d.ts.map +1 -1
  82. package/dist/forms-core/validation.js +152 -17
  83. package/dist/forms-core/validation.js.map +1 -1
  84. package/dist/forms.d.ts +2 -2
  85. package/dist/forms.d.ts.map +1 -1
  86. package/dist/forms.js +2 -2
  87. package/dist/forms.js.map +1 -1
  88. package/dist/framework-support.js +1 -1
  89. package/dist/framework-support.js.map +1 -1
  90. package/dist/gallery-types.d.ts +1 -1
  91. package/dist/graphql.d.ts +3 -3
  92. package/dist/graphql.d.ts.map +1 -1
  93. package/dist/graphql.js +3 -3
  94. package/dist/graphql.js.map +1 -1
  95. package/dist/index.cjs +351 -272
  96. package/dist/index.d.cts +5 -5
  97. package/dist/index.d.mts +5 -5
  98. package/dist/index.d.ts +5 -5
  99. package/dist/index.d.ts.map +1 -1
  100. package/dist/index.js +5 -5
  101. package/dist/index.js.map +1 -1
  102. package/dist/legal-core/index.cjs +8 -8
  103. package/dist/legal-core/index.d.ts +3 -3
  104. package/dist/legal-core/index.d.ts.map +1 -1
  105. package/dist/legal-core/index.js +11 -11
  106. package/dist/legal-core/index.js.map +1 -1
  107. package/dist/marketing-core/index.d.ts +51 -1
  108. package/dist/marketing-core/index.d.ts.map +1 -1
  109. package/dist/marketing-core/index.js +35 -0
  110. package/dist/marketing-core/index.js.map +1 -1
  111. package/dist/marketing-schema.d.ts +9 -9
  112. package/dist/marketing-schema.d.ts.map +1 -1
  113. package/dist/marketing-schema.js +1 -1
  114. package/dist/mcp.d.ts +4 -4
  115. package/dist/mcp.js +4 -4
  116. package/dist/native.d.ts +6 -6
  117. package/dist/native.d.ts.map +1 -1
  118. package/dist/native.js +4 -4
  119. package/dist/native.js.map +1 -1
  120. package/dist/next-revalidate.d.ts +8 -8
  121. package/dist/next-revalidate.d.ts.map +1 -1
  122. package/dist/next-revalidate.js +7 -7
  123. package/dist/next-revalidate.js.map +1 -1
  124. package/dist/notifications-core/index.d.ts +1 -1
  125. package/dist/notifications-core/index.js +1 -1
  126. package/dist/parallel-pages.d.ts +4 -4
  127. package/dist/parallel-pages.d.ts.map +1 -1
  128. package/dist/parallel-pages.js +5 -5
  129. package/dist/parallel-pages.js.map +1 -1
  130. package/dist/portal-core/index.d.ts +1 -1
  131. package/dist/portal-core/index.js +1 -1
  132. package/dist/proxy.d.ts +18 -18
  133. package/dist/proxy.d.ts.map +1 -1
  134. package/dist/proxy.js +10 -10
  135. package/dist/proxy.js.map +1 -1
  136. package/dist/push-core/index.cjs +6 -2
  137. package/dist/push-core/index.d.ts +1 -1
  138. package/dist/push-core/index.d.ts.map +1 -1
  139. package/dist/push-core/index.js +6 -2
  140. package/dist/push-core/index.js.map +1 -1
  141. package/dist/react-analytics.d.ts +14 -14
  142. package/dist/react-analytics.d.ts.map +1 -1
  143. package/dist/react-analytics.js +15 -15
  144. package/dist/react-analytics.js.map +1 -1
  145. package/dist/react-attribution.d.ts +2 -2
  146. package/dist/react-attribution.d.ts.map +1 -1
  147. package/dist/react-attribution.js +1 -1
  148. package/dist/react-marketing.js +5 -5
  149. package/dist/react-marketing.js.map +1 -1
  150. package/dist/react.d.ts +38 -61
  151. package/dist/react.d.ts.map +1 -1
  152. package/dist/react.js +247 -261
  153. package/dist/react.js.map +1 -1
  154. package/dist/reserved-paths.d.ts +2 -2
  155. package/dist/reserved-paths.js +3 -3
  156. package/dist/reserved-paths.js.map +1 -1
  157. package/dist/revalidate-verify.d.ts +3 -3
  158. package/dist/revalidate-verify.d.ts.map +1 -1
  159. package/dist/revalidate-verify.js +1 -1
  160. package/dist/revalidate-verify.js.map +1 -1
  161. package/dist/sdk.d.ts +51 -45
  162. package/dist/sdk.d.ts.map +1 -1
  163. package/dist/sdk.js +48 -50
  164. package/dist/sdk.js.map +1 -1
  165. package/dist/seed-core/index.js +8 -8
  166. package/dist/seed-core/index.js.map +1 -1
  167. package/dist/seo-core/blog.d.ts +1 -1
  168. package/dist/seo-core/blog.js +1 -1
  169. package/dist/seo-core/blog.js.map +1 -1
  170. package/dist/seo-core/index.cjs +2 -2
  171. package/dist/seo-core/index.js +1 -1
  172. package/dist/seo-core/index.js.map +1 -1
  173. package/dist/seo.d.ts +1 -1
  174. package/dist/seo.d.ts.map +1 -1
  175. package/dist/seo.js +1 -1
  176. package/dist/seo.js.map +1 -1
  177. package/dist/sitemap-core/index.cjs +5 -5
  178. package/dist/sitemap-core/index.d.ts +13 -5
  179. package/dist/sitemap-core/index.d.ts.map +1 -1
  180. package/dist/sitemap-core/index.js +4 -4
  181. package/dist/sitemap-core/index.js.map +1 -1
  182. package/dist/socials.d.ts +6 -6
  183. package/dist/socials.d.ts.map +1 -1
  184. package/dist/socials.js +5 -5
  185. package/dist/socials.js.map +1 -1
  186. package/dist/solid-compat.d.ts +1 -1
  187. package/dist/solid-compat.js +4 -4
  188. package/dist/solid-compat.js.map +1 -1
  189. package/dist/store-core/index.d.ts +1 -1
  190. package/dist/vanilla.d.ts +12 -12
  191. package/dist/vanilla.d.ts.map +1 -1
  192. package/dist/vanilla.js +192 -145
  193. package/dist/vanilla.js.map +1 -1
  194. package/openapi.json +264 -1
  195. package/package.json +8 -8
  196. package/src/account-core/index.ts +1 -1
  197. package/src/adapters-revalidate.ts +3 -3
  198. package/src/analytics-core.ts +8 -8
  199. package/src/analytics-server.ts +6 -6
  200. package/src/angular/{biab-form.component.ts → bd-form.component.ts} +269 -161
  201. package/src/angular/headless.ts +12 -12
  202. package/src/angular/index.ts +9 -9
  203. package/src/auth-handler.ts +22 -22
  204. package/src/auth-links.ts +2 -2
  205. package/src/cli.ts +81 -81
  206. package/src/client.ts +218 -193
  207. package/src/consent-core/index.ts +10 -8
  208. package/src/contracts.ts +99 -15
  209. package/src/data-model-schema.ts +29 -26
  210. package/src/distribution.ts +5 -5
  211. package/src/element.ts +54 -54
  212. package/src/embed-protocol.ts +14 -14
  213. package/src/errors.ts +27 -27
  214. package/src/forms-core/autocomplete.ts +3 -3
  215. package/src/forms-core/controller.ts +12 -12
  216. package/src/forms-core/elements.ts +7 -7
  217. package/src/forms-core/formula.ts +1 -1
  218. package/src/forms-core/index.ts +8 -8
  219. package/src/forms-core/units.ts +1 -1
  220. package/src/forms-core/us-states.ts +1 -1
  221. package/src/forms-core/validation.ts +158 -14
  222. package/src/forms.ts +2 -2
  223. package/src/framework-support.ts +1 -1
  224. package/src/gallery-types.ts +1 -1
  225. package/src/graphql.ts +4 -4
  226. package/src/index.ts +38 -38
  227. package/src/legal-core/index.ts +12 -12
  228. package/src/marketing-core/index.ts +62 -0
  229. package/src/marketing-schema.ts +9 -9
  230. package/src/mcp.ts +4 -4
  231. package/src/native.ts +14 -14
  232. package/src/next-revalidate.ts +10 -10
  233. package/src/notifications-core/index.ts +1 -1
  234. package/src/parallel-pages.ts +6 -6
  235. package/src/portal-core/index.ts +1 -1
  236. package/src/proxy.ts +28 -28
  237. package/src/push-core/index.ts +11 -5
  238. package/src/qwik.tsx +262 -166
  239. package/src/react-analytics.tsx +17 -17
  240. package/src/react-attribution.tsx +2 -2
  241. package/src/react-marketing.tsx +5 -5
  242. package/src/react.tsx +541 -500
  243. package/src/reserved-paths.ts +3 -3
  244. package/src/revalidate-verify.ts +5 -5
  245. package/src/sdk.ts +131 -101
  246. package/src/seed-core/index.ts +8 -8
  247. package/src/seo-core/blog.ts +2 -2
  248. package/src/seo-core/index.ts +1 -1
  249. package/src/seo.ts +1 -1
  250. package/src/sitemap-core/index.ts +17 -9
  251. package/src/socials.ts +6 -6
  252. package/src/solid-compat.tsx +5 -5
  253. package/src/solid-surfaces.tsx +28 -28
  254. package/src/solid.tsx +264 -175
  255. package/src/store-core/index.ts +1 -1
  256. package/src/styles/{biab-forms.css → bd-forms.css} +158 -158
  257. package/src/svelte/{BiabForm.svelte → BdForm.svelte} +240 -173
  258. package/src/svelte/context.ts +16 -16
  259. package/src/svelte/headless.ts +10 -10
  260. package/src/svelte/index.ts +16 -16
  261. package/src/vanilla.ts +250 -169
  262. package/src/vue/{BiabForm.vue → BdForm.vue} +270 -157
  263. package/src/vue/headless.ts +11 -11
  264. package/src/vue/index.ts +26 -26
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # `@businessdash/sdk`
2
2
 
3
- > Formerly published as `@biab-dev/sdk` (≤0.9.53) — same API, new name.
3
+ > Formerly published as `@businessdash/sdk` (≤0.9.53) — same API, new name.
4
4
 
5
5
  **Alpha — 0.9.53.** This package declares, validates, and orchestrates your application's database schema, form layout, and type-safe field rules in a single declarative pipeline — and ships the answer-engine surface (llms.txt + product feed, MCP connector proxy, AI-crawler analytics) that makes the site you build with it legible to AI assistants.
6
6
 
@@ -425,8 +425,8 @@ the org curates it. `@businessdash/sdk/distribution` proxies the two together:
425
425
  import { llmsTxtHandler } from '@businessdash/sdk/distribution'
426
426
 
427
427
  export const GET = llmsTxtHandler({
428
- siteId: process.env.BIAB_SITE_ID!,
429
- baseUrl: process.env.NEXT_PUBLIC_BIAB_APP_URL!, // e.g. https://www.biab.app
428
+ siteId: process.env.BD_SITE_ID!,
429
+ baseUrl: process.env.NEXT_PUBLIC_BD_APP_URL!, // e.g. https://www.biab.app
430
430
  })
431
431
  ```
432
432
 
@@ -434,7 +434,7 @@ export const GET = llmsTxtHandler({
434
434
  domain root, with a 5-minute `s-maxage` + `stale-while-revalidate`. An
435
435
  upstream 404 (feed disabled or entitlement lapsed) passes through untouched;
436
436
  network failure returns a plain 503 — it never throws into the framework.
437
- - **`productFeedUrl(options)` / `llmsTxtUrl(options)`** build the public BIAB
437
+ - **`productFeedUrl(options)` / `llmsTxtUrl(options)`** build the public BD
438
438
  feed URLs. The product feed needs no proxy — submit its BusinessDash URL directly to
439
439
  merchant/feed programs; `productFeedUrl` builds it for display/submission.
440
440
 
@@ -453,15 +453,15 @@ so the URL an org hands to Claude / ChatGPT / Gemini is their own site:
453
453
  // app/api/mcp/route.ts
454
454
  import { mcpHandler } from '@businessdash/sdk/mcp'
455
455
  export const { POST, GET } = mcpHandler({
456
- siteId: process.env.BIAB_SITE_ID!,
457
- baseUrl: process.env.NEXT_PUBLIC_BIAB_APP_URL!, // https://www.biab.app
456
+ siteId: process.env.BD_SITE_ID!,
457
+ baseUrl: process.env.NEXT_PUBLIC_BD_APP_URL!, // https://www.biab.app
458
458
  })
459
459
 
460
460
  // app/.well-known/mcp.json/route.ts
461
461
  import { mcpManifestHandler } from '@businessdash/sdk/mcp'
462
462
  export const GET = mcpManifestHandler({
463
- siteId: process.env.BIAB_SITE_ID!,
464
- baseUrl: process.env.NEXT_PUBLIC_BIAB_APP_URL!,
463
+ siteId: process.env.BD_SITE_ID!,
464
+ baseUrl: process.env.NEXT_PUBLIC_BD_APP_URL!,
465
465
  })
466
466
  ```
467
467
 
@@ -477,7 +477,7 @@ export const GET = mcpManifestHandler({
477
477
  ## AI-Crawler Visibility (AEO Analytics)
478
478
 
479
479
  AI crawlers (GPTBot, ClaudeBot, PerplexityBot, …) fetch your HTML without
480
- executing JavaScript, so `<BIABAnalytics />` never sees them. Your server does
480
+ executing JavaScript, so `<BDAnalytics />` never sees them. Your server does
481
481
  — report them from middleware via `@businessdash/sdk/analytics-server`:
482
482
 
483
483
  ```ts
@@ -489,9 +489,9 @@ export function middleware(request: Request) {
489
489
  if (isAiCrawlerUserAgent(ua)) {
490
490
  // Fire-and-forget — never block or fail the crawler's request.
491
491
  void reportAiCrawlerHit({
492
- siteId: process.env.BIAB_SITE_ID!,
493
- baseUrl: process.env.NEXT_PUBLIC_BIAB_API_URL!,
494
- apiKey: process.env.NEXT_PUBLIC_BIAB_PUBLISHABLE_KEY!,
492
+ siteId: process.env.BD_SITE_ID!,
493
+ baseUrl: process.env.NEXT_PUBLIC_BD_API_URL!,
494
+ apiKey: process.env.NEXT_PUBLIC_BD_PUBLISHABLE_KEY!,
495
495
  userAgent: ua,
496
496
  path: new URL(request.url).pathname,
497
497
  });
@@ -499,7 +499,7 @@ export function middleware(request: Request) {
499
499
  }
500
500
  ```
501
501
 
502
- Read it back with `biab.site(siteId).analytics.aiVisibility({ days: 30 })` —
502
+ Read it back with `bd.site(siteId).analytics.aiVisibility({ days: 30 })` —
503
503
  per-crawler fetch counts, distinct pages read, vendor, intent
504
504
  (`training` / `search` / `user`), and last-seen timestamps. Branch on
505
505
  `.available`; same plan gating as `pageViews()`.
@@ -517,27 +517,27 @@ per-crawler fetch counts, distinct pages read, vendor, intent
517
517
  | `@businessdash/sdk/forms` | Form layout + runtime — `layoutContainers`, `createFormSchemaOrchestrator()`, `createSingleResourceForm()`, controller, formula engine |
518
518
  | `@businessdash/sdk/static` | Static data — `Enums()`, enum types |
519
519
  | `@businessdash/sdk/react` | React bindings — **the full UI surface**: forms, chat, auth, storefront, cart, checkout, blog, customer portal, marketing pages, followers, social links, verification |
520
- | `@businessdash/sdk/vue` | Vue binding — `<BiabForm>` + shared client |
521
- | `@businessdash/sdk/svelte` | Svelte binding — `<BiabForm>` + shared client |
522
- | `@businessdash/sdk/solid` | Solid binding — `<BiabForm>` + shared client |
523
- | `@businessdash/sdk/qwik` | Qwik binding — `<BiabForm>` |
524
- | `@businessdash/sdk/angular` | Angular binding — `<biab-form>` component + shared client |
525
- | `@businessdash/sdk/element` | `<biab-form>` custom element — drop-in web component |
526
- | `@businessdash/sdk/vanilla` | Framework-free imperative DOM binding — `mountBiabForm()` |
527
- | `@businessdash/sdk/react-analytics` | `<BIABAnalytics />` — browser page-view tracker for React |
520
+ | `@businessdash/sdk/vue` | Vue binding — `<BdForm>` + shared client |
521
+ | `@businessdash/sdk/svelte` | Svelte binding — `<BdForm>` + shared client |
522
+ | `@businessdash/sdk/solid` | Solid binding — `<BdForm>` + shared client |
523
+ | `@businessdash/sdk/qwik` | Qwik binding — `<BdForm>` |
524
+ | `@businessdash/sdk/angular` | Angular binding — `<bd-form>` component + shared client |
525
+ | `@businessdash/sdk/element` | `<bd-form>` custom element — drop-in web component |
526
+ | `@businessdash/sdk/vanilla` | Framework-free imperative DOM binding — `mountBdForm()` |
527
+ | `@businessdash/sdk/react-analytics` | `<BDAnalytics />` — browser page-view tracker for React |
528
528
  | `@businessdash/sdk/react-attribution` | "Powered by BusinessDash" attribution badge |
529
- | `@businessdash/sdk/analytics-core` | Framework-agnostic analytics core — `initBiabAnalytics()` |
529
+ | `@businessdash/sdk/analytics-core` | Framework-agnostic analytics core — `initBdAnalytics()` |
530
530
  | `@businessdash/sdk/analytics-server` | Server-side AEO reporting — `isAiCrawlerUserAgent()`, `reportAiCrawlerHit()` |
531
531
  | `@businessdash/sdk/distribution` | llms.txt proxy + feed URLs — `llmsTxtHandler()`, `productFeedUrl()`, `llmsTxtUrl()` |
532
532
  | `@businessdash/sdk/mcp` | MCP connector proxy — `mcpHandler()`, `mcpManifestHandler()` |
533
- | `@businessdash/sdk/proxy` | `BiabDevClient` / customer-portal client re-exports for proxy setups |
533
+ | `@businessdash/sdk/proxy` | `BdApiClient` / customer-portal client re-exports for proxy setups |
534
534
  | `@businessdash/sdk/seo` | JSON-LD builders — `localBusiness()`, `product()`, … |
535
535
  | `@businessdash/sdk/data-model-schema` | `defineDataModel` — org custom database (tables + enums) as code |
536
536
  | `@businessdash/sdk/alpha` | Legacy/deprecated pre-0.9 schema API, re-exported for compatibility |
537
537
  | `@businessdash/sdk/marketing-schema` | `defineSiteMarketingSchema()` — schema-driven marketing-page flow |
538
538
  | `@businessdash/sdk/next/revalidate` | One-line Next.js route handler for BusinessDash revalidation webhooks |
539
539
  | `@businessdash/sdk/adapters/revalidate` | Framework-agnostic revalidation handler builder |
540
- | `@businessdash/sdk/biab-forms.css` | Default form stylesheet |
540
+ | `@businessdash/sdk/bd-forms.css` | Default form stylesheet |
541
541
 
542
542
  ### Framework parity — read this before picking a stack
543
543
 
@@ -588,7 +588,7 @@ approval, so watch `reviewAwaitingModeration` rather than expecting it in
588
588
  **React remains ahead on `<ChatbotInline>`** — a rendered chat UI over the same
589
589
  shared controller.
590
590
 
591
- For anything a binding lacks, `BiabClient` from the root export is
591
+ For anything a binding lacks, `BdClient` from the root export is
592
592
  framework-agnostic: `client.storefront()`, `client.cart()`,
593
593
  `client.customerPortal()` and the rest work anywhere JavaScript runs. You write
594
594
  the markup; the data layer is already there.
@@ -615,7 +615,7 @@ a native component.
615
615
  >
616
616
  > **0.8.x = schema-driven marketing flow.** Major surface bump (still
617
617
  > alpha). New entrypoints: `defineSiteMarketingSchema()`,
618
- > `@biab-dev/sdk/seo` for typed JSON-LD builders, and a `biab-dev` CLI.
618
+ > `@businessdash/sdk/seo` for typed JSON-LD builders, and a `bd` CLI.
619
619
  >
620
620
  > **0.9.x = full programmatic consumer surface.** The largest expansion
621
621
  > yet: native storefront / cart / checkout / coupons / subscriptions,
@@ -623,972 +623,156 @@ a native component.
623
623
  > `<SignIn/>` `<SignUp/>` `<SignOut/>` `useUser`), blog, a paginated
624
624
  > reviews wall, address autocomplete + shipping, programmatic local SEO
625
625
  > (`defineParallelPage()`), the revalidation webhook channel
626
- > (`@biab-dev/sdk/next/revalidate` + `@biab-dev/sdk/adapters/revalidate`),
627
- > privacy-conscious analytics (`@biab-dev/sdk/react-analytics` +
628
- > `@biab-dev/sdk/analytics-core`), and a three-state billing-lifecycle
626
+ > (`@businessdash/sdk/next/revalidate` + `@businessdash/sdk/adapters/revalidate`),
627
+ > privacy-conscious analytics (`@businessdash/sdk/react-analytics` +
628
+ > `@businessdash/sdk/analytics-core`), and a three-state billing-lifecycle
629
629
  > degradation contract. Detailed per-version notes below.
630
630
 
631
631
  The 3 most recent releases are below. **For the complete release
632
632
  history, see the [changelog](https://www.biab.app/docs/developer/changelog).**
633
633
 
634
- ### 0.9.80
634
+ ### 0.9.90
635
635
 
636
- #### Tracking consent`@businessdash/sdk/consent`
636
+ #### Every `Biab*` export is now `Bd*` a breaking rename with no aliases
637
637
 
638
- There was no consent mechanism anywhere: `initBiabAnalytics()` started
639
- unconditionally and analytics, attribution and AEO all collected regardless.
638
+ `biab` was the old application name (Business In A Box). The product is
639
+ BusinessDash, and the old name survived across the SDK's whole public
640
+ surface long after that stopped being true.
640
641
 
641
- `loadConsent()` returns what a visitor currently allows, at THIS org, and
642
- `recordConsent()` stores their answer. Gate analytics on it:
642
+ Renamed as a clean break rather than with a deprecation layer, because
643
+ nothing outside this repo consumes these names yet. Waiting would only have
644
+ made the alias layer larger.
643
645
 
644
- ```ts
645
- const consent = await loadConsent({ client })
646
- if (consent.allows('analytics')) initBiabAnalytics({ siteId, baseUrl, apiKey })
647
- if (consent.mustAsk) showYourBanner(consent)
648
646
  ```
649
-
650
- `initBiabAnalytics` is deliberately NOT made to call this itself — a gate that
651
- fires implicitly is one nobody can see in review, and what happens before
652
- consent (render nothing, render a placeholder, collect essential-only) is the
653
- site's decision, not ours.
654
-
655
- **Consent is per-org and never travels.** A visitor who refuses at org 2 has
656
- said nothing to org 1, and one who consents at org 1 has NOT consented to org 2
657
- — that second half is the one with legal teeth. The visitor key lives in a
658
- first-party cookie on the org's own domain, so two sites cannot see each
659
- other's, and the isolation is the browser's rather than something we remember
660
- to scope.
661
-
662
- **The gate fails closed.** Any failure returns deny-everything: a gate that
663
- fails open collects data nobody agreed to, and only one of those outcomes is an
664
- incident.
665
-
666
- Also ships `client.consent.get()` / `.record()` as the transport underneath,
667
- and `browserOptOut()` for DNT and Global Privacy Control.
668
-
669
- ### 0.9.70
670
-
671
- #### 3D product models — `@businessdash/sdk/model3d`
672
-
673
- Schema columns and a three.js viewer for `model3dUrl` / `model3dUsdzUrl` shipped
674
- in migration 0162 and reached the BusinessDash-hosted storefront only. Nothing
675
- was typed in the SDK, nothing was gated, and STEP was unsupported.
676
-
677
- `planProductModel()` decides which of three surfaces applies and hands back the
678
- facts. **No renderer ships** — three.js is ~600KB and `<model-viewer>` ~300KB,
679
- and the choice of engine should be yours.
680
-
681
- - **AR Quick Look** (`.usdz`) — an `<a rel="ar">` opens the model in the room on
682
- iOS/iPadOS/visionOS with no JavaScript at all. Feature-detected via
683
- `relList.supports("ar")` rather than user-agent sniffing.
684
- - **Web viewer** (`.glb`/`.gltf`/`.obj`/`.stl`) — `neutralMaterial` flags the
685
- geometry-only formats, which render as a black silhouette in a viewer that
686
- lights them like a `.glb`.
687
- - **CAD download** (`.step`/`.iges`) — deliberately not rendered. STEP needs a
688
- geometry kernel, and the browser WASM builds are 10–30MB. It is also the wrong
689
- shape for who asks: an engineer wants the file in their own CAD package.
690
-
691
- Gated behind the new `ecommerce.product_3d` add-on (bundled at Scale). The gate
692
- is PARTIAL — an unentitled org's products still return, minus four fields.
693
- Failing the whole request would take a catalog offline over a lapsed $5.
694
-
695
- The CAD URL never appears in a payload. `client.storefront.getCadDownload(id)`
696
- issues it from a route that requires a `biab_cad` cookie, rate-limits 10/min per
697
- IP, and re-checks the entitlement — a catalog of naked URLs is a machine shop's
698
- design library published to anyone willing to write a loop.
699
-
700
- #### Legal pages — `@businessdash/sdk/legal`
701
-
702
- Privacy policy, terms and refund policy written once in the dashboard and served
703
- on the org's own domain. The SDK never claims a literal path: it exposes a
704
- resolver for a catch-all, and every file-based framework resolves a static
705
- segment first — so your own `/privacy` shadows ours with no build conflict.
706
-
707
- #### Sitemaps — `@businessdash/sdk/sitemap`
708
-
709
- `buildSitemap()` merges your own routes, what the platform owns the shape of,
710
- and platform content mapped onto paths you declare. Opt-in per content type:
711
- declare nothing and nothing is emitted, which is right for a business without
712
- that surface. Never emits the customer portal or other token-gated paths.
713
-
714
- #### Browser push — `@businessdash/sdk/push-core` and `/notifications`
715
-
716
- `enablePush()` handles the whole service-worker dance; `createNotificationFeed()`
717
- gives one handler for every notification, deduplicated across push and polling,
718
- with no toast UI of its own.
719
-
720
- #### Scheduling and conference calls on the main client
721
-
722
- Booking lived on `sdk.ts`'s separate scheduling resource. The OpenAPI generator
723
- reads `client.ts`, so it never saw any of it: all seven scheduling routes sat in
724
- the recorded spec gap, and no non-JS starter had scheduling at all.
725
-
726
- `client.site(siteId).scheduling` now covers event types, slots, booking, and
727
- reading/rescheduling/cancelling with the invitee's signed token — plus a new
728
- **staff** path (`rescheduleBookingAsStaff` / `cancelBookingAsStaff`) so an org
729
- can move a booking with its API key rather than a token from someone's email.
730
-
731
- Both sides call the same service, which is what keeps them honest: the invitee
732
- **and every host** are notified either way, the reminders queued against the old
733
- time are cancelled either way, and the booking records which side changed it.
734
- `actorUserId` is what lets a customer's history say "they rescheduled" rather
735
- than leaving an unexplained change.
736
-
737
- The staff route needs `scheduling:write`, mapped to the same `scheduling.manage`
738
- permission the dashboard requires and deliberately **not publishable** — a
739
- browser token able to cancel any booking by id would let anyone reading the page
740
- source cancel every meeting the org has.
741
-
742
- Two generator bugs surfaced while wiring this up, both the same class as the
743
- nested-template one fixed earlier: it could not see paths built through a
744
- `this.path()` helper, and it was letting query strings leak into path templates
745
- (`bookings/{token}?type={type}` was being emitted as a path). Both fixed; the
746
- corrupt-path gate stays green and the recorded spec gap dropped from 39 routes
747
- to 33.
748
-
749
- All six non-JS starters gained the full surface, including both paths — parity
750
- is 19/19 each.
751
-
752
-
753
- #### SEO reaches the page, on every framework — `@businessdash/sdk/seo-core`
754
-
755
- The platform has always produced per-page SEO — every marketing page bundle
756
- carries title, description, canonical, `noIndex`, Open Graph, Twitter card,
757
- keywords, JSON-LD and hreflang. The SDK re-exported that as a TYPE and stopped
758
- there, so every consumer hand-mapped eleven fields into their framework's
759
- metadata shape, along with the fallbacks, the absolute-URL rules and the robots
760
- string.
761
-
762
- Eleven fields is enough that everyone does four. The two people skip are
763
- `noIndex` and `canonical` — the two where being wrong costs something and says
764
- nothing.
765
-
766
- **Layout and page compose.** Precedence runs layout defaults → the platform's
767
- page SEO → overrides in code, because a developer has context the CMS does not.
768
- An omitted field inherits; an explicit `null` clears. JSON-LD accumulates
769
- rather than replaces, since a layout's `Organization` node and a page's
770
- `Product` node both belong in the document.
771
-
772
- **Every framework, no framework imports.** `toNextMetadata`, `toRemixMeta`,
773
- `toNuxtHead`, `toQwikDocumentHead`, `toTanStackHead`, `toHeadTags`,
774
- `renderHeadTags` and `applyToDocument` each return the plain shape their
775
- framework expects, so the module works in all of them and depends on none.
776
-
777
- Two rules are enforced rather than left to the caller. A **relative canonical
778
- is dropped** instead of emitted — crawlers resolve it against whatever URL they
779
- fetched, and a wrong canonical consolidates ranking onto the wrong page while a
780
- missing one is recoverable. And **`robots` is always emitted, both ways**,
781
- because absence means "index" and relying on absence to express `noindex` is
782
- how a hidden page gets published.
783
-
784
- #### SEO for blogs and storefronts
785
-
786
- `blogPostSeo` emits `BlogPosting` with dates, byline, tags and breadcrumbs, plus
787
- `og:type: article` and the `article:*` meta that make a shared link render as a
788
- dated card rather than a bare URL.
789
-
790
- **Gated posts stay indexed and say so.** A subscriber-only post should be
791
- findable — discoverability is how anyone subscribes — but serving the full
792
- article to a crawler and a paywall to a reader is cloaking. So a gated post
793
- carries schema.org's paywall markup (`isAccessibleForFree: false` plus a
794
- `hasPart` naming the region), which declares the difference rather than hiding
795
- it. Anything not `public` counts as gated, including unrecognised access
796
- levels: failing the other way would declare open access for restricted content.
797
-
798
- `productListingSeo` emits `CollectionPage` + `ItemList`, and canonicalises
799
- filtered views to the unfiltered category — faceted URLs multiply into thousands
800
- of near-identical pages that eat crawl budget and split ranking.
801
- `transactionalPageSeo` keeps cart, checkout and order confirmations out of the
802
- index; confirmations have been indexed with customer details in the query
803
- string by more than one large retailer.
804
-
805
- `productPageSeo` gained `aggregateRating` and `AggregateOffer`. The rating is
806
- emitted **only** when `reviewCount > 0`, because an `AggregateRating` with zero
807
- reviews is invalid and Google rejects the whole Product node for it — a product
808
- with no reviews yet would lose its price and availability too.
809
-
810
- Archive and listing pages past the first are `noindex`: the posts and products
811
- are what should rank, and a thin page 7 competes with them.
812
-
813
- #### Local SEO: service areas × services, and products
814
-
815
- `servicesTimesAreas()` expands the Cartesian product into page plans, each with
816
- its own canonical and a `Service` node scoped with `areaServed` to that place.
817
- Without those two, near-identical pages are deduplicated away and the node is
818
- indistinguishable from the generic services page.
819
-
820
- `describe` is a callback rather than a template on purpose: pages differing only
821
- by a swapped town name are doorway pages, which are penalised rather than
822
- ranked. `include` skips combinations the org does not actually cover, so it does
823
- not rank for work it has to turn down.
824
-
825
- `productPageSeo()` emits `Product` + `Offer` + `BreadcrumbList`, money as
826
- integer cents. It pairs with the AI Distribution product feed — the feed gets
827
- the product into the engines' catalogues, this markup is what their crawlers
828
- verify on the landing page.
829
-
830
- All six non-JS starters gained the same surface, with the same rules. Swift's
831
- `JSONValue` gained `Encodable` on the way: it could decode the org's JSON-LD
832
- and had no way to render it back out.
833
-
834
-
835
- #### Subscriptions in the customer portal — and the entitlement check that was a stub
836
-
837
- `hasActiveSubscription()` returned `true`. For everyone. Every
838
- `accessLevel: "subscribers"` gate called it, so subscriber-only content was
839
- readable by anyone signed in.
840
-
841
- That was survivable while nothing was gated, and it stopped being survivable
842
- the moment the portal grew a surface that advertises "your subscriber content":
843
- a page promising an entitlement check that does not perform one is worse than
844
- no page. It now reads `user_subscriptions` for real. There were 0
845
- subscriptions, 0 offerings and 0 subscriber-gated posts in production when this
846
- landed, so switching it on changed nothing anyone could see — which is the
847
- cheapest possible moment to fix an access-control default.
848
-
849
- Three cases grant access, and two of them a `status === "active"` check gets
850
- wrong in the direction that takes something from a customer who paid:
851
-
852
- - **lifetime** has no period, so an expiry check must not be applied to it;
853
- - **cancelled but paid through** keeps access until the period ends;
854
- - `failed` (dunning) and `pending` (incomplete) do not grant — a payment that
855
- has not succeeded has not bought anything.
856
-
857
- The rule lives in `subscription-access-rules.ts` with no database import, so it
858
- is unit-tested directly — the same split, for the same reason, as
859
- `connect-subscription-mapping.ts`.
860
-
861
- **The portal surface**: `getSubscription()` returns state plus the org's live
862
- offerings (a portal reporting "not subscribed" and nothing else is a dead end),
863
- `cancelSubscription()` / `resumeSubscription()`, and `getSubscriberContent()`.
864
-
865
- Cancelling ends the RENEWAL, not the access: `cancel_at_period_end` only, with
866
- `accessUntil` in the response. The customer paid for the period they are in,
867
- and ending access on the click is the most common way a subscription flow feels
868
- like a trap. The route deliberately writes no local status — the provider's
869
- webhook owns it, and racing that would leave the row disagreeing with the
870
- money — and the controller re-reads rather than guessing what the webhook will
871
- write.
872
-
873
- `getSubscriberContent()` answers "what am I actually getting for this?", which
874
- is the question asked right before someone cancels. When `entitled` is false it
875
- returns LOCKED previews rather than an empty list: titles and excerpts, never
876
- bodies. An empty list would hide the offer at exactly the moment it is most
877
- relevant.
878
-
879
- **Found while building it**: `org_blog_posts.access_level` is a TEXT column
880
- with two vocabularies written into it — `public|members|subscribers` by the
881
- gate and the create route, `public|followers|paid` by the public reads. Nothing
882
- reconciles or constrains them. Both subscriber spellings now gate and anything
883
- unrecognised gates too, because an access check must fail closed. Converging
884
- the column is a migration and a naming decision, not something to settle in a
885
- read path.
886
-
887
- All six non-JS starters gained the same four calls.
888
-
889
-
890
- #### Notification settings, per company
891
-
892
- A customer who buys from three businesses has three independent preference
893
- matrices — they are stored per `(org, customer)` and always have been. What did
894
- not exist was a way to reach more than one of them: `createPortalController`
895
- built its client once, pinned to the API key's org, for its whole life. A
896
- dashboard could LIST the customer's other companies and change nothing about
897
- them, so muting marketing email meant muting it for whichever company the
898
- portal happened to be pointed at.
899
-
900
- `loadNotificationPreferencesFor(orgId)` and
901
- `updateNotificationPreferencesFor(orgId, input)` close that, with
902
- `notificationPreferencesByOrg` on the snapshot keyed by org id — separate from
903
- this org's, so the current company renders without waiting on companies the
904
- customer has not expanded. Pinned clients are memoised per org, because a
905
- dashboard listing five companies would otherwise build one per render.
906
-
907
- Writes stay sparse and merged, and the controller stores what the server
908
- ANSWERED rather than what was sent: the server merges into the stored matrix,
909
- so echoing the request would show the customer a matrix it never agreed to.
910
-
911
- Every non-JS starter gained the same surface.
912
-
913
- #### Non-JS starters reach every surface — and are now measured
914
-
915
- Swift, Kotlin, Dart, PHP and Elixir consumers do not import this package; each
916
- starter carries a hand-written client. Nothing that keeps the JS bindings
917
- honest said anything about them, so they fell behind quietly and the only
918
- record of how far was an estimate that turned out to be wrong in both
919
- directions.
920
-
921
- Measured, then closed:
922
-
923
- | Starter | Before | After |
924
- |---|---|---|
925
- | Swift | 11/15 | **15/15** |
926
- | Phoenix | 12/15 | **15/15** |
927
- | Flutter | 11/15 | **15/15** |
928
- | Laravel | 11/15 | **15/15** |
929
- | Kotlin | 10/15 | **15/15** |
930
- | Vapor | 9/15 | **15/15** |
931
-
932
- The shared gap was social links and the two verification kinds — surfaces added
933
- to the JS side and never carried across. Laravel also lacked the chatbot;
934
- Kotlin, Flutter and Vapor lacked the customer portal; Vapor lacked customer
935
- auth; Kotlin lacked the data model.
936
-
937
- `non-js-parity.test.ts` measures this every run by matching **request paths**,
938
- the one thing six languages have in common — a Swift `func reviews(_ id:)` and
939
- an Elixir `product_reviews/2` share no convention, but both must contain
940
- `storefront/products`. The baseline may only grow.
941
-
942
- Two detector bugs were found and fixed while writing it, both the same shape:
943
- social links have no endpoint at all (they are derived from the branding
944
- bundle), and the two verification kinds share one endpoint separated by a
945
- `kind` field. Both would have reported "missing" forever no matter what anyone
946
- built. **A gate that cannot pass is worse than no gate** — it teaches people to
947
- ignore it.
948
-
949
- Full coverage means every surface is REACHABLE from every language. It does not
950
- mean the ergonomics match, and the gate cannot tell you that.
951
-
952
- #### The social-platform table is generated, not copied
953
-
954
- 31 platforms — key, label, icon slug, URL prefix — emitted from
955
- `src/socials.ts` into all six languages by `pnpm gen:socials`, with
956
- `gen:socials:check` failing CI when they drift. Six hand-maintained copies
957
- would separate the first time a platform was added, and the copy that drifted
958
- would be whichever language nobody was using that week.
959
-
960
- The resolver around the table stays hand-written per language, because that is
961
- what a developer reads. Generate the data, hand-write the idiom — the same
962
- split the whole non-JS plan uses.
963
-
964
-
965
- #### Exchanges, "it never arrived", and verified-buyer product reviews
966
-
967
- Three gaps in the post-purchase surface, each of which the customer could
968
- experience but not report.
969
-
970
- **Exchanges.** A return request could only ask for money back. Customers asked
971
- for replacements in the free-text `reason`, where nothing could act on it — the
972
- org read prose, then refunded and re-ordered by hand. Return requests now carry
973
- `kind` (`refund` | `exchange`), plus the variant wanted and the shipment that
974
- eventually carries it. `portal.submitExchangeRequest()` routes through the same
975
- call as a return, because an exchange is a return request with a different ask
976
- rather than a separate flow. `kind` defaults to `refund`, so existing callers
977
- mean exactly what they meant. The staff notification now names the actual ask,
978
- since staff reaching for a refund when the customer wanted a replacement is the
979
- miscommunication the field exists to remove.
980
-
981
- **"It never arrived."** `portal.reportNotReceived(shipmentId)` records the
982
- claim — and deliberately does **not** touch the shipment's status.
983
-
984
- The carrier scanned the parcel delivered; the customer says otherwise. Both are
985
- facts, and the carrier's is the more valuable one right then: it is what the org
986
- disputes with, and what decides who absorbs the loss. Overwriting it would
987
- destroy that evidence, quietly redefine "delivered" as "delivered and
988
- undisputed" in the org's own analytics, and lose the race anyway — `status` is
989
- webhook-driven, so a customer-written value survives until the next carrier
990
- event and then looks like the report vanished.
991
-
992
- So the claim sits in its own columns and the result carries both sides, with
993
- `contested: true` when they disagree. A static gate now fails the build if that
994
- route ever assigns a carrier-owned field, because this is a mistake that arrives
995
- by refactor rather than by decision.
996
-
997
- **Product reviews.** The portal could review the ORG but not a PRODUCT, despite
998
- `org_product_reviews` already existing and the storefront already reading it.
999
- `submitProductReview` / `listProductReviews` close that, and require a matching
1000
- order: the portal is standing inside the customer's purchase history, which the
1001
- public storefront is not. Verified-buyer reviews are the ones worth surfacing,
1002
- and it stops a competitor one-starring a catalogue they never bought. Approval
1003
- comes from the org's own moderation policy — the same call the storefront makes,
1004
- so an org that holds reviews does not find portal reviews bypassing it.
1005
-
1006
-
1007
- #### The customer portal can list what a customer has, and talk to staff
1008
-
1009
- Invoices, quotes, contracts and shipments were **detail-by-id only**. Work
1010
- carried the counts, so a customer could be told they had three unpaid invoices
1011
- and had no call that would name them. "Show me everything I owe" was
1012
- unanswerable without faking a list from repeated detail fetches, which is a
1013
- different bug wearing a list's clothes.
1014
-
1015
- Four list routes close it: `loadInvoices({ unpaid })`,
1016
- `loadQuotes({ status })`, `loadContracts({ status })` and
1017
- `loadShipments({ active })`. `unpaid` filters on the computed **balance**, not
1018
- the status string, because a partially-paid invoice still owes money whatever
1019
- it is called. Shipments list **stored** carrier status; `trackShipment(id)`
1020
- still spends a live carrier request, because that is one parcel the customer
1021
- deliberately opened rather than every parcel in their history on every page
1022
- load.
1023
-
1024
- Staff chat lands in the portal too — `loadMessages`, `postMessage`,
1025
- `markMessagesRead`, with `unreadMessageCount` on the snapshot. Both directions
1026
- write the same table the CRM timeline reads, so a customer's question appears
1027
- beside everything else known about them instead of in a parallel inbox nobody
1028
- opens.
1029
-
1030
- Return requests read back on the order, `resolutionNote` included: when staff
1031
- deny a return, their reason *is* the answer, and withholding it turns a decision
1032
- into silence.
1033
-
1034
- #### Products and blog posts can be seeded — the last two things you had to hand-enter
1035
-
1036
- Seeding could push a schema, its records, static collections and coupons. It
1037
- could not create a **product** or a **blog post**, because no package-API route
1038
- existed for either — the dashboard created them over tRPC. So an org could build
1039
- its entire site from code and still had to hand-enter its catalogue, which made
1040
- "seed your site" a half-answer precisely where the content mattered most.
1041
-
1042
- Two routes now exist, and `@businessdash/sdk/seed` exposes them as
1043
- `seedProducts` and `seedBlogPosts`:
1044
-
1045
- - `POST storefront/products` — scope `storefront:write`. Takes the whole tree,
1046
- product plus variants plus cross-variants, and writes it in **one
1047
- transaction**. The dashboard does this in four dependent round trips; making
1048
- a seed script replay that means threading ids by hand with no way to recover
1049
- halfway through. Either the product exists complete, or it does not exist.
1050
- - `POST blog/posts` — scope `blog:write`, a new scope that is deliberately
1051
- **not publishable**: authoring a post is an operator action, never something
1052
- a browser token should be able to do.
1053
-
1054
- Both create **unpublished** by default (`isLive` / `publishNow` are opt-in).
1055
- A seed that silently pushed a half-configured catalogue to a live storefront —
1056
- or drafts to every follower's inbox — is worse than one that needs a second,
1057
- deliberate step.
1058
-
1059
- Products land in your own tables, **not in Stripe**. Pushing the catalogue to
1060
- your Stripe account stays a separate explicit call, because it mints objects in
1061
- an external account you are billed against and that should never be a side
1062
- effect of a seed script.
1063
-
1064
- #### `businessdash seed` — one command for the whole seed
1065
-
1066
- ```bash
1067
- npx tsx node_modules/@businessdash/sdk/dist/cli.js seed [--dry-run] [--yes]
647
+ BiabDevClient -> BdApiClient (the HTTP transport)
648
+ BiabClient -> BdClient (the composed client interface)
649
+ BiabForm, BiabProvider -> BdForm, BdProvider
650
+ BiabApiError and every other error class
651
+ createBiabClient, initBiabAnalytics, mountBiabForm, getBiabEnv, …
652
+ .biab-* CSS classes -> .bd-* (54 of them)
653
+ BIAB_* env vars -> BD_*
654
+ @biab-dev/* packages -> @businessdash/*
1068
655
  ```
1069
656
 
1070
- Reads `businessdash.seed.ts` and runs schema records collections
1071
- products posts coupons in dependency order. It calls the same `runSeed`
1072
- the programmatic API exposes rather than reimplementing it, so CI and a Nuxt
1073
- module behave identically — including the refusal on a destructive schema plan,
1074
- which `--yes` overrides.
1075
-
1076
- #### Fixed: `seedTargetFromEnv()` read a variable nobody sets
1077
-
1078
- It looked for `BIAB_PACKAGE_API_KEY`. The CLI reads `BIAB_API_KEY`, and so does
1079
- every starter's `.env.example` — all fifteen. A project whose CLI worked fine
1080
- would fail to seed from a build step, and fail naming a variable the consumer
1081
- had never seen. It now reads `BIAB_API_KEY` first and falls back to
1082
- `NEXT_PUBLIC_BIAB_PACKAGE_API_BASE_URL` for the base URL, exactly as the CLI
1083
- does. A test pins the two together.
1084
-
1085
- #### The MCP connector mounts on every framework
1086
-
1087
- `mcpHandler` returns Web-standard `(Request) => Promise<Response>`, which is
1088
- right for Next, Astro, Remix and TanStack Start and wrong for everything else:
1089
- SvelteKit hands you `{ request }`, Nitro an H3 event, Qwik City a `RequestEvent`
1090
- you answer by *calling* `send()`, and Express Node's `req`/`res`. Four of eight
1091
- frameworks had to work that out themselves, for a surface whose whole point is
1092
- an org handing Claude or ChatGPT a URL on their own domain.
1093
-
1094
- `@businessdash/sdk/mcp-adapters` ships that bridging: `sveltekitMcpHandler`
1095
- (aliased `astroMcpHandler`), `nitroMcpHandler`, `qwikMcpHandler` and
1096
- `expressMcpHandler`, each with a manifest counterpart. Every one is a
1097
- shape-change over the same handler — none reimplements the proxy, the error
1098
- mapping or the manifest caching, so a fix lands everywhere at once. On Node the
1099
- manifest origin is rebuilt from `Host` and `x-forwarded-proto`, because
1100
- advertising the wrong domain is the one failure this surface cannot tolerate.
1101
-
1102
- #### `@businessdash/sdk/react` now carries its own `"use client"` boundary
1103
-
1104
- React Server Components frameworks treat a module without the directive as
1105
- server code. `react.tsx` holds every stateful component in the SDK and did not
1106
- have it, so importing `<BiabForm>` into a Next App Router page threw *"you're
1107
- importing a component that needs useState"*. The only workaround was a
1108
- hand-written `"use client"` wrapper per component — the Next starter in this
1109
- repo carries sixteen of them.
1110
-
1111
- The directive is now on `react.tsx` and `react-marketing.tsx`, so components
1112
- import directly into a server-rendered page. Every export in those files is
1113
- browser-only anyway: the components hold state, and the four programmatic
1114
- helpers (`signIn`, `signUp`, `signOut`, `requestPasswordReset`) each return
1115
- early on `typeof window === "undefined"` because they navigate.
1116
-
1117
- Existing wrappers keep working — this removes the need for new ones, it does
1118
- not invalidate old ones. Bundlers that don't implement the directive treat it
1119
- as an inert string expression.
1120
-
1121
- #### Preact is supported, and verified rather than assumed
1122
-
1123
- `@businessdash/sdk/react` runs unmodified under Preact via the standard
1124
- `preact/compat` alias, which means Preact gets the **full fourteen-capability
1125
- surface** — not the forms-only subset every non-React binding has — for the
1126
- cost of a bundler alias rather than a port.
1127
-
1128
- Two properties make it work, and both are now gated. The binding imports nine
1129
- runtime symbols from `react` (`createContext`, `Fragment`, `useCallback`,
1130
- `useContext`, `useEffect`, `useMemo`, `useRef`, `useState`,
1131
- `useSyncExternalStore`), all present in `preact/compat`; and it never imports
1132
- `react-dom`, so nothing pulls in React's reconciler.
1133
-
1134
- `test/preact-compat.test.ts` bundles the real source twice through esbuild —
1135
- once against React, once with `react` aliased to `preact/compat` — renders the
1136
- same trees on both and asserts identical markup, covering the pure-render path,
1137
- the context path and the `useSyncExternalStore` subscription path. The only
1138
- tolerated difference is inline-style serialisation (`margin:0` vs
1139
- `margin:0px`), which computes identically.
1140
-
1141
- `preact` and `preact-render-to-string` are devDependencies; nothing new ships.
1142
-
1143
- #### The customer portal, natively, on every framework
1144
-
1145
- `@businessdash/sdk/portal` — `createPortalController` over the portal's 34
1146
- methods: work, jobs, quotes, contracts, invoices, orders, downloads, shipping,
1147
- reviews, referrals, profile and notification preferences. Until now the only
1148
- way to render any of it was `<Dashboard>`, a hosted page in an iframe, so a
1149
- business could not put their own customer portal on their own domain.
1150
-
1151
- Four properties of the surface shape the controller, all from the audit in
1152
- `Resources/BIAB-Customer-Portal-SDK-Audit.md`:
1153
-
1154
- - **`sessionToken` is required.** Every route is scoped to the signed-in
1155
- customer, and without one the calls are unauthenticated — which fails quietly
1156
- enough to look like an empty portal rather than a signed-out one. The
1157
- controller takes the token up front and calls `withSession` itself.
1158
- - **There are no list endpoints** for invoices, quotes, contracts or shipments.
1159
- `work` carries the counts and each opens by id. The controller does NOT fake
1160
- a list from repeated detail fetches; the gap stays visible where someone can
1161
- decide to close it in the API.
1162
- - **Nothing can be deleted.** There is no delete action anywhere, because there
1163
- is none in the API — and a test asserts the controller never grows one.
1164
- - **A submitted review is queued for moderation.** It is deliberately not added
1165
- to `reviews`; `reviewAwaitingModeration` is set instead, so a portal can say
1166
- "awaiting review" rather than implying it is live.
1167
-
1168
- Supporting detail never blanks the thing the customer asked for: a job renders
1169
- even when its activity, ETA and comments all fail, and an order renders when it
1170
- has no digital downloads (which most orders don't).
1171
-
1172
- #### Social links on every framework
1173
-
1174
- The last gap, and the only one that was never about logic — every binding
1175
- already had `resolveSocialProfiles`. What was missing was markup, which cannot
1176
- be shared the way a controller can.
1177
-
1178
- `@businessdash/sdk/social` shares everything up to the markup: the resolved
1179
- rows, the icon URL and the handful of layout styles the list needs. Each
1180
- binding writes the six elements around it — a render function for Vue (rather
1181
- than an SFC, which would put it back outside the typechecker), a standalone
1182
- component for Angular, an SFC for Svelte, JSX for React, Solid and Qwik, an
1183
- imperative `mountSocialLinks` for plain DOM, and `<biab-social-links>` for the
1184
- web-component entry.
1185
-
1186
- React and Solid were refactored onto it, so the CDN path and colour convention
1187
- now live in one place rather than three.
1188
-
1189
- #### Marketing pages and verification on every framework
1190
-
1191
- Two final cores. Twelve of the fourteen capabilities are now native on all
1192
- eight bindings, and Solid joins React at the full fourteen.
1193
-
1194
- **`@businessdash/sdk/marketing`** — `createMarketingController` holds the page
1195
- index and loads one page at a time, because a site can have many marketing
1196
- pages and a visitor reads one. `prefetch()` warms a page you expect next; its
1197
- failures are swallowed, since a wrong guess must never surface as an error on
1198
- the page actually being read.
1199
-
1200
- **`@businessdash/sdk/verify`** — `createVerifyController` is the email/phone
1201
- state machine: request a token, confirm it, handle it being wrong. One
1202
- controller serves both kinds; `kind` selects which.
1203
-
1204
- Two details the React components had buried and that are now explicit:
1205
-
1206
- - **`flow` is on the snapshot.** The server decides per request whether to send
1207
- a one-time code or an emailed link. A binding that assumes a code field would
1208
- leave every link-flow visitor staring at a box waiting for a code that is
1209
- never coming.
1210
- - **A rejected code is retryable, not fatal.** The confirm endpoint answers
1211
- `{ ok: true }` or rejects, so a rejection is the ordinary wrong-code path: the
1212
- step stays `awaiting-code` and the visitor can try again.
1213
-
1214
- The resend cooldown lives in the controller rather than the binding. Left to
1215
- eight bindings, one of them gets it wrong and a visitor can spam themselves
1216
- with texts.
1217
-
1218
- #### Checkout, customer auth and followers on every framework
1219
-
1220
- Two more cores and bindings for all eight adapters, taking the count of
1221
- capabilities every binding has natively from six to nine.
1222
-
1223
- **`@businessdash/sdk/checkout`** — `createCheckoutController` mints a Stripe
1224
- session and resolves the outcome. `start()` deliberately does NOT navigate; it
1225
- returns the session and leaves `redirect()` as a separate call, because the
1226
- previous React component set `window.location.href` itself, which is fine in a
1227
- browser and wrong in a native app, an SSR render or a test.
1228
-
1229
- `resolve(sessionId)` reads the payment status from the server. A `session_id`
1230
- in the return URL proves the customer came back, not that they paid — that
1231
- distinction is the point of the method and has its own test.
1232
-
1233
- **`@businessdash/sdk/account`** — `createUserController` (the signed-in
1234
- customer, via the auth handler's `/me`, never an API key) and
1235
- `createFollowersController` (subscribe/unsubscribe plus the local
1236
- already-subscribed hint, which is a convenience for hiding a footer form and
1237
- explicitly not authority — `me()` is).
1238
-
1239
- Sign-in / sign-up / sign-out are navigations rather than requests, so they ship
1240
- as `signIn` / `signUp` / `signOut` plus `signInHref` / `signUpHref` /
1241
- `signOutHref` for bindings that render their own links. All of them no-op
1242
- outside a browser rather than throwing.
1243
-
1244
- React and Solid were REFACTORED onto these rather than left alone, so all eight
1245
- bindings share one implementation. That removed 104 lines of hand-rolled fetch
1246
- and local-storage handling from the Solid binding alone.
1247
-
1248
- #### Native blog on every framework — nothing is iframe-only any more
1249
-
1250
- `@businessdash/sdk/blog` — `createBlogController` owns the post list, paging,
1251
- category filtering, the active post, comments and likes. Bindings on all eight
1252
- adapters: React/Vue/Qwik `useBlog`, Solid/Svelte/Angular/vanilla/element
1253
- `createBlog`.
1254
-
1255
- Blog was the last surface with no native path anywhere, React included — the
1256
- worst place to lose control of markup, since the content is what readers and
1257
- search engines came for and an iframe hides it.
1258
-
1259
- Two behaviours worth calling out, both tested:
1260
-
1261
- - **Posting a comment re-reads the thread instead of inserting locally.** A
1262
- comment may be held for moderation, and splicing a local copy in would show
1263
- its author an approved-looking comment nobody else can see.
1264
- - **A likes outage never blanks the article.** Like state is fetched after the
1265
- post and its failure is swallowed, because a decoration must not take down
1266
- the thing it decorates.
657
+ Two names needed a decision rather than a pattern. `BiabClient` (the
658
+ interface a consumer holds) and `BiabDevClient` (the HTTP transport under it)
659
+ would both have become `BdClient`; the interface keeps the plain name and the
660
+ transport is now **`BdApiClient`**.
1267
661
 
1268
- #### Every framework binding is now typechecked
662
+ **Upgrading.** Every rename is mechanical — a find-and-replace of `Biab` to
663
+ `Bd` and `biab` to `bd` covers all of it except `BiabDevClient`, which
664
+ becomes `BdApiClient`. If you style the form components, the CSS hooks moved
665
+ from `.biab-*` to `.bd-*` in the same way.
1269
666
 
1270
- `@builder.io/qwik` is a devDependency and `tsconfig.qwik.json` checks the Qwik
1271
- binding, which closes the last hole: Solid, Qwik, Vue, Svelte and Angular were
1272
- all excluded from tsc and therefore never verified. `pnpm typecheck` runs four
1273
- configs.
667
+ **`./biab-forms.css` still resolves.** The stylesheet subpath is exported
668
+ under both names, so a stylesheet import does not have to move in lockstep
669
+ with the code.
1274
670
 
1275
- Qwik's first run found two real pre-existing errors, both
1276
- `exactOptionalPropertyTypes` violations where an optional JSX attribute was
1277
- passed as `undefined` rather than omitted.
671
+ Three things deliberately did NOT change. `biab.app` is the API host and is
672
+ served forever. The `biab.app/schemas/*` URLs are JSON-Schema `$id`s —
673
+ identifiers rather than addresses — so changing one would break validation
674
+ for anything pinned to it. And the historical note that this was formerly
675
+ Business In A Box stays true.
1278
676
 
1279
- #### Native storefront, cart and chat on EVERY framework
677
+ #### Proposals: the document a customer is sent, not just the quote
1280
678
 
1281
- Two new framework-agnostic cores, and a binding for each of the eight adapters.
1282
- Storefront, cart and chat are no longer iframe-only anywhere.
679
+ `customerProposalSchema`, `listProposals()` and `getProposal()`.
1283
680
 
1284
- **`@businessdash/sdk/store`**`createStoreController` owns products,
1285
- pagination, category filtering, the product detail, the cart, quantities and
1286
- coupons. Prices cross the boundary as integer minor units and are never
1287
- divided, rounded or formatted by the controller.
681
+ A proposal is the document built around a quote either a rendered PDF or a
682
+ live page and `kind` tells you which, so a UI can offer a download or frame
683
+ a page rather than guessing and being wrong half the time. Only rendered
684
+ proposals appear: before that it is a rep's working copy, possibly at a price
685
+ nobody has agreed, and a customer refreshing the list should not watch it
686
+ take shape.
1288
687
 
1289
- **`@businessdash/sdk/chat`** `createChatController` owns the transcript,
1290
- pending state, UI actions, availability and the human-escalation call. Lifted
1291
- out of React's `useChatbot` rather than rewritten, so React's behaviour is
1292
- preserved and now backs every other binding.
688
+ It carries no template, bindings or resolved snapshot. Those describe how an
689
+ org builds proposals; a customer's client has no business reading them.
1293
690
 
1294
- Both follow the `createFormController` contract exactly: `snapshot()`,
1295
- `subscribe()` (which does NOT replay), `destroy()`, and actions.
691
+ #### The customer quote detail carries the proposal narrative
1296
692
 
1297
- Per-framework bindings, all thin views over those cores:
693
+ `customerQuoteDetailSchema` was dropping `sections` the cover letter, scope
694
+ of work and terms that make a quote a proposal — so a consumer could fetch
695
+ one and receive only line items. It now carries them, along with the decision
696
+ state (`canAccept`, `expired`, and the sent/accepted/rejected timestamps) so
697
+ a client does not have to re-derive whether accepting is still possible.
1298
698
 
1299
- | Framework | Storefront | Chat |
1300
- |---|---|---|
1301
- | React | `useStorefront` | `useChatbot` (now core-backed) |
1302
- | Solid | `createStorefront` | `createChat` |
1303
- | Vue | `useStorefront` | `useChat` |
1304
- | Svelte | `createStorefront` | `createChat` |
1305
- | Qwik | `useStorefront` | `useChat` |
1306
- | Angular | `createStorefront` | `createChat` |
1307
- | Vanilla / element | `createStorefront` | `createChat` |
1308
-
1309
- The vanilla binding is the controllers themselves — there is no reactivity
1310
- system to bridge — which makes it the reference the other seven wrap.
1311
-
1312
- Qwik's controllers are wrapped in `noSerialize` and created inside
1313
- `useVisibleTask$`. That is not stylistic: a controller holds a client, a
1314
- subscriber set and timers, and Qwik would otherwise try to serialise it into
1315
- the HTML on pause and throw.
1316
-
1317
- #### The parity gate now checks HOW a capability is delivered
1318
-
1319
- `capabilities` in `framework-support.ts` records `native`, `embed` or `both`
1320
- per capability, and the gate derives the same levels from the source by reading
1321
- implementations rather than export names. Recorded as booleans, an iframe
1322
- wrapper looked identical to real components.
1323
-
1324
- It also asserts each binding REACHES the core behind every capability it claims
1325
- — level-aware, so the iframe path requires `embed-protocol` and the native path
1326
- requires `store-core` / `chat-core` — and that the postMessage origin check
1327
- exists in exactly one file. A second, subtly weaker copy of that check would be
1328
- a security bug affecting one framework's users only.
1329
-
1330
- #### Vue, Svelte and Angular headless bindings are typechecked
1331
-
1332
- Those bindings are excluded from the main tsconfig because of SFC and decorator
1333
- syntax. Their new headless modules are plain `.ts`, so `tsconfig.headless.json`
1334
- checks them properly; `pnpm typecheck` now runs the main, Solid and headless
1335
- configs. It caught a real generic-inference bug on the first run: the
1336
- snapshot type was resolving to `unknown`, which would have stripped types from
1337
- every Vue and Angular consumer.
1338
-
1339
- #### Solid gains most of the product surface
1340
-
1341
- `@businessdash/sdk/solid` went from forms-only to eleven of fourteen surfaces:
1342
- storefront, cart, checkout, blog, the customer portal, the chat widget, tenant
1343
- auth, followers and social links, alongside the forms binding it already had.
1344
-
1345
- Most of that was cheap for a reason worth knowing: `Storefront`, `Cart`,
1346
- `Checkout`, `Blog`, `Chatbot` and `Dashboard` are BusinessDash-hosted pages in a
1347
- sandboxed iframe, in React exactly as in Solid. Two primitives —
1348
- `createEmbedSession` and an internal `EmbedIframe` — carry all six, and each
1349
- surface is a dozen lines on top of them.
1350
-
1351
- New Solid exports: `Storefront`, `Cart`, `Checkout`, `CheckoutResult`, `Blog`,
1352
- `Chatbot`, `Dashboard`, `DashboardProvider`, `useDashboardSession`, `SignIn`,
1353
- `SignUp`, `SignOut`, `signIn`, `signUp`, `signOut`, `createUser`,
1354
- `createFollowers`, `SocialLinks`, `createEmbedSession`.
1355
-
1356
- Not ported: marketing pages, and the email/phone verification flows. The
1357
- headless chat pair (`useChatbot` + `<ChatbotInline>`) also remains React-only —
1358
- Solid gets the iframe widget, not the build-your-own-UI surface.
1359
-
1360
- #### The Solid binding is typechecked for the first time
1361
-
1362
- Source-shipped bindings are excluded from the main `tsconfig`, because tsc here
1363
- is configured for React's JSX dialect. Excluded also meant never checked.
1364
- `tsconfig.solid.json` now typechecks it under Solid's own pragma, and `pnpm
1365
- typecheck` runs both — so it is a release gate.
1366
-
1367
- It found two real errors on its first run, both in the pre-existing forms
1368
- binding and both the same Solid hazard: calling an accessor twice inside a
1369
- ternary, so the guard narrows the first read and the second is still
1370
- possibly-undefined. One of them could have passed an undefined client into
1371
- `createFormController`.
1372
-
1373
- #### Shared cores extracted, so bindings cannot fork
1374
-
1375
- `embed-protocol.ts` and `auth-links.ts` now own the postMessage protocol and
1376
- origin check, the session-refresh schedule, the auth-handler URL contract and
1377
- the follower storage format. React was refactored onto them rather than the
1378
- Solid binding copying them across.
1379
-
1380
- The origin check is the reason this matters: it decides whether a message from
1381
- an arbitrary window may drive a cart or a checkout. A second, subtly weaker copy
1382
- would be a security bug affecting one framework's users only, so a release gate
1383
- asserts it exists in exactly one file.
1384
-
1385
- #### The parity matrix now records HOW, not just whether
1386
-
1387
- `capabilities` changed from a list to a map of capability to level: `native`
1388
- (components in your DOM), `embed` (a hosted page in an iframe) or `both`.
1389
-
1390
- The distinction is not cosmetic. Recorded as booleans, Solid's iframe chat
1391
- widget looked equal to React's, which additionally ships ~1,200 lines of
1392
- headless chat UI. It also surfaces something that reads the other way: storefront,
1393
- cart and blog are `embed` on **every** binding including React, so on those
1394
- surfaces the non-React bindings were never behind.
1395
-
1396
- The release gate derives the level from the source — it reads implementations,
1397
- not export names — and asserts the manifest and the published docs table agree.
1398
-
1399
- #### Framework capability matrix is now published data
1400
-
1401
- `@businessdash/sdk/framework-support` gained a `capabilities` field per adapter
1402
- plus `CAPABILITY_LABELS`, `ALL_CAPABILITIES` and `REACT_TARGETS`. This makes an
1403
- uncomfortable fact discoverable instead of implicit: **`react` exposes all 14
1404
- capability areas and every other binding exposes forms**, plus in most cases a
1405
- shared client. Chat, customer auth, storefront, cart, checkout, blog, the
1406
- customer portal, marketing pages, followers, social links and the verification
1407
- flows are React-only.
699
+ `items` was `z.array(z.unknown())`. It is typed now: no consumer should have
700
+ to guess a line item's shape before rendering a row.
1408
701
 
1409
- `REACT_TARGETS` documents that Next, Astro, Remix / React Router 7 and TanStack
1410
- Start are not lesser targets — they render React, import
1411
- `@businessdash/sdk/react`, and get the full surface. React Native uses
1412
- `@businessdash/sdk/native`.
702
+ #### React's marketing pages run on the shared core
1413
703
 
1414
- A release gate now asserts the code, the manifest and the published docs table
1415
- all agree, so the framework page cannot advertise a capability an adapter
1416
- doesn't export.
704
+ `useMarketingPage`, `useMarketingPagesList` and `useMarketingPagePreloader`
705
+ reimplemented the fetch, the status flag and the in-flight guard that
706
+ `marketing-core` already had, so a fix in the core never reached React. They
707
+ are thin shells over `createMarketingController` now, with the
708
+ snapshot-to-view derivation moved into the core as `marketingPageView()` and
709
+ `marketingListView()` for every binding to share.
1417
710
 
1418
- #### Contract fixes found by auditing the SDK against the platform
1419
-
1420
- Four places where the SDK's schemas disagreed with what the platform actually
1421
- sends. All are fixes to existing behaviour, not new surface.
711
+ Public signatures are unchanged. The synchronous cache read stays in React,
712
+ because the controller does not duplicate the client's cache and without that
713
+ read an already-cached page flashes "loading".
1422
714
 
1423
- **Form definitions were losing most of their render contract.**
1424
- `biab.chatbot.getForm()` / `getChatForm()` stripped `settings`, `orgIcon`,
1425
- `actions` and `businessFunctions` — the server sent all four, the schema
1426
- declared none of them. Anyone building a form UI on the typed client got a
1427
- bare field list with no animation, no multi-step or progress configuration, no
1428
- exit confirmation, no branded header, and no way to compute `calculation`
1429
- fields. All four are now declared, and the schema passes unknown fields
1430
- through so the next server addition reaches you instead of vanishing.
715
+ ### 0.9.81
1431
716
 
1432
- **Dry-run submissions threw.** A validate-only submit legitimately returns
1433
- `submissionId: null`, but the schema required a string, so every test submit
1434
- raised a parse error. It is nullable now, `dryRun` is returned alongside it,
1435
- and the submit input accepts `dryRun`, `source`, `referrer` and `metadata` —
1436
- all of which the route already supported and the typed client could not send.
717
+ #### The `createBdClient` facade serves the legal and sitemap routes
1437
718
 
1438
- **Gallery items were losing their description** through the marketing bundle.
719
+ The facade lacked `legal` and `site`, so any starter handing it to
720
+ `resolveLegalPage()` or `buildSitemap()` failed to typecheck against the
721
+ published package — the template fleet caught T3-App, Qwik and Svelte on
722
+ 0.9.80. `client.legal` and `client.site(siteId)` now exist on the facade,
723
+ mirroring the raw `BdApiClient`, and compile-time contract guards in
724
+ `sdk.ts` fail `typecheck` if a helper-required surface is ever dropped
725
+ again.
1439
726
 
1440
- Docs: **Developer By feature Framework Support**.
727
+ #### Sitemaps actually include blog posts and products now
1441
728
 
1442
- #### Coupons from the SDK, live in your Stripe account
729
+ `SitemapClientLike` described responses as `{ posts }` / `{ products }`,
730
+ but the real client serves `{ items }` — the collector's destructure came
731
+ back undefined, the loop threw, and every real sitemap silently reported
732
+ those sections as "unreachable". The contract and collector now consume
733
+ the shapes the API actually serves.
1443
734
 
1444
- `biab.coupons.create()` makes a discount code and mints it in **your own**
1445
- Stripe account in the same call — a Coupon plus a Promotion Code, so the same
1446
- string works on your storefront and on Stripe-hosted Checkout.
735
+ #### Shipped source compiles on consumers' older TypeScript
1447
736
 
1448
- ```ts
1449
- const { coupon, stripeSynced } = await biab.coupons.create({
1450
- code: 'WELCOME10',
1451
- discountType: 'percent',
1452
- value: 10,
1453
- })
1454
- ```
1455
-
1456
- Creation and the Stripe sync are one operation deliberately. A coupon that
1457
- exists locally but not in Stripe is the failure worth designing out: your
1458
- storefront accepts the code and Stripe rejects it, stranding the customer at
1459
- payment. If the sync fails the coupon is still created and `stripeSynced` comes
1460
- back `false` with the reason, rather than pretending it is live.
1461
-
1462
- `biab.coupons.list()` returns every coupon with its redemption count and Stripe
1463
- ids. Both need a **secret** key holding the new `storefront:write` scope —
1464
- `validate()` is unchanged and still works with a publishable token.
737
+ `push-core` used the `Uint8Array<ArrayBuffer>` generic, which only parses
738
+ on TS 5.7+ consumers whose toolchains typecheck the shipped source
739
+ (Qwik's does) failed to build. The annotation is gone; the one
740
+ `BufferSource` call site casts instead.
1465
741
 
1466
- Buy-X-get-Y and bundle-gift coupons stay dashboard-only: Stripe has no native
1467
- BXGY, so they run through our own cart math and need offer rows the API
1468
- doesn't collect.
1469
-
1470
- Docs: **Developer → By feature → Store → Coupons**.
742
+ ### 0.9.80
1471
743
 
1472
- #### Framework version support is now declared, not implied
744
+ #### Tracking consent `@businessdash/sdk/consent`
1473
745
 
1474
- The SDK ships form bindings for eight targets, and until now said nothing
1475
- about which versions of those frameworks it was built for — `package.json`
1476
- declared peer ranges for React only. A developer on a brand-new framework
1477
- major got no signal either way.
746
+ There was no consent mechanism anywhere: `initBdAnalytics()` started
747
+ unconditionally and analytics, attribution and AEO all collected regardless.
1478
748
 
1479
- Every adapter now declares its range as an optional peer dependency, so your
1480
- package manager warns on a mismatch instead of failing later with a confusing
1481
- build error. The same data is importable:
749
+ `loadConsent()` returns what a visitor currently allows, at THIS org, and
750
+ `recordConsent()` stores their answer. Gate analytics on it:
1482
751
 
1483
752
  ```ts
1484
- import { FRAMEWORK_SUPPORT, supportFor } from '@businessdash/sdk/framework-support'
753
+ const consent = await loadConsent({ client })
754
+ if (consent.allows('analytics')) initBdAnalytics({ siteId, baseUrl, apiKey })
755
+ if (consent.mustAsk) showYourBanner(consent)
1485
756
  ```
1486
757
 
1487
- Each entry also names the framework APIs its binding depends on, which is how
1488
- we assess whether a framework's major actually breaks us a diff against a
1489
- list rather than a reread of the adapter.
1490
-
1491
- **When a framework ships a breaking major, we add a second adapter for it
1492
- rather than replacing the first.** Both ship, the docs show both, and code on
1493
- either major keeps working.
1494
-
1495
- **Solid 2.0 (August 2026)** reworked a substantial part of the framework API.
1496
- `@businessdash/sdk/solid` is written against the **1.x** dialect and is not yet
1497
- verified on 2.x; a `v2` dialect is planned alongside it. On Solid 2.0 today,
1498
- `@businessdash/sdk/element` or `@businessdash/sdk/vanilla` give you the same
1499
- form behaviour with no framework dependency.
1500
-
1501
- Docs: **Developer → By feature → Framework Support**.
1502
-
1503
-
1504
- #### Push your catalog to your own Stripe account
758
+ `initBdAnalytics` is deliberately NOT made to call this itself a gate that
759
+ fires implicitly is one nobody can see in review, and what happens before
760
+ consent (render nothing, render a placeholder, collect essential-only) is the
761
+ site's decision, not ours.
1505
762
 
1506
- `biab.storefront.syncToStripe()` takes the products you manage in
1507
- BusinessDash and creates them in **your own** Stripe account each product as
1508
- a Stripe Product, each variant combination as a Price. It is the same operation
1509
- as the dashboard's "Sync to Stripe" button, now available to your code, so you
1510
- can run it on your own schedule, in a deploy step, or immediately after
1511
- creating a product.
763
+ **Consent is per-org and never travels.** A visitor who refuses at org 2 has
764
+ said nothing to org 1, and one who consents at org 1 has NOT consented to org 2
765
+ that second half is the one with legal teeth. The visitor key lives in a
766
+ first-party cookie on the org's own domain, so two sites cannot see each
767
+ other's, and the isolation is the browser's rather than something we remember
768
+ to scope.
1512
769
 
1513
- ```ts
1514
- const result = await biab.storefront.syncToStripe()
1515
- console.log(result.synced) // [{ productId, stripeProductId, combinationsSynced }]
1516
- console.log(result.remaining) // products still awaiting a sync
1517
- ```
770
+ **The gate fails closed.** Any failure returns deny-everything: a gate that
771
+ fails open collects data nobody agreed to, and only one of those outcomes is an
772
+ incident.
1518
773
 
1519
- Pass `productId` to sync one product; omit it to sync everything not yet
1520
- pushed, in bounded batches — check `remaining` and call again until it reaches
1521
- zero. Large catalogs are paged deliberately rather than risking a timeout
1522
- halfway through.
1523
-
1524
- Re-running is safe. An existing Stripe Product is updated rather than
1525
- duplicated, and a new Price is minted only when the amount actually changed
1526
- (Stripe Prices are immutable on amount). A product that fails comes back in
1527
- `failed` with its reason instead of abandoning the rest of the batch.
1528
-
1529
- Requires a **secret** key holding the new `storefront:write` scope — never a
1530
- publishable token, because this spends against Stripe on your behalf — plus
1531
- the `ecommerce.storefront` entitlement and a connected Stripe account. The
1532
- endpoint is rate-limited more tightly than the rest of the package API for the
1533
- same reason.
1534
-
1535
- Docs: **Developer → By feature → Store → Sync to Stripe**.
1536
-
1537
- ### 0.9.60 — Seed Export, a GraphQL schema for your data model, and the complete artifact
1538
-
1539
- #### The package is now `@businessdash/sdk`
1540
-
1541
- This release publishes under the new **`@businessdash/sdk`** name (formerly
1542
- `@biab-dev/sdk`), matching the platform's move to BusinessDash. Nothing about
1543
- the API surface changes: same exports, same subpaths, same contracts. The
1544
- `biab-dev` CLI name keeps working, with `businessdash` added as an alias, and
1545
- existing `biab.config.ts` / `biab.data-model.config.ts` files are picked up
1546
- forever (`businessdash.*` variants are now preferred for new projects).
1547
- `@biab-dev/sdk` on npm stays installable at ≤0.9.53 with a deprecation notice
1548
- pointing here.
1549
-
1550
- #### Seed Export — your org's custom database, on your machine
1551
-
1552
- `npx biab-dev export-data-model` writes a complete, versionable snapshot of the
1553
- org's custom data model — every object, every field, and (non-sensitive)
1554
- records — to the developer's disk. Two jobs: a real backup you control before a
1555
- big migration, and ground truth an AI agent can read while building, so it
1556
- knows what exists without touching production. `--schema-only` for structure
1557
- without contents; pair with the "Download seed" surface in the dashboard.
1558
-
1559
- #### `@businessdash/sdk/graphql` — a GraphQL SDL for the same model
1560
-
1561
- `npx biab-dev export-graphql-schema` generates GraphQL SDL from the org's data
1562
- model (`--include-standard` folds in the platform's standard objects), and the
1563
- new `./graphql` entrypoint carries the supporting types. Schema-first for now —
1564
- the SDL is the contract your tooling, codegen, and agents can consume while the
1565
- executable query layer lands.
1566
-
1567
- **If you installed 0.9.53 from npm, this is the release you actually wanted.**
1568
- The npm artifact published as 0.9.53 predated the tree it claimed to be: its
1569
- `exports` map lacked `./distribution`, `./mcp`, `./native`, and
1570
- `./analytics-server`, and its shipped `src/` was missing files — so a fresh
1571
- install broke every template importing the AEO/MCP entrypoints under strict
1572
- resolvers (vite 7.3.6+, vite 8, rolldown, turbopack, tsc). This release ships
1573
- the full surface the 0.9.53 notes below describe.
1574
-
1575
- Compilation fixes for source-consuming frameworks (the `./angular`, `./qwik`,
1576
- `./svelte`, `./solid`, and `./vue` subpaths resolve to `src/` by design, so SDK
1577
- source must compile under consumer tsconfigs):
1578
-
1579
- - Every index-signature property access rewritten to bracket form (140 sites) —
1580
- SDK source now compiles clean under `noPropertyAccessFromIndexSignature`
1581
- (Angular's default strict template config).
1582
- - Qwik `<textarea>` used the React-style `autoComplete` attribute; qwik 1.20's
1583
- JSX types require `autocomplete`.
1584
- - Qwik `ServerCompatQRL` now names the payload-only handler shapes explicitly —
1585
- `server$((payload) => …)` from qwik-city ^1.20 assigns again (QRL's invariant
1586
- type parameter hides ordinary parameter-narrowing compatibility).
1587
- - **Packaging:** the tarball now ships the complete `src/` tree. The previous
1588
- `files` allowlist shipped `src/sdk.ts` without the modules it imports, which
1589
- broke every source-consuming framework (`Could not resolve './client.js'`).
1590
- A pack-closure CI guard now fails any release whose shipped sources reference
1591
- files missing from the tarball.
774
+ Also ships `client.consent.get()` / `.record()` as the transport underneath,
775
+ and `browserOptOut()` for DNT and Global Privacy Control.
1592
776
 
1593
777
  ## Legacy Docs
1594
778