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 35s —— View job Rollback Safety Analysis
Verdict: ✅ Safe To RollbackLabel applied: AI: Safe To Rollback AnalysisI analyzed the diff against all categories in Files changed:
Category-by-category review:
Additional rollback-safety note: The PR explicitly states the stored format is additive and rollback-safe — "Additive and rollback-safe (stored JSON is read natively by N-1)" — because a Tiptap JSON document stored by N is a valid JSON string that N-1 would have stored and read back in the same way. No migration of existing rows occurs. |
🤖 Bedrock Review —
|
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).