fix(git-sync): subpages round-trips (was {{SUBPAGES}} literal) + exhaustive all-node round-trip test

subpages exported to the literal `{{SUBPAGES}}`, which has no markdown/HTML
inverse, so on re-import it came back as a plain paragraph holding the visible
text "{{SUBPAGES}}" — the embed rendered as that literal string on the page
after a sync (round-trip data loss, seen live). It now emits the schema-matching
`<div data-type="subpages">` like every other embed node, so the schema's
parseHTML rebuilds the subpages node. Also dropped the leaf-atom content-hole
in the subpages renderHTML.

New committed regression coverage:
- packages/git-sync/test/roundtrip-all-nodes.test.ts — exhaustive serialize ->
  deserialize round trip for ALL 40 node/mark types; each asserts the node/mark
  survives and no `{{...}}` literal leaks. This is the test that caught subpages.
- §13.1 gate (git-sync-converter-gate.spec.ts): subpages added to the green
  corpus (round-trips through the REAL server schema).
- Corrected two PR-authored tests that asserted the old {{SUBPAGES}} loss as
  "by design" — they now assert the fixed round trip.

Also folds in review #1679 coverage-gap tests (no prod change): orchestrator
pollTick/enabledSpaces, datasource 3-way merge dispatch, page.repo
last_updated_source provenance SQL.

git-sync vitest 659 (+1 expected-fail), server tsc clean, server specs green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
claude code agent 227
2026-06-26 03:41:42 +03:00
parent 2c96650b4f
commit 3b859e45e8
9 changed files with 441 additions and 22 deletions

View File

@@ -932,7 +932,7 @@ const Subpages = Node.create({
return [{ tag: 'div[data-type="subpages"]' }];
},
renderHTML({ HTMLAttributes }) {
return ["div", { "data-type": "subpages", ...HTMLAttributes }, 0];
return ["div", { "data-type": "subpages", ...HTMLAttributes }];
},
});

View File

@@ -649,7 +649,12 @@ export function convertProseMirrorToMarkdown(content: any): string {
return `<div data-type="pageBreak"></div>`;
case "subpages":
return "{{SUBPAGES}}";
// Emit the schema-matching div[data-type="subpages"] so marked passes it
// through as a block and generateJSON rebuilds the subpages atom. The old
// `{{SUBPAGES}}` literal had no parseHTML inverse, so on import it stayed
// as plain text — the embed rendered as the literal "{{SUBPAGES}}" on the
// page after a round-trip (red-team: subpages round-trip data loss).
return `<div data-type="subpages"></div>`;
case "status": {
// Inline status pill. The schema reads the label from the element's