Appearance
Rich Text Formatting & Related Bugs
Reported during testing (2026-07-07). Mix of rich-text-specific and pre-existing issues.
1. CustomCSS text styles don't apply to rich text content
Status: Fixed (merged in AF#1910, CE#751)
When customCSS sets text properties like color: transparent !important on a text block, rich text <span style="color: #xxx"> elements override it because inline styles on child elements take precedence over parent CSS rules.
Fix: CreativeTextBlock.vue in CE propagates color, font-size, and font-weight from customCSS down to span[style] children with !important via getRichTextOverrides().
Follow-up fix (AF#1918, CE#754): The initial implementation passed the full otherCustomStyles object (which merges block.style with customCSS) to getRichTextOverrides. Since block.style always includes a color, this forced the block's default color onto all spans even without customCSS. Fixed to parse customCSS directly -- only explicitly set customCSS properties generate overrides.
Files changed:
Creative-Engine/src/components/creative/VisualElements/CreativeTextBlock.vue
2. Pasting formatted text keeps source font size but toolbar shows wrong value
Status: Fixed (merged in AF#1910)
When pasting rich text from another source (without holding Shift), the pasted content retains its original font-size via the textStyle mark's parseHTML. The toolbar's activeFontSize shows the block's defaultFontSize instead of the actual pasted size, making it look correct when it isn't.
Root cause: inlineStyleExtension.ts parses font-size from pasted HTML (parseHTML: (element) => element.style.fontSize || null). The toolbar reads defaultFontSize as fallback when cursor isn't inside a textStyle mark with fontSize.
Fix: Added handlePaste in editorProps that always pastes as plain text. External formatting (font sizes, colors, weights) is stripped on paste. Users can apply formatting via the toolbar after pasting.
Files changed:
Application-Frontend/src/components/common/RichTextEditor/RichTextEditor.vue
3. +/- keys zoom flow canvas instead of typing in input fields
Status: Fixed (merged in AF#1910)
The panzoom library intercepts + and - keydown events for zoom even when an input field has focus. The existing filterKey: () => true was supposed to block all keyboard zoom but wasn't working correctly.
Fix: Changed filterKey to check if the event target is an input/textarea/select/contentEditable element, only blocking zoom in those cases.
Files changed:
Application-Frontend/src/pages/Chatbots/components/CavaiFlow/CavaiFlow.vue
Note: Pre-existing bug, not introduced by the rich text branch. CavaiFlow has zero changes on rich-text-formatting.
4. Copy/paste block from group loses visibility
Status: Fixed (merged in AF#1910)
Copying a block that is inside a group and pasting it outside the group keeps the block's parent reference pointing to the group. Since the block isn't actually inside the group anymore, it becomes invisible in the preview (the engine expects blocks with a parent to be rendered inside that parent).
Same issue with duplicate if the source block's parent no longer exists (e.g. copied from another creative).
Fix: duplicateBlock mutation now validates that parent points to an existing block in creativeBlocks. If not, the parent field is removed so the block appears at top level.
Files changed:
Application-Frontend/src/store/modules/blocks.ts
Note: Pre-existing bug, not related to the rich text branch.
5. Inline rich text colors not rendering in preview
Status: Fixed (AF#1918, CE#754, CC#109)
Rich text inline colors (applied via the Tiptap editor's color picker) were stripped or overridden in the creative preview and delivery. Three independent issues contributed:
5a. Editor DOM mutation corrupting ProseMirror state (AF)
applyColorUnderlines() in RichTextEditor.vue mutated ProseMirror's DOM by setting span.style.color = 'inherit' to show colors as dotted underlines instead of colored text. ProseMirror's MutationObserver detected the external DOM change, re-parsed it into internal state with color: inherit, and subsequent getHTML() calls output <span style="color: inherit">. The engine sanitizer only allows hex/rgb/rgba color values, so the span was stripped entirely.
Fix: Replaced the JS DOM manipulation with a CSS-only approach:
-webkit-text-fill-color: inheritrenders text in the parent's color without modifying the inline style ProseMirror managestext-decoration: underline dottedwithtext-decoration-color: currentColorshows the applied color as a dotted underline
5b. Block default color overriding inline rich text (CE)
getRichTextOverrides() in CreativeTextBlock.vue received the full otherCustomStyles object, which merges block.style (including the default text color) with any customCSS. Since blocks always have a color in their style, this generated { '& span[style]': { color: '<default> !important' } } for every text block, forcing the default color onto all rich text spans.
Fix: Parse customCSS directly instead of using the merged object. When customCSS is empty (the common case), no overrides are generated.
5c. Composer stripping first span's color (CC)
separateStyleStringFromContent() in remapData.ts uses a non-global regex to extract the first style="..." attribute from operator text and apply it as bubble-level blockStyles. With rich text containing multiple inline <span style="color: ..."> elements, this strips the first span's color and sets it as the bubble color, causing all text to render in that single color.
Fix: Skip extraction when text contains <p> tags (Tiptap always wraps content in paragraphs). Pre-rich-text content never has <p> tags, so legacy creatives are unaffected.
Files changed:
Application-Frontend/src/components/common/RichTextEditor/RichTextEditor.vueCreative-Engine/src/components/creative/VisualElements/CreativeTextBlock.vueCreative-Engine/src/components/conversationflow/MessageHolder.vueCreative-Engine/src/components/blocks/basic/Choice.vueCreative-Composer/src/remapper/remapData.ts