Skip to content

fix(plan-engine): date.setValue and checkbox.setState update the visible content control, not just its stored value#3802

Open
AKhoo wants to merge 2 commits into
superdoc-dev:mainfrom
conveyor:akhoo/patches
Open

fix(plan-engine): date.setValue and checkbox.setState update the visible content control, not just its stored value#3802
AKhoo wants to merge 2 commits into
superdoc-dev:mainfrom
conveyor:akhoo/patches

Conversation

@AKhoo

@AKhoo AKhoo commented Jul 6, 2026

Copy link
Copy Markdown

Summary

Two content-control operations in the plan-engine document-API adapter mutated the underlying OOXML but left the rendered document unchanged, so callers saw the placeholder/old glyph even though the operation reported success:

  • date.setValue wrote w:sdtPr/w:date/@w:fullDate but never rewrote the SDT's run content. A date control that had never been filled in kept showing its placeholder ("Click or tap to enter a date.") forever, and a control with an existing value kept displaying the old text.
  • checkbox.setState updated the checkbox state for inline SDTs but did nothing for block-scope checkboxes, so the visible glyph (☐ / ☒) never changed.

Both are fixed by also rewriting the visible content, mirroring how textSetValueWrapper already keeps the stored value and the rendered text in sync.

Changes

date.setValue

After updating w:fullDate, also call replaceSdtTextContent to rewrite the visible run text. The mutation is reported as successful if either the metadata update or the content update lands a change.

checkbox.setState

Add a block-kind branch. The existing inline branch feeds updateStructuredContentById a bare text node, which a block SDT's schema rejects (block content must be wrapped in a paragraph). The block branch uses replaceSdtTextContent to swap the glyph instead.

@AKhoo AKhoo requested a review from a team as a code owner July 6, 2026 17:07
@AKhoo AKhoo changed the title Akhoo/patches fix(plan-engine): date.setValue and checkbox.setState update the visible content control, not just its stored value Jul 6, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: beb7b91a22

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

// ...but updating w:fullDate alone leaves the SDT showing its placeholder
// ("Click or tap to enter a date.") forever. Mirror textSetValueWrapper and
// also rewrite the visible content so the rendered date actually changes.
const contentUpdated = replaceSdtTextContent(editor, input.target, input.value);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Clear the placeholder flag when setting dates

When this runs on a date SDT imported from Word while it is still showing placeholder content (the added fixture carries w:showingPlcHdr), the code replaces the visible text but leaves w:showingPlcHdr in the passthrough sdtPr. The exporter preserves unhandled sdtPr children, so a saved DOCX can still mark the newly written value as placeholder text even though w:fullDate and the visible content changed. Clear that placeholder marker as part of date.setValue when replacing the placeholder content.

Useful? React with 👍 / 👎.

// updateStructuredContentById a bare text node, which a block SDT's schema
// rejects (block content must be wrapped in a paragraph).
// Instead use replaceSdtTextContent to swap the glyph.
const visualUpdated = replaceSdtTextContent(editor, input.target, symbol.char);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve checkbox glyph font for block updates

For block checkboxes whose checked/unchecked symbols depend on the w14:font entry (for example Wingdings/private-use glyphs), this replaces the content with plain unmarked text via replaceSdtTextContent. The inline path and checkbox creation path apply symbol.font, but this branch drops it, so toggling a block checkbox can render/export the wrong glyph even though the metadata state was updated.

Useful? React with 👍 / 👎.

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Checkbox ignores content lock 🐞 Bug ≡ Correctness
Description
checkboxSetStateWrapper and dateSetValueWrapper rewrite SDT inner content via replaceSdtTextContent
without enforcing assertNotContentLocked, so contentLocked/sdtContentLocked controls can report
success while the lock plugin blocks the glyph/text update. This can leave SDT metadata updated but
visible content unchanged, creating out-of-sync states or silent partial failures.
Code

packages/super-editor/src/editors/v1/document-api-adapters/plan-engine/content-controls-wrappers.ts[R1455-1461]

