@designcrowd/library.brand-page 6.2.3 → 6.2.4

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 (112) hide show
  1. package/lib/src/{CreatedOnBrandCrowd-de-de-Cn0M-eZ1.js → CreatedOnBrandCrowd-de-de-ystrO2LW.js} +1 -1
  2. package/lib/src/{CreatedOnBrandCrowd-en-us-DRLMSDZr.js → CreatedOnBrandCrowd-en-us-BU7hNzTZ.js} +1 -1
  3. package/lib/src/{CreatedOnBrandCrowd-es-es-BglTu73U.js → CreatedOnBrandCrowd-es-es-CbuxneQ3.js} +1 -1
  4. package/lib/src/{CreatedOnBrandCrowd-fr-fr-Bj6YZ_el.js → CreatedOnBrandCrowd-fr-ca-BuNUGOFr.js} +1 -1
  5. package/lib/src/{CreatedOnBrandCrowd-fr-ca-Bj6YZ_el.js → CreatedOnBrandCrowd-fr-fr-BuNUGOFr.js} +1 -1
  6. package/lib/src/{CreatedOnBrandCrowd-pt-pt-DEoOmQ1F.js → CreatedOnBrandCrowd-pt-br-BehL88mc.js} +1 -1
  7. package/lib/src/{CreatedOnBrandCrowd-pt-br-DEoOmQ1F.js → CreatedOnBrandCrowd-pt-pt-BehL88mc.js} +1 -1
  8. package/lib/src/{CreatedOnDcom-de-de-BZ7gTt4Z.js → CreatedOnDcom-de-de-CMxjFZt9.js} +1 -1
  9. package/lib/src/{CreatedOnDcom-en-us-CD6Km5sH.js → CreatedOnDcom-en-us-2Cf6WBvx.js} +1 -1
  10. package/lib/src/{CreatedOnDcom-es-es-CNpZHvEl.js → CreatedOnDcom-es-es-CATNnbTq.js} +1 -1
  11. package/lib/src/{CreatedOnDcom-fr-ca-URS-ydJ1.js → CreatedOnDcom-fr-ca-DQtMfB-a.js} +1 -1
  12. package/lib/src/{CreatedOnDcom-fr-fr-D2AVeA3H.js → CreatedOnDcom-fr-fr-C1_FWOQf.js} +1 -1
  13. package/lib/src/{CreatedOnDcom-pt-br-C32frnFs.js → CreatedOnDcom-pt-br-C0QOCW0L.js} +1 -1
  14. package/lib/src/{CreatedOnDcom-pt-pt-TDYGjRXS.js → CreatedOnDcom-pt-pt-lHD-p6o4.js} +1 -1
  15. package/lib/src/brand-page/models/brand-page-context.model.d.ts +5 -0
  16. package/lib/src/brand-page/models/brand-page-context.model.js +8 -1
  17. package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.d.ts +21 -0
  18. package/lib/src/brand-page-maker/components/brandpage-maker-page/BrandPageMakerPage.mixin.d.ts +21 -0
  19. package/lib/src/brand-page-maker/components/preview/Preview.mixin.d.ts +7 -0
  20. package/lib/src/brand-page-maker/components/quick-link-bar/QuickLinkBar.mixin.d.ts +21 -0
  21. package/lib/src/brand-page-maker/components/seo-settings/SeoPageSettings.mixin.d.ts +22 -1
  22. package/lib/src/brand-page-maker/components/seo-settings/UrlSettings.mixin.d.ts +21 -0
  23. package/lib/src/brand-page-maker/components/seo-settings/UseExistingDomainModal.mixin.d.ts +21 -0
  24. package/lib/src/brand-page-maker/components/template-preview/TemplatePreview.mixin.d.ts +21 -0
  25. package/lib/src/brand-page-maker/components/upgrade-to-premium-button/UpgradeToPremiumButton.mixin.d.ts +21 -0
  26. package/lib/src/brand-page-maker/composables/ai-chat/ai-builder-experiment.d.ts +10 -0
  27. package/lib/src/brand-page-maker/composables/ai-chat/ai-builder-experiment.js +11 -0
  28. package/lib/src/brand-page-maker/composables/ai-chat/useAiWebsiteBuilderChat.js +14 -4
  29. package/lib/src/brand-page-maker/stores/ai-thread/ai-website-builder-thread-store.d.ts +9 -2
  30. package/lib/src/brand-page-maker/stores/ai-thread/ai-website-builder-thread-store.js +20 -0
  31. package/lib/src/brand-page-maker/stores/brandpage-container/brand-page-container-store.d.ts +21 -0
  32. package/lib/src/clients/ai-assistant-client.d.ts +1 -0
  33. package/lib/src/clients/ai-assistant-client.js +1 -0
  34. package/lib/src/index.mjs +224 -223
  35. package/lib/src/{lib-Dm9AuOJL.js → lib-CqHUUPUW.js} +21920 -21691
  36. package/lib/src/metadata-builder/builders/global-font-propagation.d.ts +41 -0
  37. package/lib/src/metadata-builder/builders/global-font-propagation.js +146 -0
  38. package/lib/src/metadata-builder/builders/global-styles-builder.js +29 -1
  39. package/lib/src/metadata-builder/builders/header-builder.js +8 -0
  40. package/lib/src/metadata-builder/builders/reorder-pages-builder.d.ts +13 -0
  41. package/lib/src/metadata-builder/builders/reorder-pages-builder.js +49 -0
  42. package/lib/src/metadata-builder/builders/section-builders/index.d.ts +1 -0
  43. package/lib/src/metadata-builder/builders/section-builders/index.js +1 -0
  44. package/lib/src/metadata-builder/builders/section-builders/support-me-builder.d.ts +8 -0
  45. package/lib/src/metadata-builder/builders/section-builders/support-me-builder.js +25 -0
  46. package/lib/src/metadata-builder/builders/section-builders/text-section-builder.js +5 -2
  47. package/lib/src/metadata-builder/builders/section-field-maps.js +15 -0
  48. package/lib/src/metadata-builder/builders/section-field-utils.d.ts +6 -1
  49. package/lib/src/metadata-builder/builders/section-field-utils.js +48 -12
  50. package/lib/src/metadata-builder/builders/section-rules/support-me.rules.d.ts +2 -0
  51. package/lib/src/metadata-builder/builders/section-rules/support-me.rules.js +17 -0
  52. package/lib/src/metadata-builder/helpers/rte.helpers.d.ts +9 -0
  53. package/lib/src/metadata-builder/helpers/rte.helpers.js +60 -1
  54. package/lib/src/metadata-builder/metadata-executor.js +3 -1
  55. package/lib/src/metadata-builder/models.d.ts +2 -0
  56. package/lib/src/metadata-builder/models.js +2 -0
  57. package/lib/src/metadata-builder/parsers/ai-toolcall-parser.d.ts +1 -0
  58. package/lib/src/metadata-builder/parsers/ai-toolcall-parser.js +4 -0
  59. package/lib/src/metadata-builder/parsers/section-tool-arguments.d.ts +14 -2
  60. package/lib/src/shared/content-settings/components/GenericTextInput.mixin.d.ts +1 -1
  61. package/lib/src/shared/content-settings/components/email-field/EmailField.mixin.d.ts +1 -1
  62. package/lib/src/shared/content-settings/components/payment-config-field/PaymentConfigField.mixin.d.ts +21 -0
  63. package/lib/src/shared/content-settings/components/tel-field/TelField.mixin.d.ts +1 -1
  64. package/lib/src/shared/content-settings/components/text-field/TextField.mixin.d.ts +1 -1
  65. package/lib/src/shared/content-settings/components/textarea-field/TextareaField.mixin.d.ts +1 -1
  66. package/lib/src/shared/content-settings/components/url-field/UrlField.mixin.d.ts +1 -1
  67. package/lib/src/shared/content-settings/components/youtube-video-field/YoutubeVideoField.mixin.d.ts +1 -1
  68. package/lib/src/shared/logo-settings/components/LogoSettings.mixin.d.ts +21 -0
  69. package/lib/src/shared/page-settings/components/delete-page-modal/DeletePageModal.mixin.d.ts +1 -1
  70. package/lib/src/shared/page-settings/components/page-settings/PageSettings.mixin.d.ts +21 -0
  71. package/lib/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.d.ts +21 -0
  72. package/lib/src/shared/publish-brand-page-with-state/PublishBrandPageModalWithState.mixin.d.ts +28 -0
  73. package/lib/src/shared/side-bar/components/SideBar.mixin.d.ts +21 -0
  74. package/lib/src/shared/style-settings/button-style-preset.d.ts +12 -0
  75. package/lib/src/shared/style-settings/button-style-preset.js +39 -0
  76. package/lib/src/shared/style-settings/components/StyleSettings.mixin.d.ts +21 -0
  77. package/lib/src/shared/style-settings/store/style-settings-store.js +6 -32
  78. package/lib/src/utils/minify-brand-page-metadata.d.ts +9 -0
  79. package/lib/src/utils/minify-brand-page-metadata.js +23 -1
  80. package/package.json +1 -12
  81. package/src/brand-page/models/brand-page-context.model.ts +14 -1
  82. package/src/brand-page-maker/components/ai-website-builder/AiWebsiteBuilder.vue +1 -1
  83. package/src/brand-page-maker/composables/ai-chat/ai-builder-experiment.test.ts +82 -0
  84. package/src/brand-page-maker/composables/ai-chat/ai-builder-experiment.ts +19 -0
  85. package/src/brand-page-maker/composables/ai-chat/useAiWebsiteBuilderChat.ts +18 -4
  86. package/src/brand-page-maker/stores/ai-thread/ai-website-builder-thread-store.ts +30 -0
  87. package/src/clients/ai-assistant-client.ts +1 -0
  88. package/src/metadata-builder/builders/global-font-propagation.test.ts +401 -0
  89. package/src/metadata-builder/builders/global-font-propagation.ts +238 -0
  90. package/src/metadata-builder/builders/global-styles-builder.test.ts +107 -0
  91. package/src/metadata-builder/builders/global-styles-builder.ts +38 -1
  92. package/src/metadata-builder/builders/header-builder.test.ts +45 -0
  93. package/src/metadata-builder/builders/header-builder.ts +20 -0
  94. package/src/metadata-builder/builders/reorder-pages-builder.ts +80 -0
  95. package/src/metadata-builder/builders/section-builders/index.ts +1 -0
  96. package/src/metadata-builder/builders/section-builders/support-me-builder.test.ts +177 -0
  97. package/src/metadata-builder/builders/section-builders/support-me-builder.ts +43 -0
  98. package/src/metadata-builder/builders/section-builders/text-section-builder.ts +5 -2
  99. package/src/metadata-builder/builders/section-field-maps.ts +18 -0
  100. package/src/metadata-builder/builders/section-field-utils.test.ts +83 -0
  101. package/src/metadata-builder/builders/section-field-utils.ts +69 -18
  102. package/src/metadata-builder/builders/section-rules/support-me.rules.ts +39 -0
  103. package/src/metadata-builder/helpers/rte.helpers.ts +82 -1
  104. package/src/metadata-builder/metadata-executor.ts +3 -0
  105. package/src/metadata-builder/models.ts +2 -0
  106. package/src/metadata-builder/parsers/ai-toolcall-parser.ts +4 -0
  107. package/src/metadata-builder/parsers/section-tool-arguments.ts +14 -1
  108. package/src/shared/style-settings/button-style-preset.test.ts +67 -0
  109. package/src/shared/style-settings/button-style-preset.ts +56 -0
  110. package/src/shared/style-settings/store/style-settings-store.ts +9 -38
  111. package/src/utils/minify-brand-page-metadata.test.ts +166 -1
  112. package/src/utils/minify-brand-page-metadata.ts +31 -1
