@designcrowd/library.brand-page 5.19.1-error-html2canvas-2 → 5.19.1-id-based-items-1

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 (371) hide show
  1. package/lib/src/{CreatedOnBrandCrowd-de-de-DmKe7W8L.js → CreatedOnBrandCrowd-de-de-0PcGqy0N.js} +3 -3
  2. package/lib/src/{CreatedOnBrandCrowd-en-us-D2EeiNep.js → CreatedOnBrandCrowd-en-us-DB8wcFMI.js} +3 -3
  3. package/lib/src/{CreatedOnBrandCrowd-es-es-BqVwNY1D.js → CreatedOnBrandCrowd-es-es-DVWQ243C.js} +3 -3
  4. package/lib/src/{CreatedOnBrandCrowd-fr-fr-C756a27-.js → CreatedOnBrandCrowd-fr-ca-D-M_L0Du.js} +3 -3
  5. package/lib/src/{CreatedOnBrandCrowd-fr-ca-C756a27-.js → CreatedOnBrandCrowd-fr-fr-D-M_L0Du.js} +3 -3
  6. package/lib/src/{CreatedOnBrandCrowd-pt-pt-FNHAnZUw.js → CreatedOnBrandCrowd-pt-br-CNpzknxU.js} +3 -3
  7. package/lib/src/{CreatedOnBrandCrowd-pt-br-FNHAnZUw.js → CreatedOnBrandCrowd-pt-pt-CNpzknxU.js} +3 -3
  8. package/lib/src/{CreatedOnDcom-de-de-8AZZFmDt.js → CreatedOnDcom-de-de-DMJoD5wE.js} +3 -3
  9. package/lib/src/{CreatedOnDcom-en-us-DizTP32i.js → CreatedOnDcom-en-us-B-F3Pmjv.js} +3 -3
  10. package/lib/src/{CreatedOnDcom-es-es-qnp9_n_A.js → CreatedOnDcom-es-es-DmLk-cA2.js} +3 -3
  11. package/lib/src/{CreatedOnDcom-fr-ca-jUs352uK.js → CreatedOnDcom-fr-ca-ntnsCzvA.js} +3 -3
  12. package/lib/src/{CreatedOnDcom-fr-fr-CTxXtHyk.js → CreatedOnDcom-fr-fr-DFcO0gHL.js} +3 -3
  13. package/lib/src/{CreatedOnDcom-pt-br-nQHj8P7d.js → CreatedOnDcom-pt-br-CtuJoVXW.js} +3 -3
  14. package/lib/src/{CreatedOnDcom-pt-pt-DJmVYQuL.js → CreatedOnDcom-pt-pt-B7shZVVn.js} +3 -3
  15. package/lib/src/brand-page/clients/brand-page.client.d.ts +9 -9
  16. package/lib/src/brand-page/components/image-gallery/masonry/MasonryGallery.mixin.d.ts +2 -1
  17. package/lib/src/brand-page/components/navigation/hamburger/HamburgerNavigation.mixin.d.ts +2 -4
  18. package/lib/src/brand-page/components/navigation/hamburger/HamburgerNavigation.mixin.js +0 -1
  19. package/lib/src/brand-page/components/navigation/navigation-side-menu/NavigationSideMenu.mixin.d.ts +2 -4
  20. package/lib/src/brand-page/components/navigation/navigation-side-menu/NavigationSideMenu.mixin.js +4 -5
  21. package/lib/src/brand-page/components/page/Page.mixin.d.ts +1 -1
  22. package/lib/src/brand-page/components/page/PageComponent.mixin.d.ts +1 -1
  23. package/lib/src/brand-page/components/payment/Payment.mixin.d.ts +3 -3
  24. package/lib/src/brand-page/components/payment/__fixtures__/request-payment.d.ts +13 -13
  25. package/lib/src/brand-page/components/payment/__fixtures__/support-me.d.ts +13 -13
  26. package/lib/src/brand-page/constants/sections/footer-display.constants.js +1 -1
  27. package/lib/src/brand-page/models/index.d.ts +13 -2
  28. package/lib/src/brand-page/models/index.js +0 -3
  29. package/lib/src/brand-page/models/payment.model.d.ts +1 -1
  30. package/lib/src/brand-page/utils/personalisation.util.js +14 -72
  31. package/lib/src/brand-page-maker/components/action-bar/ActionBar.mixin.d.ts +19 -21
  32. package/lib/src/brand-page-maker/components/action-bar/ActionBar.mixin.js +0 -1
  33. package/lib/src/brand-page-maker/components/admin-background-settings/AdminBackgroundSettings.mixin.d.ts +10 -16
  34. package/lib/src/brand-page-maker/components/admin-logo-settings/AdminLogoSettings.mixin.d.ts +3 -3
  35. package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.d.ts +170 -181
  36. package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.js +0 -3
  37. package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.stories.js +0 -1
  38. package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMakerTemplateSettings.mixin.d.ts +12 -12
  39. package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMakerTemplateSettings.stories.js +2 -2
  40. package/lib/src/brand-page-maker/components/brand-page-maker/__fixtures__/brand-page-payment.js +7 -0
  41. package/lib/src/brand-page-maker/components/brandpage-maker-page/BrandPageMakerPage.mixin.d.ts +149 -148
  42. package/lib/src/brand-page-maker/components/brandpage-settings/ReplaceImageSettingsForBrandPageSettings.mixin.d.ts +21 -21
  43. package/lib/src/brand-page-maker/components/brandpage-settings/ReplaceImageSettingsForBrandPageSettings.mixin.js +1 -1
  44. package/lib/src/brand-page-maker/components/color-picker/components/ColorModeSelect.mixin.d.ts +2 -4
  45. package/lib/src/brand-page-maker/components/color-picker/components/ColorModeSelect.mixin.js +0 -1
  46. package/lib/src/brand-page-maker/components/color-picker/components/GradientColorSlider.mixin.d.ts +2 -4
  47. package/lib/src/brand-page-maker/components/color-picker/components/GradientColorSlider.mixin.js +0 -1
  48. package/lib/src/brand-page-maker/components/color-picker/components/GradientColorSliderHandler.mixin.d.ts +2 -5
  49. package/lib/src/brand-page-maker/components/color-picker/components/GradientColorSliderHandler.mixin.js +0 -1
  50. package/lib/src/brand-page-maker/components/color-picker/components/GradientTypeSelector.mixin.d.ts +2 -4
  51. package/lib/src/brand-page-maker/components/color-picker/components/GradientTypeSelector.mixin.js +0 -1
  52. package/lib/src/brand-page-maker/components/device/Device.mixin.d.ts +2 -4
  53. package/lib/src/brand-page-maker/components/device/Device.mixin.js +0 -1
  54. package/lib/src/brand-page-maker/components/navigation/Navigation.mixin.d.ts +2 -4
  55. package/lib/src/brand-page-maker/components/navigation/Navigation.mixin.js +0 -1
  56. package/lib/src/brand-page-maker/components/preview/Preview.mixin.d.ts +3 -3
  57. package/lib/src/brand-page-maker/components/quick-link-bar/QuickLinkBar.mixin.d.ts +11 -11
  58. package/lib/src/brand-page-maker/components/seo-settings/SeoPageSettings.mixin.d.ts +25 -27
  59. package/lib/src/brand-page-maker/components/seo-settings/SeoPageSettings.mixin.js +0 -1
  60. package/lib/src/brand-page-maker/components/seo-settings/UrlSettings.mixin.d.ts +27 -27
  61. package/lib/src/brand-page-maker/components/seo-settings/UrlSettings.mixin.js +1 -1
  62. package/lib/src/brand-page-maker/components/seo-settings/UseExistingDomainModal.mixin.d.ts +12 -17
  63. package/lib/src/brand-page-maker/components/social-share/SocialShare.mixin.d.ts +12 -12
  64. package/lib/src/brand-page-maker/components/template-preview/TemplatePreview.mixin.d.ts +32 -30
  65. package/lib/src/brand-page-maker/components/template-preview/TemplatePreview.mixin.js +5 -16
  66. package/lib/src/brand-page-maker/components/upgrade-to-premium-button/UpgradeToPremiumButton.mixin.d.ts +11 -11
  67. package/lib/src/brand-page-maker/composables/ai-chat/useAiWebsiteBuilderChat.js +1 -1
  68. package/lib/src/brand-page-maker/composables/rich-text-editor/useRichTextEditor.d.ts +1 -1
  69. package/lib/src/brand-page-maker/composables/rich-text-editor/useRichTextEditor.js +6 -6
  70. package/lib/src/brand-page-maker/composables/useEventTracker.d.ts +2 -2
  71. package/lib/src/brand-page-maker/constants/config-settings-constant.d.ts +6 -6
  72. package/lib/src/brand-page-maker/constants/config-settings-constant.js +1 -1
  73. package/lib/src/brand-page-maker/convertors/text-image-display-style.convertor.d.ts +1 -1
  74. package/lib/src/brand-page-maker/convertors/text-image-display-style.convertor.js +1 -1
  75. package/lib/src/brand-page-maker/models/content-settings.d.ts +1 -1
  76. package/lib/src/brand-page-maker/models/navigation-style-configs.js +1 -1
  77. package/lib/src/brand-page-maker/routing/brand-page-maker-router.d.ts +2 -1
  78. package/lib/src/brand-page-maker/stores/ai-checkpoint/ai-checkpoint-store.d.ts +378 -366
  79. package/lib/src/brand-page-maker/stores/brandpage-container/brand-page-container-store.d.ts +4 -5
  80. package/lib/src/brand-page-maker/stores/brandpage-container/brand-page-container-store.js +5 -8
  81. package/lib/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.d.ts +1 -3
  82. package/lib/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.js +3 -7
  83. package/lib/src/brand-page-maker/stores/history/history-store.d.ts +15 -15
  84. package/lib/src/brand-page-maker/stores/ui/ui-store.d.ts +0 -6
  85. package/lib/src/brand-page-maker/stores/ui/ui-store.js +0 -8
  86. package/lib/src/brand-page-maker/utils/content-settings.util.js +1 -1
  87. package/lib/src/brand-page-maker/utils/rich-text-editor.util.d.ts +1 -1
  88. package/lib/src/brand-page-maker/utils/rich-text-editor.util.js +4 -4
  89. package/lib/src/clients/brand-page-client.d.ts +15 -16
  90. package/lib/src/clients/design-template-client.d.ts +0 -3
  91. package/lib/src/clients/media-client.d.ts +2 -2
  92. package/lib/src/clients/models.d.ts +0 -2
  93. package/lib/src/composables/useSnapshot.js +18 -18
  94. package/lib/src/css/library.brand-page-brandCrowd.min.css +324 -342
  95. package/lib/src/css/library.brand-page-brandPage.min.css +324 -620
  96. package/lib/src/css/library.brand-page-designCom.min.css +324 -342
  97. package/lib/src/css/library.brand-page-preview.min.css +324 -598
  98. package/lib/src/index.d.ts +1 -1
  99. package/lib/src/index.mjs +15 -16
  100. package/lib/src/{lib-DityivZa.js → lib-BQmJBVbS.js} +33238 -36458
  101. package/lib/src/lib.js +1 -1
  102. package/lib/src/metadata-builder/builders/footer-builder.js +56 -91
  103. package/lib/src/metadata-builder/builders/item-id-utils.d.ts +41 -0
  104. package/lib/src/metadata-builder/builders/item-id-utils.js +68 -0
  105. package/lib/src/metadata-builder/builders/section-builders/image-gallery-builder.js +57 -68
  106. package/lib/src/metadata-builder/builders/section-builders/images-section-builder.js +58 -93
  107. package/lib/src/metadata-builder/builders/section-builders/shapes-section-builder.js +66 -99
  108. package/lib/src/metadata-builder/builders/section-builders/social-links-builder.js +57 -17
  109. package/lib/src/metadata-builder/builders/section-builders/testimonial-section-builder.js +51 -85
  110. package/lib/src/metadata-builder/builders/section-builders/text-section-builder.js +49 -74
  111. package/lib/src/metadata-builder/builders/shape-color-utils.js +1 -1
  112. package/lib/src/metadata-builder/parsers/ai-toolcall-parser.d.ts +13 -4
  113. package/lib/src/metadata-builder/parsers/section-tool-arguments.d.ts +66 -48
  114. package/lib/src/plugins/google-maps-plugin/google-maps-plugin.d.ts +1 -1
  115. package/lib/src/plugins/google-maps-plugin/google-maps-plugin.js +8 -8
  116. package/lib/src/shared/background-settings/components/background-shapes/BackgroundShapeSettings.mixin.d.ts +13 -13
  117. package/lib/src/shared/background-settings/store/background-settings-store.d.ts +3 -3
  118. package/lib/src/shared/color-picker/components/ColorPicker.mixin.d.ts +2 -4
  119. package/lib/src/shared/color-picker/components/ColorPicker.mixin.js +2 -2
  120. package/lib/src/shared/color-picker/components/ColorPickerSwatch.mixin.d.ts +2 -4
  121. package/lib/src/shared/color-picker/components/ColorPickerSwatch.mixin.js +0 -1
  122. package/lib/src/shared/confirm-modal/ConfirmModal.mixin.d.ts +2 -5
  123. package/lib/src/shared/confirm-modal/ConfirmModal.mixin.js +0 -1
  124. package/lib/src/shared/content-settings/components/GenericTextInput.mixin.d.ts +1 -0
  125. package/lib/src/shared/content-settings/components/content-settings/ContentSettings.mixin.d.ts +129 -131
  126. package/lib/src/shared/content-settings/components/content-settings/ContentSettings.mixin.js +1 -2
  127. package/lib/src/shared/content-settings/components/content-settings/ReplaceImageSettingsForContent.mixin.d.ts +110 -111
  128. package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItem.mixin.d.ts +81 -84
  129. package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItem.mixin.js +0 -5
  130. package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.d.ts +109 -117
  131. package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.js +14 -1
  132. package/lib/src/shared/content-settings/components/email-field/EmailField.mixin.d.ts +1 -0
  133. package/lib/src/shared/content-settings/components/focused-content-item-settings/FocusedContentItemSettings.mixin.d.ts +70 -72
  134. package/lib/src/shared/content-settings/components/focused-content-item-settings/FocusedContentItemSettings.mixin.js +3 -4
  135. package/lib/src/shared/content-settings/components/form-fields-field/FormFieldsField.mixin.d.ts +4 -4
  136. package/lib/src/shared/content-settings/components/icon-library/IconLibrary.mixin.d.ts +1 -3
  137. package/lib/src/shared/content-settings/components/icon-library/IconLibrary.mixin.js +0 -1
  138. package/lib/src/shared/content-settings/components/images-field/ImagesField.mixin.d.ts +2 -2
  139. package/lib/src/shared/content-settings/components/items-field/ItemsField.mixin.d.ts +3 -4
  140. package/lib/src/shared/content-settings/components/items-field/ItemsField.mixin.js +1 -1
  141. package/lib/src/shared/content-settings/components/payment-config-field/PaymentConfigField.mixin.d.ts +40 -40
  142. package/lib/src/shared/content-settings/components/payment-config-field/PaymentConfigOptionField.mixin.d.ts +6 -5
  143. package/lib/src/shared/content-settings/components/shape-field/ShapeField.mixin.d.ts +1 -0
  144. package/lib/src/shared/content-settings/components/slider-field/SliderField.mixin.d.ts +2 -2
  145. package/lib/src/shared/content-settings/components/tel-field/TelField.mixin.d.ts +1 -0
  146. package/lib/src/shared/content-settings/components/testimonial-items-field/TestimonialItemsField.mixin.d.ts +2 -1
  147. package/lib/src/shared/content-settings/components/text-field/TextField.mixin.d.ts +1 -0
  148. package/lib/src/shared/content-settings/components/text-items-field/TextItemsField.mixin.d.ts +2 -1
  149. package/lib/src/shared/content-settings/components/text-items-field/TextItemsField.mixin.js +1 -1
  150. package/lib/src/shared/content-settings/components/text-style-field/TextStyleField.mixin.d.ts +28 -28
  151. package/lib/src/shared/content-settings/components/textarea-field/TextareaField.mixin.d.ts +1 -0
  152. package/lib/src/shared/content-settings/components/toggle-field/ToggleField.mixin.d.ts +2 -2
  153. package/lib/src/shared/content-settings/components/url-field/UrlField.mixin.d.ts +1 -0
  154. package/lib/src/shared/content-settings/components/youtube-video-field/YoutubeVideoField.mixin.d.ts +1 -0
  155. package/lib/src/shared/content-settings/store/content-settings-store.d.ts +39 -39
  156. package/lib/src/shared/content-settings/utils.js +1 -0
  157. package/lib/src/shared/font-family-selector/FontFamilySelector.mixin.d.ts +4 -6
  158. package/lib/src/shared/font-family-selector/FontFamilySelector.mixin.js +0 -1
  159. package/lib/src/shared/image-gallery-style-settings/ImageGalleryStyleSettings.mixin.d.ts +2 -4
  160. package/lib/src/shared/image-gallery-style-settings/ImageGalleryStyleSettings.mixin.js +0 -1
  161. package/lib/src/shared/logo-settings/components/EditLogoSettings.mixin.d.ts +21 -29
  162. package/lib/src/shared/logo-settings/components/EditLogoSettings.mixin.js +1 -1
  163. package/lib/src/shared/logo-settings/components/LogoSettings.mixin.d.ts +51 -50
  164. package/lib/src/shared/logo-settings/components/LogoSettingsHeader.mixin.d.ts +4 -6
  165. package/lib/src/shared/logo-settings/components/LogoSettingsHeader.mixin.js +0 -1
  166. package/lib/src/shared/logo-settings/store/editable-logo-store.js +1 -1
  167. package/lib/src/shared/logo-settings/store/logo-settings-store.d.ts +1 -1
  168. package/lib/src/shared/logo-versions/LogoVersions.mixin.d.ts +5 -13
  169. package/lib/src/shared/logo-versions/LogoVersions.mixin.js +0 -1
  170. package/lib/src/shared/models.d.ts +0 -2
  171. package/lib/src/shared/my-designs/MyDesigns.mixin.d.ts +6 -14
  172. package/lib/src/shared/my-designs/MyDesigns.mixin.js +0 -1
  173. package/lib/src/shared/my-uploads/MyMedia.mixin.d.ts +5 -14
  174. package/lib/src/shared/my-uploads/MyMedia.mixin.js +0 -1
  175. package/lib/src/shared/my-uploads/MyUploads.mixin.d.ts +5 -14
  176. package/lib/src/shared/my-uploads/MyUploads.mixin.js +0 -1
  177. package/lib/src/shared/navigation-style-settings/components/navigation-style-settings/NavigationStyleSettings.mixin.d.ts +3 -5
  178. package/lib/src/shared/navigation-style-settings/components/navigation-style-settings/NavigationStyleSettings.mixin.js +0 -1
  179. package/lib/src/shared/navigation-style-settings/store/index.d.ts +2 -2
  180. package/lib/src/shared/page-navigation/store/page-navigation-store.d.ts +2 -2
  181. package/lib/src/shared/page-settings/components/new-page-modal/NewPageModal.mixin.d.ts +28 -27
  182. package/lib/src/shared/page-settings/components/new-page-modal/NewPageModal.mixin.js +2 -0
  183. package/lib/src/shared/page-settings/components/page-settings/PageSettings.mixin.d.ts +179 -188
  184. package/lib/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.d.ts +177 -181
  185. package/lib/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.js +1 -1
  186. package/lib/src/shared/page-settings/store/page-settings-store.d.ts +9 -9
  187. package/lib/src/shared/publish-brand-page-with-state/PublishBrandPageModalWithState.mixin.d.ts +39 -39
  188. package/lib/src/shared/publish-brand-page-with-state/PublishBrandPageModalWithState.mixin.js +1 -0
  189. package/lib/src/shared/side-bar/components/SideBar.mixin.d.ts +18 -18
  190. package/lib/src/shared/side-bar/components/SideBarTabButton.mixin.d.ts +6 -6
  191. package/lib/src/shared/style-settings/components/StyleSettings.mixin.d.ts +60 -66
  192. package/lib/src/shared/style-settings/store/style-settings-store.d.ts +1 -1
  193. package/lib/src/shared/style-settings/store/style-settings-store.js +1 -1
  194. package/lib/src/shared/template-settings/components/TemplatesSettings.mixin.d.ts +450 -20
  195. package/lib/src/shared/template-settings/components/TemplatesSettings.mixin.js +2 -2
  196. package/lib/src/shared/template-settings/models.d.ts +0 -1
  197. package/lib/src/shared/template-settings/store/template-settings-store.d.ts +0 -4
  198. package/lib/src/shared/template-settings/store/template-settings-store.js +11 -79
  199. package/lib/src/shared/unsplash-library/UnsplashLibrary.mixin.d.ts +4 -9
  200. package/package.json +57 -61
  201. package/src/brand-page/components/about-section/AboutSection.vue.test.ts +5 -7
  202. package/src/brand-page/components/brand-page-footer/BrandPageFooter.vue +1 -1
  203. package/src/brand-page/components/image/Image.mixin.ts +1 -1
  204. package/src/brand-page/components/image/Image.test.ts +1 -1
  205. package/src/brand-page/components/image-gallery/masonry/MasonryGallery.mixin.ts +2 -1
  206. package/src/brand-page/components/images-section/ImagesSection.vue +1 -0
  207. package/src/brand-page/components/logo/LogoRender.vue +188 -188
  208. package/src/brand-page/components/navigation/hamburger/HamburgerNavigation.mixin.ts +0 -1
  209. package/src/brand-page/components/navigation/navigation-side-menu/NavigationSideMenu.mixin.ts +6 -15
  210. package/src/brand-page/components/page-footer/PageFooter.vue +0 -1
  211. package/src/brand-page/components/page-header/HeaderLayout.vue +6 -12
  212. package/src/brand-page/components/page-header/PageHeader.vue +0 -1
  213. package/src/brand-page/components/shapes-section/ShapesSection.vue +1 -1
  214. package/src/brand-page/components/shop/CartModal.vue +3 -1
  215. package/src/brand-page/components/shop/FeaturedDisplay.vue +2 -2
  216. package/src/brand-page/components/shop/PaymentModal.vue +5 -1
  217. package/src/brand-page/components/shop/ProductDetailModal.vue +1 -1
  218. package/src/brand-page/components/shop/Shop.vue +1 -1
  219. package/src/brand-page/components/shop/TextDisplay.vue +1 -1
  220. package/src/brand-page/components/shop/ThumbnailDisplay.vue +2 -2
  221. package/src/brand-page/components/testimonial-section/TestimonialSection.vue +3 -3
  222. package/src/brand-page/components/text-wrapper/TextWrapper.vue +1 -3
  223. package/src/brand-page/constants/sections/footer-display.constants.ts +1 -1
  224. package/src/brand-page/models/index.ts +12 -4
  225. package/src/brand-page/models/payment.model.ts +1 -2
  226. package/src/brand-page/models/shop.model.ts +2 -0
  227. package/src/brand-page/models/user-personalisation.model.ts +2 -1
  228. package/src/brand-page/utils/__snapshots__/migration.util.test.ts.snap +0 -3
  229. package/src/brand-page/utils/__snapshots__/migration2.util.test.ts.snap +0 -1
  230. package/src/brand-page/utils/__snapshots__/model.util.test.ts.snap +0 -2
  231. package/src/brand-page/utils/migration/migration-01_1.ts +1 -1
  232. package/src/brand-page/utils/migration/migration-02.ts +1 -0
  233. package/src/brand-page/utils/migration/migration-03.ts +1 -0
  234. package/src/brand-page/utils/migration/migration-04.ts +1 -0
  235. package/src/brand-page/utils/migration/migration-05.ts +5 -1
  236. package/src/brand-page/utils/migration/migration-05_2.ts +1 -1
  237. package/src/brand-page/utils/migration/migration-08.test.ts +1 -1
  238. package/src/brand-page/utils/personalisation.util.test.ts +0 -35
  239. package/src/brand-page/utils/personalisation.util.ts +23 -119
  240. package/src/brand-page/view-models.ts +56 -38
  241. package/src/brand-page-maker/assessors/banner-section-description-visibility.assessor.ts +6 -6
  242. package/src/brand-page-maker/assessors/color-replacement-icon-visibility.assessor.ts +2 -2
  243. package/src/brand-page-maker/assessors/color-replacement-profile-visibility.assessor.ts +2 -2
  244. package/src/brand-page-maker/assessors/color-replacement-visibility.assessor.ts +2 -2
  245. package/src/brand-page-maker/assessors/contact-form-visibility.assessor.ts +14 -14
  246. package/src/brand-page-maker/components/action-bar/ActionBar.mixin.ts +0 -1
  247. package/src/brand-page-maker/components/ai-text-edit/TextBubble.vue +0 -1
  248. package/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.ts +1 -5
  249. package/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.stories.ts +0 -1
  250. package/src/brand-page-maker/components/brand-page-maker/BrandPageMakerTemplateSettings.stories.ts +5 -2
  251. package/src/brand-page-maker/components/brand-page-maker/__fixtures__/brand-page-examples.ts +5 -0
  252. package/src/brand-page-maker/components/brand-page-maker/__fixtures__/brand-page-payment.ts +8 -0
  253. package/src/brand-page-maker/components/brand-page-maker/__fixtures__/brand-site-template.examples.ts +2 -0
  254. package/src/brand-page-maker/components/brandpage-maker-page/BrandPageMakerPage.vue +0 -1
  255. package/src/brand-page-maker/components/brandpage-settings/ReplaceImageSettingsForBrandPageSettings.mixin.ts +1 -1
  256. package/src/brand-page-maker/components/color-palette/BpColorPalette.vue +2 -1
  257. package/src/brand-page-maker/components/color-picker/components/ColorModeSelect.mixin.ts +0 -1
  258. package/src/brand-page-maker/components/color-picker/components/GradientColorSlider.mixin.ts +0 -1
  259. package/src/brand-page-maker/components/color-picker/components/GradientColorSliderHandler.mixin.ts +0 -1
  260. package/src/brand-page-maker/components/color-picker/components/GradientTypeSelector.mixin.ts +0 -1
  261. package/src/brand-page-maker/components/device/Device.mixin.ts +0 -1
  262. package/src/brand-page-maker/components/navigation/Navigation.mixin.ts +0 -1
  263. package/src/brand-page-maker/components/rich-text-editor/RichTextEditor.vue +1 -3
  264. package/src/brand-page-maker/components/sensitive-content/SensitiveContent.vue +4 -1
  265. package/src/brand-page-maker/components/seo-settings/DeleteDomainModal.vue +1 -1
  266. package/src/brand-page-maker/components/seo-settings/SeoPageSettings.mixin.ts +0 -1
  267. package/src/brand-page-maker/components/seo-settings/SeoPageSettings.vue +1 -1
  268. package/src/brand-page-maker/components/seo-settings/UrlSettings.mixin.ts +1 -0
  269. package/src/brand-page-maker/components/seo-settings/UrlSettings.vue +1 -0
  270. package/src/brand-page-maker/components/seo-settings/UseExistingDomainModal.vue +5 -1
  271. package/src/brand-page-maker/components/shop/AddOrEditProductContainer.vue +1 -1
  272. package/src/brand-page-maker/components/shop/ReplaceImageSettingsForShop.vue +1 -1
  273. package/src/brand-page-maker/components/social-share/SocialShare.vue +1 -1
  274. package/src/brand-page-maker/components/template-preview/TemplatePreview.mixin.ts +8 -22
  275. package/src/brand-page-maker/components/template-preview/TemplatePreview.vue +10 -5
  276. package/src/brand-page-maker/components/video-play/VideoPlayButton.vue +1 -1
  277. package/src/brand-page-maker/composables/ai-chat/useAiWebsiteBuilderChat.ts +1 -1
  278. package/src/brand-page-maker/composables/rich-text-editor/useRichTextEditor.ts +6 -6
  279. package/src/brand-page-maker/constants/config-settings-constant.ts +3 -3
  280. package/src/brand-page-maker/convertors/text-image-display-style.convertor.ts +1 -1
  281. package/src/brand-page-maker/models/button-styles-config.ts +2 -4
  282. package/src/brand-page-maker/models/content-settings-configs.ts +2 -4
  283. package/src/brand-page-maker/models/navigation-style-configs.ts +6 -5
  284. package/src/brand-page-maker/stores/admin-logo-settings/admin-logo-settings-store.ts +1 -0
  285. package/src/brand-page-maker/stores/brandpage-container/brand-page-container-store.ts +5 -8
  286. package/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.test.ts +3 -1
  287. package/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.ts +4 -12
  288. package/src/brand-page-maker/stores/ui/ui-store.ts +0 -16
  289. package/src/brand-page-maker/utils/content-settings.util.ts +1 -1
  290. package/src/brand-page-maker/utils/rich-text-editor.util.ts +4 -4
  291. package/src/clients/design-template-client.ts +0 -3
  292. package/src/clients/models.ts +0 -2
  293. package/src/composables/useSnapshot.ts +22 -19
  294. package/src/lib.ts +0 -1
  295. package/src/metadata-builder/builders/footer-builder.test.ts +245 -84
  296. package/src/metadata-builder/builders/footer-builder.ts +81 -130
  297. package/src/metadata-builder/builders/item-id-utils.test.ts +118 -0
  298. package/src/metadata-builder/builders/item-id-utils.ts +85 -0
  299. package/src/metadata-builder/builders/section-builders/image-gallery-builder.ts +62 -88
  300. package/src/metadata-builder/builders/section-builders/images-section-builder.ts +66 -129
  301. package/src/metadata-builder/builders/section-builders/shapes-section-builder.ts +105 -160
  302. package/src/metadata-builder/builders/section-builders/social-links-builder.ts +65 -20
  303. package/src/metadata-builder/builders/section-builders/testimonial-section-builder.ts +83 -146
  304. package/src/metadata-builder/builders/section-builders/text-section-builder.ts +70 -122
  305. package/src/metadata-builder/builders/shape-color-utils.ts +2 -2
  306. package/src/metadata-builder/parsers/ai-toolcall-parser.ts +13 -4
  307. package/src/metadata-builder/parsers/section-tool-arguments.ts +66 -48
  308. package/src/plugins/google-maps-plugin/google-maps-plugin.ts +8 -8
  309. package/src/shared/background-settings/components/BackgroundSettings.vue +1 -1
  310. package/src/shared/background-settings/components/background-color/BackgroundColorSettings.vue +1 -1
  311. package/src/shared/color-picker/components/ColorPicker.mixin.ts +7 -2
  312. package/src/shared/color-picker/components/ColorPickerSwatch.mixin.ts +0 -1
  313. package/src/shared/common/NumberInput.vue +0 -1
  314. package/src/shared/common/SideBarSettingsHeader.vue +2 -2
  315. package/src/shared/confirm-modal/ConfirmModal.mixin.ts +0 -1
  316. package/src/shared/content-settings/components/booking-url-field/BookingUrlField.vue +0 -1
  317. package/src/shared/content-settings/components/button-configuration-field/ButtonConfigurationField.vue +0 -1
  318. package/src/shared/content-settings/components/color-field/ColorField.vue +10 -3
  319. package/src/shared/content-settings/components/content-settings/ContentSettings.mixin.ts +1 -1
  320. package/src/shared/content-settings/components/content-settings/ContentSettingsLockedButton.vue +0 -1
  321. package/src/shared/content-settings/components/content-settings-item/ContentSettingsItem.mixin.ts +0 -5
  322. package/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.ts +14 -1
  323. package/src/shared/content-settings/components/focused-content-item-settings/FocusedContentItemSettings.mixin.ts +6 -4
  324. package/src/shared/content-settings/components/icon-library/IconLibrary.mixin.ts +0 -1
  325. package/src/shared/content-settings/components/items-field/ItemsField.mixin.ts +1 -1
  326. package/src/shared/content-settings/components/logo-selection-field/LogoSelectionField.vue +1 -1
  327. package/src/shared/content-settings/components/logo-size-field/LogoSizeSliderField.vue +1 -1
  328. package/src/shared/content-settings/components/page-dropdown-field/PageDropdownField.vue +1 -1
  329. package/src/shared/content-settings/components/personalisation-role-field/PersonalisationRoleField.vue +4 -4
  330. package/src/shared/content-settings/components/text-image-field/TextImageField.vue +8 -2
  331. package/src/shared/content-settings/components/text-items-field/TextItemsField.mixin.ts +1 -1
  332. package/src/shared/content-settings/models.ts +2 -3
  333. package/src/shared/content-settings/utils.ts +1 -0
  334. package/src/shared/dropzone-image-uploader/components/DropzoneImageUploader.vue +0 -1
  335. package/src/shared/ecommerce-settings/store/cart-store.test.ts +1 -1
  336. package/src/shared/font-family-selector/FontFamilySelector.mixin.ts +0 -1
  337. package/src/shared/image-gallery-style-settings/ImageGalleryStyleSettings.mixin.ts +0 -1
  338. package/src/shared/logo-settings/components/EditLogoSettings.mixin.ts +1 -1
  339. package/src/shared/logo-settings/components/LogoSettings.mixin.ts +1 -0
  340. package/src/shared/logo-settings/components/LogoSettingsHeader.mixin.ts +0 -1
  341. package/src/shared/logo-settings/components/LogoStyleSettingsWithState.vue +2 -2
  342. package/src/shared/logo-settings/components/LogoTemplateSearch.vue +1 -1
  343. package/src/shared/logo-settings/store/editable-logo-store.ts +1 -1
  344. package/src/shared/logo-settings/store/logo-settings-store.test.ts +4 -4
  345. package/src/shared/logo-versions/LogoVersions.mixin.ts +0 -1
  346. package/src/shared/models.ts +0 -6
  347. package/src/shared/my-designs/MyDesigns.mixin.ts +0 -1
  348. package/src/shared/my-uploads/MyMedia.mixin.ts +0 -1
  349. package/src/shared/my-uploads/MyMediaSelectionItem.vue +1 -2
  350. package/src/shared/my-uploads/MyUploads.mixin.ts +0 -1
  351. package/src/shared/my-uploads/models.ts +2 -1
  352. package/src/shared/navigation-style-settings/components/navigation-style-settings/NavigationStyleSettings.mixin.ts +0 -1
  353. package/src/shared/navigation-style-settings/components/navigation-style-settings/NavigationStyleSettings.vue +1 -1
  354. package/src/shared/page-settings/components/new-page-modal/NewPageModal.mixin.ts +3 -0
  355. package/src/shared/page-settings/components/page-settings/NestedDraggable.vue +1 -1
  356. package/src/shared/page-settings/components/page-settings/PageSettings.vue +2 -1
  357. package/src/shared/page-settings/components/page-settings-section-styles/HeaderDisplayStyle.vue +1 -1
  358. package/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.ts +1 -1
  359. package/src/shared/page-settings/store/page-settings-store.test.ts +8 -4
  360. package/src/shared/publish-brand-page-with-state/PublishBrandPageModalWithState.mixin.ts +1 -0
  361. package/src/shared/shape-library/ShapeLibrary.vue +2 -2
  362. package/src/shared/style-settings/store/style-settings-store.ts +1 -1
  363. package/src/shared/template-settings/components/TemplatesSettings.mixin.ts +2 -2
  364. package/src/shared/template-settings/models.ts +0 -1
  365. package/src/shared/template-settings/store/template-settings-store.ts +18 -105
  366. package/src/shared/unsplash-library/UnsplashLibrary.vue +6 -2
  367. package/src/shared/video-library/VideoLibrary.vue +1 -1
  368. package/lib/src/brand-page/components/banner-section/BannerSection.mixin.d.ts +0 -103
  369. package/lib/src/brand-page/components/banner-section/BannerSection.mixin.js +0 -178
  370. package/src/brand-page/components/banner-section/BannerSection.mixin.ts +0 -210
  371. package/src/brand-page-maker/components/template-preview/TemplatePreviewAiBadge.vue +0 -12
