@designcrowd/library.brand-page 6.2.13-remove-v1-1 → 6.2.14

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 (49) hide show
  1. package/lib/src/{CreatedOnBrandCrowd-de-de-CoNPRkJy.js → CreatedOnBrandCrowd-de-de-Deoc6s8r.js} +1 -1
  2. package/lib/src/{CreatedOnBrandCrowd-en-us-D3099egH.js → CreatedOnBrandCrowd-en-us-BxDzsLl-.js} +1 -1
  3. package/lib/src/{CreatedOnBrandCrowd-es-es-DDKTS6CI.js → CreatedOnBrandCrowd-es-es-B-5VS2MV.js} +1 -1
  4. package/lib/src/{CreatedOnBrandCrowd-fr-fr-FbE-W5IO.js → CreatedOnBrandCrowd-fr-ca-C6h05sUZ.js} +1 -1
  5. package/lib/src/{CreatedOnBrandCrowd-fr-ca-FbE-W5IO.js → CreatedOnBrandCrowd-fr-fr-C6h05sUZ.js} +1 -1
  6. package/lib/src/{CreatedOnBrandCrowd-pt-pt-BG2-KFrl.js → CreatedOnBrandCrowd-pt-br-BC6XU3ev.js} +1 -1
  7. package/lib/src/{CreatedOnBrandCrowd-pt-br-BG2-KFrl.js → CreatedOnBrandCrowd-pt-pt-BC6XU3ev.js} +1 -1
  8. package/lib/src/{CreatedOnDcom-de-de-Br79PFya.js → CreatedOnDcom-de-de-dUTedh8v.js} +1 -1
  9. package/lib/src/{CreatedOnDcom-en-us-lqpcQj-7.js → CreatedOnDcom-en-us-DY0jkOeW.js} +1 -1
  10. package/lib/src/{CreatedOnDcom-es-es-B9OAEcGO.js → CreatedOnDcom-es-es-DRioej2A.js} +1 -1
  11. package/lib/src/{CreatedOnDcom-fr-ca-CJJLSYYb.js → CreatedOnDcom-fr-ca-CcglfcUU.js} +1 -1
  12. package/lib/src/{CreatedOnDcom-fr-fr-D5_ODr63.js → CreatedOnDcom-fr-fr-B7v_e6HI.js} +1 -1
  13. package/lib/src/{CreatedOnDcom-pt-br-BjK1aS8b.js → CreatedOnDcom-pt-br-5XGcKJb7.js} +1 -1
  14. package/lib/src/{CreatedOnDcom-pt-pt-DtH-sGF_.js → CreatedOnDcom-pt-pt-DepC53tO.js} +1 -1
  15. package/lib/src/brand-page/utils/container-text-color.util.d.ts +12 -2
  16. package/lib/src/brand-page/utils/container-text-color.util.js +1 -1
  17. package/lib/src/brand-page/utils/migration/migration-22.d.ts +3 -2
  18. package/lib/src/brand-page/utils/migration/migration-22.js +8 -3
  19. package/lib/src/brand-page-maker/composables/ai-chat/aiErrorCopy.d.ts +1 -1
  20. package/lib/src/brand-page-maker/composables/ai-chat/aiErrorCopy.js +2 -2
  21. package/lib/src/brand-page-maker/composables/ai-chat/useAiWebsiteBuilderChat.js +2 -2
  22. package/lib/src/brand-page-maker/models/content-settings.js +3 -1
  23. package/lib/src/clients/websocket-gateway-client.d.ts +5 -1
  24. package/lib/src/index.mjs +1 -1
  25. package/lib/src/{lib-b7uNnxx-.js → lib-CidddZOL.js} +2811 -2802
  26. package/lib/src/metadata-builder/builders/section-field-maps.d.ts +1 -1
  27. package/lib/src/metadata-builder/builders/section-field-maps.js +1 -1
  28. package/lib/src/metadata-builder/builders/undo-builder.d.ts +2 -2
  29. package/lib/src/metadata-builder/builders/undo-builder.js +2 -2
  30. package/lib/src/metadata-builder/parsers/section-tool-arguments.d.ts +2 -2
  31. package/lib/src/metadata-builder/parsers/section-tool-arguments.js +1 -1
  32. package/package.json +1 -1
  33. package/src/brand-page/utils/container-text-color.util.ts +3 -3
  34. package/src/brand-page/utils/migration/__snapshots__/migration-22-output/new-brandcontact-every-section.v22.json +4 -8
  35. package/src/brand-page/utils/migration/__snapshots__/migration-22-output/new-brandpage-every-section.v22.json +4 -8
  36. package/src/brand-page/utils/migration/__snapshots__/migration-22-output/new-brandsite-every-section.v22.json +4 -8
  37. package/src/brand-page/utils/migration/__snapshots__/migration-22-output/real-brandcontact.v22.json +1 -2
  38. package/src/brand-page/utils/migration/__snapshots__/migration-22-output/real-brandpage-general-link.v22.json +3 -6
  39. package/src/brand-page/utils/migration/__snapshots__/migration-22-output/real-brandpage-links.v22.json +1 -2
  40. package/src/brand-page/utils/migration/migration-22.test.ts +88 -0
  41. package/src/brand-page/utils/migration/migration-22.ts +9 -2
  42. package/src/brand-page-maker/composables/ai-chat/aiErrorCopy.ts +2 -2
  43. package/src/brand-page-maker/composables/ai-chat/useAiWebsiteBuilderChat.ts +2 -2
  44. package/src/brand-page-maker/models/content-settings.test.ts +49 -1
  45. package/src/brand-page-maker/models/content-settings.ts +3 -1
  46. package/src/clients/websocket-gateway-client.ts +5 -0
  47. package/src/metadata-builder/builders/section-field-maps.ts +1 -1
  48. package/src/metadata-builder/builders/undo-builder.ts +2 -2
  49. package/src/metadata-builder/parsers/section-tool-arguments.ts +4 -4
