Skip to content

Evaluate logic rules in frontend#954

Merged
sergei-maertens merged 22 commits intomainfrom
feature/of-5962-evaluate-logic-rules-in-frontend
Apr 1, 2026
Merged

Evaluate logic rules in frontend#954
sergei-maertens merged 22 commits intomainfrom
feature/of-5962-evaluate-logic-rules-in-frontend

Conversation

@sergei-maertens
Copy link
Copy Markdown
Member

@sergei-maertens sergei-maertens commented Mar 17, 2026

(Partly) closes open-formulieren/open-forms#5962

  • Added type definitions for the backend extensions
  • Add simple stories for interaction with server-side logic rules evaluated in frontend
    • Set field/component value
    • Component properties (validate required, read only)
    • Make component hidden/visible & handle clearOnHide
    • Disable next action
    • Step not applicable action
    • Step applicable action
  • Ensure editgrids work

@codecov
Copy link
Copy Markdown

codecov bot commented Mar 17, 2026

Bundle Report

Changes will increase total bundle size by 100.97kB (0.97%) ⬆️. This is within the configured threshold ✅

Detailed changes
Bundle name Size Change
@open-formulieren/sdk-OpenForms-umd 5.26MB 51.92kB (1.0%) ⬆️
@open-formulieren/sdk-esm 5.23MB 49.05kB (0.95%) ⬆️

Affected Assets, Files, and Routes:

view changes for bundle: @open-formulieren/sdk-esm

Assets Changed:

Asset Name Size Change Total Size Change (%)
assets/formio-*.js -79.25kB 1.95MB -3.9%
assets/sdk-*.js 72.88kB 1.26MB 6.13% ⚠️
assets/FormStepNewRenderer-*.js 55.35kB 61.95kB 838.84% ⚠️
assets/nl-*.js -2 bytes 37.91kB -0.01%
assets/index-*.js -26 bytes 23.71kB -0.11%
assets/index-*.js -78 bytes 13.52kB -0.57%
assets/normalizeInterval-*.js (New) 178 bytes 178 bytes 100.0% 🚀

Files in assets/sdk-*.js:

  • ./src/data/submissions.ts → Total Size: 2.9kB

Files in assets/FormStepNewRenderer-*.js:

  • ./src/data/submission-steps.ts → Total Size: 891 bytes

  • ./src/components/FormStep/logic.ts → Total Size: 4.82kB

  • ./src/components/FormStep/FormStepNewRenderer.tsx → Total Size: 6.65kB

  • ./src/logic/actions/variable.ts → Total Size: 807 bytes

  • ./src/components/FormStep/hooks.ts → Total Size: 5.14kB

  • ./src/logic/actions/step-not-applicable.ts → Total Size: 351 bytes

  • ./src/logic/actions/disable-next.ts → Total Size: 381 bytes

  • ./src/logic/actions/step-applicable.ts → Total Size: 340 bytes

  • ./src/logic/actions/property.ts → Total Size: 3.05kB

Files in assets/nl-*.js:

  • ./src/i18n/compiled/nl.json → Total Size: 41.8kB

Files in assets/index-*.js:

  • ./src/components/appointments/steps/ContactDetailsStep.tsx → Total Size: 3.85kB

Files in assets/index-*.js:

  • ./src/components/FormStep/index.jsx → Total Size: 16.0kB
view changes for bundle: @open-formulieren/sdk-OpenForms-umd

Assets Changed:

Asset Name Size Change Total Size Change (%)
open-*.js 51.92kB 3.9MB 1.35%

Files in open-*.js:

  • ./src/logic/actions/step-not-applicable.ts → Total Size: 351 bytes

  • ./src/logic/actions/property.ts → Total Size: 3.06kB

  • ./src/components/FormStep/hooks.ts → Total Size: 5.14kB

  • ./src/logic/actions/step-applicable.ts → Total Size: 340 bytes

  • ./src/components/FormStep/logic.ts → Total Size: 4.83kB

  • ./src/data/submission-steps.ts → Total Size: 891 bytes

  • ./src/i18n/compiled/nl.json → Total Size: 41.8kB

  • ./src/data/submissions.ts → Total Size: 2.9kB

  • ./src/logic/actions/variable.ts → Total Size: 807 bytes

  • ./src/components/FormStep/FormStepNewRenderer.tsx → Total Size: 6.66kB

  • ./src/components/appointments/steps/ContactDetailsStep.tsx → Total Size: 3.86kB

  • ./src/logic/actions/disable-next.ts → Total Size: 381 bytes

  • ./src/components/FormStep/index.jsx → Total Size: 16.34kB

@codecov
Copy link
Copy Markdown

codecov bot commented Mar 17, 2026

Codecov Report

❌ Patch coverage is 94.17989% with 11 lines in your changes missing coverage. Please review.
✅ Project coverage is 80.86%. Comparing base (c0267eb) to head (f129c00).
⚠️ Report is 23 commits behind head on main.

