Skip to content

Add native content-control operations - #484

Merged
JSv4 merged 6 commits into
mainfrom
agent/issue-452-content-controls
Aug 15, 2026
Merged

Add native content-control operations#484
JSv4 merged 6 commits into
mainfrom
agent/issue-452-content-controls

Conversation

@JSv4

@JSv4 JSv4 commented Aug 14, 2026

Copy link
Copy Markdown
Owner

Closes #452

Summary

  • adds a part-aware native content-control registry with stable package-wide identities across block, run, cell, row, repeating-section/item, picture, checkbox, date, dropdown, combo, and text controls
  • exposes inspect/fill/remove/detach/repeating-item operations through the shared .NET JSON contract, WASM, npm, Python, and MCP surfaces
  • routes all mutating MCP actions through the shared atomic/best-effort batch engine, optimistic preconditions, and isolated preview path with uniform receipts
  • preserves lock, binding, placeholder, list-selection, checkbox glyph/font, picture relationship, bookmark/link, and tracked-delete semantics
  • validates exact SDT structure/family/identity/lock cardinality and fails closed on malformed, unsafe-placement, bound, locked, or unsupported shapes
  • makes CanMutate share the same repeating-section and picture-topology eligibility rules as mutation, including zero/multiple/linked images and live revision carriers
  • prevents cloned repeating items from duplicating live revision identities, preserves unique SDT/drawing identities, and cleans orphaned relationships on replacement/removal
  • supports intentional empty inline rich-text fills without producing invalid block markup
  • documents Markdown projection behavior and the public sdt anchor/receipt lifecycle

Validation

  • full solution suite on final stacked ancestry: 3,708 passed, 3 intentionally skipped, 0 failed
  • final content-control suite: 30/30
  • final cross-feature matrix (content controls, table addressing/IR anchors, atomic batches, isolated previews, structural/surgical revisions): 199/199
  • full MCP dispatcher suite: 79/79
  • full Python suite: 89/89
  • solution build: 0 errors
  • Word-authored HC030 round-trip coverage plus package-level adversarial malformed/binding/repeating/picture fixtures
  • original transport gates: WASM and Python-host builds, npm typecheck, Python import/compile smoke
  • final worktree and diff checks: clean

Stack

Base: agent/issue-455-structural-revisions / PR #485. This is the content-control layer immediately before transaction identity/idempotency (#449).

@JSv4
JSv4 force-pushed the agent/issue-453-images branch 3 times, most recently from 90f7006 to 7664d14 Compare August 14, 2026 11:11
@JSv4
JSv4 force-pushed the agent/issue-453-images branch from 7664d14 to e767c70 Compare August 14, 2026 11:39
@JSv4
JSv4 force-pushed the agent/issue-452-content-controls branch from 7c3a722 to e1e90df Compare August 14, 2026 11:39
@JSv4
JSv4 changed the base branch from agent/issue-453-images to agent/issue-455-structural-revisions August 14, 2026 11:39
…mutation, order rPr

