Block Editor 2.0: Highlight mark not registered — Story Block field renders blank for content containing highlight marks (parity regression with legacy editor)
Nobody has claimed this yet.
- Dominant language
- Java
- Stars
- 970
- Forks
- 486
- Avg merge
- 3d 33m
- Merged PRs (30d)
- 170
Description
Problem Statement
The new TipTap-v3 Block Editor (core-web/libs/new-block-editor) does not register the Highlight mark. Any content carrying highlight marks — authored while the legacy editor was active — cannot be deserialized by the new schema, and the entire field renders blank.
| Legacy editor | New editor | |
|---|---|---|
| Registers Highlight | ✅ dot-block-editor.component.ts:35,737 |
❌ absent from editor-extensions.ts:72-178 |
| Renders affected content | ✅ correctly | ❌ blank field |
Why the whole field disappears
TipTap deserializes the document as a single atomic operation, so one unknown mark aborts Node.fromJSON for the entire doc. With enableContentCheck at its default (off), TipTap logs a warning and falls back to an empty document:
[tiptap warn]: Invalid content. Passed value: {type: 'doc', attrs: {…}, content: Array(19)}
Error: RangeError: There is no mark type highlight in this schema
All 19 blocks are lost in the editor — the stored JSON is intact, the editor simply cannot open it.
The underlying defect: marks bypass sanitisation
The new editor already guards against unknown nodes — preserveUnknownNodesInDocument swaps them for the dotUnsupportedBlock placeholder. But getKnownNodeNames reads only editor.schema.nodes:
// core-web/libs/new-block-editor/src/lib/editor/editor.component.ts:128
function getKnownNodeNames(editor: Editor): Set<string> {
return new Set(Object.keys(editor.schema.nodes)); // ← nodes only; marks skip this entirely
}
Registering Highlight fixes this instance. The unsanitised mark path is a broader hardening concern and is out of scope for this issue — recorded here only so the observation is not lost.
[!NOTE]
Where the marks come from. The legacy editor never exposed a Highlight button — there is notoggleHighlightanywhere in the codebase. It applies the mark as a temporary selection indicator in its link popover, so the author can see which text is being linked once focus moves to the search input:// libs/block-editor/.../dot-link-editor-popover.component.ts:372 private focusSearchInput() { this.editor().commands.setHighlight(); // paints the selection this.searchInput?.nativeElement.focus(); }When
unsetHighlight()never runs — popover closed unexpectedly, or the contentlet saved while it was open — the mark is persisted. The reporting customer's JSON shows exactly this: ahighlightrun sitting immediately beside alinkrun. So these marks are UI residue, not authorial intent, and the legacy editor is still producing them today. The new editor solved the same UI problem with ProseMirrorDecorations (view-only, never serialized) inSelectionPreserveExtension, which is why it has no need for the mark itself.
Same class of defect as #36572 (Underline mark, shipped in 26.07.17-01). That fix was scoped to Underline only, which is why Highlight is still unregistered.
Second defect found while testing: array-shaped values blank the field too
Opening the same contentlet through the UVE side panel blanks the field with a different error:
[tiptap warn]: Invalid content. Passed value: {0: {…}, 1: {…}, … 15: {…}, content: undefined}
Error: RangeError: Unknown node type: undefined
Note the shape of the value: numeric keys plus content: undefined. That is an array spread into an object. A Block Editor value reaches setContent either as a { type: 'doc', content: [...] } document or as a bare array of nodes — and the new editor's helper spreads unconditionally:
// core-web/libs/new-block-editor/src/lib/editor/editor.component.ts
function preserveUnknownNodesInDocument(parsed, knownNodeNames) {
return {
...parsed, // array → { 0: node, 1: node, … }
content: preserveUnknownBlockNodes(parsed.content, ...) // array.content → undefined
};
}
Spreading an array drops the document type, so TipTap throws Unknown node type: undefined. The legacy editor branches on the shape before preserving:
// libs/block-editor/.../dot-block-editor.component.ts:773
const preservedContent = Array.isArray(content)
? preserveUnknownBlockNodes(content, this.#knownEditorNodeNames)
: { ...content, content: preserveUnknownBlockNodes(content.content, ...) };
The new editor lost that Array.isArray branch.
This is independent of Highlight — it is deterministic for any array-shaped value, whatever marks the content carries. It is also independent of the mark fix above: the two defects share only the symptom (blank field).
Steps to Reproduce
- Start dotCMS ≥
26.07.04-01(no flag overrides). - Create or update a contentlet so its Block Editor field contains a
highlightmark:{ "type": "paragraph", "content": [ { "type": "text", "marks": [{ "type": "highlight" }], "text": "Good Credit History:" } ]} - Confirm the mark persisted:
GET /api/v1/content/{identifier} - Open the contentlet in the editor → the field renders blank, console shows
RangeError: There is no mark type highlight in this schema. - Compare against the legacy editor (
DOT_FEATURE_FLAG_NEW_BLOCK_EDITOR=false) → the same contentlet renders correctly, highlight applied.
Acceptance Criteria
Expected Behavior
- The new Block Editor registers
@tiptap/extension-highlight(already a dependency atcore-web/package.json:107), at parity with the legacy editor. - Existing content containing highlight marks round-trips through the new editor — marks are not stripped and the field is not emptied.
- Every mark registered by the legacy editor is audited for parity, not just Highlight.
- A Block Editor value passed as a bare array of nodes (as the UVE side panel does) loads correctly instead of being spread into an object and rejected as
Unknown node type: undefined.
Actual Behavior
- The whole Block Editor field renders blank; only a console warning indicates why.
[!WARNING]
Risk of data loss. The field loads viasetContent(..., { emitUpdate: false }), so merely opening the contentlet does not dirty it — but the editor now holds an empty document. Any subsequent edit firesonUpdate, and a Save persists the empty body over the real one.
dotCMS Version
26.08.03-01
Severity
High - Major functionality broken
Links
- Related: #36572 — same defect for the Underline mark
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with core-web/libs/new-block-editor/src/lib/editor/extensions/editor-extensions.ts and editor.component.ts, then compare the legacy editor references cited in the issue. Reproduce both highlight-mark and bare-array inputs, and inspect the existing Block Editor coverage. Done means legacy marks are audited for parity, highlighted content round-trips without blanking, and array-shaped values load successfully.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- frontend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100