docs(converter): correct #515 mark-order comment — imported order depends on extension, not fixed

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 <code>
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) <noreply@anthropic.com>
This commit is contained in:
2026-07-12 02:44:54 +03:00
parent 95c0d813b0
commit 0a53be9e81
2 changed files with 14 additions and 7 deletions
@@ -1492,11 +1492,16 @@ export function convertProseMirrorToMarkdown(
}
let t = escapeHtmlText(n.text || "");
// #515: wrap `<code>` 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 `<strong><code>` to `<code><strong>` 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 `<code>` innermost
// regardless of the imported order". That keeps `<code>` nested inside the
// emphasis tag both directions (preserving the byte fixpoint — an order-
// sensitive loop would flip `<strong><code>`↔`<code><strong>` 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 = `<code>${t}</code>`;
}
@@ -143,8 +143,10 @@ export const markedTextRunArb: fc.Arbitrary<any> = 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' }] })),