@@ -0,0 +1,118 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import {
3
+ END_ITEM_ANCHOR,
4
+ ensureItemIds,
5
+ generateItemId,
6
+ removeItemsByIds,
7
+ repositionItemByAnchor,
8
+ } from './item-id-utils';
9
+
10
+ type Item = { id?: string; label: string };
11
+
12
+ const items = (...labels: string[]): Item[] =>
13
+ labels.map((label) => ({ id: label, label }));
14
+
15
+ const labels = (list: Item[]): string[] => list.map((i) => i.label);
16
+
17
+ describe('generateItemId', () => {
18
+ it('returns unique uuid-shaped ids', () => {
19
+ const a = generateItemId();
20
+ const b = generateItemId();
21
+
22
+ expect(a).not.toBe(b);
23
+ expect(a).toMatch(
24
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,
25
+ );
26
+ });
27
+ });
28
+
29
+ describe('ensureItemIds', () => {
30
+ it('backfills only the items missing an id, preserving existing ones', () => {
31
+ const list: Item[] = [
32
+ { id: 'keep', label: 'a' },
33
+ { label: 'b' },
34
+ { id: '', label: 'c' },
35
+ ];
36
+
37
+ ensureItemIds(list);
38
+
39
+ expect(list[0].id).toBe('keep');
40
+ expect(list[1].id).toBeTruthy();
41
+ expect(list[2].id).toBeTruthy();
42
+ expect(new Set(list.map((i) => i.id)).size).toBe(3);
43
+ });
44
+
45
+ it('is a no-op for undefined', () => {
46
+ expect(() => ensureItemIds(undefined)).not.toThrow();
47
+ });
48
+ });
49
+
50
+ describe('removeItemsByIds', () => {
51
+ it('removes by id regardless of order (no index shifting)', () => {
52
+ const result = removeItemsByIds(items('a', 'b', 'c', 'd'), ['c', 'a']);
53
+
54
+ expect(labels(result)).toEqual(['b', 'd']);
55
+ });
56
+
57
+ it('returns the list unchanged when no ids given', () => {
58
+ const list = items('a', 'b');
59
+
60
+ expect(removeItemsByIds(list, undefined)).toBe(list);
61
+ expect(removeItemsByIds(list, [])).toBe(list);
62
+ });
63
+
64
+ it('returns [] for undefined items', () => {
65
+ expect(removeItemsByIds(undefined, ['x'])).toEqual([]);
66
+ });
67
+ });
68
+
69
+ describe('repositionItemByAnchor', () => {
70
+ it('appends a new item when no anchor is given', () => {
71
+ const list = items('a', 'b');
72
+
73
+ repositionItemByAnchor(list, { id: 'new', label: 'new' }, undefined);
74
+
75
+ expect(labels(list)).toEqual(['a', 'b', 'new']);
76
+ });
77
+
78
+ it('leaves an existing item in place when no anchor is given', () => {
79
+ const list = items('a', 'b', 'c');
80
+
81
+ repositionItemByAnchor(list, list[1], undefined);
82
+
83
+ expect(labels(list)).toEqual(['a', 'b', 'c']);
84
+ });
85
+
86
+ it('inserts a new item before the anchor id', () => {
87
+ const list = items('a', 'b', 'c');
88
+
89
+ repositionItemByAnchor(list, { id: 'new', label: 'new' }, 'b');
90
+
91
+ expect(labels(list)).toEqual(['a', 'new', 'b', 'c']);
92
+ });
93
+
94
+ it('moves an existing item before the anchor id', () => {
95
+ const list = items('a', 'b', 'c', 'd');
96
+
97
+ // move 'd' before 'b'
98
+ repositionItemByAnchor(list, list[3], 'b');
99
+
100
+ expect(labels(list)).toEqual(['a', 'd', 'b', 'c']);
101
+ });
102
+
103
+ it('moves an existing item to the end with the __end__ sentinel', () => {
104
+ const list = items('a', 'b', 'c');
105
+
106
+ repositionItemByAnchor(list, list[0], END_ITEM_ANCHOR);
107
+
108
+ expect(labels(list)).toEqual(['b', 'c', 'a']);
109
+ });
110
+
111
+ it('falls through to the end when the anchor id is not found', () => {
112
+ const list = items('a', 'b');
113
+
114
+ repositionItemByAnchor(list, { id: 'new', label: 'new' }, 'missing');
115
+
116
+ expect(labels(list)).toEqual(['a', 'b', 'new']);
117
+ });
118
+ });
@@ -0,0 +1,85 @@
1
+ import { v4 as uuidv4 } from 'uuid';
2
+
3
+ /**
4
+ * Sentinel anchor meaning "the end of the list", mirroring the section-level
5
+ * `move_before_content_id: "__end__"` convention used in base-section-builder.
6
+ */
7
+ export const END_ITEM_ANCHOR = '__end__';
8
+
9
+ /** Generate a stable item id. Mirrors section-level `uuid.v4()` id generation. */
10
+ export const generateItemId = (): string => uuidv4();
11
+
12
+ /**
13
+ * Backfill ids onto any items missing one. Per BP-5603 this runs in the
14
+ * metadata builder when a section is processed, so the persisted metadata
15
+ * returned to the client gains stable ids that subsequent AI tool calls can
16
+ * reference. Mutates in place.
17
+ */
18
+ export const ensureItemIds = <T extends { id?: string }>(
19
+ items: T[] | undefined,
20
+ ): void => {
21
+ if (!items) return;
22
+
23
+ for (const item of items) {
24
+ if (!item.id) item.id = generateItemId();
25
+ }
26
+ };
27
+
28
+ /**
29
+ * Remove items whose id appears in `ids`. Order-independent (no descending
30
+ * index sort needed) — the whole point of the id-based migration. Items
31
+ * without an id are never removed by id.
32
+ */
33
+ export const removeItemsByIds = <T extends { id?: string }>(
34
+ items: T[] | undefined,
35
+ ids: string[] | undefined,
36
+ ): T[] => {
37
+ if (!items?.length) return items ?? [];
38
+
39
+ if (!ids?.length) return items;
40
+
41
+ const toRemove = new Set(ids);
42
+
43
+ return items.filter((item) => !item.id || !toRemove.has(item.id));
44
+ };
45
+
46
+ /**
47
+ * Position `item` within `items` relative to an anchor id — the item-level
48
+ * mirror of base-section-builder's `moveContent`:
49
+ *
50
+ * - `moveBeforeItemId === undefined` → leave an existing item where it is;
51
+ * append a new (not-yet-present) item to the end.
52
+ * - `moveBeforeItemId === "__end__"` → move/insert at the very end.
53
+ * - anchor id found → insert immediately before it.
54
+ * - anchor id not found → fall through to the end (same
55
+ * forgiving behaviour as `moveContent`).
56
+ *
57
+ * Mutates `items` in place. `item` may be an existing element of `items`
58
+ * (a move) or a brand-new object (an insert).
59
+ */
60
+ export const repositionItemByAnchor = <T extends { id?: string }>(
61
+ items: T[],
62
+ item: T,
63
+ moveBeforeItemId: string | undefined,
64
+ ): void => {
65
+ const currentIndex = items.indexOf(item);
66
+
67
+ if (moveBeforeItemId === undefined) {
68
+ if (currentIndex === -1) items.push(item);
69
+
70
+ return;
71
+ }
72
+
73
+ if (currentIndex !== -1) items.splice(currentIndex, 1);
74
+
75
+ const anchorIndex =
76
+ moveBeforeItemId === END_ITEM_ANCHOR
77
+ ? -1
78
+ : items.findIndex((i) => i.id === moveBeforeItemId);
79
+
80
+ if (anchorIndex !== -1) {
81
+ items.splice(anchorIndex, 0, item);
82
+ } else {
83
+ items.push(item);
84
+ }
85
+ };
@@ -10,6 +10,12 @@ import type { BuilderResult } from '../../models';
10
10
  import { applyScalarFields, isValidUrl } from '../section-field-utils';
