36002 workflow fire convert block editor markdown to prosemirror json on save server side#36253
Conversation
…minators Add two pure helpers to TiptapMarkdown that the save path needs to safely ingest Story Block values: - isTiptapDoc(String): cheap detector for an already-valid Tiptap/ProseMirror document (peeks the first non-whitespace char before parsing), so editor- authored JSON can be stored unchanged instead of re-parsed as Markdown. - isMarkdownRepresentable(String): true only when every block is Markdown- expressible, used to refuse a Markdown overwrite that would silently drop rich blocks (dotContent, dotVideo, grid, etc.). Marks are ignored on purpose (losing a mark loses styling, not content). Covered by TiptapMarkdownDocDetectionTest (13 cases incl. nested rich blocks, marks-only docs, malformed/empty/null input). Refs #36002
…t save path
Wire the converter into MapToContentletPopulator.fillFields, the shared seam
that the workflow fire endpoints and the content REST API all funnel through.
For a Story Block field whose incoming value is Markdown (begins with neither
'{' nor '<'), convert it to a ProseMirror JSON document and store that, so non-
interactive clients (AI agents, headless imports) no longer require a human to
open and re-save the contentlet.
Guards:
- Already-valid Tiptap JSON and (deferred) HTML are stored unchanged.
- A Markdown update is refused when the existing stored document contains rich
blocks Markdown cannot represent, rather than silently destroying them.
- A conversion failure never blocks the save: the raw value is stored and a
warning logged (graceful degradation, consistent with #35728).
The converter stays pure; conversion and guards live at the ingestion seam.
Covered by StoryBlockMarkdownPopulatorTest (convert + GraphQL read-back, JSON
passthrough, HTML passthrough, primitive replace, rich-overwrite reject);
registered in MainSuite1b.
Refs #36002
…wn conversion The fire endpoints' Block Editor note promised Markdown/HTML acceptance but admitted it only took effect after a human re-saved in the editor — documenting the exact bug #36002 fixes. Update the shared @operation note to state that Markdown is converted to ProseMirror JSON automatically on save (and already- valid JSON is stored unchanged), drop the "converted when opened in the editor" caveat, and use a Markdown example. Regenerate openapi.yaml (all 6 fire operations share the constant). Refs #36002
|
Claude finished @hassandotcms's task in 2m 21s —— View job Rollback Safety Analysis
Result: Unsafe to RollbackCategory: M-3 — REST / GraphQL / Headless API Contract Change Why it's unsafe: The save path for Story Block (Block Editor) fields now auto-converts Markdown input to ProseMirror JSON on write ( Code that makes it unsafe:
Alternative: The change is inherently additive (ProseMirror JSON written by N is natively readable by N-1 as structured content). No two-phase DB migration is required. To protect newly-built headless clients from a rollback, document the Markdown auto-conversion as a rollback-incompatible behavior in the release notes and advise consumers that this feature is only available on version N and above.
|
🤖 Bedrock Review —
|
#36002 normalizes a plain-text/Markdown Story Block value to a ProseMirror JSON document on save, so a webPageContent `body` sent as plain text now reads back as a structured doc (object), not the raw string. Update the two API tests that asserted the old raw-string round-trip to assert the normalized doc instead, keeping plain-text input so they still exercise the server-side conversion: - Karate CheckingJSONAttributes.feature: assert body.type == 'doc' and the paragraph text, instead of body == "<raw string>". - Postman JsScriptAPI: assert body.type == 'doc' and that the surviving text segments are present (inline <b> markup is dropped by the Markdown converter), across fireNew/fireEdit/firePublish via the JS workflows viewtool. Refs #36002
…ntent checks CI surfaced two more API tests that asserted a webPageContent `body` (a Story Block field) read back as the raw plain-text string. #36002 now normalizes plain-text to a ProseMirror doc on save, so the field comes back as structured JSON. Update only the content (webPageContent) assertions to read the text from the doc — body.content[0].content[0].text — leaving the template `body` assertions (template markup, not a Story Block field) untouched: - BringBack: 3 content version checks (create/edit/bring-back). - VersionableResource: 1 content working-version check. Determined the complete affected set by parsing every collection's body assertions against its request endpoint (content vs template), so template, GraphQL seeded/bundle, and response-body assertions are correctly excluded. Refs #36002
…lmarkdown-to-prosemirror-json-on-save-server-side
🤖 Bedrock Review —
|
|
Pull Request Unsafe to Rollback!!!
|
…a Story Block field JsScriptAPI runs after Bundle_Resource imports a bundle that downgrades webPageContent.body to a WYSIWYG field, so its body reads back as the raw string (HTML), not a ProseMirror doc. VersionableResource runs in the default-split instance where webPageContent.body is likewise a plain string. In both cases the markdown->ProseMirror conversion correctly does not apply, so revert these two collections to their original raw-string assertions. BringBack (runs before the bundle downgrade) and the Karate CheckingJSONAttributes test (separate instance) still assert the normalized doc, since they hit a genuine Story Block field.
🤖 Bedrock Review —
|
…flow-fire-convert-block-editor-htmlmarkdown-to-prosemirror-json-on-save-server-side
…f rejecting it When a Story Block field already holds rich blocks that Markdown cannot represent (embedded contentlets, video, layout grids) and a Markdown value is sent on the save path, keep the existing document and log a warning rather than throwing an exception. This preserves the rich content (no silent data loss) without interrupting the save flow, matching the documented contract that Markdown is for plain content; modifying such a field still requires a full Tiptap/ProseMirror JSON document. Update the fire endpoints' Block Editor note (regenerated openapi.yaml) to document this, and adjust the integration test to assert the existing document is preserved. Addresses review feedback on PR #36253.
Review polish on the #36002 discriminators: - isTiptapDoc: peek the first AND last non-whitespace character before parsing. - isMarkdownRepresentable: short-circuit blank input, and log at debug when a value is not parseable JSON instead of swallowing it silently. Addresses review feedback on PR #36253.
🤖 dotBot Review (Bedrock)Reviewed 9 file(s); 2 candidate(s) → 2 confirmed, 0 uncertain (unverified, kept for review). Confirmed findings
us.deepseek.r1-v1:0 · Run: #28449277404 · tokens: in: 37901 · out: 10198 · total: 48099 · calls: 12 · est. ~$0.106 |
|
Pull Request Unsafe to Rollback!!!
|
fabrizzio-dotCMS
left a comment
There was a problem hiding this comment.
I'm approving it but Im keeping my concerns about raising the IllegalStateException
…flow-fire-convert-block-editor-htmlmarkdown-to-prosemirror-json-on-save-server-side
…ehavior
The class-level javadoc still described the old throw behavior ("refuses a
Markdown overwrite that would destroy rich content"). Update it to match the
current behavior: the Markdown overwrite of rich content is ignored and the
existing document is preserved.
…behavior The method javadoc still said it is used to "refuse" a Markdown overwrite; the guard now detects such an overwrite and ignores it (preserving the existing document) rather than rejecting the save. Wording-only change.
now we are logging, not throwing and interrupting the save flow. |
|
Tick the box to add this pull request to the merge queue (same as
|
What
Converts Story Block (Block Editor) field values supplied as Markdown to Tiptap/ProseMirror JSON server-side, on the shared content save path. Non-interactive clients (AI agents, headless imports) no longer need a human to open and re-save the contentlet for the field to read back as structured content.
Closes #36002 (Markdown scope; see Scope below).
How
TiptapMarkdown.isTiptapDoc/isMarkdownRepresentable— pure discriminators.MapToContentletPopulator.fillFields— the seam shared by the workflow fire endpoints and the content REST API. For a Story Block value:{(JSON) or<(HTML) → stored unchanged;TiptapMarkdown.toTiptap.dotContent,dotVideo, grid, …) instead of silently destroying them; a conversion failure never blocks the save (stores raw + logs).Scope
Behavior change
Testing
TiptapMarkdownDocDetectionTest(13) + existingTiptapMarkdownTest/RoundTripContractTest.StoryBlockMarkdownPopulatorTest— convert + GraphQL read-back, JSONpassthrough, HTML passthrough, primitive replace, rich-overwrite ignored.
MapToContentletPopulatorTest(20),StoryBlockValidationTest(28).