Context
Operator code-read (2026-08-17, while the compose-surface work surfaced the need): "while list.Chip looks pretty advanced, form.field.Chip was never finished => just extending a combobox with listConfig => useCheckboxes: true. now this would be real engine work." Verified in source — the assessment is exact.
The immediate consumer waiting on it: #17311 (operator compose recipients want removable-chip multi-select); the in-code comment there already documents the gap from the consumer side: the ComboBox/Chip primitive "collapses its value to one scalar (getSelection()[0]) and cannot express 'several peers'".
The Problem
src/form/field/Chip.mjs is 29 lines: class Chip extends ComboBox + className/ntype + listConfig: {useCheckBoxes: true}. Nothing else. No chip rendering of selected values, no multi-value model, no per-chip remove, no keyboard interaction — a ComboBox costume whose dropdown shows checkboxes while the FIELD half of "chip field" does not exist.
Meanwhile src/list/Chip.mjs (107 lines) is the finished rendering substrate: ComponentList-based, component itemDefaults, stacked_ mode, proper neo-chip-list cls chain — and the field never composes it.
The Architectural Reality
form.field.ComboBox's value contract is scalar — the base the stub extends structurally cannot carry "several selected records".
list.Chip already solves chip RENDERING; what is missing is the FIELD: an array-valued form field whose selected values render as removable chips, with a picker for adding more.
- Existing consumers of the stub (blast radius for a value-contract change, swept):
apps/realworld2/view/article/FormContainer.mjs (the classic tag-input use case — would become the reference consumer) and examples/form/field/chip/MainContainer.mjs. list.Chip: its own example only.
- The multi-select SELECTION model exists and works (
selection-listmodel with singleSelect: false — the compose form uses it today); the field wraps that fact into a form-field contract.
The Fix (shape, implementer owns the detail)
A real Neo.form.field.Chip:
- Array value contract:
value is an array of record ids/objects; form containers collect it as such.
- Chips render the selection via
list.Chip composition (removable: × affordance per chip; backspace removes the last chip when the input is empty — the established chip-field grammar).
- Picker adds: the dropdown list (checkbox presentation optional) toggles membership; typing filters as ComboBox does today.
- Keyboard + a11y: chip focus/removal reachable without pointer; focus order documented.
apps/realworld2 tag input migrates as the in-repo reference consumer; the example gets rebuilt against the real field.
- Theme coverage in both shipped neo themes (token-governed; gaps to the theme layer named, not painted over).
Contract Ledger Matrix
| Target Surface |
Source of Authority |
Proposed Behavior |
Fallback |
Docs |
Evidence |
Neo.form.field.Chip value |
this ticket (redefines the stub) |
Array (multi-value) |
none — the stub's scalar behavior is the defect; consumers swept (2, both in-repo) |
class JSDoc + example |
consumer migration in-PR |
chipfield ntype |
unchanged |
same ntype, real behavior |
n/a |
same |
example renders |
list.Chip |
untouched authority |
consumed as rendering substrate, no API change |
n/a |
n/a |
composition only |
Decision Record impact
none (component-level engine work; no ADR governs form-field contracts today — if the implementer finds one, upgrade this line before merge).
Acceptance Criteria
Out of Scope
The operator compose adoption (#17311 decides list-with-chip-presentation vs this field at ITS implementation) · a generic tag-CREATION (free-text new-tag) mode unless it falls out naturally — if deferred, it gets a named follow-up, not silence · theme redesign beyond field coverage.
Related
#17311 (waiting consumer, agentos) · apps/realworld2 FormContainer (legacy consumer → reference consumer) · examples/form/field/chip · examples/list/chip
Live latest-open sweep: REST created-descending, latest 10 checked 2026-08-17T18:24Z, no equivalent; A2A window clean (zeroed 18:16Z).
Origin Session ID: 7ee47ccf-d1c7-469d-a75e-15cebf3b5ea5
Retrieval Hint: query_raw_memories("form field Chip stub multi-select array value list.Chip composition")
Context
Operator code-read (2026-08-17, while the compose-surface work surfaced the need): "while list.Chip looks pretty advanced, form.field.Chip was never finished => just extending a combobox with listConfig => useCheckboxes: true. now this would be real engine work." Verified in source — the assessment is exact.
The immediate consumer waiting on it: #17311 (operator compose recipients want removable-chip multi-select); the in-code comment there already documents the gap from the consumer side: the ComboBox/Chip primitive "collapses its value to one scalar (
getSelection()[0]) and cannot express 'several peers'".The Problem
src/form/field/Chip.mjsis 29 lines:class Chip extends ComboBox+ className/ntype +listConfig: {useCheckBoxes: true}. Nothing else. No chip rendering of selected values, no multi-value model, no per-chip remove, no keyboard interaction — a ComboBox costume whose dropdown shows checkboxes while the FIELD half of "chip field" does not exist.Meanwhile
src/list/Chip.mjs(107 lines) is the finished rendering substrate: ComponentList-based, componentitemDefaults,stacked_mode, properneo-chip-listcls chain — and the field never composes it.The Architectural Reality
form.field.ComboBox's value contract is scalar — the base the stub extends structurally cannot carry "several selected records".list.Chipalready solves chip RENDERING; what is missing is the FIELD: an array-valued form field whose selected values render as removable chips, with a picker for adding more.apps/realworld2/view/article/FormContainer.mjs(the classic tag-input use case — would become the reference consumer) andexamples/form/field/chip/MainContainer.mjs.list.Chip: its own example only.selection-listmodelwithsingleSelect: false— the compose form uses it today); the field wraps that fact into a form-field contract.The Fix (shape, implementer owns the detail)
A real
Neo.form.field.Chip:valueis an array of record ids/objects; form containers collect it as such.list.Chipcomposition (removable: × affordance per chip; backspace removes the last chip when the input is empty — the established chip-field grammar).apps/realworld2tag input migrates as the in-repo reference consumer; the example gets rebuilt against the real field.Contract Ledger Matrix
Neo.form.field.Chipvaluechipfieldntypelist.ChipDecision Record impact
none (component-level engine work; no ADR governs form-field contracts today — if the implementer finds one, upgrade this line before merge).
Acceptance Criteria
apps/realworld2tag input + the chip-field example run on the real field.form/fieldgreen.Out of Scope
The operator compose adoption (#17311 decides list-with-chip-presentation vs this field at ITS implementation) · a generic tag-CREATION (free-text new-tag) mode unless it falls out naturally — if deferred, it gets a named follow-up, not silence · theme redesign beyond field coverage.
Related
#17311 (waiting consumer, agentos) ·
apps/realworld2FormContainer (legacy consumer → reference consumer) · examples/form/field/chip · examples/list/chipLive latest-open sweep: REST created-descending, latest 10 checked 2026-08-17T18:24Z, no equivalent; A2A window clean (zeroed 18:16Z).
Origin Session ID: 7ee47ccf-d1c7-469d-a75e-15cebf3b5ea5
Retrieval Hint:
query_raw_memories("form field Chip stub multi-select array value list.Chip composition")