test(behavior): document-api locked SDT mutations (SD-3144)#3291
Merged
Conversation
…ge writes (SD-3123) Per ECMA-376, sdtLocked protects the SDT wrapper from removal but the content stays editable. Before this fix, programmatic mutations flowed through editor.commands.updateStructuredContentById, which dispatches tr.replaceWith(pos, pos + node.nodeSize, ...) spanning the entire SDT. The structured-content lock plugin's filterTransaction reads that as wrapper damage and silently filters the transaction on sdtLocked controls - producing false-success no-ops on operations the API allows (text.setValue, replaceContent, appendContent, prependContent, setLockMode). Two engine changes: - applyAttrsUpdate (sdt-properties-write.ts) - switch from editor.commands.updateStructuredContentById to per-key tr.setNodeAttribute. AttrSteps have no from/to range and are explicitly skipped by the lock plugin's filterTransaction. This is the path that patch, setLockMode, patchRawProperties, setType, and the date/binding/multiline family use. - replaceSdtTextContent (content-controls-wrappers.ts) - replace the SDT's inner content range (pos+1 to pos+nodeSize-1) instead of rewriting the whole wrapper. The lock plugin classifies inner-range steps as wouldModifyContent: allowed on sdtLocked, still blocked on contentLocked / sdtContentLocked (both per spec). The search in applyAttrsUpdate uses SDT_NODE_NAMES (imported from target-resolution.ts) so it resolves the same nodes the upstream resolveSdtByTarget would resolve. documentSection / documentPartObject are intentionally excluded - they have their own write paths and could collide on id. No changes to API-level lock guards (assertNotSdtLocked on patch, setType, setBinding, etc.) - relaxing those is a separate behavior decision and out of scope here. Tests: - 4 new regression tests for sdtLocked (setLockMode, text.setValue, text.clearValue, replaceContent) that assert the actual inner-range positions (pos+1, pos+nodeSize-1), not just call counts. - 11 existing tests migrated from "expect updateStructuredContentById called" to "expect tr.setNodeAttribute / tr.replaceWith / tr.delete". - 19 metadata-mutation operations added to CC_DIRECT_DISPATCH_OPS in conformance (no synthetic-failure mode after this refactor, matching the existing precedent for wrap/unwrap/etc.). All pass: 46 wrappers + 1170 conformance + 104 SDT-adjacent + 1398 document-api. Zero new TypeScript errors. Related: SD-3131, SD-3139, IT-1046.
Real-editor regression coverage for SD-3123 (#3287). The engine fix was verified at the unit-test level (mock transaction shapes: AttrSteps for metadata, inner-range ReplaceSteps for content), but mocks don't run the real structured-content lock plugin's filterTransaction. This spec closes that gap by exercising the customer surface (editor.doc.contentControls.*) in a real browser with the real lock plugin loaded. Five tests: - text.setValue updates an sdtLocked inline plain-text control - replaceContent updates an sdtLocked block rich-text control - clearContent empties an sdtLocked block control (wrapper preserved) - round-trip sdtLocked → unlocked → text.setValue (lockMode AttrStep writes) - contentLocked + text.setValue returns LOCK_VIOLATION (API guard fires) Lives in tests/behavior/tests/sdt/document-api-locked-mutations.spec.ts. Does NOT un-fixme the older sdt-lock-modes.spec.ts (different concern — that one exercises editor.commands.*, SD-3123 is about the Document API). 5/5 pass on chromium. Stacked on SD-3123 (#3287).
msviderok
pushed a commit
to msviderok/superdoc
that referenced
this pull request
May 16, 2026
…-dev#3291) * fix(content-controls): mutate locked SDTs via AttrSteps and inner-range writes (SD-3123) Per ECMA-376, sdtLocked protects the SDT wrapper from removal but the content stays editable. Before this fix, programmatic mutations flowed through editor.commands.updateStructuredContentById, which dispatches tr.replaceWith(pos, pos + node.nodeSize, ...) spanning the entire SDT. The structured-content lock plugin's filterTransaction reads that as wrapper damage and silently filters the transaction on sdtLocked controls - producing false-success no-ops on operations the API allows (text.setValue, replaceContent, appendContent, prependContent, setLockMode). Two engine changes: - applyAttrsUpdate (sdt-properties-write.ts) - switch from editor.commands.updateStructuredContentById to per-key tr.setNodeAttribute. AttrSteps have no from/to range and are explicitly skipped by the lock plugin's filterTransaction. This is the path that patch, setLockMode, patchRawProperties, setType, and the date/binding/multiline family use. - replaceSdtTextContent (content-controls-wrappers.ts) - replace the SDT's inner content range (pos+1 to pos+nodeSize-1) instead of rewriting the whole wrapper. The lock plugin classifies inner-range steps as wouldModifyContent: allowed on sdtLocked, still blocked on contentLocked / sdtContentLocked (both per spec). The search in applyAttrsUpdate uses SDT_NODE_NAMES (imported from target-resolution.ts) so it resolves the same nodes the upstream resolveSdtByTarget would resolve. documentSection / documentPartObject are intentionally excluded - they have their own write paths and could collide on id. No changes to API-level lock guards (assertNotSdtLocked on patch, setType, setBinding, etc.) - relaxing those is a separate behavior decision and out of scope here. Tests: - 4 new regression tests for sdtLocked (setLockMode, text.setValue, text.clearValue, replaceContent) that assert the actual inner-range positions (pos+1, pos+nodeSize-1), not just call counts. - 11 existing tests migrated from "expect updateStructuredContentById called" to "expect tr.setNodeAttribute / tr.replaceWith / tr.delete". - 19 metadata-mutation operations added to CC_DIRECT_DISPATCH_OPS in conformance (no synthetic-failure mode after this refactor, matching the existing precedent for wrap/unwrap/etc.). All pass: 46 wrappers + 1170 conformance + 104 SDT-adjacent + 1398 document-api. Zero new TypeScript errors. Related: SD-3131, SD-3139, IT-1046. * test(content-controls): add clearContent sdtLocked regression test (SD-3123) * test(behavior): document-api locked SDT mutations (SD-3144) Real-editor regression coverage for SD-3123 (superdoc-dev#3287). The engine fix was verified at the unit-test level (mock transaction shapes: AttrSteps for metadata, inner-range ReplaceSteps for content), but mocks don't run the real structured-content lock plugin's filterTransaction. This spec closes that gap by exercising the customer surface (editor.doc.contentControls.*) in a real browser with the real lock plugin loaded. Five tests: - text.setValue updates an sdtLocked inline plain-text control - replaceContent updates an sdtLocked block rich-text control - clearContent empties an sdtLocked block control (wrapper preserved) - round-trip sdtLocked → unlocked → text.setValue (lockMode AttrStep writes) - contentLocked + text.setValue returns LOCK_VIOLATION (API guard fires) Lives in tests/behavior/tests/sdt/document-api-locked-mutations.spec.ts. Does NOT un-fixme the older sdt-lock-modes.spec.ts (different concern — that one exercises editor.commands.*, SD-3123 is about the Document API). 5/5 pass on chromium. Stacked on SD-3123 (superdoc-dev#3287).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Real-editor regression coverage for SD-3123 (#3287). Mock-based unit tests in #3287 prove the engine emits AttrSteps for metadata writes and inner-range ReplaceSteps for content writes, but they don't run the real structured-content lock plugin's
filterTransaction. This spec closes that gap by exercisingeditor.doc.contentControls.*in a real browser.Five tests in
tests/behavior/tests/sdt/document-api-locked-mutations.spec.ts:text.setValueupdates ansdtLockedinline plain-text controlreplaceContentupdates ansdtLockedblock rich-text controlclearContentempties ansdtLockedblock control (wrapper preserved)sdtLocked→unlocked→text.setValue(lockMode AttrStep writes)contentLocked+text.setValuereturnsLOCK_VIOLATION(API guard fires)Does not un-fixme the older
sdt-lock-modes.spec.ts— that one exerciseseditor.commands.*, while SD-3123 is about the Document API. A new focused spec is the right level.Verified: 5/5 pass on Chromium against #3287's branch. Cross-browser will run in CI.
Merge order: this PR must merge after #3287, since it depends on the engine fix. Once #3287 merges, GitHub will auto-retarget this PR's base to
main.Related: SD-3123.