Files with missing lines Patch % Lines
src/components/FormStep/logic.ts 85.71% 7 Missing and 2 partials ⚠️
src/components/FormStep/FormStepNewRenderer.tsx 92.85% 1 Missing ⚠️
src/logic/actions/property.ts 95.45% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##             main     #954       +/-   ##
===========================================
+ Coverage   61.65%   80.86%   +19.21%     
===========================================
  Files         238      244        +6     
  Lines        4825     4945      +120     
  Branches      762      788       +26     
===========================================
+ Hits         2975     3999     +1024     
+ Misses       1623      800      -823     
+ Partials      227      146       -81     
Flag Coverage Δ
storybook 70.57% <85.71%> (?)
vitest 61.90% <71.42%> (+0.24%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@sergei-maertens sergei-maertens force-pushed the chore/of-6037-shared-json-logic-testset branch from 80691fc to e641268 Compare March 17, 2026 14:11
@sergei-maertens sergei-maertens force-pushed the feature/of-5962-evaluate-logic-rules-in-frontend branch from d1761ca to daf7e75 Compare March 17, 2026 14:13
@sergei-maertens sergei-maertens force-pushed the chore/of-6037-shared-json-logic-testset branch from e641268 to f97d61b Compare March 17, 2026 14:35
@sergei-maertens sergei-maertens force-pushed the feature/of-5962-evaluate-logic-rules-in-frontend branch 3 times, most recently from eb12d01 to fedc8e3 Compare March 17, 2026 16:34
@sergei-maertens sergei-maertens force-pushed the chore/of-6037-shared-json-logic-testset branch from f97d61b to bff7444 Compare March 17, 2026 17:56
Base automatically changed from chore/of-6037-shared-json-logic-testset to main March 18, 2026 09:18
@sergei-maertens sergei-maertens force-pushed the feature/of-5962-evaluate-logic-rules-in-frontend branch 7 times, most recently from 3a50e18 to a6620c5 Compare March 18, 2026 11:35
@sergei-maertens sergei-maertens changed the title Feature/of 5962 evaluate logic rules in frontend Evaluate logic rules in frontend Mar 18, 2026
@sergei-maertens sergei-maertens force-pushed the feature/of-5962-evaluate-logic-rules-in-frontend branch 2 times, most recently from daa7de3 to ba6f363 Compare March 19, 2026 15:53
Comment on lines +184 to +185
// FIXME: these deep structures don't type-narrow, so we must use type guards for
// now. It requires proper backend re-design as well.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requires (big) backend changes

@sergei-maertens sergei-maertens force-pushed the feature/of-5962-evaluate-logic-rules-in-frontend branch 3 times, most recently from 6877ada to f715861 Compare March 20, 2026 14:25
@sergei-maertens sergei-maertens force-pushed the feature/of-5962-evaluate-logic-rules-in-frontend branch 2 times, most recently from 9a19e26 to f2ca967 Compare March 20, 2026 17:11
Move json-logic related code into a subpackage, so that we have a sibling
directory/package for the Open Forms logic rules that make use of it,
without creating confusion what belongs where.
The backend PR with the schema/API response changes is merged, so the
compatibility layer can be removed. Additionally, the properties
`isApplicable` and `completed` at the submission step level are
not used - the SDK looks at `Submission.steps` instead.

In the future, these can be removed from the backend, see
open-formulieren/open-forms#6107
…tion

Update how the FormStep component handles the logic rules and
dispatches input data changes. Rather than always scheduling a backend
logic check, the step-specific configuration and form feature flags
are now introspected to test if we can run frontend evaluation.

When opting into frontend evaluation, we no longer 'schedule' a logic
check, but rather immediately evaluate it, assuming that the execution
speed of the logic is fast enough to not need debouncing/scheduling.

Similarly to the old situation, this results in an updated components
configuration and possible data diff to be passed to the renderer,
which takes care of the updated form configuration and calculation
results. The step applicability and whether the step can be submitted
or not are also updated and reflected in the React state.

TODO:

* handle clearOnHide when a component becomes hidden, as this must be
  reflected in the `updatedData` immediately for the next logic
  trigger evaluation
* check that the step completion state is properly tracked when
  actually submitting the step
* cleanup TODOs in the code
Vaguely related, this also updates @utrecht/calendar-react to match
the date-fns transitive dependency. Now only @utrecht/component-library-react
pulls in the outdated date-fns, but it doesn't actually use it, so it
*should* be excluded from our bundle.
… desktop mode

The mobile mode has a collapsed progress indicator, which breaks the
assertions on the applicable/not-applicable toggles and fails the
tests in Chromatic.

For the actual logic evaluation, the mobile/desktop viewports are not
relevant, as long as the implementation executes the right state
updates.

According to the chromatic docs, this patch should ensure the story
only gets executed in the one specified mode.
Pointed out via PR feedback, the backend logic rule evaluation (in the
backend) actually ensures that every variable exists in the variables
state, which is passed along to the next logic action and rule trigger
evaluation. In the case of a hidden component with clear on hide,
it will actually ensure that the original input data from the start
of the logic evaluation is set, and if that is absent, it will use
the appropriate empty value for the component type.

Intuitively, we'd use clear-on-hide to remove it so that it immediately
affects the next tick in the logic evaluation, but that would introduce
different behaviour between backend and frontend.
…mponentEmptyValue behaviour

Validated against the backend behaviour - this makes all the input
component types explicit so that regressions can be prevented.
…ar on hide during backend logic evaluation

This knowingly introduces the backend bug where the component value
is not properly cleared, but instead set or reset to the input data or
empty value appropriate for that component. The frontend evaluation
yielding the exact same result as the backend is more important than
fixing the bug in one place and not the other. Also, fixing the bug
can possibly break existing forms where people (unknowlingly) rely
on the buggy behaviour.
…y in property action

The backend does not perform a similar check - instead it calls the process visibility
for itself (and child components) and passes the `parent_hidden` based on its own new
state (so the outcome of the action). However, because the backend code does not
actually clear values, this doesn't matter. I've verified that the output of that logic
check has:

* a `get_data(...)` state that includes the empty values for cleared components,
  together with the input data of course
* a `submission_step.data` delta that is empty, i.e. -> change nothing in the input

The resulting updated state/render in the frontend then ensures this converges into a
stable UI state without infinite render loops.

By matching that "empty value / input data value" restoring behaviour from the backend,
this `hasHiddenParent` part has become irrelevant, because it was targeting the "remove
the key from the data" flow. I've confirmed that the frontend implementation now also
returns an empty delta for the submission step, matching the backend, and there are
tests that assert on the in-between-logic-actions-or-rules data state that also match
the backend.
…heck

Upon navigating to a step, we must make a check logic call to get the
up-to-date metadata of the steps in the submission. See
1d8f783 for details.
…entation

Validated by implementing the same tests in the backend.
…d to prevent re-introducing them

Yep, this turns out to be necessary...
…atch backend

What a nightmare this is...

The formio-renderer needs some changes to be able to emulate the
behaviour of the backend so that we arrive at the same result. It's
either passing this emulate flag, or re-implement the processVisibility
entirely because there are subtle differences in behaviour. Roughly set,
clearing the value by removing the key from the data is not an option,
because the frontend falls back to either the default value or the
component empty value.

We also cannot apply the visibility processing only if there's a
change in visibility, because the backend logic replays everything
from the beginning and requires the intermediate value changes/
resets to the empty value.
@sergei-maertens sergei-maertens force-pushed the feature/of-5962-evaluate-logic-rules-in-frontend branch from 1bd0583 to b96e35a Compare March 30, 2026 15:00
@sergei-maertens sergei-maertens marked this pull request as ready for review March 30, 2026 15:00
Copy link
Copy Markdown
Contributor

@viktorvanwijk viktorvanwijk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm so sorry :(

@sergei-maertens sergei-maertens force-pushed the feature/of-5962-evaluate-logic-rules-in-frontend branch 2 times, most recently from d9e85b2 to f628a5c Compare March 31, 2026 17:59
@sergei-maertens
Copy link
Copy Markdown
Member Author

@viktorvanwijk I'm satisfied with my latest patches and consider them ready for review again!

Copy link
Copy Markdown
Contributor

@viktorvanwijk viktorvanwijk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Time for testing :)

Commit 5953d98 contains more than just changes for the type of jsonLogicTrigger, though. Could you extract those and combine with the last commit that fixes the bug 😇

@sergei-maertens
Copy link
Copy Markdown
Member Author

Time for testing :)

