You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
kanban: render the login form through morph::forms (#357)
* kanban: render the login form through morph::forms
`examples/LADDER.md` designates kanban the ladder's single polished
showcase, and its GUI used none of `morph::forms`: seven QML files, 26
hand-written input controls, and zero schema-driven forms. That is what
`examples/IMPLEMENTATION.md` rule 2 forbids by default, and the rung README
recorded the gap without supplying either justification rule 2 allows.
Triage settled which of the two applies by running the experiment rather
than arguing it: `morph::forms::schemaJson<A>()` compiled against this rung's
real, unmodified DTOs produces a usable schema for `Login`, `CreateColumn`,
`CreateSwimlane`, `CreateTask`, `AddComment` and `MoveTaskPosition` alike.
The rule-3 strong ids are not a blocker -- `TaskId` emits as
`{"type":["integer","null"],...}` under `$defs`, and `DynamicForm` resolves
`$ref` into `$defs` -- and `MoveTaskPosition::optionalFields` is honoured. So
justification (a), "the generated UI cannot express the interaction", is not
available for any of the form-shaped inputs here.
`LoginView.qml` is converted first: it is the smallest complete instance of
the whole path -- schema document, controller contract, shipped renderer,
typed reply -- and it establishes the pattern the remaining screens follow.
It now has no hand-written field and no hand-written submit button; if
`Login` grows a second member, that file does not change.
`Login` gains `explicitSubmit = true` so the renderer draws its own Submit
button. Without it `DynamicForm` submits as soon as every required field is
`ready`, which for a one-field login means firing mid-typing on the first
username prefix that happens to be a valid principal.
`ProjectAdminBridge` grows the controller contract the renderer calls:
`schemasJson` and `submitIfValid`, forwarding to a presenter `submitForm`
that dispatches through the type-erased `executeJson` path. `login` is
demoted from `Q_INVOKABLE` to a plain method -- nothing in `gui/qml/` calls
it any more, and dead QML surface is exactly what
`test_kanban_qml_surface.cpp` exists to catch. `submitIfValid` needs an
audit exemption for the opposite reason: its caller is the shipped
`DynamicForm.qml`, outside this rung's tree, which the audit does not scan.
`replyReceived` introduces a seam this rung did not have -- it broadcasts a
result JSON to every bound QML handler, and `Login`'s result carries the
token. `submitForm` redacts the token from its copy, and a new test asserts
that directly; the pre-existing signal-parameter sweep only sees `QString`
and could not have caught a leak through it. That test's comment claimed
this rung had no such seam at all, which is no longer true, so it is
rewritten rather than left to mislead.
The README's "not exercised" paragraph becomes a per-element rule-2 ledger
naming what is still hand-built, which action is behind it, and why each one
is unjustified -- with the drag-and-drop board the single plausible (a).
Verified on a `MORPH_BUILD_LADDER=ON -DMORPH_BUILD_FORMS_QML=ON` configure:
140 kanban tests pass, including the offscreen QML smoke test that loads
Main.qml and every file reachable from it. That test was mutation-checked by
pointing the import at a non-existent module, which fails it -- so its pass
is real evidence the renderer resolves, not a vacuous one.
Part of #344; the remaining screens are still to convert.
Signed-off-by: Yaraslau Tamashevich <yaraslau.tamashevich@gmail.com>
* kanban: clang-format the forms-conversion changes
The whole-tree clang-format gate flagged the presenter, the QML bridge and
their test. No behaviour change; all 140 kanban cases still pass.
Signed-off-by: Yaraslau Tamashevich <yaraslau.tamashevich@gmail.com>
---------
Signed-off-by: Yaraslau Tamashevich <yaraslau.tamashevich@gmail.com>
widgets by default and requires a written justification here for each one; no
138
-
such justification has been written, and the GUI design spec the QML comments
139
-
point at (`docs/superpowers/specs/2026-08-17-kanban-gui-design.md` §4)
140
-
settles the bridge/property-bag architecture without addressing forms at all.
141
-
Recorded as
142
-
[#344](https://github.com/LASTRADA-Software/morph/issues/344) (the
143
-
flagship-GUI forms-bypass finding) rather than justified after the fact here:
144
-
deciding *which* of rule 2's two justifications applies is a design call this
145
-
rung has not made.
127
+
**`morph::forms`: the login form only.**`gui/qml/LoginView.qml` renders
128
+
`Login` through `morph::forms::schemaJson<Login>()` and the shipped
129
+
`MorphForms``DynamicForm`, submitting via the renderer's own explicit Submit
130
+
button — the same shape `bookmarks`, `polls`, `lims` and `pastebin` ship. It
131
+
has no hand-written field and no hand-written submit button; if `Login` grows
132
+
a second member, that file does not change.
133
+
134
+
Every *other* input in this rung is still hand-built Qt Quick, which
135
+
[`IMPLEMENTATION.md`](../IMPLEMENTATION.md)'s rule 2 forbids by default and
136
+
which this README owes a per-element justification for. It does not have one,
137
+
and the honest position is that most of these have no justification available:
138
+
139
+
| Hand-built input | Action behind it | Rule 2 status |
140
+
|---|---|---|
141
+
|`gui/qml/RulesView.qml:89`, `:97`, `:103`| rule definition |**Unjustified.** Not yet attempted through the renderer. |
142
+
|`gui/qml/MembersView.qml:56`, `:81`, `:87`|`SetMemberRole` / `RemoveMember`|**Unjustified.** A principal string plus a role `enum class` is squarely in the forms palette. |
143
+
|`gui/qml/ProjectListView.qml:125`|`CreateProject`|**Unjustified.** One `std::string name`. |
144
+
|`gui/qml/BoardView.qml` column/task/comment entry |`CreateColumn`, `CreateSwimlane`, `CreateTask`, `AddComment`|**Unjustified.**`schemaJson<A>()` renders all four today — verified, see below. |
145
+
|`gui/qml/BoardView.qml` drag-and-drop board |`MoveTaskPosition`|**Rule 2(a), plausibly.** A drag gesture is not a form somebody fills in; a schema-rendered version of it would be beside the point. Still owes a written finding if kept. |
146
+
147
+
The claim in that last column is measured, not assumed:
148
+
`morph::forms::schemaJson<A>()` was compiled against this rung's real,
149
+
unmodified DTOs and produces a usable schema for `Login`, `CreateColumn`,
150
+
`CreateSwimlane`, `CreateTask`, `AddComment` and `MoveTaskPosition` alike.
151
+
The rule-3 strong ids are not a blocker — `TaskId` emits as
152
+
`{"type":["integer","null"],…}` under `$defs`, and `DynamicForm` resolves
153
+
`$ref` into `$defs` — and `MoveTaskPosition::optionalFields` is honoured.
154
+
So rule 2's justification (a), "the generated UI *cannot* express the
155
+
interaction", is **not available** for any of the form-shaped rows above.
156
+
157
+
That leaves converting them, which is tracked as
158
+
[morph#344](https://github.com/LASTRADA-Software/morph/issues/344). Login went
159
+
first because it is the smallest complete instance of the whole path — schema
160
+
document, controller contract, renderer, typed reply — and it establishes the
161
+
pattern the rest follow. The rung's GUI design spec
162
+
(`docs/superpowers/specs/2026-08-17-kanban-gui-design.md` §4) settles the
163
+
two-bridge/property-bag architecture and never mentions forms, schemas or
164
+
`DynamicForm`, so it was never the justification the QML comments treated it
0 commit comments