@@ -2,7 +2,7 @@
2
2
  * Maps AI tool call argument names → BrandPageModel content field names
3
3
  * per section type.
4
4
  *
5
- * Reference: service.ai-pipelines/src/agents/websiteV2/platform/tool_schemas/
5
+ * Reference: service.ai-pipelines/src/agents/website/section_schemas.py
6
6
  * Reference: library.brand-page/src/brand-page/models/index.ts
7
7
  */
8
8
  export type SectionFieldMap = Record<string, string>;
@@ -2,7 +2,7 @@
2
2
  * Maps AI tool call argument names → BrandPageModel content field names
3
3
  * per section type.
4
4
  *
5
- * Reference: service.ai-pipelines/src/agents/websiteV2/platform/tool_schemas/
5
+ * Reference: service.ai-pipelines/src/agents/website/section_schemas.py
6
6
  * Reference: library.brand-page/src/brand-page/models/index.ts
7
7
  */
8
8
  /**
@@ -32,10 +32,10 @@ export interface UndoPayload {
32
32
  * can observe that an undo was executed (useful for telemetry, UI messaging,
33
33
  * and audit logs).
34
34
  *
35
- * Contract guarantee from the AI pipeline (WebsiteV2 main agent): the `undo`
35
+ * Contract guarantee from the AI pipeline (Website Planner WL9): the `undo`
36
36
  * tool call is exclusive — it never co-occurs with build/update/delete tool
37
37
  * calls in the same response. If that contract is ever violated upstream,
38
- * fix the agent — do not add defensive ordering here. The sequential
38
+ * fix the planner — do not add defensive ordering here. The sequential
39
39
  * MetadataExecutor will apply any subsequent builders on top of the restored
40
40
  * snapshot, which is the semantically correct "undo-then-apply" ordering.
41
41
  */
@@ -9,10 +9,10 @@ import { BaseMetadataBuilder, } from '../models';
9
9
  * can observe that an undo was executed (useful for telemetry, UI messaging,
10
10
  * and audit logs).
11
11
  *
12
- * Contract guarantee from the AI pipeline (WebsiteV2 main agent): the `undo`
12
+ * Contract guarantee from the AI pipeline (Website Planner WL9): the `undo`
13
13
  * tool call is exclusive — it never co-occurs with build/update/delete tool
14
14
  * calls in the same response. If that contract is ever violated upstream,
15
- * fix the agent — do not add defensive ordering here. The sequential
15
+ * fix the planner — do not add defensive ordering here. The sequential
16
16
  * MetadataExecutor will apply any subsequent builders on top of the restored
