Summary
FormAutocomplete.svelte:305-318 (handleInput) calls onChange(inputValue) on every keystroke when allowFreeText: true. The search-box text and the field's committed value are conflated, so partial strings typed into the search box land in the parent form's edits buffer and get committed to history on the next blur.
Surfaced while investigating #31 — same root concern (autocomplete's internal UI state leaking into form/history state), different path.
Repro
- Configure a
FormAutocomplete with allowFreeText: true.
- Mount it inside a
ConfigForm and select a real option, e.g. option-A.
- Click into the autocomplete and type
opt-B (or any partial text), then tab out without selecting an option.
- Inspect the workflow store / history.
- The committed value is the literal string
'opt-B' — a partial, never-selected value — and that's now the top history entry.
Why it's wrong
<select> works fine with onChange-per-change because the value is atomic at every point. Text/number/range work fine because the keystroke is the value. Autocomplete is the outlier: it has an internal inputValue (search query) that is not the field's value. Treating them as one means transient UI state pollutes domain state.
Suggested fix
FormAutocomplete should only emit onChange on commit-worthy events:
- Option selected (already correct —
handleOption)
- Tag added/removed in
multiple mode (already correct)
- Enter pressed with
allowFreeText and non-empty input (already correct)
- Blur with
allowFreeText and non-empty input (currently this commits transitively via the per-keystroke path; should be the only free-text commit path)
Drop the per-keystroke onChange(inputValue) in handleInput (line 314). The component already maintains inputValue as $state for the search UI; it just shouldn't be telling the form about it until the user has signalled intent.
Scope
~10 lines in FormAutocomplete.svelte. Doesn't touch the form layer or the history layer. Independent of #31's structural fix.
Out of scope (separate issue)
The clearing-effect on dependency change (onChange('') when a params field's value flips) has the same architectural problem — child unilaterally mutates parent state. That manifests on undo of a dependency field. Worth its own issue.
Summary
FormAutocomplete.svelte:305-318(handleInput) callsonChange(inputValue)on every keystroke whenallowFreeText: true. The search-box text and the field's committed value are conflated, so partial strings typed into the search box land in the parent form's edits buffer and get committed to history on the next blur.Surfaced while investigating #31 — same root concern (autocomplete's internal UI state leaking into form/history state), different path.
Repro
FormAutocompletewithallowFreeText: true.ConfigFormand select a real option, e.g.option-A.opt-B(or any partial text), then tab out without selecting an option.'opt-B'— a partial, never-selected value — and that's now the top history entry.Why it's wrong
<select>works fine withonChange-per-change because the value is atomic at every point. Text/number/range work fine because the keystroke is the value. Autocomplete is the outlier: it has an internalinputValue(search query) that is not the field's value. Treating them as one means transient UI state pollutes domain state.Suggested fix
FormAutocompleteshould only emitonChangeon commit-worthy events:handleOption)multiplemode (already correct)allowFreeTextand non-empty input (already correct)allowFreeTextand non-empty input (currently this commits transitively via the per-keystroke path; should be the only free-text commit path)Drop the per-keystroke
onChange(inputValue)inhandleInput(line 314). The component already maintainsinputValueas$statefor the search UI; it just shouldn't be telling the form about it until the user has signalled intent.Scope
~10 lines in
FormAutocomplete.svelte. Doesn't touch the form layer or the history layer. Independent of #31's structural fix.Out of scope (separate issue)
The clearing-effect on dependency change (
onChange('')when aparamsfield's value flips) has the same architectural problem — child unilaterally mutates parent state. That manifests on undo of a dependency field. Worth its own issue.