@@ -224,7 +224,13 @@ const minifyContent = (content, editor, assets) => {
224
224
  })),
225
225
  }),
226
226
  ...(includeStyle(c.textStyle) && { textStyle: c.textStyle }),
227
- ...includeBackgroundStyle(c.backgroundStyle),
227
+ // A text section keeps its band on the flat `backgroundColor` key — as a
228
+ // `{ backgroundColor }` object or a bare string — not on `backgroundStyle`,
229
+ // so the shared helper saw nothing and the AI never knew the band (BP-6081).
230
+ ...includeBackgroundStyle(c.backgroundStyle ??
231
+ (typeof c.backgroundColor === 'string'
232
+ ? { backgroundColor: c.backgroundColor }
233
+ : c.backgroundColor)),
228
234
  };
229
235
  case 'profile':
230
236
  return {
@@ -359,11 +365,18 @@ const minifyContent = (content, editor, assets) => {
359
365
  case 'logo':
360
366
  return base;
361
367
  case 'request-payment':
368
+ return {
369
+ ...base,
370
+ ...st('title', c.title, editor),
371
+ ...st('description', c.description, editor),
372
+ };
362
373
  case 'support-me':
363
374
  return {
364
375
  ...base,
365
376
  ...st('title', c.title, editor),
366
377
  ...st('description', c.description, editor),
378
+ ...st('successMessage', c.successMessage, editor),
379
+ ...(includeString(c.displayStyle) && { displayStyle: c.displayStyle }),
367
380
  };
368
381
  default:
369
382
  return base;
@@ -387,6 +400,15 @@ const minifyFooter = (footer, logoAssetKey) => {
387
400
  url: l.url || '',
388
401
  }))