17
17
  * snapshot, which is the semantically correct "undo-then-apply" ordering.
18
18
  */
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Typed interfaces for website builder section tool arguments.
3
3
  *
4
- * Canonical reference: service.ai-pipelines/src/agents/websiteV2/platform/tool_schemas/
4
+ * Canonical reference: service.ai-pipelines/src/agents/website/section_schemas.py
5
5
  * Every field defined there has a corresponding TS field here.
6
6
  */
7
7
  import type { ContentType, CanonicalTextStyleType } from '../../brand-page/models';
@@ -81,7 +81,7 @@ export interface TextStyleArguments {
81
81
  * the APPLICATION is per-section: every section's tool call writes the
82
82
  * same site-decided hex into this field.
83
83
  *
84
- * See `ai-pipelines/src/agents/websiteV2/platform/tools/section_types_tool.py`
84
+ * See `ai-pipelines/src/agents/website/tools/section_types_tool.py`
85
85
  * (`_button_text_style_field`) for the AI-side mirror.
86
86
  */
87
87
  export interface ButtonTextStyleArguments {
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Typed interfaces for website builder section tool arguments.
3
3
  *
4
- * Canonical reference: service.ai-pipelines/src/agents/websiteV2/platform/tool_schemas/
4
+ * Canonical reference: service.ai-pipelines/src/agents/website/section_schemas.py
5
5
  * Every field defined there has a corresponding TS field here.
6
6
  */
7
7
  /** Type guard: checks if a value is a TiptapDocument object. */
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.13-remove-v1-1",
4
+ "version": "6.2.14",
5
5
  "author": "Design.com Eng",
6
6
  "license": "UNLICENSED",
7
7
  "type": "module",
@@ -9,8 +9,8 @@
9
9
  * reason `resolveTextColor` lives in one place (`text-style.util.ts`).
10
10
  *
11
11
  * MIGRATION-22 SNAPSHOT DEPENDENCY: the v21 -> v22 colour bake calls
12
- * `containerTextColorDefaults` and `titleSurfaceContrastFill`. Pin that migration's behaviour
13
- * before changing either helper after it releases.
12
+ * `containerTextColorDefaults`, `titleSurfaceContrastFill` and `isButtonLabelField`. Pin that
13
+ * migration's behaviour before changing any of them after it releases.
14
14
  */
15
15
  import type {
16
16
  Content,
@@ -180,7 +180,7 @@ const NON_SECTION_SURFACE_KEYS = new Set<ContentTextStylePropertyKey>([
180
180
  * contrast the PAGE instead and collide with the fill (the default theme paints both from the same
181
181
  * brand role, hiding the text).
182
182
  */
183
- const isButtonLabelField = (
183
+ export const isButtonLabelField = (
184
184
  content: Content,
185
185
  key: ContentTextStylePropertyKey,
186
186
  ): boolean => key === 'titleStyle' && isButtonContent(content);
@@ -646,8 +646,7 @@
646
646
  "textStyleType": "button",
647
647
  "textAlign": "center",
648
648
  "lineHeight": "1.5",
649
- "letterSpacing": "0",
650
- "color": "var(--palette-1)"
649
+ "letterSpacing": "0"
651
650
  },
652
651
  "url": "",
653
652
  "colorReplacements": [],
@@ -665,8 +664,7 @@
665
664
  "textStyleType": "button",
666
665
  "textAlign": "center",
667
666
  "lineHeight": "1.5",
668
- "letterSpacing": "0",
669
- "color": "var(--palette-1)"
667
+ "letterSpacing": "0"
670
668
  },
671
669
  "url": "",
672
670
  "colorReplacements": [],
@@ -684,8 +682,7 @@
684
682
  "textStyleType": "button",
685
683
  "textAlign": "center",
686
684
  "lineHeight": "1.5",
687
- "letterSpacing": "0",
688
- "color": "var(--palette-1)"
685
+ "letterSpacing": "0"
689
686
  },
690
687
  "url": "",
691
688
  "colorReplacements": [],
@@ -708,8 +705,7 @@
708
705
  "textStyleType": "button",
709
706
  "textAlign": "center",
710
707
  "lineHeight": "1.5",
711
- "letterSpacing": "0",
712
- "color": "var(--palette-1)"
708
+ "letterSpacing": "0"
713
709
  },
714
710
  "fieldRole": "button"
715
711
  },
@@ -646,8 +646,7 @@
646
646
  "textStyleType": "button",
647
647
  "textAlign": "center",
648
648
  "lineHeight": "1.5",
649
- "letterSpacing": "0",
650
- "color": "var(--palette-1)"
649
+ "letterSpacing": "0"
651
650
  },
