From 1ae37437089024743d4bb06bf37c6517eec56449 Mon Sep 17 00:00:00 2001 From: Philipp Reinking Date: Tue, 13 Oct 2026 03:41:53 +0200 Subject: [PATCH 1/6] fix: word the rule errors in the editor's terms Co-Authored-By: Claude Opus 5.5 --- app/Http/Requests/FormBlockLogicRequest.php | 16 ++++++++-------- tests/Feature/FormBlockLogicTest.php | 20 ++++++++++++++++++++ 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/app/Http/Requests/FormBlockLogicRequest.php b/app/Http/Requests/FormBlockLogicRequest.php index 4db086bc..60cefb18 100644 --- a/app/Http/Requests/FormBlockLogicRequest.php +++ b/app/Http/Requests/FormBlockLogicRequest.php @@ -65,14 +65,14 @@ public function after(): array public function messages() { return [ - 'name.required' => 'The name is required.', - 'conditions.required' => 'At least one condition is required.', - 'conditions.*.source.required' => 'The source block for your #:position condition is required.', - 'conditions.*.value.required' => 'The value for your #:position condition is required.', - 'conditions.*.operator.required' => 'The operator for the #:position condition is required.', - 'conditions.*.chainOperator.required' => 'The chain operator for the #:position condition is required.', - 'action.required' => 'The action is required.', - 'evaluate.required' => 'The evaluate is required.', + 'name.required' => 'Give the rule a name.', + 'conditions.required' => 'Add at least one condition.', + 'conditions.*.source.required' => 'Pick a question for condition :position.', + 'conditions.*.value.required' => 'Enter a value for condition :position.', + 'conditions.*.operator.required' => 'Pick how to compare in condition :position.', + 'conditions.*.chainOperator.required' => 'Pick "and" or "or" for condition :position.', + 'action.required' => 'Pick what the rule does: hide, show or go to.', + 'evaluate.required' => 'Pick when the rule runs: before or after the question.', ]; } } diff --git a/tests/Feature/FormBlockLogicTest.php b/tests/Feature/FormBlockLogicTest.php index 3d677592..350477f0 100644 --- a/tests/Feature/FormBlockLogicTest.php +++ b/tests/Feature/FormBlockLogicTest.php @@ -110,3 +110,23 @@ expect(FormBlockLogic::pluck('name', 'id')->all())->toBe([$logic->id => 'Old']); })->with(['xlf', '.0', 'e0']); + +test('a rule with empty fields gets errors in the editor\'s words', function () { + $block = FormBlock::factory()->create(); + + $this->actingAs($block->form->user) + ->json('post', route('api.logics.create', $block->id), ['conditions' => [[]]]) + ->assertStatus(422) + ->assertJsonValidationErrors([ + 'name' => 'Give the rule a name.', + 'conditions.0.source' => 'Pick a question for condition 1.', + 'conditions.0.value' => 'Enter a value for condition 1.', + 'conditions.0.operator' => 'Pick how to compare in condition 1.', + 'conditions.0.chainOperator' => 'Pick "and" or "or" for condition 1.', + 'action' => 'Pick what the rule does: hide, show or go to.', + 'evaluate' => 'Pick when the rule runs: before or after the question.', + ]); + + $this->json('post', route('api.logics.create', $block->id), []) + ->assertJsonValidationErrors(['conditions' => 'Add at least one condition.']); +}); From b7026e0511632fb4d65f3af3c8a1c2bf2a9ff095 Mon Sep 17 00:00:00 2001 From: Philipp Reinking Date: Tue, 13 Oct 2026 03:41:53 +0200 Subject: [PATCH 2/6] docs: name the question types that submit a list of answers Co-Authored-By: Claude Opus 5.5 --- app/Http/Controllers/Api/FormSubmitController.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/Api/FormSubmitController.php b/app/Http/Controllers/Api/FormSubmitController.php index 2122b04c..77eb1746 100644 --- a/app/Http/Controllers/Api/FormSubmitController.php +++ b/app/Http/Controllers/Api/FormSubmitController.php @@ -23,11 +23,11 @@ class FormSubmitController extends Controller For form blocks that have only one input field, you submit one payload-object with the keys "payload" and "actionId". The payload is the user input and the actionId is the ID of the form block interaction field. - For multiple choice questions, the payload is an array of payload-objects. + For single choice, checkbox and consent questions, the payload is an array of payload-objects. DESC, example: [ '7j' => ['payload' => 'Secret', 'actionId' => '59R'], '4x1' => ['payload' => 'mail@example.com', 'actionId' => 'pQ6'], - // For multiple choice questions, the payload is an array of the selected options + // For single choice and checkbox questions, the payload is an array of the selected choices '5yB' => [ ['payload' => 'Pineapple', 'actionId' => 'qYr'], ['payload' => 'Cherry', 'actionId' => 'vor'], From a781985a724ef8511765383ea868b485e0e266bf Mon Sep 17 00:00:00 2001 From: Philipp Reinking Date: Tue, 13 Oct 2026 03:42:12 +0200 Subject: [PATCH 3/6] docs: add changelog line for #278 Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7dc3c0b4..a8e0875b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ Lines marked **Upgrade step** need you to act after updating. - Archiving a form now also unpublishes it, so Restore brings it back unpublished. This also goes for forms you archived before this update. (#247) - The form list loads faster when a team has many forms. It now needs the same few database queries for one form or a hundred. Before, every form added 3 queries. (#256) - On screens narrower than 1280 px, a form's editor bar now sits in two rows under the app header: the form with Dashboard and Preview, then Editor, Settings, Integrations and Submissions as tabs. Nothing in it scrolls sideways any more, and the header keeps the logo and your team name. On every screen, keyboard focus in the editor bar and the account menu now shows a blue ring. (#257) +- A logic rule with a missing field now tells you in plain words what to fill in, like "Pick a question for condition 1." (#278) ### Fixed From 78114e7c3550a730dafd699970b37a4072ac3063 Mon Sep 17 00:00:00 2001 From: Philipp Reinking Date: Tue, 13 Oct 2026 09:14:23 +0200 Subject: [PATCH 4/6] docs: only checkbox and consent questions submit a list Co-Authored-By: Claude Opus 5.5 --- app/Http/Controllers/Api/FormSubmitController.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/Api/FormSubmitController.php b/app/Http/Controllers/Api/FormSubmitController.php index 77eb1746..8bde7002 100644 --- a/app/Http/Controllers/Api/FormSubmitController.php +++ b/app/Http/Controllers/Api/FormSubmitController.php @@ -23,11 +23,11 @@ class FormSubmitController extends Controller For form blocks that have only one input field, you submit one payload-object with the keys "payload" and "actionId". The payload is the user input and the actionId is the ID of the form block interaction field. - For single choice, checkbox and consent questions, the payload is an array of payload-objects. + For checkbox and consent questions, the payload is an array of payload-objects, one for each ticked choice or policy. A single choice question sends one payload-object for the picked choice. DESC, example: [ '7j' => ['payload' => 'Secret', 'actionId' => '59R'], '4x1' => ['payload' => 'mail@example.com', 'actionId' => 'pQ6'], - // For single choice and checkbox questions, the payload is an array of the selected choices + // For checkbox questions, the payload is an array of the ticked choices '5yB' => [ ['payload' => 'Pineapple', 'actionId' => 'qYr'], ['payload' => 'Cherry', 'actionId' => 'vor'], From 0aa6a12788b982635e4973e07799cfbb3085b532 Mon Sep 17 00:00:00 2001 From: Philipp Reinking Date: Tue, 13 Oct 2026 09:14:23 +0200 Subject: [PATCH 5/6] fix: ask for an answer, not a value, in a rule's condition Co-Authored-By: Claude Opus 5.5 --- app/Http/Requests/FormBlockLogicRequest.php | 2 +- tests/Feature/FormBlockLogicTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Http/Requests/FormBlockLogicRequest.php b/app/Http/Requests/FormBlockLogicRequest.php index 60cefb18..a6c5e505 100644 --- a/app/Http/Requests/FormBlockLogicRequest.php +++ b/app/Http/Requests/FormBlockLogicRequest.php @@ -68,7 +68,7 @@ public function messages() 'name.required' => 'Give the rule a name.', 'conditions.required' => 'Add at least one condition.', 'conditions.*.source.required' => 'Pick a question for condition :position.', - 'conditions.*.value.required' => 'Enter a value for condition :position.', + 'conditions.*.value.required' => 'Add an answer for condition :position.', 'conditions.*.operator.required' => 'Pick how to compare in condition :position.', 'conditions.*.chainOperator.required' => 'Pick "and" or "or" for condition :position.', 'action.required' => 'Pick what the rule does: hide, show or go to.', diff --git a/tests/Feature/FormBlockLogicTest.php b/tests/Feature/FormBlockLogicTest.php index 350477f0..e1c9901f 100644 --- a/tests/Feature/FormBlockLogicTest.php +++ b/tests/Feature/FormBlockLogicTest.php @@ -120,7 +120,7 @@ ->assertJsonValidationErrors([ 'name' => 'Give the rule a name.', 'conditions.0.source' => 'Pick a question for condition 1.', - 'conditions.0.value' => 'Enter a value for condition 1.', + 'conditions.0.value' => 'Add an answer for condition 1.', 'conditions.0.operator' => 'Pick how to compare in condition 1.', 'conditions.0.chainOperator' => 'Pick "and" or "or" for condition 1.', 'action' => 'Pick what the rule does: hide, show or go to.', From 8b02c1a409a39ce429ce70267da5619206830d4a Mon Sep 17 00:00:00 2001 From: Philipp Reinking Date: Tue, 13 Oct 2026 12:41:47 +0200 Subject: [PATCH 6/6] fix: ask for a question from this form in a rule's condition The check from #264 still said "The question of your #1 condition is not in this form." Now it reads like the other rule messages. Co-Authored-By: Claude Opus 5.5 --- app/Http/Requests/FormBlockLogicRequest.php | 2 +- tests/Feature/FormBlockLogicTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Http/Requests/FormBlockLogicRequest.php b/app/Http/Requests/FormBlockLogicRequest.php index bcd2179f..3f13a23c 100644 --- a/app/Http/Requests/FormBlockLogicRequest.php +++ b/app/Http/Requests/FormBlockLogicRequest.php @@ -45,7 +45,7 @@ public function rules(): array return [ 'name' => 'required|string|max:255', 'conditions' => 'required|array', - 'conditions.*.source' => ['required', 'string', $this->inThisForm('The question of your #:position condition is not in this form.')], + 'conditions.*.source' => ['required', 'string', $this->inThisForm('Pick a question from this form for condition :position.')], 'conditions.*.operator' => 'required|string|in:equals,equalsNot,contains,containsNot,isLowerThan,isGreaterThan', 'conditions.*.value' => 'required|string', 'conditions.*.chainOperator' => 'required|string|in:and,or', diff --git a/tests/Feature/FormBlockLogicTest.php b/tests/Feature/FormBlockLogicTest.php index 51eeebe7..606d0590 100644 --- a/tests/Feature/FormBlockLogicTest.php +++ b/tests/Feature/FormBlockLogicTest.php @@ -155,7 +155,7 @@ $this->json('post', $route, $payload) ->assertStatus(422) - ->assertJsonValidationErrors([$field => 'is not in this form']) + ->assertJsonValidationErrors([$field => $field === 'action_payload' ? 'The question to go to is not in this form.' : 'Pick a question from this form for condition 1.']) ->assertJsonMissingValidationErrors($field === 'action_payload' ? 'conditions.0.source' : 'action_payload'); }