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 1m 53s —— View job Rollback Safety Analysis
Result: 🟡 MEDIUM — Not Safe To RollbackCategory matched: M-3 — REST / GraphQL / Headless API Contract Change Why: The save path for Story Block (Block Editor) fields now auto-converts Markdown input to ProseMirror JSON on write ( No unsafe categories found in: database migrations, ES mapping changes, content JSON model version, DROP TABLE/COLUMN, runonce tasks, or any CRITICAL / HIGH category. Safer alternative: The ProseMirror JSON written by N is natively readable by N-1, so no data-layer two-phase migration is needed. The recommendation is to document the Markdown auto-conversion as rollback-incompatible behavior in the release notes, advising consumers that this feature requires version N or 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!!!
|
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
corrupted the field. Additive and rollback-safe (stored JSON is read natively by N-1).
Testing
TiptapMarkdownDocDetectionTest(13) + existingTiptapMarkdownTest/RoundTripContractTest.StoryBlockMarkdownPopulatorTest— convert + GraphQL read-back, JSONpassthrough, HTML passthrough, primitive replace, rich-overwrite reject.
MapToContentletPopulatorTest(20),StoryBlockValidationTest(28).