Skip to content

qt: promote FormsControllerCore into a generic, Sharing-aware model bridge core - #828

Merged
Yaraslaut merged 1 commit into
masterfrom
feature/793-generic-model-bridge-core
Sep 26, 2026
Merged

Yaraslaut merged 1 commit into
masterfrom
feature/793-generic-model-bridge-core

Conversation

@Yaraslaut

Copy link
Copy Markdown
Member

Summary

  • Extracts the model-agnostic core of FormsControllerCore<Model> into a new morph::qt::bridge::GenericModelBridgeCore<Model, Sharing>, promoting the handler's sharing policy from a hardcoded NoSharing to an explicit template parameter, and naming its one dispatch operation execute after the call it forwards to (BridgeHandler::executeJson).
  • FormsControllerCore<Model, Sharing> becomes a thin facade composing the new core (not inheriting — neither type declares a virtual destructor), adding only the schemasJson document DynamicForm.qml needs. DynamicForm.qml and every existing FormsController wrapper are source-compatible and unaffected.
  • Retrofits examples/pastebin's FormsBridge to compose FormsControllerCore<PasteModel> directly, deleting the now-redundant PasteFormsController (a near-byte-for-byte duplicate whose own doc comment already flagged the gap as closed-but-unadopted). PasteBridge (the read/query half) is untouched.
  • Registers the new header in morph_qt_forms's FILE_SET HEADERS so it installs with the qt_forms component.

Towards morph#793 (does not close it — the issue's own close condition is a new rung written in materially fewer lines, which this does not attempt). Filed morph#827 as a separate, adjacent finding surfaced while retrofitting pastebin: examples/bookmarks's BookmarkFormsController hand-rolls multi-model action routing that no core template currently supports — out of scope here.

Test plan

  • ladder_pastebin_gui_lib and ladder_pastebin_tests build clean
  • ladder_pastebin_tests: all 764 assertions / 58 test cases pass
  • ladder_bookmarks_gui_lib builds clean (doc-only change there)
  • Full ctest suite (macos-ladder, all 7 rungs): 2759/2759 tests pass
  • Docs build (-DMORPH_BUILD_DOCUMENTATION=ON, WARN_AS_ERROR=FAIL_ON_WARNINGS): clean, no warnings
  • Empirically verified the new public header installs correctly via cmake --install against a -DMORPH_BUILD_QT=ON -DMORPH_BUILD_FORMS_QML=ON configuration (this was broken before the CMakeLists.txt fix — caught by review)

Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com
🤖 Generated with Claude Code

@codecov

codecov Bot commented Sep 25, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

…ridge core

Every example rung's *_qml_bridge pair hand-rolls the same schema-driven
dispatch wiring (morph#793). The shipped FormsControllerCore<Model> already
solved this generically, but examples/pastebin predates its composing
constructor and still carried a near-byte-for-byte duplicate,
PasteFormsController, whose own doc comment already flagged the gap as
closed but unadopted.

Extract the model-agnostic core into morph::qt::bridge::GenericModelBridgeCore
<Model, Sharing>, promoting the sharing policy (previously hardcoded to
NoSharing) to an explicit template parameter and naming its one dispatch
operation `execute` after the call it forwards to
(BridgeHandler::executeJson), rather than a form-specific verb. The core
deliberately does not carry a schemasJson document itself -- that is a
forms-rendering concept nothing in execute() reads.

FormsControllerCore<Model, Sharing> becomes a thin facade over the core,
composing it (not inheriting -- neither type declares a virtual destructor)
and forwarding Sharing unchanged, adding only the schemasJson document
DynamicForm.qml needs and naming the dispatch operation submitIfValid/
fetchOptions. DynamicForm.qml and every existing FormsController wrapper are
source-compatible and unaffected.

Retrofit examples/pastebin's FormsBridge to compose FormsControllerCore
<PasteModel> directly, deleting PasteFormsController. PasteBridge (the
read/query half) is untouched -- its DTO-to-QVariantMap field curation and
display formatting is genuinely per-model, not part of this seam.

Register the new header in morph_qt_forms's FILE_SET HEADERS so it installs
with the qt_forms component alongside forms_controller_core.hpp, which
includes it.

clang-tidy-diff: pass -Wno-pragma-once-outside-header, same fix already
landed on build/core-cpp (45daa1c, unmerged): a brand-new header is pinned
and analysed as its own main file, so its #pragma once is reported against
it. Also tighten a pre-existing pastebin lambda's by-value std::string
parameter (only read, never moved) to const std::string& -- flagged because
this diff's re-indentation put that line in the analysed range for the first
time.

Towards morph#793 (does not close it: the issue's own close condition is a
new rung written in materially fewer lines, which this does not attempt).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Yaraslau Tamashevich <yaraslau.tamashevich@gmail.com>
@Yaraslaut
Yaraslaut force-pushed the feature/793-generic-model-bridge-core branch from febf85c to 9e8b109 Compare September 26, 2026 07:11
@Yaraslaut
Yaraslaut merged commit aa57761 into master Sep 26, 2026
40 checks passed
@Yaraslaut
Yaraslaut deleted the feature/793-generic-model-bridge-core branch September 26, 2026 09:15
Yaraslaut added a commit that referenced this pull request Sep 26, 2026
…ollerCore

BookmarkFormsController hand-rolled exactly what morph::qt::forms::
MultiModelFormsControllerCore (previous commit) now provides generically:
one BridgeHandler per model (AuthModel, BookmarkModel, TagModel) and an
actionType -> handler routing table, plus a try/catch converting the
routing throw into the same onError shape every other failure takes.

Delete BookmarkFormsController entirely. FormsBridge now composes
MultiModelFormsControllerCore<NoSharing, AuthModel, BookmarkModel, TagModel>
directly, over the Bridge&/IExecutor* AppContext::onReady() hands it --
same pattern examples/pastebin's FormsBridge already uses for
FormsControllerCore<PasteModel> (#828). The rung's existing suite
(test_bookmark_qml_bridges.cpp) already covered all six actions' routing and
the unrouted-action error message end-to-end through FormsBridge, so it
proves the auto-derived routing reproduces the hand-written table's
behaviour unchanged -- verified: same 837 assertions across 130 test cases
pass unmodified, including the six-action routing case and the
CreateBookmarks/ListSharedFeed unrouted-action case.

Update every other place that named BookmarkFormsController by symbol
(bookmark_schemas.hpp, main_wasm.cpp, polls' and kanban's own doc comments
comparing their shape to it, the DynamicForm choiceless-controller test) to
point at FormsBridge/MultiModelFormsControllerCore instead, so no comment is
left describing a class that no longer exists. Kanban's ProjectAdminPresenter
comment is updated to explain, rather than merely note, why its own routing
stays hand-written: its per-action side effects (re-decoding CreateProject's
body for a typed signal, installing a session on Login, redacting a token)
are tangled into the routing in a way MultiModelBridgeCore's generic
dispatch cannot serve without also templating those effects -- confirmed by
reading ProjectAdminPresenter::submitForm and BookmarkFormsController::
dispatch side by side, per the triage's own named contingency for this case.

Closes #827

Claude-Session: https://claude.ai/code/session_018fEUahMFF32wQLiWjbsfkc
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Yaraslau Tamashevich <yaraslau.tamashevich@gmail.com>
Yaraslaut added a commit that referenced this pull request Sep 26, 2026
…ollerCore

BookmarkFormsController hand-rolled exactly what morph::qt::forms::
MultiModelFormsControllerCore (previous commit) now provides generically:
one BridgeHandler per model (AuthModel, BookmarkModel, TagModel) and an
actionType -> handler routing table, plus a try/catch converting the
routing throw into the same onError shape every other failure takes.

Delete BookmarkFormsController entirely. FormsBridge now composes
MultiModelFormsControllerCore<NoSharing, AuthModel, BookmarkModel, TagModel>
directly, over the Bridge&/IExecutor* AppContext::onReady() hands it --
same pattern examples/pastebin's FormsBridge already uses for
FormsControllerCore<PasteModel> (#828). The rung's existing suite
(test_bookmark_qml_bridges.cpp) already covered all six actions' routing and
the unrouted-action error message end-to-end through FormsBridge, so it
proves the auto-derived routing reproduces the hand-written table's
behaviour unchanged -- verified: same 837 assertions across 130 test cases
pass unmodified, including the six-action routing case and the
CreateBookmarks/ListSharedFeed unrouted-action case.

Update every other place that named BookmarkFormsController by symbol
(bookmark_schemas.hpp, main_wasm.cpp, polls' and kanban's own doc comments
comparing their shape to it, the DynamicForm choiceless-controller test) to
point at FormsBridge/MultiModelFormsControllerCore instead, so no comment is
left describing a class that no longer exists. Kanban's ProjectAdminPresenter
comment is updated to explain, rather than merely note, why its own routing
stays hand-written: its per-action side effects (re-decoding CreateProject's
body for a typed signal, installing a session on Login, redacting a token)
are tangled into the routing in a way MultiModelBridgeCore's generic
dispatch cannot serve without also templating those effects -- confirmed by
reading ProjectAdminPresenter::submitForm and BookmarkFormsController::
dispatch side by side, per the triage's own named contingency for this case.

Closes #827

Claude-Session: https://claude.ai/code/session_018fEUahMFF32wQLiWjbsfkc
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Yaraslau Tamashevich <yaraslau.tamashevich@gmail.com>
Yaraslaut added a commit that referenced this pull request Sep 26, 2026
…ollerCore

BookmarkFormsController hand-rolled exactly what morph::qt::forms::
MultiModelFormsControllerCore (previous commit) now provides generically:
one BridgeHandler per model (AuthModel, BookmarkModel, TagModel) and an
actionType -> handler routing table, plus a try/catch converting the
routing throw into the same onError shape every other failure takes.

Delete BookmarkFormsController entirely. FormsBridge now composes
MultiModelFormsControllerCore<NoSharing, AuthModel, BookmarkModel, TagModel>
directly, over the Bridge&/IExecutor* AppContext::onReady() hands it --
same pattern examples/pastebin's FormsBridge already uses for
FormsControllerCore<PasteModel> (#828). The rung's existing suite
(test_bookmark_qml_bridges.cpp) already covered all six actions' routing and
the unrouted-action error message end-to-end through FormsBridge, so it
proves the auto-derived routing reproduces the hand-written table's
behaviour unchanged -- verified: same 837 assertions across 130 test cases
pass unmodified, including the six-action routing case and the
CreateBookmarks/ListSharedFeed unrouted-action case.

Update every other place that named BookmarkFormsController by symbol
(bookmark_schemas.hpp, main_wasm.cpp, polls' and kanban's own doc comments
comparing their shape to it, the DynamicForm choiceless-controller test) to
point at FormsBridge/MultiModelFormsControllerCore instead, so no comment is
left describing a class that no longer exists. Kanban's ProjectAdminPresenter
comment is updated to explain, rather than merely note, why its own routing
stays hand-written: its per-action side effects (re-decoding CreateProject's
body for a typed signal, installing a session on Login, redacting a token)
are tangled into the routing in a way MultiModelBridgeCore's generic
dispatch cannot serve without also templating those effects -- confirmed by
reading ProjectAdminPresenter::submitForm and BookmarkFormsController::
dispatch side by side, per the triage's own named contingency for this case.

Closes #827

Claude-Session: https://claude.ai/code/session_018fEUahMFF32wQLiWjbsfkc
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Yaraslau Tamashevich <yaraslau.tamashevich@gmail.com>
Yaraslaut added a commit that referenced this pull request Sep 26, 2026
…ollerCore

BookmarkFormsController hand-rolled exactly what morph::qt::forms::
MultiModelFormsControllerCore (previous commit) now provides generically:
one BridgeHandler per model (AuthModel, BookmarkModel, TagModel) and an
actionType -> handler routing table, plus a try/catch converting the
routing throw into the same onError shape every other failure takes.

Delete BookmarkFormsController entirely. FormsBridge now composes
MultiModelFormsControllerCore<NoSharing, AuthModel, BookmarkModel, TagModel>
directly, over the Bridge&/IExecutor* AppContext::onReady() hands it --
same pattern examples/pastebin's FormsBridge already uses for
FormsControllerCore<PasteModel> (#828). The rung's existing suite
(test_bookmark_qml_bridges.cpp) already covered all six actions' routing and
the unrouted-action error message end-to-end through FormsBridge, so it
proves the auto-derived routing reproduces the hand-written table's
behaviour unchanged -- verified: same 837 assertions across 130 test cases
pass unmodified, including the six-action routing case and the
CreateBookmarks/ListSharedFeed unrouted-action case.

Update every other place that named BookmarkFormsController by symbol
(bookmark_schemas.hpp, main_wasm.cpp, polls' and kanban's own doc comments
comparing their shape to it, the DynamicForm choiceless-controller test) to
point at FormsBridge/MultiModelFormsControllerCore instead, so no comment is
left describing a class that no longer exists. Kanban's ProjectAdminPresenter
comment is updated to explain, rather than merely note, why its own routing
stays hand-written: its per-action side effects (re-decoding CreateProject's
body for a typed signal, installing a session on Login, redacting a token)
are tangled into the routing in a way MultiModelBridgeCore's generic
dispatch cannot serve without also templating those effects -- confirmed by
reading ProjectAdminPresenter::submitForm and BookmarkFormsController::
dispatch side by side, per the triage's own named contingency for this case.

Closes #827

Claude-Session: https://claude.ai/code/session_018fEUahMFF32wQLiWjbsfkc
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Yaraslau Tamashevich <yaraslau.tamashevich@gmail.com>
Yaraslaut added a commit that referenced this pull request Sep 26, 2026
…ollerCore

BookmarkFormsController hand-rolled exactly what morph::qt::forms::
MultiModelFormsControllerCore (previous commit) now provides generically:
one BridgeHandler per model (AuthModel, BookmarkModel, TagModel) and an
actionType -> handler routing table, plus a try/catch converting the
routing throw into the same onError shape every other failure takes.

Delete BookmarkFormsController entirely. FormsBridge now composes
MultiModelFormsControllerCore<NoSharing, AuthModel, BookmarkModel, TagModel>
directly, over the Bridge&/IExecutor* AppContext::onReady() hands it --
same pattern examples/pastebin's FormsBridge already uses for
FormsControllerCore<PasteModel> (#828). The rung's existing suite
(test_bookmark_qml_bridges.cpp) already covered all six actions' routing and
the unrouted-action error message end-to-end through FormsBridge, so it
proves the auto-derived routing reproduces the hand-written table's
behaviour unchanged -- verified: same 837 assertions across 130 test cases
pass unmodified, including the six-action routing case and the
CreateBookmarks/ListSharedFeed unrouted-action case.

Update every other place that named BookmarkFormsController by symbol
(bookmark_schemas.hpp, main_wasm.cpp, polls' and kanban's own doc comments
comparing their shape to it, the DynamicForm choiceless-controller test) to
point at FormsBridge/MultiModelFormsControllerCore instead, so no comment is
left describing a class that no longer exists. Kanban's ProjectAdminPresenter
comment is updated to explain, rather than merely note, why its own routing
stays hand-written: its per-action side effects (re-decoding CreateProject's
body for a typed signal, installing a session on Login, redacting a token)
are tangled into the routing in a way MultiModelBridgeCore's generic
dispatch cannot serve without also templating those effects -- confirmed by
reading ProjectAdminPresenter::submitForm and BookmarkFormsController::
dispatch side by side, per the triage's own named contingency for this case.

Closes #827

Claude-Session: https://claude.ai/code/session_018fEUahMFF32wQLiWjbsfkc
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Yaraslau Tamashevich <yaraslau.tamashevich@gmail.com>
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.

1 participant