652
651
  "url": "",
653
652
  "colorReplacements": [],
@@ -665,8 +664,7 @@
665
664
  "textStyleType": "button",
666
665
  "textAlign": "center",
667
666
  "lineHeight": "1.5",
668
- "letterSpacing": "0",
669
- "color": "var(--palette-1)"
667
+ "letterSpacing": "0"
670
668
  },
671
669
  "url": "",
672
670
  "colorReplacements": [],
@@ -684,8 +682,7 @@
684
682
  "textStyleType": "button",
685
683
  "textAlign": "center",
686
684
  "lineHeight": "1.5",
687
- "letterSpacing": "0",
688
- "color": "var(--palette-1)"
685
+ "letterSpacing": "0"
689
686
  },
690
687
  "url": "",
691
688
  "colorReplacements": [],
@@ -708,8 +705,7 @@
708
705
  "textStyleType": "button",
709
706
  "textAlign": "center",
710
707
  "lineHeight": "1.5",
711
- "letterSpacing": "0",
712
- "color": "var(--palette-1)"
708
+ "letterSpacing": "0"
713
709
  },
714
710
  "fieldRole": "button"
715
711
  },
@@ -646,8 +646,7 @@
646
646
  "textStyleType": "button",
647
647
  "textAlign": "center",
648
648
  "lineHeight": "1.5",
649
- "letterSpacing": "0",
650
- "color": "var(--palette-1)"
649
+ "letterSpacing": "0"
651
650
  },
652
651
  "url": "",
653
652
  "colorReplacements": [],
@@ -665,8 +664,7 @@
665
664
  "textStyleType": "button",
666
665
  "textAlign": "center",
667
666
  "lineHeight": "1.5",
668
- "letterSpacing": "0",
669
- "color": "var(--palette-1)"
667
+ "letterSpacing": "0"
670
668
  },
671
669
  "url": "",
672
670
  "colorReplacements": [],
@@ -684,8 +682,7 @@
684
682
  "textStyleType": "button",
685
683
  "textAlign": "center",
686
684
  "lineHeight": "1.5",
687
- "letterSpacing": "0",
688
- "color": "var(--palette-1)"
685
+ "letterSpacing": "0"
689
686
  },
690
687
  "url": "",
691
688
  "colorReplacements": [],
@@ -708,8 +705,7 @@
708
705
  "textStyleType": "button",
709
706
  "textAlign": "center",
710
707
  "lineHeight": "1.5",
711
- "letterSpacing": "0",
712
- "color": "var(--palette-1)"
708
+ "letterSpacing": "0"
713
709
  },
714
710
  "fieldRole": "button"
715
711
  },
@@ -659,8 +659,7 @@
659
659
  "textAlign": "center",
660
660
  "lineHeight": "1.5",
661
661
  "applyAutoScale": false,
662
- "textStyleType": "button",
663
- "color": "var(--palette-0)"
662
+ "textStyleType": "button"
664
663
  },
665
664
  "colorReplacements": [],
666
665
  "page": ""
@@ -531,8 +531,7 @@
531
531
  "textStyleType": "button",
532
532
  "lineHeight": "1.5",
533
533
  "applyAutoScale": false,
534
- "letterSpacing": "0",
535
- "color": "var(--palette-1)"
534
+ "letterSpacing": "0"
536
535
  },
537
536
  "modalImageUrl": "",
538
537
  "clickActionField": "url",
@@ -551,8 +550,7 @@
551
550
  "textStyleType": "button",
552
551
  "lineHeight": "1.5",
553
552
  "applyAutoScale": false,
554
- "letterSpacing": "0",
555
- "color": "var(--palette-1)"
553
+ "letterSpacing": "0"
556
554
  },
557
555
  "modalImageUrl": "https://bc-user-uploads.brandcrowd.com/public/media-uat4/b0377e32-964c-430d-b0c4-05062e5d240d/fb0ec8f5-713e-43e0-8a0a-469109ca5876.png",
