Conversation
eshiota
self-requested a review
August 31, 2026 14:59
eshiota
requested changes
Aug 31, 2026
eshiota
left a comment
Collaborator
There was a problem hiding this comment.
Everything looks solid! I left two nits as comments, and a request to add additional tests 😄
Contributor
Author
thanks for the review, addressed feedback in 590d4f7 |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Reviewed by Cursor Bugbot for commit 4cff944. Configure here.
vermaxik
force-pushed
the
fix/per-row-validation-for-items-conditionals
branch
from
September 2, 2026 15:26
b27e680 to
4cff944
Compare
calculateFinalSchema pre-applies conditional rules by merging the matching
branch into the schema and deleting it. For array items it evaluated every
rule against an empty object, so the {}-matching branch was permanently
baked into the one shared items schema: rules with an else branch (or a
negated if) were wrong for every row, in both validation and field state.
Only constant conditionals (if: true / if: false) are pre-applied now --
their branch is row-independent, which is what the workaround originally
protected (schema-driven visibility inside items). Value-dependent rules
keep their then/else so validateSchema evaluates them per row against the
row's actual value.
Per-row FIELD mutations (visibility / required flags) remain unsupported
for group-array items: all rows share a single fields array.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
eshiota
force-pushed
the
fix/per-row-validation-for-items-conditionals
branch
from
September 10, 2026 09:46
4cff944 to
b1d4962
Compare
eshiota
approved these changes
Sep 10, 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
Conditionals (
if/then/elseinallOf) inside an array'sitemsschema areevaluated against an empty object instead of each row's value. Whichever branch
matches
{}gets permanently merged into the shareditemsschema and deleted,so rules with an
elsebranch (or a negatedif) validate wrongly for everyrow. This PR pre-applies only constant conditionals inside
itemsand leavesvalue-dependent ones intact, so
validateSchemaevaluates them per row.Json Schema
{ "title": "Per-row conditionals in nested arrays", "type": "object", "properties": { "discount_rules": { "type": "array", "title": "Discount rules", "x-jsf-presentation": { "inputType": "group-array", "addFieldText": "Add rule" }, "items": { "type": "object", "title": "Rule", "x-jsf-order": ["name", "mode", "percent", "tiering"], "required": ["name", "mode"], "x-jsf-presentation": { "inputType": "fieldset" }, "properties": { "name": { "type": "string", "title": "Name", "x-jsf-presentation": { "inputType": "text" } }, "mode": { "type": "string", "title": "Mode", "default": "flat", "oneOf": [ { "const": "flat", "title": "Flat" }, { "const": "tiered", "title": "Tiered" } ], "x-jsf-presentation": { "inputType": "radio" } }, "percent": { "type": ["string", "null"], "title": "Percent", "x-jsf-presentation": { "inputType": "text" } }, "tiering": { "type": ["object", "null"], "title": "Tiering", "x-jsf-order": ["period", "tiers"], "x-jsf-presentation": { "inputType": "fieldset" }, "properties": { "period": { "type": "string", "title": "Period", "oneOf": [ { "const": "monthly", "title": "Monthly" }, { "const": "yearly", "title": "Yearly" } ], "x-jsf-presentation": { "inputType": "select" } }, "tiers": { "type": "array", "title": "Tiers", "x-jsf-presentation": { "inputType": "group-array", "addFieldText": "Add tier" }, "items": { "type": "object", "title": "Tier", "x-jsf-order": ["up_to", "percent"], "required": ["percent"], "x-jsf-presentation": { "inputType": "fieldset" }, "properties": { "up_to": { "type": "integer", "title": "Up to", "x-jsf-presentation": { "inputType": "number" } }, "percent": { "type": "string", "title": "Percent", "x-jsf-presentation": { "inputType": "text" } } } } } } } }, "allOf": [ { "if": { "properties": { "mode": { "const": "tiered" } }, "required": ["mode"] }, "then": { "required": ["tiering"], "properties": { "tiering": { "type": "object", "required": ["period", "tiers"], "properties": { "tiers": { "minItems": 1, "x-jsf-errorMessage": { "minItems": "Add at least one tier, or switch the mode to flat." } } } }, "percent": { "maxLength": 0, "x-jsf-errorMessage": { "maxLength": "Not used for tiered rules. Clear it or switch the mode to flat." } } } }, "else": { "required": ["percent"], "properties": { "percent": { "type": "string" }, "tiering": { "properties": { "tiers": { "maxItems": 0, "x-jsf-errorMessage": { "maxItems": "Tiers only apply to tiered rules. Remove them or switch the mode." } } } } } } } ] } } } }Changes Made
calculateFinalSchemapre-applies conditional rules by merging the matchingbranch into the schema and deleting the branch. That is correct at the root,
where
valuesis the real form value, but foritemsthe existing workaroundin
applySchemaRulespassed a hardcoded{}:elsebranch had theelsebaked in for all rows (anifthat requires a field never matches
{}), producing wrong validation evenfor rows where the condition is true;
ifalways matches{}, baking thethenin for all rows;evaluation in
validateCondition(which is correct) never saw it.The change threads a
constantIfsOnlyflag throughapplySchemaRules. Insideitems, only conditionals whoseifis a boolean (if: true/if: false)are pre-applied — their branch is row-independent, which is what the original
workaround supported (schema-driven visibility inside items, covered by the
existing "with constant logic" tests). Value-dependent rules keep their
then/elseand are evaluated per item with the row's actual value.Known limitation, unchanged by this PR: per-row FIELD state (visibility,
required flags, titles) is still not representable, since all rows of a
group-array share a single fields array. This PR fixes validation only; the
describe.skip('with logic based on answers')for group-array field visibilitystays skipped.
Note
Medium Risk
Touches core schema mutation in
applySchemaRules, which affects validation and field derivation for nested arrays; behavior change is intentional but could surface edge cases in complex conditional schemas.Overview
Fixes wrong validation for array row schemas when
itemsuses value-dependentif/then/elserules. PreviouslyapplySchemaRulesran against{}for shareditemsschemas, permanently merging one branch and breakingelse/negatediffor every row.The change adds
constantIfsOnly(viashouldProcessRule) so onlyif: true/if: falseconditionals are pre-merged intoitems; value-dependent rules stay on the schema and are evaluated per row during validation. Constant conditionals on nested objects and array items still drive shared field metadata (e.g. visibility/required).New array tests cover
elsebranches, negatedif, nested constant+value-dependent rules, andif: trueon objects and items. Per-row field UI mutations for group-arrays remain out of scope.Reviewed by Cursor Bugbot for commit b1d4962. Bugbot is set up for automated code reviews on this repo. Configure here.