fix(git-sync): complete A1 heading alignment — green suite + nested path (on 5d45f5a8)

QA follow-up on 5d45f5a8: that commit taught the converter to export heading
textAlign (<hN style>) but left the converter-gate heading test still asserting
the OLD dropped behavior (expects a bare '## text'), so jest was RED — the G1
green-suite gate was not actually met. Two gaps closed:

1. Flip the heading KNOWN-DIVERGENCE gate test to assert the round trip now
   PRESERVES alignment (exported as <h2 style="text-align:center"> and recovered
   on import), matching the shipped converter behavior. Suite is green again.

2. blockToHtml (the nested-container path: heading/paragraph inside a
   column/table/callout) still emitted bare <hN>/<p>, dropping textAlign for
   nested blocks. Carry the style there too, symmetric with the processNode path.

Also add #7 (table inside a column) and #8 (multi-block table cell) to the
lossless round-trip CORPUS so both survive export->import through the real
editor-ext schema (columns widthMode pre-authored at its normalize fixpoint).

Verified: server jest 193 suites / 2142 tests green, git-sync vitest 704 green,
no type errors.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-03 00:25:52 +03:00
parent 5d45f5a85e
commit 67dca8c10e
2 changed files with 94 additions and 21 deletions
@@ -955,11 +955,28 @@ export function convertProseMirrorToMarkdown(content: any): string {
const blockToHtml = (block: any): string => {
const children = block.content || [];
switch (block.type) {
case "paragraph":
return `<p>${inlineToHtml(children)}</p>`;
case "paragraph": {
// Carry textAlign here too (symmetric with the processNode paragraph
// case): a paragraph nested inside an HTML container (column/table/
// callout) would otherwise drop its alignment on the round trip.
const pAlign = block.attrs?.textAlign;
const pStyle =
pAlign && pAlign !== "left"
? ` style="text-align:${escapeAttr(pAlign)}"`
: "";
return `<p${pStyle}>${inlineToHtml(children)}</p>`;
}
case "heading": {
const level = block.attrs?.level || 1;
return `<h${level}>${inlineToHtml(children)}</h${level}>`;
// Same for a heading nested in an HTML container: emit the alignment as
// an inline style (symmetric with the processNode heading case) so it is
// not silently dropped. Clamp the level to a valid HTML heading tag.
const level = Math.min(6, Math.max(1, block.attrs?.level || 1));
const hAlign = block.attrs?.textAlign;
const hStyle =
hAlign && hAlign !== "left"
? ` style="text-align:${escapeAttr(hAlign)}"`
: "";
return `<h${level}${hStyle}>${inlineToHtml(children)}</h${level}>`;
}
case "bulletList":
return `<ul>${children