389
402
  : undefined,
403
+ backgroundStyle: footer.backgroundStyle?.backgroundColor
404
+ ? { backgroundColor: footer.backgroundStyle.backgroundColor }
405
+ : undefined,
406
+ copyrightStyle: footer.copyrightStyle?.color
407
+ ? { color: footer.copyrightStyle.color }
408
+ : undefined,
409
+ iconStyle: footer.iconStyle?.fill
410
+ ? { fill: footer.iconStyle.fill }
411
+ : undefined,
390
412
  };
391
413
  };
392
414
  // Fully opaque (100) is the default, so it's omitted to keep the AI payload small.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@designcrowd/library.brand-page",
3
3
  "description": "A collection of Maker/Designer applications",
4
- "version": "6.2.3",
4
+ "version": "6.2.4",
5
5
  "author": "Design.com Eng",
6
6
  "license": "UNLICENSED",
7
7
  "type": "module",
@@ -48,10 +48,6 @@
48
48
  "copy:css": "copyfiles -fF ./public/css/global.css ./public/css/reset.css ./lib/src/css/",
49
49
  "test": "vitest run",
50
50
  "test:watch": "vitest watch",
51
- "test:e2e": "vitest run --reporter=basic -c vitest.e2e.config.ts",
52
- "test:e2e:watch": "vitest watch --reporter=basic -c vitest.e2e.config.ts",
53
- "docker:test:e2e": "docker-compose -f docker-compose-coverage.yml build e2e && docker-compose -f docker-compose-coverage.yml run e2e",
54
- "docker:test:e2e:env": "docker-compose -f docker-compose-coverage.yml build e2e-test-env && docker-compose -f docker-compose-coverage.yml run e2e-test-env",
55
51
  "coverage": "echo \"To be migrated to vitest\"",