+    } else if (sdt.kind === 'block') {
+      // Block-scope checkboxes can't reuse the inline branch above: it feeds
+      // updateStructuredContentById a bare text node, which a block SDT's schema
+      // rejects (block content must be wrapped in a paragraph).
+      // Instead use replaceSdtTextContent to swap the glyph.
+      const visualUpdated = replaceSdtTextContent(editor, input.target, symbol.char);
+      return visualUpdated || checkboxUpdated;
Relevance

⭐⭐⭐ High

PR #3287/#3291 focus on correct lock enforcement; missing assertNotContentLocked likely treated as
bug/false-success.

PR-#3287
PR-#3291

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Both wrappers now call replaceSdtTextContent to mutate the SDT’s inner range, but they only
assertNotSdtLocked and never assertNotContentLocked even though replaceSdtTextContent’s contract
explicitly requires callers to enforce contentLocked/sdtContentLocked. The evidence indicates the
structured-content lock plugin blocks content modifications inside content-locked SDTs, so these
code paths can proceed to update metadata (e.g., checkbox state or w:fullDate) while the visible
glyph/text replacement is prevented, yielding inconsistent outcomes.

packages/super-editor/src/editors/v1/document-api-adapters/plan-engine/content-controls-wrappers.ts[1425-1465]
packages/super-editor/src/editors/v1/document-api-adapters/plan-engine/content-controls-wrappers.ts[349-385]
packages/super-editor/src/editors/v1/document-api-adapters/helpers/content-controls/lock-enforcement.ts[33-46]
packages/super-editor/src/editors/v1/extensions/structured-content/structured-content-lock-plugin.js[45-75]
packages/super-editor/src/editors/v1/document-api-adapters/plan-engine/content-controls-wrappers.ts[1324-1349]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`checkboxSetStateWrapper` and `dateSetValueWrapper` now mutate SDT inner content via `replaceSdtTextContent`, but they only check `assertNotSdtLocked` and do not enforce `assertNotContentLocked`. This bypasses the required content-lock contract and can produce partial updates under the structured-content lock plugin (metadata changes succeed while visible glyph/text remains stale), leaving controls in an inconsistent state.

## Issue Context
- `replaceSdtTextContent` relies on callers to enforce `contentLocked` / `sdtContentLocked` before mutating the SDT inner range.
- The structured-content lock plugin blocks content modifications inside `contentLocked` / `sdtContentLocked` SDTs, so missing `assertNotContentLocked` can cause silent failures or metadata/visible-content divergence.

## Fix Focus Areas
- packages/super-editor/src/editors/v1/document-api-adapters/plan-engine/content-controls-wrappers.ts[1425-1465]
- packages/super-editor/src/editors/v1/document-api-adapters/plan-engine/content-controls-wrappers.ts[1324-1349]
- packages/super-editor/src/editors/v1/document-api-adapters/plan-engine/content-controls-wrappers.ts[349-385]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Checkbox glyph loses font 🐞 Bug ≡ Correctness
Description
The new block-scope checkbox visual update inserts only symbol.char via replaceSdtTextContent,
dropping the symbol.font styling that other checkbox rendering paths apply (textStyle fontFamily),
which can change the rendered glyph or strip intended styling.
Code

packages/super-editor/src/editors/v1/document-api-adapters/plan-engine/content-controls-wrappers.ts[R1459-1461]

+      // Instead use replaceSdtTextContent to swap the glyph.
+      const visualUpdated = replaceSdtTextContent(editor, input.target, symbol.char);
+      return visualUpdated || checkboxUpdated;
Relevance

⭐⭐⭐ High

Team has accepted multiple “preserve marks/font” fixes (e.g. tabs/TOC). Dropping checkbox font
likely unacceptable.

PR-#2832
PR-#3120

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Checkbox visuals are designed to carry a font (buildCheckboxTextJson applies textStyle fontFamily
and createWrapper applies checkboxSymbol.font), but replaceSdtTextContent’s block replacement builds
text with marks undefined, so the new block checkbox.setState path drops font styling.

packages/super-editor/src/editors/v1/document-api-adapters/plan-engine/content-controls-wrappers.ts[1425-1465]
packages/super-editor/src/editors/v1/document-api-adapters/plan-engine/content-controls-wrappers.ts[769-801]
packages/super-editor/src/editors/v1/document-api-adapters/plan-engine/content-controls-wrappers.ts[1827-1931]
packages/super-editor/src/editors/v1/document-api-adapters/plan-engine/content-controls-wrappers.ts[361-385]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
For block-scope checkboxes, `checkbox.setState` swaps the glyph using `replaceSdtTextContent(editor, target, symbol.char)`, but this replacement does not apply `symbol.font` as a `textStyle` mark. Other checkbox paths (inline updates and create.checkbox) explicitly apply `fontFamily`.

### Issue Context
- `buildCheckboxTextJson(symbol)` includes a `textStyle` mark with `fontFamily: symbol.font`.
- `replaceSdtTextContent`’s block path builds plain text with `marks=undefined`.

### Fix Focus Areas
- packages/super-editor/src/editors/v1/document-api-adapters/plan-engine/content-controls-wrappers.ts[1425-1465]
- packages/super-editor/src/editors/v1/document-api-adapters/plan-engine/content-controls-wrappers.ts[769-801]
- packages/super-editor/src/editors/v1/document-api-adapters/plan-engine/content-controls-wrappers.ts[349-385]

### Suggested change
1. In the block checkbox branch, replace the paragraph content with a node that applies `textStyle`/fontFamily (e.g., reuse `createTextWithOptionalFont(editor, symbol.char, symbol.font)` and wrap in a paragraph), instead of calling the generic plain-text helper.
2. Alternatively, extend `replaceSdtTextContent` to accept optional marks (or a factory callback) so checkbox can supply the appropriate font mark while other callers remain unchanged.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment on lines +1455 to +1461
} else if (sdt.kind === 'block') {
// Block-scope checkboxes can't reuse the inline branch above: it feeds
// updateStructuredContentById a bare text node, which a block SDT's schema
// rejects (block content must be wrapped in a paragraph).
// Instead use replaceSdtTextContent to swap the glyph.
const visualUpdated = replaceSdtTextContent(editor, input.target, symbol.char);
return visualUpdated || checkboxUpdated;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Checkbox ignores content lock 🐞 Bug ≡ Correctness

checkboxSetStateWrapper and dateSetValueWrapper rewrite SDT inner content via replaceSdtTextContent
without enforcing assertNotContentLocked, so contentLocked/sdtContentLocked controls can report
success while the lock plugin blocks the glyph/text update. This can leave SDT metadata updated but
visible content unchanged, creating out-of-sync states or silent partial failures.
Agent Prompt
## Issue description
`checkboxSetStateWrapper` and `dateSetValueWrapper` now mutate SDT inner content via `replaceSdtTextContent`, but they only check `assertNotSdtLocked` and do not enforce `assertNotContentLocked`. This bypasses the required content-lock contract and can produce partial updates under the structured-content lock plugin (metadata changes succeed while visible glyph/text remains stale), leaving controls in an inconsistent state.

## Issue Context
- `replaceSdtTextContent` relies on callers to enforce `contentLocked` / `sdtContentLocked` before mutating the SDT inner range.
- The structured-content lock plugin blocks content modifications inside `contentLocked` / `sdtContentLocked` SDTs, so missing `assertNotContentLocked` can cause silent failures or metadata/visible-content divergence.

## Fix Focus Areas
- packages/super-editor/src/editors/v1/document-api-adapters/plan-engine/content-controls-wrappers.ts[1425-1465]
- packages/super-editor/src/editors/v1/document-api-adapters/plan-engine/content-controls-wrappers.ts[1324-1349]
- packages/super-editor/src/editors/v1/document-api-adapters/plan-engine/content-controls-wrappers.ts[349-385]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +1459 to +1461
// Instead use replaceSdtTextContent to swap the glyph.
const visualUpdated = replaceSdtTextContent(editor, input.target, symbol.char);
return visualUpdated || checkboxUpdated;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

2. Checkbox glyph loses font 🐞 Bug ≡ Correctness

The new block-scope checkbox visual update inserts only symbol.char via replaceSdtTextContent,
dropping the symbol.font styling that other checkbox rendering paths apply (textStyle fontFamily),
which can change the rendered glyph or strip intended styling.
Agent Prompt
### Issue description
For block-scope checkboxes, `checkbox.setState` swaps the glyph using `replaceSdtTextContent(editor, target, symbol.char)`, but this replacement does not apply `symbol.font` as a `textStyle` mark. Other checkbox paths (inline updates and create.checkbox) explicitly apply `fontFamily`.

### Issue Context
- `buildCheckboxTextJson(symbol)` includes a `textStyle` mark with `fontFamily: symbol.font`.
- `replaceSdtTextContent`’s block path builds plain text with `marks=undefined`.

### Fix Focus Areas
- packages/super-editor/src/editors/v1/document-api-adapters/plan-engine/content-controls-wrappers.ts[1425-1465]
- packages/super-editor/src/editors/v1/document-api-adapters/plan-engine/content-controls-wrappers.ts[769-801]
- packages/super-editor/src/editors/v1/document-api-adapters/plan-engine/content-controls-wrappers.ts[349-385]

### Suggested change
1. In the block checkbox branch, replace the paragraph content with a node that applies `textStyle`/fontFamily (e.g., reuse `createTextWithOptionalFont(editor, symbol.char, symbol.font)` and wrap in a paragraph), instead of calling the generic plain-text helper.
2. Alternatively, extend `replaceSdtTextContent` to accept optional marks (or a factory callback) so checkbox can supply the appropriate font mark while other callers remain unchanged.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants