forms+qml: and/or/not rule conditions, pre-decode Quantity bounds, explicit submit mode, array-field control - #78
Merged
Conversation
Yaraslaut
force-pushed
the
issue-cluster-k-forms-qml
branch
from
August 12, 2026 06:24
49a113e to
2416d40
Compare
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
Yaraslaut
force-pushed
the
issue-cluster-k-forms-qml
branch
2 times, most recently
from
August 12, 2026 13:46
fb8075d to
a8b1570
Compare
Closes #50: RuleKind gains And/Or/Not, with andOf()/orOf()/notOf() factories building a recursive condition tree (any leaf or another compound node as a child). Usable both nested inside a requiredWhen/visibleWhen/readonlyWhen `when` clause and directly as a top-level formRules entry. DynamicForm.qml's testCondition()/testRule() evaluate the new "and"/"or"/"not" kinds the same way the client already evaluates every other condition kind. Closes #51: Quantity gains an optional pre-decode wire validation seam. UnitTraits<E> may declare a static bounds(E) -> QuantityBounds customisation point (HasUnitBounds<E>); Quantity::withinDeclaredBounds() checks the engaged value against it. morph::forms::checkQuantityBounds<A>/ enforceQuantityBounds<A> walk an action's Quantity members and throw QuantityDecodeError (distinct from ValidationError) for the first out-of-bounds field. Both wire-decode dispatch runners (ActionDispatcher::registerAction, ActionExecuteRegistry::registerAction) call enforceQuantityBounds right after reconcileDeclaredPrecision, before recomputeAll/ActionValidator::ready -- so a decode-level physical/unit constraint violation is rejected before an action's own validate() ever runs. No-op for actions with no Quantity members, or units with no bounds(). docs/spec/forms/forms.md and docs/spec/util/quantity_type.md updated with the new vocabulary/customisation point. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Closes #52: DynamicForm.qml's revalidate() previously called controller.submitIfValid(...) automatically the instant every field/rule was satisfied -- safe for a read-only query action, unsafe for any side-effectful action (a mutation could fire on every keystroke that happens to leave the form momentarily valid). A schema may now set the top-level "x-submitMode": "explicit" key. When set: revalidate() still recomputes ready/previewLine live but never auto-fires; the renderer instead loads an explicit Submit Button (objectName "submitButton"), enabled only while ready, whose onClicked calls the new submit() function. The button is loaded via a Loader (active: explicitSubmitMode) so a default schema has no such control in its item tree at all, not merely a hidden one. Absent (or any other value), behavior is byte-identical to before. Adds src/qt/forms/tests/tst_DynamicFormSubmitMode.qml covering: default schemas keep auto-submitting and render no button; explicit-mode schemas suppress auto-fire even while ready; the button is disabled until ready and enabled once ready; clicking it calls submitIfValid with the current previewLine. docs/spec/forms/forms.md documents x-submitMode in the renderer contract key-vocabulary table and a new "Explicit submit mode" section; the QML file's header comment lists the new key alongside x-order/required. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Closes #66: DynamicForm.qml's type dispatch had no "array" branch, so an array-typed field (glaze's std::vector<T> schema shape, {"type": "array", "items": {...}}) fell through to the plain text field, and fieldJsonLiteral's generic fallback (JSON.stringify(text)) wrapped the typed text as a JSON *string*, not an array -- a body the server's schema validation always rejects, with no indication in the UI why. Adds an "array" branch: field descriptors gain isArray (types.indexOf ("array") !== -1); a dedicated comma-separated-with-validation TextField (reusing the plain field's "field_"+name objectName -- the two are mutually exclusive per field) replaces the plain-text fallback for it; and fieldJsonLiteral gains a new arrayJsonLiteral() encoding arm: splits the typed text on comma, trims each entry, drops empties, and JSON.stringifies the result as a genuine array (e.g. "red, green, blue" -> ["red","green","blue"]). A fully-blank field is still "unengaged" (null, per the existing blank-text convention); once any non-whitespace text is present -- including a comma-only entry like " , , " -- it encodes to a real (possibly empty) array, satisfying a `required` array field by engagement, same as every other field type. Adds src/qt/forms/tests/tst_DynamicFormArrayField.qml covering: the dedicated control renders; an optional blank array field doesn't block submit and is omitted from the preview; comma-separated entries encode as a genuine JSON array; whitespace/empty entries are dropped; a single entry still encodes as a one-element array; a required array field gates submission until engaged; and the comma-only-entry edge case. docs/spec/forms/forms.md documents the new "Array fields" section and lists the array control in the shipped-renderer bullet and the file's own header comment. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…arry it) Clang's own -Wdocumentation (distinct from the Doxygen doc-completeness gate) rejects @tparam on a comment attached to a concept declaration -- it only recognizes function/class template declarations as valid @tparam targets, unlike Doxygen itself. The sibling concept HasUnitRelations right above already avoids @tparam for the same reason; fold E's description into prose to match. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…pecs The Header <-> spec sync CI gate correctly flagged that this branch touches include/morph/core/bridge.hpp and include/morph/core/registry.hpp (wiring in the new pre-decode bounds-check step) without a matching docs/spec/core/** update. Both dispatch-runner sequences in bridge.md and registry.md described only three/two normalisation steps; add the fourth (bounds enforcement, between precision reconciliation and computed-field recompute) to both, matching the existing prose style and cross-referencing forms.md's "Pre-decode wire validation" section. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…alizers
Two strict-compilation warnings, real on Windows/clang-cl (clangcl-debug,
clangcl-release presets) but silent under plain MSVC cl.exe, so they went
undetected in earlier local verification:
- UnitTraits<QDVUnit>::bounds()'s switch had a `default:` covering `mass`
without an explicit `case QDVUnit::mass:` label -- -Wswitch-enum wants
every enumerator named even when a default exists. Matches the sibling
meta() function's already-correct pattern.
- QDVReading{...} designated-initializer literals across the file omitted
trailing fields (sampleMass, note) whenever a later field wasn't named
explicitly. List every field at each call site.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… history Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The if constexpr's else branch (a hand-written-codec action type with no reflectable shape) had no test exercising it; add one using a genuine non-aggregate type, mirroring reconcileDeclaredPrecision's identical fallback. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…mo_module apply_bigobj(lab_forms_demo_module) already covers this target; the manual target_compile_options block duplicated it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Yaraslaut
force-pushed
the
issue-cluster-k-forms-qml
branch
from
August 12, 2026 19:12
b70002c to
3398a20
Compare
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
Closes #50, closes #51, closes #52, closes #66.
RuleKindgainsAnd/Or/Not;andOf()/orOf()/notOf()build a recursive condition tree, usable nested or as a top-level rule. Existing single-node kinds remain valid as leaves.UnitTraits<E>gains an optionalbounds(E)customization point;Quantity::withinDeclaredBounds()checks against it.checkQuantityBounds/enforceQuantityBoundsrun at wire-decode time, throwingQuantityDecodeError(distinct fromValidationError) before an action's ownvalidate()runs."x-submitMode": "explicit"disablesDynamicForm.qml's auto-fire-on-validity and renders an explicit submitButtoninstead, gated onready.DynamicForm.qmlgains an"array"branch: a comma-separated-with-validation control replaces the fallback for array-typed fields, emitting a genuine JSON array instead of a stringified-text fallback.Review
Ran
contour-workflows:cpp-reviewagainst the C++ portions of the diff (real skill invocation) — no CONFIRMED/PLAUSIBLE findings, one low-severity doc-wording nit left as-is (an unenforced "at least two conditions" claim onandOf/orOfthat compiles fine with one). QML portions reviewed manually per the skill's C++-only scope.Test plan
C++: 8522 assertions / 867 cases, all passing. QML: 115/115 passing.
🤖 Generated with Claude Code