56
52
  "storybook": "npm run clean:vite && npm run storybook:css && npm run storybook:themes && npm run storybook:start",
57
53
  "storybook:themes": "node .storybook/tailwind.themes.cjs",
@@ -63,10 +59,6 @@
63
59
  "watch": "npm run clean:lib && run-p watch:lib watch:css build:bp:typings",
64
60
  "watch:lib": "NODE_ENV=development npm run build:bp:vite -- --watch",
65
61
  "watch:css": "NODE_ENV=development nodemon --config nodemon.css.json",
66
- "cy:run:local": "cypress run --browser chrome --headless --config \"baseUrl=http://localhost:8080\"",
67
- "cy:run:ci": "cypress run --browser chrome --headless --config \"baseUrl=https://maker.designcrowd.ninja\"",
68
- "cy:report": "npx junit-merge -o ./cypress/results/report.xml ./cypress/results/*.xml",
69
- "cy:open": "cypress open",
70
62
  "pre-push": "npm run test"
71
63
  },
72
64
  "dependencies": {
@@ -152,7 +144,6 @@
152
144
  },
153
145
  "devDependencies": {
154
146
  "@faker-js/faker": "8.4.0",
155
- "@frsource/cypress-plugin-visual-regression-diff": "3.3.10",
156
147
  "@nuxt/test-utils": "4.0.0",
157
148
  "@nuxt/types": "2.18.1",
158
149
  "@nuxt/typescript-build": "3.0.2",
@@ -193,11 +184,9 @@
193
184
  "cors": "2.8.6",
194
185
  "cross-env": "7.0.3",
195
186
  "css-loader": "6.9.1",
196
- "cypress": "13.6.3",
197
187
  "eslint": "8.56.0",
198
188
  "eslint-config-prettier": "9.1.0",
199
189
  "eslint-friendly-formatter": "4.0.1",
200
- "eslint-plugin-cypress": "2.15.1",
201
190
  "eslint-plugin-prettier": "5.5.5",
202
191
  "eslint-plugin-vue": "9.21.1",
203
192
  "file-loader": "6.2.0",
@@ -46,12 +46,16 @@ export interface AbTestResult {
46
46
  isExcluded: boolean;
47
47
  }
48
48
 
49
- const AB_TEST_KEYS = {
49
+ export const AB_TEST_KEYS = {
50
50
  BP_3926_FREEMIUM_WATERMARK: 'bc-bp-3926-freemium-cta-in-website-editor',
51
+ // V1 (control) vs V2 (variation-1) AI website builder, sent by bc-net in the editor
52
+ // payload at load. Key must match `Bp6136V2AiBuilderStrategy.CampaignKey`.
53
+ BP_6136_V2_AI_BUILDER: 'bc-bp-6136-v2-ai-builder',
51
54
  };
52
55
 
53
56
  export interface AbTest {
54
57
  bp_3926_freemium_watermark?: AbTestResult;
58
+ bp_6136_v2_ai_builder?: AbTestResult;
55
59
  }
56
60
 
57
61
  export const MEDIA_SIZES = {
@@ -132,5 +136,14 @@ export const getAbTestResults = (abtestKeyValues: AbTest): AbTest => {
132
136
  );
133
137
  }
134
138
 
139
+ // AB Test - V1 vs V2 AI website builder
140
+
141
+ if (abtestKeyValues[AB_TEST_KEYS.BP_6136_V2_AI_BUILDER]) {
142
+ abTests.bp_6136_v2_ai_builder = getValueType(
143
+ AB_TEST_KEYS.BP_6136_V2_AI_BUILDER,
144
+ abtestKeyValues[AB_TEST_KEYS.BP_6136_V2_AI_BUILDER],
145
+ );
146
+ }
147
+
135
148
  return abTests;
136
149
  };
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <div class="ai-container">
2
+ <div data-component="ai-website-builder" class="ai-container">
3
3
  <div class="tw-px-5">
4
4
  <SideBarSettingsHeader
5
5
  title="AI Website Builder"
@@ -0,0 +1,82 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import { createPinia, setActivePinia } from 'pinia';
3
+ import { aiBuilderAgentTypeForArm } from './ai-builder-experiment';
4
+ import { getAbTestResults } from '../../../brand-page/models/brand-page-context.model';
5
+ import { useAiWebsiteBuilderThreadStore } from '../../stores';
6
+
7
+ // Must match `Bp6136V2AiBuilderStrategy` in bc-net and the LaunchDarkly flag.
8
+ const CAMPAIGN = 'bc-bp-6136-v2-ai-builder';
9
+
10
+ // The arm arrives as the raw bc-net campaign string, so go through the real parser.
11
+ const armFor = (value?: string) =>
12
+ getAbTestResults(value ? ({ [CAMPAIGN]: value } as never) : {})
13
+ .bp_6136_v2_ai_builder;
14
+
15
+ describe('BP-6136 arm to AI builder agent type', () => {
16
+ it('variation-1 gets the V2 builder', () => {
17
+ expect(aiBuilderAgentTypeForArm(armFor('bp-6136-variation-1'))).toBe(
18
+ 'website_builder_v2',
19
+ );
20
+ });
21
+
22
+ it('control stays on V1', () => {
23
+ expect(aiBuilderAgentTypeForArm(armFor('bp-6136-control'))).toBe(
24
+ 'website_builder',
25
+ );
26
+ });
27
+
28
+ it('excluded stays on V1', () => {
29
+ expect(aiBuilderAgentTypeForArm(armFor('bp-6136-excluded'))).toBe(
30
+ 'website_builder',
31
+ );
32
+ });
33
+
34
+ it('no arm at all (pre-purchase, AI tab off, older bc-net) stays on V1', () => {
35
+ expect(aiBuilderAgentTypeForArm(armFor(undefined))).toBe('website_builder');
36
+ expect(aiBuilderAgentTypeForArm(undefined)).toBe('website_builder');
37
+ });
38
+ });
39
+
40
+ describe('thread store agent type', () => {
41
+ beforeEach(() => {
42
+ setActivePinia(createPinia());
43
+ });
44
+
45
+ it('starts on V1, the production default', () => {
46
+ expect(useAiWebsiteBuilderThreadStore().agentType).toBe('website_builder');
47
+ });
48
+
49
+ it('switching before any thread exists keeps the pending prompt and does not reset', () => {
50
+ const threadStore = useAiWebsiteBuilderThreadStore();
51
+
52
+ threadStore.promptText = 'draft the user is typing';
53
+
54
+ threadStore.setAgentType('website_builder_v2');
55
+
56
+ expect(threadStore.agentType).toBe('website_builder_v2');
57
+ expect(threadStore.promptText).toBe('draft the user is typing');
58
+ expect(threadStore.resetToken).toBe(0);
59
+ });
60
+
61
+ it('switching under a running thread drops that thread', () => {
62
+ const threadStore = useAiWebsiteBuilderThreadStore();
63
+
64
+ threadStore.threadId = 'thread-on-v1';
65
+
66
+ threadStore.setAgentType('website_builder_v2');
67
+
68
+ expect(threadStore.threadId).toBeNull();
69
+ expect(threadStore.resetToken).toBe(1);
70
+ });
71
+
72
+ it('setting the same agent again is a no-op', () => {
73
+ const threadStore = useAiWebsiteBuilderThreadStore();
74
+
75
+ threadStore.threadId = 'thread-on-v1';
76
+
77
+ threadStore.setAgentType('website_builder');
78
+
79
+ expect(threadStore.threadId).toBe('thread-on-v1');
80
+ expect(threadStore.resetToken).toBe(0);
81
+ });
82
+ });
@@ -0,0 +1,19 @@
1
+ import type { AbTestResult } from '../../../brand-page/models/brand-page-context.model';
2
+ import {
3
+ AI_ASSISTANT_AGENT_TYPES,
4
+ type AiAssistantAgentType,
5
+ } from '../../../clients/ai-assistant-client';
6
+
7
+ /**
8
+ * A/B test: control keeps the V1 AI website builder, variation-1 gets V2.
9
+ *
10
+ * bc-net (WebsiteMakerInteractor) buckets post-purchase users who get the AI tab at
11
+ * editor load and sends the arm in `abTests`. No arm, control and excluded all stay
12
+ * on V1, so a missing value is today's production behaviour.
13
+ */
14
+ export const aiBuilderAgentTypeForArm = (
15
+ arm: AbTestResult | undefined,
16
+ ): AiAssistantAgentType =>
17
+ arm?.isVariation
18
+ ? AI_ASSISTANT_AGENT_TYPES.WEBSITE_BUILDER_V2
19
+ : AI_ASSISTANT_AGENT_TYPES.WEBSITE_BUILDER;
@@ -3,7 +3,6 @@ import type { BrandPageModel } from '../../../brand-page/models';
3
3
  import { getCurrentLocale } from '../../../composables/useTranslate';
4
4
  import {
5
5
  getInstance as getAiAssistantClient,
6
- AI_ASSISTANT_AGENT_TYPES,
7
6
  type AiAssistantResponse,
8
7
  type AiAssistantToolResult,
9
8
  type AiAssistantToolCall,
@@ -38,6 +37,7 @@ import { useHistoryStore } from '../../stores/history/history-store';
38
37
  import { useUndoRedoFlag } from '../useUndoRedoFlag';
39
38
  import { useBrandPageMakerUserStore } from '../../stores/user/user-store';
40
39
  import { useAiMetadataBuilder } from './useAiMetadataBuilder';
40
+ import { aiBuilderAgentTypeForArm } from './ai-builder-experiment';
41
41
  import {
42
42
  pickErrorMessage,
43
43
  pickWsErrorMessage,
@@ -529,6 +529,16 @@ export function useAiWebsiteBuilderChat(): UseAiChatResult {
529
529
  ? 'premium'
530
530
  : 'free';
531
531
 
532
+ if (!aiThreadStore.threadId) {
533
+ // The A/B arm bc-net bucketed at editor load decides which builder owns the
534
+ // new thread; the message and tool-result calls below reuse it.
535
+ aiThreadStore.setAgentType(
536
+ aiBuilderAgentTypeForArm(
537
+ containerStore.abTests.bp_6136_v2_ai_builder,
538
+ ),
539
+ );
540
+ }
541
+
532
542
  let buildResponse;
533
543
 
534
544
  if (!aiThreadStore.threadId) {
@@ -536,7 +546,7 @@ export function useAiWebsiteBuilderChat(): UseAiChatResult {
536
546
  message,
537
547
  websiteContext,
538
548
  websiteSnapshot: snapshotStore.snapshotBase64 || undefined,
539
- agentType: AI_ASSISTANT_AGENT_TYPES.WEBSITE_BUILDER,
549
+ agentType: aiThreadStore.agentType,
540
550
  userPlan,
541
551
  connectionId,
542
552
  ...buildAttributionFields(),
@@ -548,7 +558,7 @@ export function useAiWebsiteBuilderChat(): UseAiChatResult {
548
558
  message,
549
559
  websiteContext,
550
560
  websiteSnapshot: snapshotStore.snapshotBase64 || undefined,
551
- agentType: AI_ASSISTANT_AGENT_TYPES.WEBSITE_BUILDER,
561
+ agentType: aiThreadStore.agentType,
552
562
  userPlan,
553
563
  connectionId,
554
564
  ...buildAttributionFields(),
@@ -752,11 +762,15 @@ export function useAiWebsiteBuilderChat(): UseAiChatResult {
752
762
  retryCount,
753
763
  });
754
764
 
765
+ // Tool submissions must go to the SAME agent that ran the thread
766
+ // (AI QA 2026-07-05 T19: a mismatched agentType routes tool results
767
+ // to a service with no state for the thread and fails the build).
768
+ // The thread store holds this thread's agent (A/B arm: V1 control, V2 variation-1).
755
769
  const submitResult = await client.submitToolResultsBuildAsync(
756
770
  aiThreadStore.threadId!,
757
771
  {
758
772
  toolCalls: toolResults,
759
- agentType: AI_ASSISTANT_AGENT_TYPES.WEBSITE_BUILDER,
773
+ agentType: aiThreadStore.agentType,
760
774
  connectionId,
761
775
  retryCount,
762
776
  userPlan,
@@ -1,5 +1,9 @@
1
1
  import { defineStore } from 'pinia';
2
2
  import { ref } from 'vue';
3
+ import {
4
+ AI_ASSISTANT_AGENT_TYPES,
5
+ type AiAssistantAgentType,
6
+ } from '../../../clients/ai-assistant-client';
3
7
 
4
8
  export const useAiWebsiteBuilderThreadStore = defineStore(
5
9
  'aiWebsiteBuilderThread',
@@ -14,6 +18,14 @@ export const useAiWebsiteBuilderThreadStore = defineStore(
14
18
  const promptText = ref<string>('');
15
19
  const stopGeneration = ref<(() => void) | null>(null);
16
20
 
21
+ // Which builder this thread talks to. V1 (control) is the default, what production
22
+ // runs today; the chat composable sets V2 for the variation-1 arm when a thread
23
+ // starts (aiBuilderAgentTypeForArm). Every send site reads it, because tool results
24
+ // must reach the agent that owns the thread (AI QA 2026-07-05 T19).
25
+ const agentType = ref<AiAssistantAgentType>(
26
+ AI_ASSISTANT_AGENT_TYPES.WEBSITE_BUILDER,
27
+ );
28
+
17
29
  // Bumped on every reset so consumers (useAiWebsiteBuilderChat) can react
18
30
  // without watching `messages`, which would loop with its own deep watch.
19
31
  const resetToken = ref<number>(0);
@@ -35,6 +47,22 @@ export const useAiWebsiteBuilderThreadStore = defineStore(
35
47
  resetToken.value += 1;
36
48
  };
37
49
 
50
+ const setAgentType = (type: AiAssistantAgentType) => {
51
+ if (agentType.value === type) {
52
+ return;
53
+ }
54
+
55
+ const hadThread = threadId.value !== null;
56
+
57
+ agentType.value = type;
58
+
59
+ // V1 and V2 threads are not interchangeable, so a thread already running on
60
+ // the other agent is dropped. Before the first prompt there is nothing to drop.
61
+ if (hadThread) {
62
+ reset();
63
+ }
64
+ };
65
+
38
66
  return {
39
67
  threadId,
40
68
  runId,
@@ -45,6 +73,8 @@ export const useAiWebsiteBuilderThreadStore = defineStore(
45
73
  buildSectionsCompleted,
46
74
  promptText,
47
75
  stopGeneration,
76
+ agentType,
77
+ setAgentType,
48
78
  resetToken,
49
79
  reset,
50
80
  };
@@ -30,6 +30,7 @@ export interface WebsiteContext {
30
30
  // Agent types
31
31
  export const AI_ASSISTANT_AGENT_TYPES = {
32
32
  WEBSITE_BUILDER: 'website_builder',
33
+ WEBSITE_BUILDER_V2: 'website_builder_v2',
33
34
  } as const;
34
35
 
35
36
  export type AiAssistantAgentType =