Commit 5953d98 contains more than just changes for the type of jsonLogicTrigger, though. Could you extract those and combine with the last commit that fixes the bug 😇

Ah crap, rebase gone wrong. Will fix yeah!

While an expression is typically an object, feeding any JSON
into json-logic is valid and yields expected results.
…untriggered rule

The process visibility must be called even when the logic rule is not
triggered. The patch for that broke another thing, because the value
action was not updating the value references for values to 'restore',
so it was effectively forgetting the value action type side effect.

Patching that then broke data diff logic, because the logicState
initialValues is being updated, so the dataUpdates value would match
the entry in initialValues, and that would in turn cause it to be unset
again from the dataUpdates, causing the renderer to miss data updates.

The data updates are now no longer done in-flight, but at the end of
the logic evaluation process - we actually don't have a use for them
while evaluating logic/actions so this does clean up a bit too.
@sergei-maertens sergei-maertens force-pushed the feature/of-5962-evaluate-logic-rules-in-frontend branch from f628a5c to f129c00 Compare April 1, 2026 10:17
@sergei-maertens sergei-maertens merged commit 620921f into main Apr 1, 2026
19 checks passed
@sergei-maertens sergei-maertens deleted the feature/of-5962-evaluate-logic-rules-in-frontend branch April 1, 2026 10:24
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.

Moving logic to frontend

2 participants