11
11
  import { BaseSectionBuilder } from './base-section-builder';
12
12
  import { validateImageGallery } from '../section-rules/image-gallery.rules';
13
+ import {
14
+ ensureItemIds,
15
+ generateItemId,
16
+ removeItemsByIds,
17
+ repositionItemByAnchor,
18
+ } from '../item-id-utils';
13
19
 
14
20
  const applyGalleryType = (
15
21
  content: ImageGalleryContent,
@@ -21,11 +27,17 @@ const applyGalleryType = (
21
27
  };
22
28
  };
23
29
 
30
+ /**
31
+ * Full rebuild: discard existing images and map the payload directly.
32
+ * Used on create and when `_replace_items` is set. Each item gets a fresh id
33
+ * (or keeps the caller-supplied `item_id` when provided).
34
+ */
24
35
  const applyGalleryImages = (
25
36
  content: ImageGalleryContent,
26
37
  images: ImageGalleryItem[],
27
38
  ): void => {
28
39
  content.images = images.map((img) => ({
40
+ id: img.item_id || generateItemId(),
29
41
  imageUrl: isValidUrl(img.image_url) ? img.image_url! : '',
30
42
  imageTitle: img.caption || '',
31
43
  width: 0,
@@ -35,84 +47,53 @@ const applyGalleryImages = (
35
47
  }));
36
48
  };
37
49
 
50
+ /**
51
+ * Id-based merge (BP-5603), mirroring testimonial-section-builder one level
52
+ * deeper into `content.images`:
53
+ * - `item_id` matches existing item → update in place; reposition if
54
+ * `move_before_item_id` is set.
55
+ * - no `item_id` → add new item, positioned by
56
+ * `move_before_item_id`.
57
+ * - `removeIds` → delete by id (order-independent).
58
+ *
59
+ * Existing items are id-backfilled first so legacy galleries become
60
+ * referenceable as soon as the metadata builder touches them.
61
+ */
38
62
  const mergeGalleryImages = (
39
63
  content: ImageGalleryContent,
40
64
  images: ImageGalleryItem[],
41
- replaceAll: boolean,
65
+ removeIds: string[] | undefined,
42
66
  ): void => {
43
- const itemsToMerge = images.filter((img) => img.insert_at === undefined);
44
-
45
- const updatedImages = itemsToMerge.map((img, index) => {
46
- const existing = content.images?.[index];
47
- const imageUrl = isValidUrl(img.image_url)
48
- ? img.image_url!
49
- : (existing?.imageUrl ?? '');
50
-
51
- return {
52
- ...(existing || {}),
53
- imageUrl,
54
- imageTitle: img.caption ?? existing?.imageTitle ?? '',
55
- width: existing?.width ?? 0,
56
- height: existing?.height ?? 0,
57
- mediaType: existing?.mediaType ?? 'image',
58
- imageLink: existing?.imageLink ?? '',
59
- };
60
- });
61
-
62
- if (
63
- !replaceAll &&
64
- content.images &&
65
- content.images.length > itemsToMerge.length
66
- ) {
67
- const extras = content.images.slice(itemsToMerge.length).map((img) => ({
68
- imageUrl: img.imageUrl ?? '',
69
- imageTitle: img.imageTitle ?? '',
70
- width: img.width ?? 0,
71
- height: img.height ?? 0,
72
- mediaType: img.mediaType ?? ('image' as const),
73
- imageLink: img.imageLink ?? '',
74
- }));
75
-
76
- updatedImages.push(...extras);
77
- }
78
-
79
- content.images = updatedImages;
80
- };
81
-
82
- const insertGalleryImages = (
83
- content: ImageGalleryContent,
84
- images: ImageGalleryItem[],
85
- ): void => {
86
- const inserts = images.filter((img) => img.insert_at !== undefined);
87
-
88
- if (!inserts.length) return;
89
-
90
- const sorted = [...inserts].sort(
91
- (a, b) => (b.insert_at ?? 0) - (a.insert_at ?? 0),
92
- );
67
+ ensureItemIds(content.images);
68
+ content.images = removeItemsByIds(content.images, removeIds);
93
69
 
94
- for (const img of sorted) {
95
- if (!isValidUrl(img.image_url)) continue;
70
+ for (const img of images) {
71
+ if (img.item_id) {
72
+ const existing = content.images.find((i) => i.id === img.item_id);
96
73
 
97
- if (content.images?.some((existing) => existing.imageUrl === img.image_url))
98
- continue;
74
+ // Unknown id — skip rather than guess at a position.
75
+ if (!existing) continue;
99
76
 
100
- content.images = content.images ?? [];
101
-
102
- const pos = Math.max(
103
- 0,
104
- Math.min(img.insert_at ?? 0, content.images.length),
105
- );
77
+ if (img.image_url !== undefined) {
78
+ existing.imageUrl = isValidUrl(img.image_url) ? img.image_url! : '';
79
+ }
106
80
 
107
- content.images.splice(pos, 0, {
108
- imageUrl: img.image_url!,
109
- imageTitle: img.caption ?? '',
110
- width: 0,
111
- height: 0,
112
- mediaType: 'image',
113
- imageLink: '',
114
- colorReplacements: [],
115
- });
81
+ if (img.caption !== undefined) existing.imageTitle = img.caption;
82
+
83
+ repositionItemByAnchor(content.images, existing, img.move_before_item_id);
84
+ } else {
85
+ const newItem = {
86
+ id: generateItemId(),
87
+ imageUrl: isValidUrl(img.image_url) ? img.image_url! : '',
88
+ imageTitle: img.caption ?? '',
89
+ width: 0,
90
+ height: 0,
91
+ mediaType: 'image' as const,
92
+ imageLink: '',
93
+ };
94
+
95
+ repositionItemByAnchor(content.images, newItem, img.move_before_item_id);
96
+ }
116
97
  }
117
98
  };
118
99
 
@@ -144,26 +125,19 @@ export class ImageGalleryBuilder extends BaseSectionBuilder<ImageGalleryContent>
144
125
  applyGalleryType(content, args.gallery_type);
145
126
  }
146
127
 
147
- if (args.images && Array.isArray(args.images)) {
148
- if (this.mode === 'create') {
128
+ if (args._replace_items && args.images && Array.isArray(args.images)) {
129
+ // Full replacement: discard existing images and rebuild from the payload.
130
+ applyGalleryImages(content, args.images);
131
+ } else if (this.mode === 'create') {
132
+ if (args.images && Array.isArray(args.images)) {
149
133
  applyGalleryImages(content, args.images);
150
- } else {
151
- mergeGalleryImages(content, args.images, !!args._replace_items);
152
134
  }
153
- }
154
-
155
- if (args._remove_item_indices?.length && content.images) {
156
- const sorted = [...args._remove_item_indices].sort((a, b) => b - a);
157
-
158
- for (const idx of sorted) {
159
- if (idx >= 0 && idx < content.images.length) {
160
- content.images.splice(idx, 1);
161
- }
162
- }
163
- }
164
-
165
- if (args.images && Array.isArray(args.images) && this.mode === 'update') {
166
- insertGalleryImages(content, args.images);
135
+ } else if (
136
+ (args.images && Array.isArray(args.images)) ||
137
+ args._remove_item_ids?.length
138
+ ) {
139
+ // Update: id-based add / edit / move / remove in a single pass.
140
+ mergeGalleryImages(content, args.images ?? [], args._remove_item_ids);
167
141
  }
168
142
 
169
143
  return { request: this.request, model };
@@ -18,12 +18,24 @@ import {
18
18
  } from '../section-field-utils';
19
19
  import { BaseSectionBuilder } from './base-section-builder';
20
20
  import { validateImagesSection } from '../section-rules/images-section.rules';
21
-
21
+ import {
22
+ ensureItemIds,
23
+ generateItemId,
24
+ removeItemsByIds,
25
+ repositionItemByAnchor,
26
+ } from '../item-id-utils';
27
+
28
+ /**
29
+ * Full replacement: discard existing items and rebuild from the payload.
30
+ * Assigns a stable `id` to each item; `itemsId` is set positionally here
31
+ * but will be re-indexed at the end of buildAsync.
32
+ */
22
33
  const applyImageItems = (
23
34
  content: ImagesSectionContent,
24
35
  items: ImagesSectionItem[],
25
36
  ): void => {
26
37
  content.items = items.map((item, index) => ({
38
+ id: item.item_id || generateItemId(),
27
39
  imageUrl: isValidUrl(item.image_url) ? item.image_url! : '',
28
40
  imageTitle: '',
29
41
  width: 0,
@@ -38,25 +50,33 @@ const applyImageItems = (
38
50
  }));
39
51
  };
40
52
 
53
+ /**
54
+ * Apply a batch of item operations by id (BP-5603), mirroring the
55
+ * section-level id-based contract one level deeper:
56
+ * - item with `item_id` matching an existing item → update in place (and
57
+ * reposition if `move_before_item_id` is set);
58
+ * - item without `item_id` → add a new item,
59
+ * positioned by `move_before_item_id`;
60
+ * - `_remove_item_ids` → delete by id.
61
+ *
62
+ * Existing items are id-backfilled first so legacy sections become
63
+ * referenceable the moment the metadata builder touches them.
64
+ * Order-independent: there are no positional indices to shift between ops.
65
+ */
41
66
  const mergeImageItems = (
42
67
  content: ImagesSectionContent,
43
68
  items: ImagesSectionItem[],
44
- replaceAll: boolean,
69
+ removeIds: string[] | undefined,
45
70
  ): void => {
46
- const hasIndexedItems = items.some((item) => item.item_index !== undefined);
71
+ ensureItemIds(content.items);
72
+ content.items = removeItemsByIds(content.items, removeIds);
47
73
 
48
- if (hasIndexedItems) {
49
- for (const item of items) {
50
- const targetIndex = item.item_index;
74
+ for (const item of items) {
75
+ if (item.item_id) {
76
+ const existing = content.items.find((i) => i.id === item.item_id);
51
77
 
52
- if (
53
- targetIndex === undefined ||
54
- targetIndex < 0 ||
55
- !content.items?.[targetIndex]
56
- )
57
- continue;
58
-
59
- const existing = content.items[targetIndex];
78
+ // Unknown id — skip rather than guess at a position.
79
+ if (!existing) continue;
60
80
 
61
81
  if (isValidUrl(item.image_url)) existing.imageUrl = item.image_url!;
62
82
 
@@ -66,47 +86,27 @@ const mergeImageItems = (
66
86
 
67
87
  if (item.button_text !== undefined)
68
88
  existing.buttonText = item.button_text || false;
69
- }
70
-
71
- return;
72
- }
73
-
74
- const itemsToMerge = items.filter((item) => item.insert_at === undefined);
75
-
76
- const updatedItems = itemsToMerge.map((item, index) => {
77
- const existing = content.items?.[index];
78
89
 
79
- return {
80
- ...(existing || {}),
81
- imageUrl: isValidUrl(item.image_url)
82
- ? item.image_url!
83
- : (existing?.imageUrl ?? ''),
84
- imageTitle: existing?.imageTitle ?? '',
85
- width: existing?.width ?? 0,
86
- height: existing?.height ?? 0,
87
- mediaType: existing?.mediaType ?? 'image',
88
- imageLink: existing?.imageLink ?? '',
89
- title: item.heading ?? existing?.title ?? '',
90
- paragraph: item.text ?? existing?.paragraph ?? '',
91
- buttonText: item.button_text ?? existing?.buttonText ?? '',
92
- itemsId: existing?.itemsId ?? index,
93
- colorReplacements: existing?.colorReplacements ?? [],
94
- };
95
- });
96
-
97
- if (
98
- !replaceAll &&
99
- content.items &&
100
- content.items.length > itemsToMerge.length
101
- ) {
102
- const remaining = content.items.slice(
103
- itemsToMerge.length,
104
- ) as typeof updatedItems;
105
-
106
- updatedItems.push(...remaining);
90
+ repositionItemByAnchor(content.items, existing, item.move_before_item_id);
91
+ } else {
92
+ const newItem = {
93
+ id: generateItemId(),
94
+ imageUrl: isValidUrl(item.image_url) ? item.image_url! : '',
95
+ imageTitle: '',
96
+ width: 0,
97
+ height: 0,
98
+ mediaType: 'image',
99
+ imageLink: '',
100
+ title: item.heading ?? '',
101
+ paragraph: item.text ?? '',
102
+ buttonText: item.button_text || false,
103
+ itemsId: 0, // re-indexed at the end of buildAsync
104
+ colorReplacements: [],
105
+ } as ImagesSectionContent['items'][number];
106
+
107
+ repositionItemByAnchor(content.items, newItem, item.move_before_item_id);
108
+ }
107
109
  }
108
-
109
- content.items = updatedItems;
110
110
  };
111
111
 
112
112
  export class ImagesSectionBuilder extends BaseSectionBuilder<ImagesSectionContent> {
@@ -192,86 +192,23 @@ export class ImagesSectionBuilder extends BaseSectionBuilder<ImagesSectionConten
192
192
  };
193
193
  }
194
194
 
195
- if (args.items && Array.isArray(args.items)) {
196
- if (this.mode === 'create') {
195
+ if (args._replace_items && args.items && Array.isArray(args.items)) {
196
+ // Full replacement: discard existing items and rebuild from the payload.
197
+ applyImageItems(content, args.items);
198
+ } else if (this.mode === 'create') {
199
+ if (args.items && Array.isArray(args.items)) {
197
200
  applyImageItems(content, args.items);
198
- } else {
199
- mergeImageItems(content, args.items, !!args._replace_items);
200
- }
201
- }
202
-
203
- // Phase 2: Item operations (remove, insert, reorder)
204
- if (args._remove_item_indices?.length && content.items) {
205
- const sorted = [...args._remove_item_indices].sort((a, b) => b - a);
206
-
207
- for (const idx of sorted) {
208
- if (idx >= 0 && idx < content.items.length) {
209
- content.items.splice(idx, 1);
210
- }
211
- }
212
- }
213
-
214
- if (args.items && Array.isArray(args.items)) {
215
- const insertItems = args.items.filter(
216
- (item) => item.insert_at !== undefined && item.item_index === undefined,
217
- );
218
- // Process inserts in reverse order of insert_at to preserve indices
219
- const sortedInserts = [...insertItems].sort(
220
- (a, b) => (b.insert_at ?? 0) - (a.insert_at ?? 0),
221
- );
222
-
223
- for (const item of sortedInserts) {
224
- if (!isValidUrl(item.image_url)) continue;
225
-
226
- // Skip if this image_url already exists — defensive against duplicate
227
- // tool call application
228
- if (
229
- content.items?.some(
230
- (existing: any) => existing.imageUrl === item.image_url,
231
- )
232
- ) {
233
- continue;
234
- }
235
-
236
- const pos = Math.max(
237
- 0,
238
- Math.min(item.insert_at ?? 0, content.items?.length ?? 0),
239
- );
240
- const newItem = {
241
- imageUrl: item.image_url,
242
- imageTitle: '',
243
- width: 0,
244
- height: 0,
245
- mediaType: 'image',
246
- imageLink: '',
247
- title: item.heading ?? '',
248
- paragraph: item.text ?? '',
249
- buttonText: item.button_text || '',
250
- itemsId: pos,
251
- colorReplacements: [],
252
- };
253
-
254
- content.items = content.items ?? [];
255
- content.items.splice(pos, 0, newItem as any);
256
- }
257
- }
258
-
259
- if (args._reorder_items?.length && content.items) {
260
- const newOrder = args._reorder_items;
261
- const unique = new Set(newOrder);
262
-
263
- if (unique.size === newOrder.length) {
264
- const reordered = newOrder
265
- .filter((idx) => idx >= 0 && idx < content.items!.length)
266
- .map((idx) => content.items![idx]);
267
-
268
- if (reordered.length === content.items.length) {
269
- content.items = reordered;
270
- }
271
201
  }
202
+ } else if (
203
+ (args.items && Array.isArray(args.items)) ||
204
+ args._remove_item_ids?.length
205
+ ) {
206
+ // Update: id-based add / edit / move / remove in a single pass.
207
+ mergeImageItems(content, args.items ?? [], args._remove_item_ids);
272
208
  }
273
209
 
274
- // Re-index itemsId after any item operations
210
+ // Re-index itemsId after all id-based item operations (render concern —
211
+ // separate from the stable `id` used for targeting).
275
212
  if (content.items) {
276
213
  content.items.forEach((item, i) => {
277
214
  item.itemsId = i;