Review fixes on the native content-control surface (#452).

Repeating-section clones now freshen `w14:paraId` instead of refusing any item
that carries one. Word 2013+ stamps a paraId on essentially every `w:p`, so the
reject made the feature inert on real templates: HC031 carries 158 of them,
while the only fixture exercising the clone path carries zero — which is why no
test caught it. The clone mints package-unique values from a new
`CommentOps.ParaIdAllocator`, extracted out of `NextParaId` so a detached
subtree can mint several ids without aliasing, and kept as the single owner of
paraId minting. `w14:textId` is copied verbatim: it is a hash of the paragraph's
text rather than an identity, and Word emits the same value for two paragraphs
with the same content — which is exactly what a clone is.

`ListContentControls` now consults the session's tracked-change mode and the
bookmark-removal gate, so `canMutate`/`unsupportedReason` agree with what a
mutation actually does. Under `RenderInline` every control reported
`canMutate: true` while every fill failed with `tracked_operation_unsupported`;
an agent planning off the registry — and the MCP `list` action feeds exactly
this — built a batch guaranteed to fail. Both gates are now shared predicates
that discovery and `ResolveContentControlForMutation` read, closing two more
instances of the divergence class the PR's three Harden commits each closed one
of.

`SetContentControlChecked` inserted `w:rFonts` at position 0 of a cloned
`w:rPr`, which is schema-invalid whenever the glyph run already carries an
earlier `CT_RPr` member (`w:ins`, `w:del`, `w:rStyle`, the move markers). Routed
through the existing `WordprocessingMLUtil.InsertRPrChildInOrder`.

Coverage: CC031-CC035 (paraId freshening with OpenXmlValidator, tracked-mode
registry agreement across all nine ops, bookmark gate visible in discovery,
rFonts schema slot, header/footer `ScopeIncluded`), an end-to-end
`python/tests/test_content_controls.py` over all nine stdio-host route names,
and `npm/tests/docx-session-content-controls.spec.ts` for the WASM bridge.

Docs: the `[Unreleased]` CHANGELOG entry, the missing
`### docxodus_content_controls` section plus the corrected tool count and
batch-step list in `docx_agent_server.md`, and two corrected claims in
`native_content_controls.md` — the SDT envelope is stricter than `CT_SdtPr`
(both `w:sdtPr` and `w:id` are `minOccurs="0"`), and `w:lock` is honoured by
content-control operations rather than by the generic anchor-addressed surface.
@JSv4
JSv4 changed the base branch from agent/issue-455-structural-revisions to main August 15, 2026 04:02
@JSv4
JSv4 marked this pull request as ready for review August 15, 2026 04:02
Bring the native content-control work up to date with main after PRs #479,
#481, #477, #480, #482 and #485 landed.

CHANGELOG.md was the only conflict: both sides added an entry at the top of
[Unreleased]. Resolved by keeping both, main's #455 structural tracked
revisions first and this branch's #452 native content-control operations
second; the rest of main's list is unchanged.

Two semantic fixes a clean textual merge would have hidden:

- Renumber this branch's MCP content-control tests from MCP146/147/148 to
  MCP147/148/149. Main's #485 independently claimed MCP146 for
  MCP146_TrackChangesBatchPreviewIsIsolatedAndAtomicApplyResolvesRevision in
  the same file, so both sides had defined an MCP146.

- docs/architecture/docx_agent_server.md: correct the tools/list count from 17
  to 19 (the catalog now registers 19 tools, three lifecycle plus sixteen
  read/grouped-intent), add docxodus_track_changes to the docxodus_mutations
  batch-step list so it matches the catalog enum and the dispatcher's accepted
  set, and add images/content-controls to the grouped-intent enumeration.
@JSv4
JSv4 merged commit f5ac07f into main Aug 15, 2026
13 of 14 checks passed
JSv4 added a commit that referenced this pull request Aug 15, 2026
Brings the branch up to date with main, which had advanced by 20
commits (PRs #479, #481, #477, #480, #482, #485 and #484).

CHANGELOG.md was the only conflict: both sides appended to the same
[Unreleased] / ### Added region. Resolved by keeping every entry from
both sides, with this branch's idempotent mutation transaction entry
ahead of main's accumulated entries. Main's block is otherwise
unchanged, and the historic "[Unreleased] - .NET 8" heading further
down the file is untouched.

EditErrorCode gained four transaction codes on this branch and image
and revision codes on main; the merged enum retains all of them, and
the npm and Python mirrors carry this branch's four. The enum has no
explicit ordinals and is serialized by name, so the reordering #485
introduced does not affect the wire.

This PR remains MCP-transport-only: DocxSessionOps, DocxSessionJson
and the WASM bridge are byte-identical to main after the merge.
JSv4 added a commit that referenced this pull request Aug 15, 2026
Brings the native-image branch up to date with main through PR #484
(native content controls).

Three conflicts:

- npm/src/types.ts and python/src/docx_scalpel/enums.py: additive
  EditErrorCode collisions, resolved by keeping every code from both
  sides. All three surfaces (the C# enum in DocxSession.cs, which stays
  the single owner, plus the two clients) now carry the same 68 codes.
- docs/architecture/docx_mutation_api.md: main still described images as
  "still a v2 op, currently rejected", which this branch supersedes.
  Kept this branch's InsertImage bullet and main's content-control note.

One semantic fixup beyond the textual conflicts:
FillContentControlPicture called SweepOrphanedImages with main's
three-argument signature. This branch narrowed that helper to a single
argument because it no longer whitelists {r:embed, r:link, r:id} but
scans every attribute value, so the call site drops the two attribute
names. The resulting behaviour is strictly more conservative — it can
only keep more media, never delete more.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Agent] Add first-class content-control operations

1 participant