feat(forms): a maker can prefill, calculate and check a form while it is filled in - #1024
Merged
Merged
Conversation
…execution log
POST /api/rules/{slug}/evaluate accepts mode: preview. It keeps the
authentication, RBAC, rate limit and size guard, writes no execution log
entry and runs no side-effecting action. The rule set schema endpoint now
lists the decision table's inputs and outputs for a form to bind to.
… is filled in The field details area gets a default picker (a fixed value, the signed-in user, their name or e-mail, today, or a field of the record) and a binding to a rule set output with the answers it reads. A form page gets an eligibility check. formLiveValues.js resolves the defaults and debounces live preview evaluations for the renderer.
…cord is stored A listener on ObjectCreatingEvent and ObjectUpdatingEvent evaluates every calculated field again with the record's own values and hands the result back through setModifiedData, so a fee changed in the browser is replaced. A blocking eligibility check that fails refuses the save with its explanation; a rule set that cannot be evaluated refuses it too. Which forms write where is indexed from ApplicationVersion saves.
English and Dutch strings for the default picker, the calculated field and the eligibility check, and a section in form-logic-authoring.md.
phpmd flagged handle() for complexity and two else branches, phpcs two long type lines and an inline if; the type shapes are now psalm/phpstan aliases. Four JS hooks get their @SPEC tag.
phpstan: apply() takes the two OpenRegister save events, so handle() narrows the Event before resolving the object.
Contributor
Quality Report — ConductionNL/buildiq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| test-l10n | ✅ | ||||
| check-gitignore | ✅ | ||||
| check-nc-floor | ✅ | ||||
| format | ✅ | ||||
| check-l10n-js | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| composer | ✅ | ✅ 106/106 | |||
| npm | ✅ | ✅ 752/752 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| lockfile sync | ✅ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ⏭️ deferred: E2E runs locally and on the promotion path only. This pull request targets development, so the suite is asked once per promotion into beta and main rather than once per push per open pull request. Run it locally with npx playwright test, or from the Actions tab on a branch with no open pull request into development. |
||||
| Hydra gates | ✅ |
Quality workflow — 2026-10-01 23:32 UTC
Download the full PDF report from the workflow artifacts.
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.
Change
forms-live-values-and-checks, the buildiq half (not archived: the renderer half is nextcloud-vue's). A maker can now say what a form field is filled in with when it opens, have a field worked out by a rule set, and give a form an eligibility check that explains why someone does not qualify. The server works every calculated field out again before the record is stored, so a value changed in the browser never lands.What is built:
POST /api/rules/{slug}/evaluateacceptsmode: preview. Same authentication, RBAC, rate limit and size guard; no execution log entry, and no side-effecting action either, since it runs on every keystroke.@object.<field>). Stored as the field'sdefault.calculate: {ruleSet, output, inputs[]}.GET /api/rules/{slug}/schemanow also answers the decision table's inputs and outputs. A binding to a rule set that is not active is marked.eligibility: {ruleSet, passWhen: {output, equals}, explainWith, blockSubmit}.src/services/formLiveValues.js: token resolution, the preview client, and a debounced bridge that produces{values, unmet, submitBlocked}for the renderer.FormLiveValuesListeneron ObjectCreatingEvent and ObjectUpdatingEvent. Calculated values go back through OpenRegister'ssetModifiedData()(merged by MagicMapper before the write, openregister development); a failing blocking check, or a rule set that cannot be evaluated, stops the event with a 422 and the explanation. Design correction: the listener cannot read every app's manifest on every object write, so the forms that compute something are indexed by the register and schema they write to (FormLiveBindingIndex, app config), rebuilt on every ApplicationVersion save. Every write no live form targets returns after one app config read.docs/form-logic-authoring.md.Not built here, and why the rows stay
specified: T08, the renderer hooks in nextcloud-vue'sCnFormPage(resolve the default tokens, take the host's live values, show the unmet conditions). Drafted for Ruben, not filed: lanes file nothing on other repos. T07, the Playwright run on a live form, waits on those hooks. Until then a form shows these fields as plain inputs, and the server still recomputes and checks on save.Tests, each red before its code (logs kept in the lane):
RuleEngineServiceTest::testPreviewWritesNoExecutionLog,testPreviewDoesNotInvokeDispatcher;RulesControllerTest::testEvaluatePreviewModeReachesTheEngine,testEvaluateWithoutPreviewIsLogged.RulesControllerTest::testSchemaListsTheDecisionTableInputsAndOutputs.tests/components/FormLiveValuesEditors.spec.js,tests/components/page-editor/FormPageEditor.eligibility.spec.js.tests/services/formLiveValues.spec.js(fake timers: one evaluation per settled change, none for an answer the calculation does not read, submit blocked with the explanation, blocked when the check cannot run).tests/Unit/Listener/FormLiveValuesListenerTest.php: the scenario "a fee changed in the browser is corrected" (evaluated logged, not preview), a failing blocking check refused with its explanation, an unrunnable calculation refused, an unrelated object untouched, and the registration asserted fromApplication::register()(red without it). The test stubs of ObjectCreatingEvent and ObjectUpdatingEvent gainsetModifiedData/getModifiedData, as on openregister development.Inherited, not fixed here: the vendored hydra-gates in
composer check:strictfail gate-101 on the five buildiq schemas that carry reasoned no-demo skips since #853; CI's required gate run (hydra-gates@main) honours the reasons.Live check: in an app's page designer, a form page posting to
/apps/openregister/api/objects/{registerSlug}/permit, fieldfeeDetails, Calculated: rule setevent-fee, outcomefee, tickattendees; save the version. ThenPOST /apps/openregister/api/objects/<register>/permitwith{"attendees": 250, "fee": 0}: the storedfeeis the table's amount, and one rule execution log entry is written.POST /apps/buildiq/api/rules/event-fee/evaluatewith{"payload": {"attendees": 250}, "mode": "preview"}answers the same amount and writes no log entry.Verification (base development f05abe1, which the branch contains): composer check:strict: lint, phpcs, phpmd, psalm green, PHPUnit OK (1339 tests); phpstan's one finding (an un-narrowed event) is fixed in the last commit and phpstan, phpcs, phpmd, psalm and the listener tests were re-run on that file green; the vendored gates fail only gate-101 (inherited, above). CI's gate run (hydra-gates@main, full scope, --require-full-coverage) on the final head: all 84 applicable gates passed, exit 0. npm lint 0 (warnings only), stylelint 0, format 0, vitest 0 (1710 tests), test:l10n 0, check:l10n-js 0, check:schema-l10n 0, check:manifest 0, check:gitignore 0, check:nc-floor 0.
openspec validate forms-live-values-and-checks --strict: valid. No local production build; CI's Frontend Build answers that.🤖 Generated with Claude Code