558
556
  "clickActionField": "modalImageUrl",
@@ -571,8 +569,7 @@
571
569
  "textStyleType": "button",
572
570
  "lineHeight": "1.5",
573
571
  "applyAutoScale": false,
574
- "letterSpacing": "0",
575
- "color": "var(--palette-1)"
572
+ "letterSpacing": "0"
576
573
  },
577
574
  "modalImageUrl": "",
578
575
  "clickActionField": "page",
@@ -659,8 +659,7 @@
659
659
  "textAlign": "center",
660
660
  "lineHeight": "1.5",
661
661
  "applyAutoScale": false,
662
- "textStyleType": "button",
663
- "color": "var(--palette-0)"
662
+ "textStyleType": "button"
664
663
  },
665
664
  "colorReplacements": [],
666
665
  "page": ""
@@ -48,6 +48,11 @@ import { DETACHED_UNSET_EFFECTIVE } from '../text-role-reconnect.util';
48
48
  import * as textRoleCss from '../text-role-css.util';
49
49
  import * as rteRoleLift from '../rte-role-lift.util';
50
50
  import { applyTextContrast } from '../container-text-color.util';
51
+ import {
52
+ resolveColorReference,
53
+ STRATEGY_COPY_MIN_CONTRAST,
54
+ } from '../color-palette.util';
55
+ import Color from 'color';
51
56
  import {
52
57
  PRODUCTION_BUTTON_DEFAULTS,
53
58
  PRODUCTION_HEADER_BUTTON_STYLE,
@@ -3103,6 +3108,89 @@ describe('migration-22 · bakeElementColours (D — freeze resolved colour)', ()
3103
3108
  });
3104
3109
  });
3105
3110
 
