cap-creatives-ui 8.0.280 → 8.0.321

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 (247) hide show
  1. package/.github/workflows/pr-title-check.yml +88 -0
  2. package/app/constants/unified.js +21 -1
  3. package/app/containers/App/constants.js +0 -1
  4. package/app/containers/Login/test/index.test.js +123 -0
  5. package/app/containers/Login/test/selectors.test.js +165 -0
  6. package/app/initialState.js +0 -2
  7. package/app/services/api.js +6 -0
  8. package/app/services/tests/api.test.js +7 -0
  9. package/app/services/tests/getSchema.test.js +95 -0
  10. package/app/utils/common.js +23 -9
  11. package/app/utils/commonUtils.js +64 -93
  12. package/app/utils/tagValidations.js +83 -219
  13. package/app/utils/templateVarUtils.js +172 -0
  14. package/app/utils/tests/common.test.js +265 -323
  15. package/app/utils/tests/commonUtil.test.js +461 -118
  16. package/app/utils/tests/commonUtils.test.js +581 -0
  17. package/app/utils/tests/messageUtils.test.js +95 -0
  18. package/app/utils/tests/smsCharCount.test.js +304 -0
  19. package/app/utils/tests/smsCharCountV2.test.js +213 -10
  20. package/app/utils/tests/tagValidations.test.js +474 -357
  21. package/app/utils/tests/templateVarUtils.test.js +160 -0
  22. package/app/v2Components/CapDeviceContent/index.js +10 -7
  23. package/app/v2Components/CapTagList/index.js +32 -24
  24. package/app/v2Components/CapTagList/style.scss +48 -0
  25. package/app/v2Components/CapTagListWithInput/__tests__/CapTagListWithInput.test.js +63 -0
  26. package/app/v2Components/CapTagListWithInput/index.js +8 -0
  27. package/app/v2Components/CapWhatsappCTA/index.js +2 -0
  28. package/app/v2Components/CapWhatsappCarouselButton/index.js +32 -14
  29. package/app/v2Components/CapWhatsappCarouselButton/tests/index.test.js +120 -2
  30. package/app/v2Components/CommonTestAndPreview/CustomValuesEditor.js +70 -49
  31. package/app/v2Components/CommonTestAndPreview/DeliverySettings/DeliverySettings.scss +39 -0
  32. package/app/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.js +606 -0
  33. package/app/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.scss +36 -0
  34. package/app/v2Components/CommonTestAndPreview/DeliverySettings/constants.js +79 -0
  35. package/app/v2Components/CommonTestAndPreview/DeliverySettings/index.js +314 -0
  36. package/app/v2Components/CommonTestAndPreview/DeliverySettings/messages.js +141 -0
  37. package/app/v2Components/CommonTestAndPreview/DeliverySettings/utils/parseSenderDetailsResponse.js +156 -0
  38. package/app/v2Components/CommonTestAndPreview/SendTestMessage.js +57 -1
  39. package/app/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +20 -1
  40. package/app/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +133 -4
  41. package/app/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +210 -4
  42. package/app/v2Components/CommonTestAndPreview/actions.js +20 -0
  43. package/app/v2Components/CommonTestAndPreview/constants.js +57 -1
  44. package/app/v2Components/CommonTestAndPreview/index.js +878 -156
  45. package/app/v2Components/CommonTestAndPreview/messages.js +41 -3
  46. package/app/v2Components/CommonTestAndPreview/previewApiUtils.js +59 -0
  47. package/app/v2Components/CommonTestAndPreview/reducer.js +47 -0
  48. package/app/v2Components/CommonTestAndPreview/sagas.js +75 -5
  49. package/app/v2Components/CommonTestAndPreview/selectors.js +51 -0
  50. package/app/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +352 -0
  51. package/app/v2Components/CommonTestAndPreview/tests/DeliverySettings/ModifyDeliverySettings.test.js +1156 -0
  52. package/app/v2Components/CommonTestAndPreview/tests/DeliverySettings/index.test.js +334 -0
  53. package/app/v2Components/CommonTestAndPreview/tests/DeliverySettings/utils/parseSenderDetailsResponse.test.js +576 -0
  54. package/app/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +156 -0
  55. package/app/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +199 -1
  56. package/app/v2Components/CommonTestAndPreview/tests/actions.test.js +50 -0
  57. package/app/v2Components/CommonTestAndPreview/tests/constants.test.js +18 -7
  58. package/app/v2Components/CommonTestAndPreview/tests/index.test.js +914 -5
  59. package/app/v2Components/CommonTestAndPreview/tests/previewApiUtils.test.js +67 -0
  60. package/app/v2Components/CommonTestAndPreview/tests/reducer.test.js +118 -0
  61. package/app/v2Components/CommonTestAndPreview/tests/sagas.test.js +146 -378
  62. package/app/v2Components/CommonTestAndPreview/tests/selectors.test.js +146 -0
  63. package/app/v2Components/ErrorInfoNote/index.js +24 -26
  64. package/app/v2Components/FormBuilder/index.js +182 -204
  65. package/app/v2Components/FormBuilder/messages.js +4 -8
  66. package/app/v2Components/HtmlEditor/HTMLEditor.js +7 -6
  67. package/app/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +1 -1
  68. package/app/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +928 -17
  69. package/app/v2Components/HtmlEditor/components/CodeEditorPane/index.js +4 -2
  70. package/app/v2Components/HtmlEditor/hooks/__tests__/useValidation.test.js +452 -3
  71. package/app/v2Components/HtmlEditor/hooks/useValidation.js +12 -9
  72. package/app/v2Components/HtmlEditor/utils/__tests__/htmlValidator.enhanced.test.js +132 -0
  73. package/app/v2Components/HtmlEditor/utils/htmlValidator.js +4 -2
  74. package/app/v2Components/SmsFallback/SmsFallbackLocalSelector.js +87 -0
  75. package/app/v2Components/SmsFallback/constants.js +73 -0
  76. package/app/v2Components/SmsFallback/index.js +956 -0
  77. package/app/v2Components/SmsFallback/index.scss +265 -0
  78. package/app/v2Components/SmsFallback/messages.js +78 -0
  79. package/app/v2Components/SmsFallback/smsFallbackUtils.js +107 -0
  80. package/app/v2Components/SmsFallback/tests/SmsFallbackLocalSelector.test.js +50 -0
  81. package/app/v2Components/SmsFallback/tests/rcsSmsFallback.acceptance.test.js +147 -0
  82. package/app/v2Components/SmsFallback/tests/smsFallbackHandlers.test.js +304 -0
  83. package/app/v2Components/SmsFallback/tests/smsFallbackUi.test.js +197 -0
  84. package/app/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +261 -0
  85. package/app/v2Components/SmsFallback/tests/useLocalTemplateList.test.js +422 -0
  86. package/app/v2Components/SmsFallback/useLocalTemplateList.js +92 -0
  87. package/app/v2Components/TestAndPreviewSlidebox/index.js +22 -1
  88. package/app/v2Components/TestAndPreviewSlidebox/sagas.js +11 -4
  89. package/app/v2Components/TestAndPreviewSlidebox/tests/saga.test.js +3 -1
  90. package/app/v2Components/VarSegmentMessageEditor/constants.js +2 -0
  91. package/app/v2Components/VarSegmentMessageEditor/index.js +125 -0
  92. package/app/v2Components/VarSegmentMessageEditor/index.scss +46 -0
  93. package/app/v2Containers/BeeEditor/index.js +3 -0
  94. package/app/v2Containers/BeePopupEditor/index.js +9 -2
  95. package/app/v2Containers/Cap/mockData.js +0 -14
  96. package/app/v2Containers/Cap/reducer.js +3 -55
  97. package/app/v2Containers/Cap/tests/reducer.test.js +0 -102
  98. package/app/v2Containers/CommunicationFlow/CommunicationFlow.js +291 -0
  99. package/app/v2Containers/CommunicationFlow/CommunicationFlow.scss +25 -0
  100. package/app/v2Containers/CommunicationFlow/Tests/CommunicationFlow.test.js +255 -0
  101. package/app/v2Containers/CommunicationFlow/constants.js +200 -0
  102. package/app/v2Containers/CommunicationFlow/index.js +102 -0
  103. package/app/v2Containers/CommunicationFlow/messages.js +346 -0
  104. package/app/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/ChannelSelectionStep.js +522 -0
  105. package/app/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/ChannelSelectionStep.scss +170 -0
  106. package/app/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/Tests/ChannelSelectionStep.test.js +796 -0
  107. package/app/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/index.js +5 -0
  108. package/app/v2Containers/CommunicationFlow/steps/CommunicationStrategyStep/CommunicationStrategyStep.js +95 -0
  109. package/app/v2Containers/CommunicationFlow/steps/CommunicationStrategyStep/Tests/CommunicationStrategyStep.test.js +133 -0
  110. package/app/v2Containers/CommunicationFlow/steps/CommunicationStrategyStep/index.js +5 -0
  111. package/app/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/DeliverySettingsSection.js +289 -0
  112. package/app/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/DeliverySettingsSection.scss +70 -0
  113. package/app/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/SenderDetails.js +319 -0
  114. package/app/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/SenderDetails.scss +69 -0
  115. package/app/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/DeliverySettingsSection.test.js +616 -0
  116. package/app/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/SenderDetails.test.js +577 -0
  117. package/app/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/deliverySettingsConfig.test.js +1111 -0
  118. package/app/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/deliverySettingsConfig.js +696 -0
  119. package/app/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/index.js +7 -0
  120. package/app/v2Containers/CommunicationFlow/steps/DynamicControlsStep/DynamicControlsStep.js +102 -0
  121. package/app/v2Containers/CommunicationFlow/steps/DynamicControlsStep/DynamicControlsStep.scss +36 -0
  122. package/app/v2Containers/CommunicationFlow/steps/DynamicControlsStep/Tests/DynamicControlsStep.test.js +91 -0
  123. package/app/v2Containers/CommunicationFlow/steps/DynamicControlsStep/index.js +5 -0
  124. package/app/v2Containers/CommunicationFlow/steps/MessageTypeStep/MessageTypeStep.js +86 -0
  125. package/app/v2Containers/CommunicationFlow/steps/MessageTypeStep/Tests/MessageTypeStep.test.js +100 -0
  126. package/app/v2Containers/CommunicationFlow/steps/MessageTypeStep/index.js +5 -0
  127. package/app/v2Containers/CommunicationFlow/utils/getEnabledSteps.js +30 -0
  128. package/app/v2Containers/CreativesContainer/CreativesSlideBoxWrapper.js +43 -0
  129. package/app/v2Containers/CreativesContainer/SlideBoxContent.js +127 -11
  130. package/app/v2Containers/CreativesContainer/SlideBoxFooter.js +62 -9
  131. package/app/v2Containers/CreativesContainer/SlideBoxHeader.js +29 -4
  132. package/app/v2Containers/CreativesContainer/constants.js +24 -0
  133. package/app/v2Containers/CreativesContainer/embeddedSlideboxUtils.js +67 -0
  134. package/app/v2Containers/CreativesContainer/index.js +346 -71
  135. package/app/v2Containers/CreativesContainer/index.scss +51 -1
  136. package/app/v2Containers/CreativesContainer/messages.js +12 -0
  137. package/app/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +90 -0
  138. package/app/v2Containers/CreativesContainer/tests/SlideBoxContent.test.js +69 -1
  139. package/app/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +443 -0
  140. package/app/v2Containers/CreativesContainer/tests/SlideBoxHeader.test.js +110 -0
  141. package/app/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +147 -4
  142. package/app/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxHeader.test.js.snap +363 -0
  143. package/app/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +57 -10
  144. package/app/v2Containers/CreativesContainer/tests/embeddedSlideboxUtils.test.js +258 -0
  145. package/app/v2Containers/CreativesContainer/tests/index.test.js +71 -9
  146. package/app/v2Containers/CreativesContainer/tests/useLocalTemplatesProp.test.js +125 -0
  147. package/app/v2Containers/Email/index.js +2 -5
  148. package/app/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +58 -77
  149. package/app/v2Containers/EmailWrapper/components/EmailWrapperView.js +3 -0
  150. package/app/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +158 -89
  151. package/app/v2Containers/EmailWrapper/components/__tests__/EmailWrapperView.test.js +16 -1
  152. package/app/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +17 -12
  153. package/app/v2Containers/EmailWrapper/index.js +4 -0
  154. package/app/v2Containers/EmailWrapper/tests/useEmailWrapper.edgeCases.test.js +1 -0
  155. package/app/v2Containers/EmailWrapper/tests/useEmailWrapper.test.js +133 -0
  156. package/app/v2Containers/FTP/index.js +2 -51
  157. package/app/v2Containers/FTP/messages.js +0 -4
  158. package/app/v2Containers/InApp/__tests__/InAppHTMLEditor.test.js +110 -155
  159. package/app/v2Containers/InApp/index.js +297 -118
  160. package/app/v2Containers/InApp/tests/index.test.js +17 -6
  161. package/app/v2Containers/InApp/tests/mockData.js +1 -1
  162. package/app/v2Containers/InAppWrapper/hooks/__tests__/useInAppWrapper.test.js +19 -0
  163. package/app/v2Containers/InAppWrapper/hooks/useInAppWrapper.js +3 -0
  164. package/app/v2Containers/InAppWrapper/index.js +3 -0
  165. package/app/v2Containers/InappAdvance/index.js +5 -104
  166. package/app/v2Containers/InappAdvance/tests/index.test.js +2 -0
  167. package/app/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/content.test.js.snap +24 -3
  168. package/app/v2Containers/Line/Container/Text/index.js +0 -1
  169. package/app/v2Containers/MobilePush/Create/index.js +105 -28
  170. package/app/v2Containers/MobilePush/Create/messages.js +4 -0
  171. package/app/v2Containers/MobilePush/Edit/index.js +250 -68
  172. package/app/v2Containers/MobilePush/Edit/messages.js +4 -0
  173. package/app/v2Containers/MobilePushNew/components/PlatformContentFields.js +36 -12
  174. package/app/v2Containers/MobilePushNew/components/tests/PlatformContentFields.test.js +68 -27
  175. package/app/v2Containers/MobilePushNew/index.js +78 -35
  176. package/app/v2Containers/MobilePushNew/messages.js +8 -0
  177. package/app/v2Containers/MobilepushWrapper/index.js +11 -1
  178. package/app/v2Containers/Rcs/constants.js +32 -1
  179. package/app/v2Containers/Rcs/index.js +963 -916
  180. package/app/v2Containers/Rcs/index.scss +85 -6
  181. package/app/v2Containers/Rcs/messages.js +10 -1
  182. package/app/v2Containers/Rcs/rcsLibraryHydrationUtils.js +205 -0
  183. package/app/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +41136 -1566
  184. package/app/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +0 -5
  185. package/app/v2Containers/Rcs/tests/index.test.js +41 -38
  186. package/app/v2Containers/Rcs/tests/mockData.js +38 -0
  187. package/app/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +251 -0
  188. package/app/v2Containers/Rcs/tests/utils.test.js +379 -1
  189. package/app/v2Containers/Rcs/utils.js +358 -10
  190. package/app/v2Containers/Sms/Create/index.js +122 -39
  191. package/app/v2Containers/Sms/Create/messages.js +4 -0
  192. package/app/v2Containers/Sms/Edit/index.js +37 -3
  193. package/app/v2Containers/Sms/commonMethods.js +3 -6
  194. package/app/v2Containers/Sms/smsFormDataHelpers.js +67 -0
  195. package/app/v2Containers/Sms/tests/commonMethods.test.js +122 -0
  196. package/app/v2Containers/Sms/tests/smsFormDataHelpers.test.js +253 -0
  197. package/app/v2Containers/SmsTrai/Create/index.js +9 -4
  198. package/app/v2Containers/SmsTrai/Create/index.scss +1 -1
  199. package/app/v2Containers/SmsTrai/Edit/constants.js +2 -0
  200. package/app/v2Containers/SmsTrai/Edit/index.js +667 -160
  201. package/app/v2Containers/SmsTrai/Edit/index.scss +121 -0
  202. package/app/v2Containers/SmsTrai/Edit/messages.js +9 -4
  203. package/app/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +4590 -2436
  204. package/app/v2Containers/SmsWrapper/index.js +41 -8
  205. package/app/v2Containers/TagList/index.js +63 -2
  206. package/app/v2Containers/TagList/messages.js +8 -0
  207. package/app/v2Containers/TagList/tests/TagList.test.js +122 -20
  208. package/app/v2Containers/TagList/tests/mockdata.js +17 -0
  209. package/app/v2Containers/Templates/TemplatesActionBar.js +101 -0
  210. package/app/v2Containers/Templates/_templates.scss +61 -2
  211. package/app/v2Containers/Templates/actions.js +11 -0
  212. package/app/v2Containers/Templates/constants.js +2 -0
  213. package/app/v2Containers/Templates/index.js +90 -40
  214. package/app/v2Containers/Templates/reducer.js +3 -1
  215. package/app/v2Containers/Templates/sagas.js +57 -12
  216. package/app/v2Containers/Templates/tests/TemplatesActionBar.test.js +120 -0
  217. package/app/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1043 -1079
  218. package/app/v2Containers/Templates/tests/reducer.test.js +12 -0
  219. package/app/v2Containers/Templates/tests/sagas.test.js +193 -12
  220. package/app/v2Containers/Templates/tests/smsTemplatesListApi.test.js +180 -0
  221. package/app/v2Containers/Templates/utils/smsTemplatesListApi.js +79 -0
  222. package/app/v2Containers/TemplatesV2/TemplatesV2.style.js +72 -1
  223. package/app/v2Containers/TemplatesV2/index.js +147 -49
  224. package/app/v2Containers/TemplatesV2/tests/TemplatesV2.localTemplates.test.js +131 -0
  225. package/app/v2Containers/Viber/index.js +9 -10
  226. package/app/v2Containers/Viber/index.scss +1 -1
  227. package/app/v2Containers/WebPush/Create/components/BrandIconSection.test.js +264 -0
  228. package/app/v2Containers/WebPush/Create/components/MessageSection.js +78 -19
  229. package/app/v2Containers/WebPush/Create/components/MessageSection.test.js +82 -0
  230. package/app/v2Containers/WebPush/Create/components/__snapshots__/BrandIconSection.test.js.snap +187 -0
  231. package/app/v2Containers/WebPush/Create/components/__snapshots__/MessageSection.test.js.snap +25 -17
  232. package/app/v2Containers/WebPush/Create/hooks/useAiraTriggerPosition.js +80 -0
  233. package/app/v2Containers/WebPush/Create/hooks/useAiraTriggerPosition.test.js +210 -0
  234. package/app/v2Containers/WebPush/Create/hooks/useTagManagement.js +1 -5
  235. package/app/v2Containers/WebPush/Create/hooks/useTagManagement.test.js +0 -7
  236. package/app/v2Containers/WebPush/Create/index.js +36 -6
  237. package/app/v2Containers/WebPush/Create/index.scss +5 -0
  238. package/app/v2Containers/WebPush/Create/messages.js +8 -1
  239. package/app/v2Containers/WebPush/Create/preview/tests/NotificationContainer.test.js +269 -0
  240. package/app/v2Containers/WebPush/Create/utils/validation.js +31 -15
  241. package/app/v2Containers/WebPush/Create/utils/validation.test.js +72 -24
  242. package/app/v2Containers/Whatsapp/index.js +28 -53
  243. package/app/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +26939 -3982
  244. package/app/v2Containers/Whatsapp/tests/index.test.js +172 -0
  245. package/app/v2Containers/Zalo/index.js +5 -11
  246. package/package.json +2 -2
  247. package/version +9 -0
