Skip to content

fix: radio value clobbering + validation controller path resolution (#13 #12) - #15

Merged
mhenrixon merged 2 commits into
mainfrom
fix-issues-12-13
Jul 11, 2026
Merged

fix: radio value clobbering + validation controller path resolution (#13 #12)#15
mhenrixon merged 2 commits into
mainfrom
fix-issues-12-13

Conversation

@mhenrixon

Copy link
Copy Markdown
Collaborator

Summary

Closes two client-side-validation / binding bugs, two commits on one branch.

Commit Issue Type
fix(components): Field#radio keeps its own value, not the model's #13 🐛 Bug
fix(engine): validation controller identifiers resolve via lazyLoadControllersFrom #12 🐛 Bug (⚠️ breaking)

#13Field#radio clobbered every radio's value

field_attributes carries value: field_value (the model's current value) and
was splatted after the explicit positional radio value, so field_value won.
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.

radio now does field_attributes.except(:value).merge(options) — each radio
keeps its own positional value, and an explicit value: option still wins. The
sibling checkbox / toggle / file builders already did .except(:value), so
radio 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 (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, controllers never connected, client-side validation
silently did nothing.

Fix: drop the forms-- prefix. CONTROLLER_PREFIX is now "validations", so
data-controller="validations--length" resolves to the shipped path. The change
cascades from that one constant to the emitted identifier and the
data-validations--*-value attribute names (data_key reuses the prefix).
Everything coupled was updated to match:

  • form.rb derives the validations--form coordinator id from the constant,
    so form-level and field-level identifiers can't drift.
  • JS: the invalidate:validations event, the validations-- filter in the
    coordinator, and the data-validations--error / data-validations--counter
    markers.
  • Specs, README wiring note, and the docs page.

⚠️ Breaking: hosts that registered forms--validations--* controllers
explicitly must update the identifier to validations--*. (Pre-1.0; the
documented lazyLoadControllersFrom path was broken before, so anyone relying
on lazy-loading was getting no validation at all.)

Test Coverage

Verified independently: every emitted identifier resolves to a shipped
validations/*_controller.js; the JS dispatch/listen event names and the
write/read marker strings are internally consistent; a repo-wide grep finds zero
surviving forms--validations / forms__validations references (only the
intentional CHANGELOG migration note).

Full suite: 144 examples, 0 failures · rubocop lib spec clean.

Deviations & judgment calls

  • Bug: validation Stimulus controllers can't be resolved by lazyLoadControllersFrom #12 chose option 2 (rename identifier) over option 1 (move files) — your
    call. Option 1 (move controllers under forms/validations/) would have been
    non-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.rb derives the coordinator from CONTROLLER_PREFIX rather than
    repeating a literal "validations--form" string — so the form-level and
    field-level identifiers share one source of truth and can never drift on a
    future rename.
  • Bug: Field#radio lets field_attributes clobber the positional radio value #13 needed no sibling fix: an adversarial review pass confirmed
    checkbox/toggle/file already drop the model value, and input/textarea/hidden
    intentionally keep it. Radio was the sole outlier.

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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant