From 0a53be9e810bc2211cb9a2a17abc6c8087943923 Mon Sep 17 00:00:00 2001 From: agent_coder Date: Sun, 12 Jul 2026 02:44:54 +0300 Subject: [PATCH] =?UTF-8?q?docs(converter):=20correct=20#515=20mark-order?= =?UTF-8?q?=20comment=20=E2=80=94=20imported=20order=20depends=20on=20exte?= =?UTF-8?q?nsion,=20not=20fixed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The inlineToHtml comment falsely claimed import ALWAYS yields the code mark last; import yields code last for bold/italic/strike but code FIRST for the ==-highlight extension. Reworded to state the real invariant (wrap innermost regardless of imported order) and de-universalized the parallel phrase in text-arbitraries.ts. Comment-only, no behavior change. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../src/lib/markdown-converter.ts | 15 ++++++++++----- .../test/generative/text-arbitraries.ts | 6 ++++-- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/packages/prosemirror-markdown/src/lib/markdown-converter.ts b/packages/prosemirror-markdown/src/lib/markdown-converter.ts index e96cbad3..eae8c578 100644 --- a/packages/prosemirror-markdown/src/lib/markdown-converter.ts +++ b/packages/prosemirror-markdown/src/lib/markdown-converter.ts @@ -1492,11 +1492,16 @@ export function convertProseMirrorToMarkdown( } let t = escapeHtmlText(n.text || ""); // #515: wrap `` INNERMOST first (before the array-order mark loop), - // then skip `code` in the loop. Import (`generateJSON`) always yields the - // code mark LAST in the array (canonical order `[emphasis, code]`), so an - // order-sensitive loop would flip `` to `` on - // re-export and break the byte fixpoint. Code-innermost is stable in both - // directions and matches the markdown path (case "text" / run factoring). + // then skip `code` in the loop. The imported mark order is NOT fixed — it + // DEPENDS on the emphasis extension: import (`generateJSON`) yields code + // LAST for bold/italic/strike (`[emphasis, code]`) but code FIRST for the + // `==`-highlight extension (`[code, highlight]`). So we cannot rely on a + // fixed array position; the invariant is instead "wrap `` innermost + // regardless of the imported order". That keeps `` nested inside the + // emphasis tag both directions (preserving the byte fixpoint — an order- + // sensitive loop would flip ``↔`` depending on + // which order it happened to see) and matches the markdown path (case + // "text" / run factoring). if ((n.marks || []).some((m: any) => m.type === "code")) { t = `${t}`; } diff --git a/packages/prosemirror-markdown/test/generative/text-arbitraries.ts b/packages/prosemirror-markdown/test/generative/text-arbitraries.ts index ae15692a..266fd963 100644 --- a/packages/prosemirror-markdown/test/generative/text-arbitraries.ts +++ b/packages/prosemirror-markdown/test/generative/text-arbitraries.ts @@ -143,8 +143,10 @@ export const markedTextRunArb: fc.Arbitrary = fc.oneof( // #515: code COMBINED with a bare-delimiter emphasis mark. The converter nests // the backtick span inside the emphasis delimiters (`` **`x`** ``) and, when // such runs sit adjacent, factors a shared mark or falls back to schema-HTML. - // Mark order is `[emphasis, code]` — the canonical order the HTML->PM import - // yields (code last) — so the P1 semantic round-trip is order-exact. + // Mark order here is `[emphasis, code]` — the order the HTML->PM import yields + // for bold/italic/strike specifically (code last). This is NOT universal: the + // `==`-highlight case below imports code FIRST — so match each case to its own + // imported order for the order-exact P1 round-trip (do not assume a fixed order). fc .tuple(safeTextArb, fc.constantFrom('bold', 'italic', 'strike')) .map(([t, m]) => ({ type: 'text', text: t, marks: [{ type: m }, { type: 'code' }] })),