@@ -59,6 +59,7 @@ const CodeEditorPaneComponent = ({
59
59
  injectedTags = {},
60
60
  location,
61
61
  eventContextTags = [],
62
+ waitEventContextTags = {},
62
63
  selectedOfferDetails = [],
63
64
  channel,
64
65
  userLocale = 'en',
@@ -69,7 +70,7 @@ const CodeEditorPaneComponent = ({
69
70
  }) => {
70
71
  const context = useEditorContext();
71
72
  const {
72
- content, validation, variant, isLiquidEnabled,
73
+ content, validation, variant,
73
74
  } = context || {};
74
75
  const { content: contentValue, updateContent } = content || {};
75
76
  const editorRef = useRef(null);
@@ -289,6 +290,7 @@ const CodeEditorPaneComponent = ({
289
290
  location={location}
290
291
  selectedOfferDetails={selectedOfferDetails}
291
292
  eventContextTags={eventContextTags}
293
+ waitEventContextTags={waitEventContextTags}
292
294
  popoverPlacement="rightTop"
293
295
  />
294
296
  </CapRow>
@@ -298,7 +300,6 @@ const CodeEditorPaneComponent = ({
298
300
  <ValidationErrorDisplay
299
301
  validation={validation}
300
302
  onErrorClick={onErrorClick}
301
- isLiquidEnabled={isLiquidEnabled}
302
303
  className="code-editor-pane__validation"
303
304
  />
304
305
  </div>
@@ -328,6 +329,7 @@ CodeEditorPane.propTypes = {
328
329
  injectedTags: PropTypes.object,
329
330
  location: PropTypes.object,
330
331
  eventContextTags: PropTypes.array,
332
+ waitEventContextTags: PropTypes.object,
331
333
  selectedOfferDetails: PropTypes.array,
332
334
  channel: PropTypes.string,
333
335
  userLocale: PropTypes.string,
@@ -155,7 +155,7 @@ describe('useValidation', () => {
155
155
  await Promise.resolve();
156
156
  });
157
157
 
158
- expect(validateHTML).toHaveBeenCalledWith('<p>Test</p>', 'email', null);
158
+ expect(validateHTML).toHaveBeenCalledWith('<p>Test</p>', 'email', null, { skipLiquidValidation: false });
159
159
  });
160
160
 
161
161
  it('updates validation when content changes', async () => {
@@ -472,7 +472,7 @@ describe('useValidation', () => {
472
472
  await Promise.resolve();
473
473
  });
474
474
 
475
- expect(validateHTML).toHaveBeenCalledWith('<p>Test</p>', 'inapp', null);
475
+ expect(validateHTML).toHaveBeenCalledWith('<p>Test</p>', 'inapp', null, { skipLiquidValidation: false });
476
476
  });
477
477
 
478
478
  it('defaults to email variant', async () => {
@@ -487,7 +487,7 @@ describe('useValidation', () => {
487
487
  await Promise.resolve();
488
488
  });
489
489
 
490
- expect(validateHTML).toHaveBeenCalledWith('<p>Test</p>', 'email', null);
490
+ expect(validateHTML).toHaveBeenCalledWith('<p>Test</p>', 'email', null, { skipLiquidValidation: false });
491
491
  });
492
492
  });
493
493
 
@@ -644,6 +644,326 @@ describe('useValidation', () => {
644
644
  // The functionality is verified through integration tests and the hasErrors test above
645
645
  });
646
646
 
647
+ describe('getLineAndColumnFromPosition utility (lines 36-46)', () => {
648
+ it('computes correct line and column from position in security issue', async () => {
649
+ const { isContentSafe, findUnsafeContent } = require('../../utils/contentSanitizer');
650
+ const { validateHTML, extractAndValidateCSS } = require('../../utils/htmlValidator');
651
+ let validationState;
652
+
653
+ validateHTML.mockImplementationOnce(() => ({ isValid: true, errors: [], warnings: [], info: [] }));
654
+ extractAndValidateCSS.mockImplementationOnce(() => ({ isValid: true, errors: [], warnings: [], info: [] }));
655
+ isContentSafe.mockImplementationOnce(() => false);
656
+ // position 12 in "line1\nline2\nX" → line 3, column 1
657
+ const content = 'line1\nline2\njavascript:alert(1)';
658
+ findUnsafeContent.mockImplementationOnce(() => [{ type: 'JavaScript Protocol', position: 12 }]);
659
+
660
+ render(
661
+ <TestComponent
662
+ content={content}
663
+ options={{ enableRealTime: false }}
664
+ onStateChange={(state) => { validationState = state; }}
665
+ />
666
+ );
667
+
668
+ await waitFor(() => { expect(validationState).toBeDefined(); });
669
+ await act(async () => { validationState.forceValidation(); await Promise.resolve(); });
670
+
671
+ await waitFor(() => {
672
+ const issues = validationState.getAllIssues();
673
+ const secIssue = issues.find((i) => i.source === 'security');
674
+ expect(secIssue).toBeDefined();
675
+ expect(secIssue.line).toBe(3);
676
+ expect(secIssue.column).toBe(1);
677
+ });
678
+ });
679
+
680
+ it('defaults line/column to 1 when security issue has no position', async () => {
681
+ const { isContentSafe, findUnsafeContent } = require('../../utils/contentSanitizer');
682
+ const { validateHTML, extractAndValidateCSS } = require('../../utils/htmlValidator');
683
+ let validationState;
684
+
685
+ validateHTML.mockImplementationOnce(() => ({ isValid: true, errors: [], warnings: [], info: [] }));
686
+ extractAndValidateCSS.mockImplementationOnce(() => ({ isValid: true, errors: [], warnings: [], info: [] }));
687
+ isContentSafe.mockImplementationOnce(() => false);
688
+ findUnsafeContent.mockImplementationOnce(() => [{ type: 'JavaScript Protocol' }]); // no position
689
+
690
+ render(
691
+ <TestComponent
692
+ content="<a href='javascript:x'>x</a>"
693
+ options={{ enableRealTime: false }}
694
+ onStateChange={(state) => { validationState = state; }}
695
+ />
696
+ );
697
+
698
+ await waitFor(() => { expect(validationState).toBeDefined(); });
699
+ await act(async () => { validationState.forceValidation(); await Promise.resolve(); });
700
+
701
+ await waitFor(() => {
702
+ const issues = validationState.getAllIssues();
703
+ const secIssue = issues.find((i) => i.source === 'security');
704
+ expect(secIssue).toBeDefined();
705
+ expect(secIssue.line).toBe(1);
706
+ expect(secIssue.column).toBe(1);
707
+ });
708
+ });
709
+
710
+ it('handles negative or undefined position in getLineAndColumnFromPosition', async () => {
711
+ const { isContentSafe, findUnsafeContent } = require('../../utils/contentSanitizer');
712
+ const { validateHTML, extractAndValidateCSS } = require('../../utils/htmlValidator');
713
+ let validationState;
714
+
715
+ validateHTML.mockImplementationOnce(() => ({ isValid: true, errors: [], warnings: [], info: [] }));
716
+ extractAndValidateCSS.mockImplementationOnce(() => ({ isValid: true, errors: [], warnings: [], info: [] }));
717
+ isContentSafe.mockImplementationOnce(() => false);
718
+ findUnsafeContent.mockImplementationOnce(() => [{ type: 'JavaScript Protocol', position: -5 }]);
719
+
720
+ render(
721
+ <TestComponent
722
+ content="<a href='javascript:x'>x</a>"
723
+ options={{ enableRealTime: false }}
724
+ onStateChange={(state) => { validationState = state; }}
725
+ />
726
+ );
727
+
728
+ await waitFor(() => { expect(validationState).toBeDefined(); });
729
+ await act(async () => { validationState.forceValidation(); await Promise.resolve(); });
730
+
731
+ await waitFor(() => {
732
+ const issues = validationState.getAllIssues();
733
+ const secIssue = issues.find((i) => i.source === 'security');
734
+ expect(secIssue).toBeDefined();
735
+ // negative position falls back to line 1, column 1
736
+ expect(secIssue.line).toBe(1);
737
+ expect(secIssue.column).toBe(1);
738
+ });
739
+ });
740
+ });
741
+
742
+ describe('getAllIssues spread — all six arrays included (lines 401-411)', () => {
743
+ it('includes items from htmlErrors, htmlWarnings, htmlInfo, cssErrors, cssWarnings, cssInfo', async () => {
744
+ const { validateHTML, extractAndValidateCSS } = require('../../utils/htmlValidator');
745
+ const { isContentSafe, findUnsafeContent } = require('../../utils/contentSanitizer');
746
+ let validationState;
747
+
748
+ validateHTML.mockImplementationOnce(() => ({
749
+ isValid: false,
750
+ errors: [{ type: 'error', message: 'html error', line: 1, column: 1, rule: 'r1', severity: 'error', source: 'htmlhint' }],
751
+ warnings: [{ type: 'warning', message: 'html warning', line: 2, column: 1, rule: 'r2', severity: 'warning', source: 'htmlhint' }],
752
+ info: [{ type: 'info', message: 'html info', line: 3, column: 1, rule: 'r3', severity: 'info', source: 'htmlhint' }],
753
+ }));
754
+ extractAndValidateCSS.mockImplementationOnce(() => ({
755
+ isValid: false,
756
+ errors: [{ type: 'error', message: 'css error', line: 4, column: 1, rule: 'r4', severity: 'error', source: 'css-validator' }],
757
+ warnings: [{ type: 'warning', message: 'css warning', line: 5, column: 1, rule: 'r5', severity: 'warning', source: 'css-validator' }],
758
+ info: [{ type: 'info', message: 'css info', line: 6, column: 1, rule: 'r6', severity: 'info', source: 'css-validator' }],
759
+ }));
760
+ isContentSafe.mockImplementationOnce(() => true);
761
+ findUnsafeContent.mockImplementationOnce(() => []);
762
+
763
+ render(
764
+ <TestComponent
765
+ content="<div>test</div>"
766
+ options={{ enableRealTime: false, enableSanitization: false }}
767
+ onStateChange={(state) => { validationState = state; }}
768
+ />
769
+ );
770
+
771
+ await waitFor(() => { expect(validationState).toBeDefined(); });
772
+ await act(async () => { validationState.forceValidation(); await Promise.resolve(); });
773
+
774
+ await waitFor(() => {
775
+ const issues = validationState.getAllIssues();
776
+ const rules = issues.map((i) => i.rule);
777
+ expect(rules).toContain('r1'); // htmlErrors
778
+ expect(rules).toContain('r2'); // htmlWarnings
779
+ expect(rules).toContain('r3'); // htmlInfo
780
+ expect(rules).toContain('r4'); // cssErrors
781
+ expect(rules).toContain('r5'); // cssWarnings
782
+ expect(rules).toContain('r6'); // cssInfo
783
+ expect(issues.length).toBeGreaterThanOrEqual(6);
784
+ });
785
+ });
786
+
787
+ it('sorts issues: errors before warnings before info', async () => {
788
+ const { validateHTML, extractAndValidateCSS } = require('../../utils/htmlValidator');
789
+ const { isContentSafe, findUnsafeContent } = require('../../utils/contentSanitizer');
790
+ let validationState;
791
+
792
+ validateHTML.mockImplementationOnce(() => ({
793
+ isValid: false,
794
+ errors: [{ type: 'error', message: 'html error', line: 10, column: 1, rule: 're', severity: 'error', source: 'htmlhint' }],
795
+ warnings: [{ type: 'warning', message: 'html warning', line: 1, column: 1, rule: 'rw', severity: 'warning', source: 'htmlhint' }],
796
+ info: [{ type: 'info', message: 'html info', line: 1, column: 1, rule: 'ri', severity: 'info', source: 'htmlhint' }],
797
+ }));
798
+ extractAndValidateCSS.mockImplementationOnce(() => ({ isValid: true, errors: [], warnings: [], info: [] }));
799
+ isContentSafe.mockImplementationOnce(() => true);
800
+ findUnsafeContent.mockImplementationOnce(() => []);
801
+
802
+ render(
803
+ <TestComponent
804
+ content="<div>test</div>"
805
+ options={{ enableRealTime: false, enableSanitization: false }}
806
+ onStateChange={(state) => { validationState = state; }}
807
+ />
808
+ );
809
+
810
+ await waitFor(() => { expect(validationState).toBeDefined(); });
811
+ await act(async () => { validationState.forceValidation(); await Promise.resolve(); });
812
+
813
+ await waitFor(() => {
814
+ const issues = validationState.getAllIssues();
815
+ const severities = issues.map((i) => i.severity);
816
+ // Errors should come before warnings, warnings before info
817
+ const firstError = severities.indexOf('error');
818
+ const firstWarning = severities.indexOf('warning');
819
+ const firstInfo = severities.indexOf('info');
820
+ if (firstError !== -1 && firstWarning !== -1) expect(firstError).toBeLessThan(firstWarning);
821
+ if (firstWarning !== -1 && firstInfo !== -1) expect(firstWarning).toBeLessThan(firstInfo);
822
+ });
823
+ });
824
+ });
825
+
826
+ describe('hasClientSideLiquidErrors and hasBlockingErrors (lines 455-456)', () => {
827
+ it('sets hasBlockingErrors=true when htmlErrors has a liquid-validator error', async () => {
828
+ const { validateHTML, extractAndValidateCSS } = require('../../utils/htmlValidator');
829
+ const { isContentSafe, findUnsafeContent } = require('../../utils/contentSanitizer');
830
+ let validationState;
831
+
832
+ validateHTML.mockImplementationOnce(() => ({
833
+ isValid: false,
834
+ errors: [{
835
+ type: 'error',
836
+ message: 'Liquid syntax error',
837
+ line: 1,
838
+ column: 1,
839
+ rule: 'liquid-syntax',
840
+ severity: 'error',
841
+ source: 'liquid-validator', // ISSUE_SOURCES.LIQUID
842
+ }],
843
+ warnings: [],
844
+ info: [],
845
+ }));
846
+ extractAndValidateCSS.mockImplementationOnce(() => ({ isValid: true, errors: [], warnings: [], info: [] }));
847
+ isContentSafe.mockImplementationOnce(() => true);
848
+ findUnsafeContent.mockImplementationOnce(() => []);
849
+
850
+ render(
851
+ <TestComponent
852
+ content="{{ invalid liquid }}"
853
+ options={{ enableRealTime: false, enableSanitization: false }}
854
+ onStateChange={(state) => { validationState = state; }}
855
+ />
856
+ );
857
+
858
+ await waitFor(() => { expect(validationState).toBeDefined(); });
859
+ await act(async () => { validationState.forceValidation(); await Promise.resolve(); });
860
+
861
+ await waitFor(() => {
862
+ // hasClientSideLiquidErrors → true because htmlErrors has liquid-validator+error item
863
+ // therefore hasBlockingErrors → true
864
+ expect(validationState.hasBlockingErrors).toBe(true);
865
+ });
866
+ });
867
+
868
+ it('does NOT set hasBlockingErrors from liquid-validator warning (non-error severity)', async () => {
869
+ const { validateHTML, extractAndValidateCSS } = require('../../utils/htmlValidator');
870
+ const { isContentSafe, findUnsafeContent } = require('../../utils/contentSanitizer');
871
+ let validationState;
872
+
873
+ validateHTML.mockImplementationOnce(() => ({
874
+ isValid: true,
875
+ errors: [],
876
+ warnings: [{
877
+ type: 'warning',
878
+ message: 'Liquid warning',
879
+ line: 1,
880
+ column: 1,
881
+ rule: 'liquid-warning',
882
+ severity: 'warning',
883
+ source: 'liquid-validator',
884
+ }],
885
+ info: [],
886
+ }));
887
+ extractAndValidateCSS.mockImplementationOnce(() => ({ isValid: true, errors: [], warnings: [], info: [] }));
888
+ isContentSafe.mockImplementationOnce(() => true);
889
+ findUnsafeContent.mockImplementationOnce(() => []);
890
+
891
+ render(
892
+ <TestComponent
893
+ content="<div>test</div>"
894
+ options={{ enableRealTime: false, enableSanitization: false }}
895
+ onStateChange={(state) => { validationState = state; }}
896
+ />
897
+ );
898
+
899
+ await waitFor(() => { expect(validationState).toBeDefined(); });
900
+ await act(async () => { validationState.forceValidation(); await Promise.resolve(); });
901
+
902
+ await waitFor(() => {
903
+ // hasClientSideLiquidErrors → false (severity is warning, not error)
904
+ // No other blocking conditions → hasBlockingErrors → false
905
+ expect(validationState.hasBlockingErrors).toBe(false);
906
+ });
907
+ });
908
+
909
+ it('sets hasBlockingErrors=true via API liquid errors', async () => {
910
+ const { validateHTML, extractAndValidateCSS } = require('../../utils/htmlValidator');
911
+ const { isContentSafe, findUnsafeContent } = require('../../utils/contentSanitizer');
912
+ let validationState;
913
+
914
+ validateHTML.mockImplementationOnce(() => ({ isValid: true, errors: [], warnings: [], info: [] }));
915
+ extractAndValidateCSS.mockImplementationOnce(() => ({ isValid: true, errors: [], warnings: [], info: [] }));
916
+ isContentSafe.mockImplementationOnce(() => true);
917
+ findUnsafeContent.mockImplementationOnce(() => []);
918
+
919
+ render(
920
+ <TestComponent
921
+ content="<div>test</div>"
922
+ options={{
923
+ enableRealTime: false,
924
+ enableSanitization: false,
925
+ apiValidationErrors: { liquidErrors: ['API liquid error'], standardErrors: [] },
926
+ }}
927
+ onStateChange={(state) => { validationState = state; }}
928
+ />
929
+ );
930
+
931
+ await waitFor(() => { expect(validationState).toBeDefined(); });
932
+ await act(async () => { validationState.forceValidation(); await Promise.resolve(); });
933
+
934
+ await waitFor(() => {
935
+ // hasApiErrors → true → hasBlockingErrors → true
936
+ expect(validationState.hasBlockingErrors).toBe(true);
937
+ });
938
+ });
939
+
940
+ it('hasBlockingErrors=false when no blocking conditions are present', async () => {
941
+ const { validateHTML, extractAndValidateCSS } = require('../../utils/htmlValidator');
942
+ const { isContentSafe, findUnsafeContent } = require('../../utils/contentSanitizer');
943
+ let validationState;
944
+
945
+ validateHTML.mockImplementationOnce(() => ({ isValid: true, errors: [], warnings: [], info: [] }));
946
+ extractAndValidateCSS.mockImplementationOnce(() => ({ isValid: true, errors: [], warnings: [], info: [] }));
947
+ isContentSafe.mockImplementationOnce(() => true);
948
+ findUnsafeContent.mockImplementationOnce(() => []);
949
+
950
+ render(
951
+ <TestComponent
952
+ content="<div>clean</div>"
953
+ options={{ enableRealTime: false, enableSanitization: false }}
954
+ onStateChange={(state) => { validationState = state; }}
955
+ />
956
+ );
957
+
958
+ await waitFor(() => { expect(validationState).toBeDefined(); });
959
+ await act(async () => { validationState.forceValidation(); await Promise.resolve(); });
960
+
961
+ await waitFor(() => {
962
+ expect(validationState.hasBlockingErrors).toBe(false);
963
+ });
964
+ });
965
+ });
966
+
647
967
  describe('Blocking errors', () => {
648
968
  it('treats protocol security issues as blocking errors', async () => {
649
969
  const { isContentSafe, findUnsafeContent } = require('../../utils/contentSanitizer');
@@ -922,4 +1242,133 @@ describe('useValidation', () => {
922
1242
  });
923
1243
  });
924
1244
  });
1245
+
1246
+ describe('isFullMode - skip liquid validation', () => {
1247
+ it('passes skipLiquidValidation: true to validateHTML when isFullMode is true', async () => {
1248
+ const { validateHTML } = require('../../utils/htmlValidator');
1249
+
1250
+ render(<TestComponent content="<p>Test</p>" options={{ isFullMode: true }} />);
1251
+
1252
+ await act(async () => {
1253
+ jest.advanceTimersByTime(500);
1254
+ await Promise.resolve();
1255
+ await Promise.resolve();
1256
+ await Promise.resolve();
1257
+ });
1258
+
1259
+ expect(validateHTML).toHaveBeenCalledWith('<p>Test</p>', 'email', null, { skipLiquidValidation: true });
1260
+ });
1261
+
1262
+ it('excludes API liquid errors from getAllIssues when isFullMode is true', async () => {
1263
+ let validationState;
1264
+ const onStateChange = (state) => { validationState = state; };
1265
+
1266
+ const apiValidationErrors = {
1267
+ liquidErrors: ['Unsupported tag: points_balance'],
1268
+ standardErrors: [],
1269
+ };
1270
+
1271
+ render(
1272
+ <TestComponent
1273
+ content="<p>Test</p>"
1274
+ options={{ apiValidationErrors, isFullMode: true }}
1275
+ onStateChange={onStateChange}
1276
+ />
1277
+ );
1278
+
1279
+ await act(async () => {
1280
+ jest.advanceTimersByTime(500);
1281
+ await Promise.resolve();
1282
+ await Promise.resolve();
1283
+ await Promise.resolve();
1284
+ });
1285
+
1286
+ await waitFor(() => {
1287
+ expect(validationState).toBeDefined();
1288
+ });
1289
+
1290
+ const issues = validationState.getAllIssues();
1291
+ const liquidIssues = issues.filter((i) => i.source === 'liquid-validator');
1292
+ expect(liquidIssues).toHaveLength(0);
1293
+ });
1294
+
1295
+ it('returns isClean true when only liquid errors exist and isFullMode is true', () => {
1296
+ const apiValidationErrors = {
1297
+ liquidErrors: ['Unsupported tag: points_balance'],
1298
+ standardErrors: [],
1299
+ };
1300
+
1301
+ render(
1302
+ <TestComponent
1303
+ content=""
1304
+ options={{ apiValidationErrors, isFullMode: true }}
1305
+ />
1306
+ );
1307
+
1308
+ // Before validation runs, isClean should be true because liquid errors are ignored in full mode
1309
+ expect(screen.getByTestId('is-clean')).toHaveTextContent('true');
1310
+ });
1311
+
1312
+ it('does not treat liquid errors as blocking when isFullMode is true', async () => {
1313
+ let validationState;
1314
+ const onStateChange = (state) => { validationState = state; };
1315
+
1316
+ const apiValidationErrors = {
1317
+ liquidErrors: ['Unsupported tag: points_balance'],
1318
+ standardErrors: [],
1319
+ };
1320
+
1321
+ render(
1322
+ <TestComponent
1323
+ content="<p>Valid</p>"
1324
+ options={{ apiValidationErrors, isFullMode: true }}
1325
+ onStateChange={onStateChange}
1326
+ />
1327
+ );
1328
+
1329
+ await act(async () => {
1330
+ jest.advanceTimersByTime(500);
1331
+ await Promise.resolve();
1332
+ await Promise.resolve();
1333
+ await Promise.resolve();
1334
+ });
1335
+
1336
+ await waitFor(() => {
1337
+ expect(validationState).toBeDefined();
1338
+ });
1339
+
1340
+ expect(validationState.hasBlockingErrors).toBe(false);
1341
+ });
1342
+
1343
+ it('still treats standard API errors as blocking when isFullMode is true', async () => {
1344
+ let validationState;
1345
+ const onStateChange = (state) => { validationState = state; };
1346
+
1347
+ const apiValidationErrors = {
1348
+ liquidErrors: ['Liquid error'],
1349
+ standardErrors: ['Standard error'],
1350
+ };
1351
+
1352
+ render(
1353
+ <TestComponent
1354
+ content="<p>Valid</p>"
1355
+ options={{ apiValidationErrors, isFullMode: true }}
1356
+ onStateChange={onStateChange}
1357
+ />
1358
+ );
1359
+
1360
+ await act(async () => {
1361
+ jest.advanceTimersByTime(500);
1362
+ await Promise.resolve();
1363
+ await Promise.resolve();
1364
+ await Promise.resolve();
1365
+ });
1366
+
1367
+ await waitFor(() => {
1368
+ expect(validationState).toBeDefined();
1369
+ });
1370
+
1371
+ expect(validationState.hasBlockingErrors).toBe(true);
1372
+ });
1373
+ });
925
1374
  });
@@ -77,6 +77,7 @@ export const useValidation = (content, variant = 'email', options = {}, formatSa
77
77
  enableSanitization = true,
78
78
  securityLevel = 'standard',
79
79
  apiValidationErrors = null, // API validation errors from validateLiquidTemplateContent
80
+ isFullMode = false, // When true, skip liquid validation (standalone/full mode)
80
81
  } = options;
81
82
 
82
83
  // Validation state
@@ -137,7 +138,7 @@ export const useValidation = (content, variant = 'email', options = {}, formatSa
137
138
 
138
139
  try {
139
140
  // 1. HTML Validation
140
- const htmlValidation = validateHTML(htmlContent, variant, formatValidatorMessage);
141
+ const htmlValidation = validateHTML(htmlContent, variant, formatValidatorMessage, { skipLiquidValidation: isFullMode });
141
142
 
142
143
  // 2. CSS Validation (extract from HTML)
143
144
  const cssValidation = extractAndValidateCSS(htmlContent, formatValidatorMessage);
@@ -206,7 +207,7 @@ export const useValidation = (content, variant = 'email', options = {}, formatSa
206
207
  },
207
208
  }));
208
209
  }
