Summary
In multiple-selection mode, applying a value through v-model (the apply() path) can select a disabled item, even though select(), unselect(), and toggle() all correctly reject disabled items. This violates the documented contract that a disabled model/item makes all selection operations no-ops.
Location
packages/0/src/composables/createModel/index.ts:434 — apply() browse-fallback adds the resolved id directly (selectedIds.add(id)) with no disabled check.
packages/0/src/composables/createSelection/index.ts:260 — apply() multiple branch adds directly (model.selectedIds.add(id)) with no disabled check.
- Contrast:
createModel.select() (:385-393) guards both instance-level and per-ticket disabled; createSelection.apply() single branch (:266) routes through model.select(next) and is already guarded.
- Documented contract:
createModel/index.ts:355-360 JSDoc — "Disabled model — all selection operations are no-ops".
Impact
Affects multiple-mode createSelection / createGroup / createNested via useProxyModel (multi-Select, Combobox, CheckboxGroup, Treeview, multi-ExpansionPanel). Pushing a disabled item's value into the bound array (reset-to-default, server-restored state) renders the disabled item as selected. useProxyModel does not self-correct — once the id is in selectedIds, its value is reported back. Non-destructive state inconsistency, not a crash; no attacker surface (consumer owns the v-model).
Suggested fix
Guard only the add paths; do not blanket-route through select():
createSelection/index.ts:260 — add an inline per-ticket guard (resolve model.get(id), skip if toValue(ticket.disabled)). ⚠️ Do not replace with model.select(id): select clears based on the model's own multiple (false in single-mode-with-override), which would break the existing apply(['A','B'], { multiple: true }) override test (createSelection/index.test.ts:997-1008).
createModel/index.ts:434 — routing through select(id) is safe here.
- Leave the ref-write value-sync path (
createModel:419-425) unguarded — it writes into already-selected tickets and must keep syncing.
- Add a disabled + apply regression test (none currently exists).
Open question (decide before fixing)
Should v-model sync honor instance-level disabled at all, or does disabled gate only user interaction (so programmatic/restored state should mirror regardless)? The per-ticket guard is clearly correct either way; the instance-level question affects whether useProxyModel should revert a consumer's programmatic write.
Summary
In multiple-selection mode, applying a value through
v-model(theapply()path) can select a disabled item, even thoughselect(),unselect(), andtoggle()all correctly reject disabled items. This violates the documented contract that a disabled model/item makes all selection operations no-ops.Location
packages/0/src/composables/createModel/index.ts:434—apply()browse-fallback adds the resolved id directly (selectedIds.add(id)) with no disabled check.packages/0/src/composables/createSelection/index.ts:260—apply()multiple branch adds directly (model.selectedIds.add(id)) with no disabled check.createModel.select()(:385-393) guards both instance-level and per-ticketdisabled;createSelection.apply()single branch (:266) routes throughmodel.select(next)and is already guarded.createModel/index.ts:355-360JSDoc — "Disabled model — all selection operations are no-ops".Impact
Affects multiple-mode
createSelection/createGroup/createNestedviauseProxyModel(multi-Select,Combobox,CheckboxGroup,Treeview, multi-ExpansionPanel). Pushing a disabled item's value into the bound array (reset-to-default, server-restored state) renders the disabled item as selected.useProxyModeldoes not self-correct — once the id is inselectedIds, its value is reported back. Non-destructive state inconsistency, not a crash; no attacker surface (consumer owns thev-model).Suggested fix
Guard only the add paths; do not blanket-route through
select():createSelection/index.ts:260— add an inline per-ticket guard (resolvemodel.get(id), skip iftoValue(ticket.disabled)).model.select(id):selectclears based on the model's ownmultiple(false in single-mode-with-override), which would break the existingapply(['A','B'], { multiple: true })override test (createSelection/index.test.ts:997-1008).createModel/index.ts:434— routing throughselect(id)is safe here.createModel:419-425) unguarded — it writes into already-selected tickets and must keep syncing.Open question (decide before fixing)
Should
v-modelsync honor instance-leveldisabledat all, or doesdisabledgate only user interaction (so programmatic/restored state should mirror regardless)? The per-ticket guard is clearly correct either way; the instance-level question affects whetheruseProxyModelshould revert a consumer's programmatic write.