3111
+ describe('migration-22 · standalone button label colour (plain text on the one global button fill)', () => {
3112
+ // The old logo-token sites: the page text colour, and so the base coat, is also the button fill.
3113
+ const linkButtonSite = (content: Record<string, unknown>): BrandPageModel => {
3114
+ const options = TextStyles.GetDefaultTextStyle().textStyleOptions;
3115
+
3116
+ setRoleColor(options.paragraphs.style, 'var(--palette-0)');
3117
+ setRoleColor(options.buttons.style, 'var(--palette-1)');
3118
+
3119
+ return {
3120
+ pages: [{ contents: [content] }],
3121
+ styles: {
3122
+ textStyles: { textFontFamily: 'lato', textStyleOptions: options },
3123
+ buttonStyles: {
3124
+ style: {
3125
+ backgroundColor: 'var(--palette-0)',
3126
+ color: 'var(--palette-1)',
3127
+ },
3128
+ },
3129
+ colors: [
3130
+ { id: 'palette-0', value: '#1b2a41', type: 'palette' },
3131
+ { id: 'palette-1', value: '#f4f1de', type: 'palette' },
3132
+ ],
3133
+ },
3134
+ brandPageType: BRAND_PAGE_TYPES.default,
3135
+ } as unknown as BrandPageModel;
3136
+ };
3137
+
3138
+ // WCAG ratio of what the label finally draws (its own colour, else the button text colour it
3139
+ // inherits) on the global button fill.
3140
+ const labelContrastOnFill = (
3141
+ model: BrandPageModel,
3142
+ label: { color?: string },
3143
+ ): number => {
3144
+ const { buttonStyles, colors } = model.styles;
3145
+ const resolve = (value: string): string =>
3146
+ resolveColorReference(value, colors, { withRoleDefaults: true });
3147
+
3148
+ return Color(resolve(label.color || buttonStyles.style.color!)).contrast(
3149
+ Color(resolve(buttonStyles.style.backgroundColor!)),
3150
+ );
3151
+ };
3152
+
3153
+ it.each(['general-link', 'email', 'facebook'])(
3154
+ 'a %s label on no role reads on the global button fill',
3155
+ async (type) => {
3156
+ const model = await runMigration22(
3157
+ linkButtonSite({
3158
+ type,
3159
+ title: 'Get A Quote',
3160
+ titleStyle: { textAlign: 'center', fontFamily: 'Lato' },
3161
+ }),
3162
+ );
3163
+ const content = model.pages[0].contents[0] as unknown as {
3164
+ title: unknown;
3165
+ titleStyle: { color?: string; textStyleType?: string };
3166
+ };
3167
+
3168
+ expect(typeof content.title).toBe('string'); // not RTE: no run mark can hold a colour
3169
+ expect(content.titleStyle.textStyleType).toBe('button');
3170
+ expect(
3171
+ labelContrastOnFill(model, content.titleStyle),
3172
+ ).toBeGreaterThanOrEqual(STRATEGY_COPY_MIN_CONTRAST);
3173
+ },
3174
+ );
3175
+
3176
+ it('a label on the retired Buttons role still reads on the global button fill', async () => {
3177
+ const model = await runMigration22(
3178
+ linkButtonSite({
3179
+ type: 'general-link',
3180
+ title: 'Reviews',
3181
+ titleStyle: { textStyleType: 'buttons' },
3182
+ }),
3183
+ );
3184
+ const { titleStyle } = model.pages[0].contents[0] as unknown as {
3185
+ titleStyle: { color?: string };
3186
+ };
3187
+
3188
+ expect(labelContrastOnFill(model, titleStyle)).toBeGreaterThanOrEqual(
3189
+ STRATEGY_COPY_MIN_CONTRAST,
3190
+ );
3191
+ });
3192
+ });
3193
+
3106
3194
  describe('migration-22 · convertPlainRteFields (B1 — plain text → Tiptap doc)', () => {
3107
3195
  it('converts a plain-string RTE field to a Tiptap doc', async () => {
3108
3196
  const content = {
@@ -44,6 +44,7 @@ import {
44
44
  } from '@shared/color-strategies/rte-field-pairs';
45
45
  import {
46
46
  containerTextColorDefaults,
47
+ isButtonLabelField,
47
48
  titleSurfaceContrastFill,
48
49
  } from '../container-text-color.util';
49
50
  import {
@@ -776,8 +777,9 @@ const roleColour = (
776
777
  * and the role colour (E) changes no pixels. migration-21 already baked element→frame→role
777
778
  * colour into RTE runs, so the only uncoloured runs reaching here inherit the base coat;
778
779
  * non-RTE fields (21 left them alone) resolve element→container-default→role→base coat.
779
- * `buttonTextStyle` is excluded — the button label takes its colour from `buttonStyles`,
780
- * never the base coat. A social-links section's unset icon fill is frozen here too.
780
+ * `buttonTextStyle` and a link button's `titleStyle` are excluded — the button label takes its
781
+ * colour from `buttonStyles`, never the base coat. A social-links section's unset icon fill is
782
+ * frozen here too.
781
783
  */
782
784
  export const bakeElementColours = (
783
785
  brandPageModel: BrandPageModel,
@@ -812,6 +814,11 @@ export const bakeElementColours = (
812
814
  for (const key of Object.keys(CONTENT_TEXT_STYLE_PROPERTY_KEYS)) {
813
815
  if (key === 'buttonTextStyle' || rteStyleFields.has(key)) continue;
814
816
 
817
+ // A link button's label sits on the global button fill, not the page the base coat
818
+ // contrasts: left bare it inherits the button text colour (`isButtonLabelField`).
819
+ if (isButtonLabelField(content, key as ContentTextStylePropertyKey))
820
+ continue;
821
+
815
822
  const style = fields[key] as RawStyleObject | undefined;
816
823
 
817
824
  if (!style || !isBlankColorValue(style.color)) continue;
@@ -1,7 +1,7 @@
1
1
  // REL 1 Phase 1 / 1.5 — friendly chat copy keyed by the backend `code` field.
2
2
  //
3
3
  // Backend returns `{ error, code, message }` for known failure paths
4
- // (both HTTP responses and WebSocket build_error frames).
4
+ // (both HTTP responses and WebSocket build_error / server_error frames).
5
5
  // We look up the code first, then fall back to message → error → generic.
6
6
  // English-only for now; Phase 2 promotes this to proper i18n keys.
7
7
 
@@ -50,7 +50,7 @@ export function pickErrorMessage(error: unknown): string {
50
50
  * Pick a friendly chat message from a WebSocket error frame.
51
51
  *
52
52
  * Reads `msg.{code, message, error}` directly — the shape pushed by the
53
- * ai-pipelines build_error WebSocket frames.
53
+ * ai-pipelines build_error / server_error WebSocket frames.
54
54
  */
55
55
  export function pickWsErrorMessage(
56
56
  msg: ErrorPayload | undefined | null,
@@ -886,7 +886,7 @@ export function useAiWebsiteBuilderChat(): UseAiChatResult {
886
886
  return;
887
887
  }
888
888
 
889
- if (msg.type === 'build_error') {
889
+ if (msg.type === 'server_error' || msg.type === 'build_error') {
890
890
  handleBuildError(msg);
891
891
 
892
892
  return;
@@ -999,7 +999,7 @@ export function useAiWebsiteBuilderChat(): UseAiChatResult {
999
999
  threadItems.value[messageIndex].isTyping = false;
1000
1000
 
1001
1001
  // Honour the isError flag set by error paths in callApiBuildAsync
1002
- // (apply-failure catch, build_error WS frames).
1002
+ // (apply-failure catch, server_error / build_error WS frames).
1003
1003
  // Without this, error text would render as a normal AI reply.
1004
1004
  if (result.isError) {
1005
1005
  threadItems.value[messageIndex].isError = true;
@@ -25,7 +25,10 @@ import {
25
25
  import { applyTextContrast } from '@brand-page/utils/container-text-color.util';
26
26
  import { authorContentRteFields } from '@brand-page-maker/utils/rte-authoring.util';
27
27
  import * as contentFactories from './content-settings';
28
- import { isTiptapDocument } from '@brand-page-maker/utils';
28
+ import {
29
+ isTiptapDocument,
30
+ mapBrandPageContentToContentSettingsItem,
31
+ } from '@brand-page-maker/utils';
29
32
  import { setLocaleAsync } from '@src/composables/useTranslate';
30
33
  import {
31
34
  BRAND_PAGE_TYPES,
@@ -215,6 +218,51 @@ describe('createInitialBannerSectionContent — RTE source detail', () => {
215
218
  });
216
219
  });
217
220
 
221
+ describe('createInitialMailingListContent — default title', () => {
222
+ beforeAll(async () => {
223
+ await setLocaleAsync('en-US');
224
+ });
225
+
226
+ const blocksOf = (doc: unknown) =>
227
+ (
228
+ doc as {
229
+ content?: {
230
+ attrs?: { textAlign?: string };
231
+ content?: { text?: string }[];
232
+ }[];
233
+ }
234
+ ).content ?? [];
235
+
236
+ it('is a centred doc, matching its title style', () => {
237
+ const content = createInitialMailingListContent(opts);
238
+ const blocks = blocksOf(content.title);
239
+
240
+ expect(blocks[0].content?.[0]?.text).toBe('Join mailing list');
241
+
242
+ for (const block of blocks) {
243
+ expect(block.attrs?.textAlign).toBe('center');
244
+ }
245
+
246
+ expect(content.titleStyle.textAlign).toBe('center');
247
+ });
248
+
249
+ // The editor loads a block with no title through this factory alone, without the add-content
250
+ // step that stamps alignment from the style, so the doc has to carry it.
251
+ it('is centred on an existing block with no title when the editor loads it', () => {
252
+ const item = mapBrandPageContentToContentSettingsItem(
253
+ { id: 'ml-1', type: 'mailing-list', buttonText: 'SUBSCRIBE' } as never,
254
+ opts,
255
+ );
256
+ const blocks = blocksOf(item.settings.content.title);
257
+
258
+ expect(blocks[0].content?.[0]?.text).toBe('Join mailing list');
259
+
260
+ for (const block of blocks) {
261
+ expect(block.attrs?.textAlign).toBe('center');
262
+ }
263
+ });
264
+ });
265
+
218
266
  // Every text a factory creates is linked to one of the ten roles at birth, so nothing new needs the
219
267
  // reconnect, and saves no typography of its own, so every role edit reaches it. The two button
220
268
  // factories are curried by their content type, and capitalise their translated title, so they need
@@ -306,7 +306,9 @@ export const createInitialMailingListContent = (
306
306
  type: 'mailing-list',
307
307
  topPadding: 0,
308
308
  bottomPadding: 0,
309
- title: rteDoc(contentSettingsTr(`mailingListTitle`)),
309
+ // Centred in the doc itself: the editor fills a loaded block's missing title from this factory
310
+ // alone, without the add-content step that stamps alignment from `titleStyle`.
311
+ title: rteDoc(contentSettingsTr(`mailingListTitle`), 'center'),
310
312
  buttonText: contentSettingsTr(`subscribePlaceholder`),
311
313
  acceptedFields: 'emailOnly',
312
314
  titleStyle: {
@@ -16,12 +16,17 @@ export interface WebSocketMessage {
16
16
  type:
17
17
  | 'connected'
18
18
  | 'server_response'
19
+ | 'server_error'
19
20
  | 'build_error'
20
21
  | 'chat_started'
21
22
  | 'plan_announced'
22
23
  | 'item_complete';
23
24
  buildId?: string;
24
25
  connectionId?: string;
26
+ pageId?: string;
27
+ order?: number;
28
+ sectionType?: string;
29
+ mode?: string;
25
30
  toolCalls?: unknown[];
26
31
  message?: string;
27
32
  error?: string;
@@ -2,7 +2,7 @@
2
2
  * Maps AI tool call argument names → BrandPageModel content field names
3
3
  * per section type.
4
4
  *
5
- * Reference: service.ai-pipelines/src/agents/websiteV2/platform/tool_schemas/
5
+ * Reference: service.ai-pipelines/src/agents/website/section_schemas.py
6
6
  * Reference: library.brand-page/src/brand-page/models/index.ts
7
7
  */
8
8
 
@@ -43,10 +43,10 @@ export interface UndoPayload {
43
43
  * can observe that an undo was executed (useful for telemetry, UI messaging,
44
44
  * and audit logs).
45
45
  *
46
- * Contract guarantee from the AI pipeline (WebsiteV2 main agent): the `undo`
46
+ * Contract guarantee from the AI pipeline (Website Planner WL9): the `undo`
47
47
  * tool call is exclusive — it never co-occurs with build/update/delete tool
48
48
  * calls in the same response. If that contract is ever violated upstream,
49
- * fix the agent — do not add defensive ordering here. The sequential
49
+ * fix the planner — do not add defensive ordering here. The sequential
50
50
  * MetadataExecutor will apply any subsequent builders on top of the restored
51
51
  * snapshot, which is the semantically correct "undo-then-apply" ordering.
52
52
  */
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Typed interfaces for website builder section tool arguments.
3
3
  *
4
- * Canonical reference: service.ai-pipelines/src/agents/websiteV2/platform/tool_schemas/
4
+ * Canonical reference: service.ai-pipelines/src/agents/website/section_schemas.py
5
5
  * Every field defined there has a corresponding TS field here.
6
6
  */
7
7
 
@@ -25,7 +25,7 @@ type UnsupportedContentType =
25
25
  export type SectionType = Exclude<ContentType, UnsupportedContentType>;
26
26
 
27
27
  // ---------------------------------------------------------------------------
28
- // Shared style type (matches TEXT_STYLE_SCHEMA in websiteV2/platform/tools/section_types_tool.py)
28
+ // Shared style type (matches TEXT_STYLE_SCHEMA in section_schemas.py)
29
29
  // ---------------------------------------------------------------------------
30
30
 
31
31
  // ---------------------------------------------------------------------------
@@ -91,7 +91,7 @@ export const isTiptapDocument = (value: unknown): value is TiptapDocument =>
91
91
  Array.isArray((value as TiptapDocument).content);
92
92
 
93
93
  // ---------------------------------------------------------------------------
94
- // Shared style type (matches TEXT_STYLE_SCHEMA in websiteV2/platform/tools/section_types_tool.py)
94
+ // Shared style type (matches TEXT_STYLE_SCHEMA in section_schemas.py)
95
95
  // ---------------------------------------------------------------------------
96
96
 
97
97
  export interface TextStyleArguments {
@@ -127,7 +127,7 @@ export interface TextStyleArguments {
127
127
  * the APPLICATION is per-section: every section's tool call writes the
128
128
  * same site-decided hex into this field.
129
129
  *
130
- * See `ai-pipelines/src/agents/websiteV2/platform/tools/section_types_tool.py`
130
+ * See `ai-pipelines/src/agents/website/tools/section_types_tool.py`
131
131
  * (`_button_text_style_field`) for the AI-side mirror.
132
132
  */
133
133
  export interface ButtonTextStyleArguments {