@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.
- package/lib/src/{CreatedOnBrandCrowd-de-de-CoNPRkJy.js → CreatedOnBrandCrowd-de-de-Deoc6s8r.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-en-us-D3099egH.js → CreatedOnBrandCrowd-en-us-BxDzsLl-.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-es-es-DDKTS6CI.js → CreatedOnBrandCrowd-es-es-B-5VS2MV.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-fr-FbE-W5IO.js → CreatedOnBrandCrowd-fr-ca-C6h05sUZ.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-ca-FbE-W5IO.js → CreatedOnBrandCrowd-fr-fr-C6h05sUZ.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-pt-BG2-KFrl.js → CreatedOnBrandCrowd-pt-br-BC6XU3ev.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-br-BG2-KFrl.js → CreatedOnBrandCrowd-pt-pt-BC6XU3ev.js} +1 -1
- package/lib/src/{CreatedOnDcom-de-de-Br79PFya.js → CreatedOnDcom-de-de-dUTedh8v.js} +1 -1
- package/lib/src/{CreatedOnDcom-en-us-lqpcQj-7.js → CreatedOnDcom-en-us-DY0jkOeW.js} +1 -1
- package/lib/src/{CreatedOnDcom-es-es-B9OAEcGO.js → CreatedOnDcom-es-es-DRioej2A.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-ca-CJJLSYYb.js → CreatedOnDcom-fr-ca-CcglfcUU.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-fr-D5_ODr63.js → CreatedOnDcom-fr-fr-B7v_e6HI.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-br-BjK1aS8b.js → CreatedOnDcom-pt-br-5XGcKJb7.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-pt-DtH-sGF_.js → CreatedOnDcom-pt-pt-DepC53tO.js} +1 -1
- package/lib/src/brand-page/utils/container-text-color.util.d.ts +12 -2
- package/lib/src/brand-page/utils/container-text-color.util.js +1 -1
- package/lib/src/brand-page/utils/migration/migration-22.d.ts +3 -2
- package/lib/src/brand-page/utils/migration/migration-22.js +8 -3
- package/lib/src/brand-page-maker/composables/ai-chat/aiErrorCopy.d.ts +1 -1
- package/lib/src/brand-page-maker/composables/ai-chat/aiErrorCopy.js +2 -2
- package/lib/src/brand-page-maker/composables/ai-chat/useAiWebsiteBuilderChat.js +2 -2
- package/lib/src/brand-page-maker/models/content-settings.js +3 -1
- package/lib/src/clients/websocket-gateway-client.d.ts +5 -1
- package/lib/src/index.mjs +1 -1
- package/lib/src/{lib-b7uNnxx-.js → lib-CidddZOL.js} +2811 -2802
- package/lib/src/metadata-builder/builders/section-field-maps.d.ts +1 -1
- package/lib/src/metadata-builder/builders/section-field-maps.js +1 -1
- package/lib/src/metadata-builder/builders/undo-builder.d.ts +2 -2
- package/lib/src/metadata-builder/builders/undo-builder.js +2 -2
- package/lib/src/metadata-builder/parsers/section-tool-arguments.d.ts +2 -2
- package/lib/src/metadata-builder/parsers/section-tool-arguments.js +1 -1
- package/package.json +1 -1
- package/src/brand-page/utils/container-text-color.util.ts +3 -3
- package/src/brand-page/utils/migration/__snapshots__/migration-22-output/new-brandcontact-every-section.v22.json +4 -8
- package/src/brand-page/utils/migration/__snapshots__/migration-22-output/new-brandpage-every-section.v22.json +4 -8
- package/src/brand-page/utils/migration/__snapshots__/migration-22-output/new-brandsite-every-section.v22.json +4 -8
- package/src/brand-page/utils/migration/__snapshots__/migration-22-output/real-brandcontact.v22.json +1 -2
- package/src/brand-page/utils/migration/__snapshots__/migration-22-output/real-brandpage-general-link.v22.json +3 -6
- package/src/brand-page/utils/migration/__snapshots__/migration-22-output/real-brandpage-links.v22.json +1 -2
- package/src/brand-page/utils/migration/migration-22.test.ts +88 -0
- package/src/brand-page/utils/migration/migration-22.ts +9 -2
- package/src/brand-page-maker/composables/ai-chat/aiErrorCopy.ts +2 -2
- package/src/brand-page-maker/composables/ai-chat/useAiWebsiteBuilderChat.ts +2 -2
- package/src/brand-page-maker/models/content-settings.test.ts +49 -1
- package/src/brand-page-maker/models/content-settings.ts +3 -1
- package/src/clients/websocket-gateway-client.ts +5 -0
- package/src/metadata-builder/builders/section-field-maps.ts +1 -1
- package/src/metadata-builder/builders/undo-builder.ts +2 -2
- 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/
|
|
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/
|
|
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 (
|
|
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
|
|
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 (
|
|
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
|
|
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/
|
|
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/
|
|
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/
|
|
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
|
@@ -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 `
|
|
13
|
-
* before changing
|
|
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
|
},
|
|
@@ -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",
|
|
@@ -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`
|
|
780
|
-
* never the base coat. A social-links section's unset icon fill is
|
|
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 {
|
|
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
|
-
|
|
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/
|
|
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 (
|
|
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
|
|
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/
|
|
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
|
|
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
|
|
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/
|
|
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 {
|