Context
@neo-opus-grace's defect-note from 2026-09-16T13:36Z, broadcast unowned while she closed #18792 for form.field.Text: the same class lives in form.field.CheckBox, and nothing was tracking it.
Reproduced deterministically on dev (aadafbad1b), unit runtime. Two clicks with the first render held open between its payload collection and its landing:
HELD : []
SETTLED: ["checked-sync-…__input=false"]
The settled render re-sends checked="false" that the DOM already holds. Held open, the intermediate click is visually reverted until the next render lands — invisible at normal speed, which is what makes it expensive to diagnose from the symptom.
The symptom class has cost a debugging session before. On 2026-02-25 (session 6c17bf58-3525-492f-af63-98411c45ef22) @tobiu reported "clicks no longer change the checked state at all" and wrote "i suppose the checkbox might get reset so fast on dom level, that i can not visually see it… even if we monkey-patch around this bug, the next user will encounter it sooner or later." That session attributed the failure to a state-provider binding and repaired it there. This ticket does not claim that incident was this defect — the attribution then was its own, and the engine mechanism below was not measured until now. It is cited because the reading "reset so fast on dom level" describes exactly what the reproducer shows, and because it is the reason to fix the engine rather than the consumer.
Live latest-open sweep: latest 20 open issues read at 2026-09-17T09:12Z; nothing equivalent. Targeted in:title searches on checkbox / checked / vnode sync returned empty, as did grep over resources/content/issues and resources/content/discussions for form.field.CheckBox and onInputValueChange. A2A sweep of the latest 30 messages: Grace's defect-note is the only mention and carries no lane claim. Own-assignment sweep: no overlap across my 7 open tickets.
The Problem
CheckBox#onInputValueChange writes what the DOM reports into the input's vnode so the next diff has nothing to send — the DOM already holds it:
me.vnode.childNodes[0].childNodes[me.hideLabel ? 0 : 1].attributes.checked = `${checked}`;A render collected before that report lands after it, carrying the vnode it was diffed against. That vnode does not hold the reported state, so the sync is silently undone, and the next diff computes against a stale checked. The user's click is written back over.
This is the identical mechanism #18792 measured for Text, one field class across. Text writes attributes.value; CheckBox writes attributes.checked. Neither write survives a render that was already in the air.
The Architectural Reality
src/form/field/CheckBox.mjs:610-620 — onInputValueChange() performs the sync by index-walking me.vnode.childNodes[0].childNodes[me.hideLabel ? 0 : 1], then assigns me.checked.
src/form/field/CheckBox.mjs:9 — class CheckBox extends Field (form.field.Base), not form.field.Text. So #18798's repair is not inherited; the two fields are siblings, and this one has no resolveVdomUpdate override at all.
src/form/field/Text.mjs:1539 — the merged shape: resolveVdomUpdate() restores reportedInputValue into the landed vnode unless that render wrote the input's value itself (checked by scanning data.deltas for a removeNode or a value attribute on the input's id), and afterSetMounted drops it on unmount.
src/mixin/VdomLifecycle.mjs#resolveVdomUpdate — the landing hook both overrides chain into.
test/playwright/unit/form/field/InputValueSync.spec.mjs — #18798's three arms (one defect, two controls), the fixture shape this ticket's arms extend: hold VdomHelper.updateBatch open, record every render's deltas, assert on what was written.
The Fix
Mirror the merged Text repair at the sibling, adapted to checked:
- A
reportedChecked protected member holding what the DOM last reported through onInputValueChange().
- A
resolveVdomUpdate() override that restores it into the landed vnode, unless that render wrote the input's checked itself — same delta scan as Text, keyed on the input node rather than the id-walk, so the repair does not inherit the index-walk's fragility.
- Cleared when the field unmounts, as
Text clears on unmount.
Not by moving CheckBox under Text, and not by lifting the repair into form.field.Base: the two fields sync different attributes through different node lookups, and a shared base method would have to know both. If a third field class turns up with the same shape, that is the moment to extract — named here so the next reader does not re-derive it.
Contract Ledger
| Target Surface |
Source of Authority |
Proposed Behavior |
Fallback |
Docs |
Evidence |
CheckBox#resolveVdomUpdate (new override) |
src/form/field/Text.mjs:1539 — the merged sibling repair |
restores the DOM-reported checked into the landed vnode, unless that render wrote checked for the input itself |
none — absent a report (reportedChecked === null) the method is a straight super call, so an untouched field is byte-unchanged |
override JSDoc naming the in-the-air window and what it restores, as Text's does |
the reproducer arm below, red on dev; plus the two controls |
CheckBox#reportedChecked (new protected member) |
src/form/field/Text.mjs:321 — reportedInputValue |
the last DOM-reported checked state, held until a render writes it or the field unmounts |
null — the untouched state |
member JSDoc pointing at resolveVdomUpdate |
cleared-on-unmount arm |
Retirement: both rows retire together if a third field class needs the same repair and it is extracted to a shared owner. Until then, two siblings with one shape each is cheaper than a base method that knows both.
Decision Record impact
none — a defect repair inside an existing component, no contract surface added beyond the two protected members above.
Acceptance Criteria
Out of Scope
form.field.Radio and any other field sharing the shape — unmeasured. If one turns up, it is its own ticket and the extraction question above opens with it.
- The index-walk in
onInputValueChange itself (childNodes[0].childNodes[hideLabel ? 0 : 1]). It is fragile and it is not this defect; touching it here would mix a repair with a refactor.
- The
Text repair, merged as #18798.
mergedCallbackMap orphan entries — @neo-opus-grace's separate defect-note, out of scope of #18802 too.
Avoided Traps
- ⛔ Do not fix this in the consumer. The 2026-02-25 session repaired a checkbox symptom at the binding layer and the engine mechanism survived it. A consumer-side repair here would do the same again.
- ⛔ Do not widen
Text's reportedInputValue to carry both. The name would then lie about one of its two callers, and CheckBox does not extend Text.
- ⛔ Do not assert the reproducer by reading
me.checked. The config is correct throughout — the defect is entirely in what the render writes to the DOM, so the arm must read the deltas, as #18798's do.
Related
- #18792 / PR #18798 — the
Text sibling, merged; the repair shape this mirrors
- #18762 — the grid-native cell-editing epic where the
Text instance surfaced as the CellEditingPooling:225 flake
- @neo-opus-grace's
defect-note of 2026-09-16T13:36Z — the origin
Origin Session ID: 2b78af80-54a8-4e14-afd8-85c6dfe41004
Retrieval Hint: "checkbox checked vnode sync undone by a landing render, reportedChecked resolveVdomUpdate, sibling of the Text input value repair"
Context
@neo-opus-grace's
defect-notefrom 2026-09-16T13:36Z, broadcast unowned while she closed #18792 forform.field.Text: the same class lives inform.field.CheckBox, and nothing was tracking it.Reproduced deterministically on
dev(aadafbad1b), unit runtime. Two clicks with the first render held open between its payload collection and its landing:The settled render re-sends
checked="false"that the DOM already holds. Held open, the intermediate click is visually reverted until the next render lands — invisible at normal speed, which is what makes it expensive to diagnose from the symptom.The symptom class has cost a debugging session before. On 2026-02-25 (session
6c17bf58-3525-492f-af63-98411c45ef22) @tobiu reported "clicks no longer change the checked state at all" and wrote "i suppose the checkbox might get reset so fast on dom level, that i can not visually see it… even if we monkey-patch around this bug, the next user will encounter it sooner or later." That session attributed the failure to a state-provider binding and repaired it there. This ticket does not claim that incident was this defect — the attribution then was its own, and the engine mechanism below was not measured until now. It is cited because the reading "reset so fast on dom level" describes exactly what the reproducer shows, and because it is the reason to fix the engine rather than the consumer.Live latest-open sweep: latest 20 open issues read at 2026-09-17T09:12Z; nothing equivalent. Targeted
in:titlesearches on checkbox / checked / vnode sync returned empty, as didgrepoverresources/content/issuesandresources/content/discussionsforform.field.CheckBoxandonInputValueChange. A2A sweep of the latest 30 messages: Grace'sdefect-noteis the only mention and carries no lane claim. Own-assignment sweep: no overlap across my 7 open tickets.The Problem
CheckBox#onInputValueChangewrites what the DOM reports into the input's vnode so the next diff has nothing to send — the DOM already holds it:me.vnode.childNodes[0].childNodes[me.hideLabel ? 0 : 1].attributes.checked = `${checked}`;A render collected before that report lands after it, carrying the vnode it was diffed against. That vnode does not hold the reported state, so the sync is silently undone, and the next diff computes against a stale
checked. The user's click is written back over.This is the identical mechanism #18792 measured for
Text, one field class across.Textwritesattributes.value;CheckBoxwritesattributes.checked. Neither write survives a render that was already in the air.The Architectural Reality
src/form/field/CheckBox.mjs:610-620—onInputValueChange()performs the sync by index-walkingme.vnode.childNodes[0].childNodes[me.hideLabel ? 0 : 1], then assignsme.checked.src/form/field/CheckBox.mjs:9—class CheckBox extends Field(form.field.Base), notform.field.Text. So #18798's repair is not inherited; the two fields are siblings, and this one has noresolveVdomUpdateoverride at all.src/form/field/Text.mjs:1539— the merged shape:resolveVdomUpdate()restoresreportedInputValueinto the landed vnode unless that render wrote the input's value itself (checked by scanningdata.deltasfor aremoveNodeor avalueattribute on the input's id), andafterSetMounteddrops it on unmount.src/mixin/VdomLifecycle.mjs#resolveVdomUpdate— the landing hook both overrides chain into.test/playwright/unit/form/field/InputValueSync.spec.mjs— #18798's three arms (one defect, two controls), the fixture shape this ticket's arms extend: holdVdomHelper.updateBatchopen, record every render's deltas, assert on what was written.The Fix
Mirror the merged
Textrepair at the sibling, adapted tochecked:reportedCheckedprotected member holding what the DOM last reported throughonInputValueChange().resolveVdomUpdate()override that restores it into the landed vnode, unless that render wrote the input'scheckeditself — same delta scan asText, keyed on the input node rather than the id-walk, so the repair does not inherit the index-walk's fragility.Textclears on unmount.Not by moving
CheckBoxunderText, and not by lifting the repair intoform.field.Base: the two fields sync different attributes through different node lookups, and a shared base method would have to know both. If a third field class turns up with the same shape, that is the moment to extract — named here so the next reader does not re-derive it.Contract Ledger
CheckBox#resolveVdomUpdate(new override)src/form/field/Text.mjs:1539— the merged sibling repaircheckedinto the landed vnode, unless that render wrotecheckedfor the input itselfreportedChecked === null) the method is a straightsupercall, so an untouched field is byte-unchangedText's doesdev; plus the two controlsCheckBox#reportedChecked(new protected member)src/form/field/Text.mjs:321—reportedInputValuenull— the untouched stateresolveVdomUpdateRetirement: both rows retire together if a third field class needs the same repair and it is extracted to a shared owner. Until then, two siblings with one shape each is cheaper than a base method that knows both.
Decision Record impact
none— a defect repair inside an existing component, no contract surface added beyond the two protected members above.Acceptance Criteria
checkedthe DOM already holds. Red ondev(measured: the settled render sendschecked="false").checkedset in code while a render is in the air still reaches the DOM — the repair must not suppress a programmatic write.checkeditself, the next render restores what the user clicked, rather than the repair silently losing to it.checkedinto a fresh vnode.unitandcomponentsuites green;examples/form/fieldand thedashboardunits unchanged.Out of Scope
form.field.Radioand any other field sharing the shape — unmeasured. If one turns up, it is its own ticket and the extraction question above opens with it.onInputValueChangeitself (childNodes[0].childNodes[hideLabel ? 0 : 1]). It is fragile and it is not this defect; touching it here would mix a repair with a refactor.Textrepair, merged as #18798.mergedCallbackMaporphan entries — @neo-opus-grace's separatedefect-note, out of scope of #18802 too.Avoided Traps
Text'sreportedInputValueto carry both. The name would then lie about one of its two callers, andCheckBoxdoes not extendText.me.checked. The config is correct throughout — the defect is entirely in what the render writes to the DOM, so the arm must read the deltas, as #18798's do.Related
Textsibling, merged; the repair shape this mirrorsTextinstance surfaced as theCellEditingPooling:225flakedefect-noteof 2026-09-16T13:36Z — the originOrigin Session ID: 2b78af80-54a8-4e14-afd8-85c6dfe41004
Retrieval Hint: "checkbox checked vnode sync undone by a landing render, reportedChecked resolveVdomUpdate, sibling of the Text input value repair"