Skip to content

forms+qml: and/or/not rule conditions, pre-decode Quantity bounds, explicit submit mode, array-field control - #78

Merged
Yaraslaut merged 9 commits into
masterfrom
issue-cluster-k-forms-qml
Aug 12, 2026
Merged

forms+qml: and/or/not rule conditions, pre-decode Quantity bounds, explicit submit mode, array-field control#78
Yaraslaut merged 9 commits into
masterfrom
issue-cluster-k-forms-qml

Conversation

@Yaraslaut

Copy link
Copy Markdown
Member

Summary

Closes #50, closes #51, closes #52, closes #66.

Review

Ran contour-workflows:cpp-review against 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 on andOf/orOf that 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

@Yaraslaut
Yaraslaut force-pushed the issue-cluster-k-forms-qml branch from 49a113e to 2416d40 Compare August 12, 2026 06:24
@codecov

codecov Bot commented Aug 12, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.75362% with 5 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
include/morph/forms/forms.hpp 91.37% 5 Missing ⚠️

📢 Thoughts on this report? Let us know!

@Yaraslaut
Yaraslaut force-pushed the issue-cluster-k-forms-qml branch 2 times, most recently from fb8075d to a8b1570 Compare August 12, 2026 13:46
Yaraslau Tamashevich and others added 9 commits August 12, 2026 22:02
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
Yaraslaut force-pushed the issue-cluster-k-forms-qml branch from b70002c to 3398a20 Compare August 12, 2026 19:12
@Yaraslaut
Yaraslaut merged commit 332f82c into master Aug 12, 2026
53 of 60 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant