From b2ff89d7d3313c7c30d06d6665674f5cdd1d3f43 Mon Sep 17 00:00:00 2001 From: Caio Pizzol Date: Mon, 18 May 2026 18:48:39 -0300 Subject: [PATCH 1/3] test(document-api): word-in-the-loop validation for anchored metadata (SD-3201) Closes the round-trip gap from SD-3104: SuperDoc -> DOCX -> Word save / edit -> SuperDoc. The SuperDoc -> DOCX -> SuperDoc round-trip was already proven by tests/doc-api-stories/tests/metadata/all-commands.ts; this adds Word in the middle as deterministic fixtures replayed in CI. Two fixtures, both generated from one SuperDoc-side source (one paragraph, one anchored citation 'fixture-cite-001' over 'duty of care'): - baseline-word-resaved.docx: opened in Word, re-saved without edits. Proves Word does not strip the customXml part or the hidden SDT wrapper on save. - baseline-word-edited.docx: opened in Word with text inserted inside the cited span. Proves the SDT survives content edits and the anchor expands to cover the edited content (no orphan, no detach). Replay test under tests/doc-api-stories/tests/word-roundtrip/: - list({ namespace }) returns the citation - get({ id }) returns the payload byte-for-byte (Word does not renormalize the customXml part) - resolve({ id }) returns a SelectionTarget - list({ within: resolvedTarget }) confirms the anchor still covers the post-edit range What this does not cover - New Word version regressions. A separate live script gated on Word API access is the pre-customer-rollout pre-flight; not in this PR. - Word's 'Inspect Document' / Document Inspector cleanup, which can strip Custom XML Data as 'personal information.' Documented as a known limitation in the namespace adoption guide (SD-3209). --- .../tests/word-roundtrip/all-commands.ts | 121 ++++++++++++++++++ .../fixtures/baseline-word-edited.docx | Bin 0 -> 17210 bytes .../fixtures/baseline-word-resaved.docx | Bin 0 -> 17177 bytes .../fixtures/source-baseline.docx | Bin 0 -> 14793 bytes 4 files changed, 121 insertions(+) create mode 100644 tests/doc-api-stories/tests/word-roundtrip/all-commands.ts create mode 100644 tests/doc-api-stories/tests/word-roundtrip/fixtures/baseline-word-edited.docx create mode 100644 tests/doc-api-stories/tests/word-roundtrip/fixtures/baseline-word-resaved.docx create mode 100644 tests/doc-api-stories/tests/word-roundtrip/fixtures/source-baseline.docx diff --git a/tests/doc-api-stories/tests/word-roundtrip/all-commands.ts b/tests/doc-api-stories/tests/word-roundtrip/all-commands.ts new file mode 100644 index 0000000000..271339afdc --- /dev/null +++ b/tests/doc-api-stories/tests/word-roundtrip/all-commands.ts @@ -0,0 +1,121 @@ +/** + * Word-in-the-loop validation for anchored metadata (SD-3201). + * + * The SuperDoc → DOCX → SuperDoc round-trip is proven by + * `tests/doc-api-stories/tests/metadata/all-commands.ts` — same + * session model, save and reopen. This story proves the harder case: + * Microsoft Word in the middle. + * + * Fixtures under `./fixtures/` were generated as follows: + * + * 1. `source-baseline.docx` — a SuperDoc-exported DOCX with one + * anchored citation (`id="fixture-cite-001"`, namespace + * `urn:superdoc:test:word-roundtrip:1`, anchor over the phrase + * "duty of care"). + * 2. `baseline-word-resaved.docx` — `source-baseline.docx` opened + * in Word and re-saved without edits. Proves Word does not + * strip the customXml part or the hidden SDT wrapper. + * 3. `baseline-word-edited.docx` — `source-baseline.docx` opened + * in Word with text inserted inside the cited span. Proves the + * SDT survives content edits and resolves to a target whose + * text content includes the edit. + * + * The fixtures are deterministic snapshots — they run in CI without + * needing Word access. New Word version regressions are caught by a + * separate pre-rollout live script (not in this PR). + */ +import { describe, expect, it } from 'vitest'; +import path from 'node:path'; +import { unwrap, useStoryHarness } from '../harness'; + +const NAMESPACE = 'urn:superdoc:test:word-roundtrip:1'; +const CITE_ID = 'fixture-cite-001'; + +const EXPECTED_PAYLOAD = { + citationId: 'fixture-cite-001', + sourceId: 'src-restatement', + sourceType: 'statute', + provider: 'lexisnexis', + displayText: 'Restatement (Third) of Torts § 6', + locator: '§ 6', + excerpt: 'An actor must exercise reasonable care.', + confidence: 0.92, +}; + +const FIXTURE_DIR = path.resolve(import.meta.dirname, 'fixtures'); +const FIXTURE_RESAVED = path.join(FIXTURE_DIR, 'baseline-word-resaved.docx'); +const FIXTURE_EDITED = path.join(FIXTURE_DIR, 'baseline-word-edited.docx'); + +describe('document-api story: Word round-trip preserves anchored metadata', () => { + const { client } = useStoryHarness('word-roundtrip/all-commands', { preserveResults: true }); + const api = client as any; + + function makeSessionId(prefix: string): string { + return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`; + } + + async function callDocOperation(operationId: string, input: Record): Promise { + const segments = operationId.split('.'); + let fn: any = api.doc; + for (const segment of segments) fn = fn?.[segment]; + if (typeof fn !== 'function') throw new Error(`Unknown doc operation: ${operationId}`); + return unwrap(await fn(input)); + } + + async function withReopenedSession(fixturePath: string, fn: (sessionId: string) => Promise): Promise { + const sessionId = makeSessionId('word-roundtrip'); + try { + await callDocOperation('open', { sessionId, doc: fixturePath }); + return await fn(sessionId); + } finally { + await callDocOperation('close', { sessionId, discard: true }).catch(() => {}); + } + } + + it('Word save (no edits): metadata.list/get/resolve all recover the citation', async () => { + await withReopenedSession(FIXTURE_RESAVED, async (sessionId) => { + // list + const list = await callDocOperation('metadata.list', { sessionId, namespace: NAMESPACE }); + expect(list?.total).toBe(1); + const ids = (list?.items ?? []).map((item: any) => item?.id ?? item?.domain?.id); + expect(ids).toContain(CITE_ID); + + // get — payload survives byte-for-byte (Word does not normalize the customXml part) + const info = await callDocOperation('metadata.get', { sessionId, id: CITE_ID }); + expect(info?.id).toBe(CITE_ID); + expect(info?.namespace).toBe(NAMESPACE); + expect(info?.payload).toEqual(EXPECTED_PAYLOAD); + + // resolve — anchor still resolves to a text-range target + const resolved = await callDocOperation('metadata.resolve', { sessionId, id: CITE_ID }); + expect(resolved?.id).toBe(CITE_ID); + expect(resolved?.target?.kind).toBe('selection'); + expect(resolved?.target?.start?.kind).toBe('text'); + expect(resolved?.target?.end?.kind).toBe('text'); + }); + }); + + it('Word edit inside anchor: metadata recovers, anchor expands to cover the edited text', async () => { + await withReopenedSession(FIXTURE_EDITED, async (sessionId) => { + // payload still recovers — Word does not touch the customXml part during inline edits + const info = await callDocOperation('metadata.get', { sessionId, id: CITE_ID }); + expect(info?.payload).toEqual(EXPECTED_PAYLOAD); + + // resolve still returns a target — the anchor expanded around the edit rather than detaching + const resolved = await callDocOperation('metadata.resolve', { sessionId, id: CITE_ID }); + expect(resolved?.id).toBe(CITE_ID); + expect(resolved?.target?.kind).toBe('selection'); + + // The anchored text now includes the word the editor inserted inside the original "duty of care" span. + // The exact run-splitting may shift whitespace; the load-bearing assertion is that the original + // anchor head and tail are still both inside the resolved range. + const within = await callDocOperation('metadata.list', { + sessionId, + namespace: NAMESPACE, + within: resolved.target, + }); + const withinIds = (within?.items ?? []).map((item: any) => item?.id ?? item?.domain?.id); + expect(withinIds).toContain(CITE_ID); + }); + }); +}); diff --git a/tests/doc-api-stories/tests/word-roundtrip/fixtures/baseline-word-edited.docx b/tests/doc-api-stories/tests/word-roundtrip/fixtures/baseline-word-edited.docx new file mode 100644 index 0000000000000000000000000000000000000000..08f7920cde40d8b50859ce73a622fb993ccf50c9 GIT binary patch literal 17210 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 TKR3eg04VRNBe#lePDLS60^g>eTuE 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 literal 0 HcmV?d00001 diff --git a/tests/doc-api-stories/tests/word-roundtrip/fixtures/source-baseline.docx b/tests/doc-api-stories/tests/word-roundtrip/fixtures/source-baseline.docx new file mode 100644 index 0000000000000000000000000000000000000000..73d0951ba41c1e647ab3fb1a9db8227e719d9796 GIT binary patch literal 14793 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 literal 0 HcmV?d00001 From 3d5fa8dee732d92877ec8440586fbc82ad9edba0 Mon Sep 17 00:00:00 2001 From: Caio Pizzol Date: Mon, 18 May 2026 18:54:36 -0300 Subject: [PATCH 2/3] test(document-api): tighten edited-anchor assertions and wording (SD-3201) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Soften the inline comment to match what the test actually asserts: the anchor did not detach (resolve returns a selection target with text endpoints), and the citation is still listable via within-scoped list. Text-content equality of the resolved range against the edited string is explicitly not asserted here. Also drop the 'byte-for-byte' wording on the no-edit fixture — the assertion is parsed-payload equality, not raw XML byte equality. --- .../tests/word-roundtrip/all-commands.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/tests/doc-api-stories/tests/word-roundtrip/all-commands.ts b/tests/doc-api-stories/tests/word-roundtrip/all-commands.ts index 271339afdc..24ed50d258 100644 --- a/tests/doc-api-stories/tests/word-roundtrip/all-commands.ts +++ b/tests/doc-api-stories/tests/word-roundtrip/all-commands.ts @@ -80,7 +80,7 @@ describe('document-api story: Word round-trip preserves anchored metadata', () = const ids = (list?.items ?? []).map((item: any) => item?.id ?? item?.domain?.id); expect(ids).toContain(CITE_ID); - // get — payload survives byte-for-byte (Word does not normalize the customXml part) + // get — payload survives intact (Word does not normalize the customXml part) const info = await callDocOperation('metadata.get', { sessionId, id: CITE_ID }); expect(info?.id).toBe(CITE_ID); expect(info?.namespace).toBe(NAMESPACE); @@ -95,20 +95,24 @@ describe('document-api story: Word round-trip preserves anchored metadata', () = }); }); - it('Word edit inside anchor: metadata recovers, anchor expands to cover the edited text', async () => { + it('Word edit inside anchor: metadata recovers, anchor survives without detaching', async () => { await withReopenedSession(FIXTURE_EDITED, async (sessionId) => { // payload still recovers — Word does not touch the customXml part during inline edits const info = await callDocOperation('metadata.get', { sessionId, id: CITE_ID }); expect(info?.payload).toEqual(EXPECTED_PAYLOAD); - // resolve still returns a target — the anchor expanded around the edit rather than detaching + // resolve returns a `selection` target with text endpoints — the anchor did not detach + // when Word split runs around the inserted word. (A detached / orphaned anchor would + // resolve to a different `target.kind`, or no target at all.) const resolved = await callDocOperation('metadata.resolve', { sessionId, id: CITE_ID }); expect(resolved?.id).toBe(CITE_ID); expect(resolved?.target?.kind).toBe('selection'); + expect(resolved?.target?.start?.kind).toBe('text'); + expect(resolved?.target?.end?.kind).toBe('text'); - // The anchored text now includes the word the editor inserted inside the original "duty of care" span. - // The exact run-splitting may shift whitespace; the load-bearing assertion is that the original - // anchor head and tail are still both inside the resolved range. + // `list({ within })` scoped to the resolved range still returns the citation — the anchor + // is still inside its own resolved range after Word's run-splitting. Text-content equality + // of the resolved range against the edited string is not asserted here. const within = await callDocOperation('metadata.list', { sessionId, namespace: NAMESPACE, From 742cc33e85bb67e321319ec03d505b5c0cbdf1bc Mon Sep 17 00:00:00 2001 From: Caio Pizzol Date: Mon, 18 May 2026 19:01:37 -0300 Subject: [PATCH 3/3] test(document-api): independent text-slice check for edited anchor (SD-3201) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the self-referential metadata.list({ within: resolved.target }) block with a slice of the resolved block's text via editor.doc.extract. The slice now asserts the SDT range covers 'duty' + 'reasonable' + 'care' — proving the Word-inserted word is inside the anchor using a read surface (extract) that's independent of the metadata store. --- .../tests/word-roundtrip/all-commands.ts | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/tests/doc-api-stories/tests/word-roundtrip/all-commands.ts b/tests/doc-api-stories/tests/word-roundtrip/all-commands.ts index 24ed50d258..12a88ea3a8 100644 --- a/tests/doc-api-stories/tests/word-roundtrip/all-commands.ts +++ b/tests/doc-api-stories/tests/word-roundtrip/all-commands.ts @@ -110,16 +110,20 @@ describe('document-api story: Word round-trip preserves anchored metadata', () = expect(resolved?.target?.start?.kind).toBe('text'); expect(resolved?.target?.end?.kind).toBe('text'); - // `list({ within })` scoped to the resolved range still returns the citation — the anchor - // is still inside its own resolved range after Word's run-splitting. Text-content equality - // of the resolved range against the edited string is not asserted here. - const within = await callDocOperation('metadata.list', { - sessionId, - namespace: NAMESPACE, - within: resolved.target, - }); - const withinIds = (within?.items ?? []).map((item: any) => item?.id ?? item?.domain?.id); - expect(withinIds).toContain(CITE_ID); + // Independent text-content check via `extract` — slice the resolved block's text using the + // resolved start/end offsets and assert the inserted word ("reasonable") plus the original + // anchor head and tail ("duty", "care") all fall inside the SDT range. This is independent + // of `metadata.resolve` because `extract` reads the document body, not the customXml store. + const extracted = await callDocOperation('extract', { sessionId }); + const startBlockId = resolved.target.start.blockId; + const endBlockId = resolved.target.end.blockId; + expect(endBlockId).toBe(startBlockId); // fixture invariant: anchor is intra-paragraph + const block = (extracted?.blocks ?? []).find((b: any) => b?.nodeId === startBlockId); + expect(block?.text).toBeTruthy(); + const selectedText = String(block.text).slice(resolved.target.start.offset, resolved.target.end.offset); + expect(selectedText).toContain('duty'); + expect(selectedText).toContain('reasonable'); + expect(selectedText).toContain('care'); }); }); });