fix: radio value clobbering + validation controller path resolution (#13 #12) - #15
Merged
Conversation
field_attributes carries value: field_value (the model's CURRENT value) and was splatted after the explicit positional radio value, clobbering it. A new record lost the value entirely; an edit form gave every radio in the group the same value. radio now drops field_attributes' value (the sibling checkbox/toggle/file builders already do .except(:value)), so each radio keeps its own value and an explicit value: option still wins. ## Test Coverage - keeps each radio's own value instead of the model's current value - checks the radio whose value matches the model, not all of them - still lets an explicit value: option win ## Verification - [x] bundle exec rubocop lib spec passes - [x] bundle exec rspec passes Refs #13
…ntrollersFrom data-controller="forms--validations--length" derives (via stimulus-loading's controllerFilename) the import path phlex_forms/controllers/forms/validations/ length_controller — but the files ship at phlex_forms/controllers/validations/ length_controller (no forms/ segment). The import 404'd and the controllers never connected, so client-side validation silently did nothing (issue #12). Drop the forms-- prefix: CONTROLLER_PREFIX is now "validations", so data-controller="validations--length" resolves to the shipped path. The change cascades from the single constant to the emitted identifier AND the data-validations--*-value attribute names (data_key uses the same prefix). Also updated to match: form.rb now derives the coordinator id from the constant (so form-level and field-level can't drift), the JS invalidate:validations event, the data-validations--error / --counter markers, specs, README, and the docs page. BREAKING: hosts that registered forms--validations--* controllers explicitly must update the identifier to validations--*. ## Test Coverage - introspector emits validations--presence / validations--length + the validations__*_value data keys - components_spec: the coordinator (validations--form), submit action, and per-field controllers all use the new prefix - verified every emitted identifier resolves to a shipped *_controller.js path ## Verification - [x] bundle exec rubocop lib spec passes - [x] bundle exec rspec passes (144 examples) - [x] no surviving forms--validations / forms__validations references anywhere Refs #12
This was referenced Jul 11, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes two client-side-validation / binding bugs, two commits on one branch.
fix(components): Field#radio keeps its own value, not the model'sfix(engine): validation controller identifiers resolve via lazyLoadControllersFrom#13 —
Field#radioclobbered every radio's valuefield_attributescarriesvalue: field_value(the model's current value) andwas splatted after the explicit positional radio value, so
field_valuewon.A new record lost the value entirely (
<input type="radio" name="user[role]">with no
value); an edit form gave every radio in the group the same value.radionow doesfield_attributes.except(:value).merge(options)— each radiokeeps its own positional value, and an explicit
value:option still wins. Thesibling
checkbox/toggle/filebuilders already did.except(:value), soradio was the only one missing it (no unfixed sibling bug — confirmed by review).
#12 — validation Stimulus controllers never connected
data-controller="forms--validations--length"derives (viastimulus-loading'scontrollerFilename) the import pathphlex_forms/controllers/forms/validations/length_controller— but the filesship at
phlex_forms/controllers/validations/length_controller(noforms/segment). The import 404'd, controllers never connected, client-side validation
silently did nothing.
Fix: drop the
forms--prefix.CONTROLLER_PREFIXis now"validations", sodata-controller="validations--length"resolves to the shipped path. The changecascades from that one constant to the emitted identifier and the
data-validations--*-valueattribute names (data_keyreuses the prefix).Everything coupled was updated to match:
form.rbderives thevalidations--formcoordinator id from the constant,so form-level and field-level identifiers can't drift.
invalidate:validationsevent, thevalidations--filter in thecoordinator, and the
data-validations--error/data-validations--countermarkers.
Test Coverage
value:still wins (3 new specs).validations--presence/validations--length+the
validations__*_valuedata keys;components_specasserts the coordinator,submit action, and per-field controllers all use the new prefix.
Verified independently: every emitted identifier resolves to a shipped
validations/*_controller.js; the JS dispatch/listen event names and thewrite/read marker strings are internally consistent; a repo-wide grep finds zero
surviving
forms--validations/forms__validationsreferences (only theintentional CHANGELOG migration note).
Full suite: 144 examples, 0 failures ·
rubocop lib specclean.Deviations & judgment calls
call. Option 1 (move controllers under
forms/validations/) would have beennon-breaking, but option 2 keeps the files where they are and shrinks the
identifier. Justified pre-1.0, and the previously-documented lazy-load path was
already broken, so there's no working host wiring to break.
form.rbderives the coordinator fromCONTROLLER_PREFIXrather thanrepeating a literal
"validations--form"string — so the form-level andfield-level identifiers share one source of truth and can never drift on a
future rename.
checkbox/toggle/file already drop the model value, and input/textarea/hidden
intentionally keep it. Radio was the sole outlier.