209
- }, [variant, enableSanitization, securityLevel, formatSanitizerMessage, formatValidatorMessage]);
210
+ }, [variant, enableSanitization, securityLevel, formatSanitizerMessage, formatValidatorMessage, isFullMode]);
210
211
 
211
212
  /**
212
213
  * Validates content with debouncing
@@ -339,7 +340,7 @@ export const useValidation = (content, variant = 'email', options = {}, formatSa
339
340
  */
340
341
  const getAllIssues = useCallback(() => {
341
342
  // API errors (liquid + standard) are blocking – they block Save/Update/Preview/Test
342
- const apiLiquidErrors = (apiValidationErrors?.liquidErrors || []).map((errorMessage) => {
343
+ const apiLiquidErrors = (isFullMode ? [] : (apiValidationErrors?.liquidErrors || [])).map((errorMessage) => {
343
344
  const extractedLine = extractLineNumberFromMessage(errorMessage);
344
345
  return {
345
346
  type: VALIDATION_SEVERITY.ERROR,
@@ -420,19 +421,20 @@ export const useValidation = (content, variant = 'email', options = {}, formatSa
420
421
 
421
422
  // Ensure we always return an array
422
423
  return Array.isArray(allIssues) ? allIssues : [];
423
- }, [validationState, apiValidationErrors, extractLineNumberFromMessage, content]);
424
+ }, [validationState, apiValidationErrors, extractLineNumberFromMessage, content, isFullMode]);
424
425
 
425
426
  /**
426
427
  * Check if validation is clean (no errors or warnings)
427
428
  * Includes API validation errors in the check
428
429
  */
429
430
  const isClean = useCallback(() => {
430
- const hasApiErrors = (apiValidationErrors?.liquidErrors?.length || 0) + (apiValidationErrors?.standardErrors?.length || 0) > 0;
431
+ const liquidErrorCount = isFullMode ? 0 : (apiValidationErrors?.liquidErrors?.length || 0);
432
+ const hasApiErrors = liquidErrorCount + (apiValidationErrors?.standardErrors?.length || 0) > 0;
431
433
  return validationState.summary.totalErrors === 0
432
434
  && validationState.summary.totalWarnings === 0
433
435
  && !validationState.summary.hasSecurityIssues
434
436
  && !hasApiErrors;
435
- }, [validationState.summary, apiValidationErrors]);
437
+ }, [validationState.summary, apiValidationErrors, isFullMode]);
436
438
 
437
439
  // Effect to validate content when it changes
438
440
  useEffect(() => {
@@ -448,11 +450,12 @@ export const useValidation = (content, variant = 'email', options = {}, formatSa
448
450
  }
449
451
  }, []);
450
452
 
451
- const hasApiErrors = (apiValidationErrors?.liquidErrors?.length || 0) + (apiValidationErrors?.standardErrors?.length || 0) > 0;
453
+ const hasApiLiquidErrors = isFullMode ? false : (apiValidationErrors?.liquidErrors?.length || 0) > 0;
454
+ const hasApiErrors = hasApiLiquidErrors || (apiValidationErrors?.standardErrors?.length || 0) > 0;
452
455
 
453
456
  const protocolTypes = ['JavaScript Protocol', 'Data URL', 'VBScript Protocol'];
454
- // Client-side Liquid validation errors are blocking (genuine syntax errors)
455
- const hasClientSideLiquidErrors = (validationState.htmlErrors || []).some((e) => e.source === ISSUE_SOURCES.LIQUID && e.severity === VALIDATION_SEVERITY.ERROR);
457
+ // Client-side Liquid validation errors are blocking (genuine syntax errors) - skip in full mode
458
+ const hasClientSideLiquidErrors = isFullMode ? false : (validationState.htmlErrors || []).some((e) => e.source === ISSUE_SOURCES.LIQUID && e.severity === VALIDATION_SEVERITY.ERROR);
456
459
  const hasBlockingErrors = (validationState.sanitizationWarnings || []).some((w) => BLOCKING_ERROR_RULE_IDS.includes(w.rule)) || (validationState.securityIssues || []).some((s) => protocolTypes.includes(s?.type)) || hasApiErrors || hasClientSideLiquidErrors;
457
460
 
458
461
  return {