Summary
In the shipped lims GUI, registering a client or a sample through the schema-driven form leaves the app in a broken state: the client id is never populated, the sample's shared handler is never attached, and every subsequent dispatch on it fails with handler not bound.
SamplePresenter::submitIfValid routes both RegisterClient and RegisterSample to the plain _creator handler, then calls refresh() on the shared _handler — which nothing ever attached.
Found while fixing #304 §A3 (silently swallowed form failures). Reported rather than folded in, per AGENTS.md: this is a routing defect, not an error-surface one.
Reproduction
Driven against the real bridges with a temporary probe (since removed):
clientId property after form-path RegisterClient: -1
sample reply ok=1 payload={"id":1,"clientId":1,"reference":"WW-1","state":"Registered",...}
after refresh: changes=0 fails=2 lastFail=handler not bound sampleMap=0
The action itself succeeds — the model creates the row and returns a correct payload. What fails is everything downstream of it in the client.
Why the typed invokables were masking it
SampleBridge::registerClient is the only dispatch that emits clientRegistered, and therefore the only thing that ever sets the clientId property that SampleView.qml:77 binds ("Latest client id: "). SampleBridge::registerSample is the only dispatch that leaves the shared handler attached.
Both look like redundant duplicates of the form path — morph#287 lists them as such — but they are not: they are the only paths that work. The form path is the broken one.
Consequences in the running app
SampleView's "Latest client id" label is permanently -1.
- Registering a sample from the form never populates the
sample property, so Main.qml's onSampleChanged cross-wiring never fires and the result surface never attaches.
- Every form-path registration produces a spurious
handler not bound error.
That last one was invisible before. The fix for #304 §A3 binds replyReceived, so these failures now reach the operator as a red error — correct behaviour surfacing a pre-existing defect, not a regression introduced by that change. Expect the error to become visible the moment that branch lands.
Interaction with morph#287
The two exemptions surviving on the #287 branch are keyed to exactly this: registerClient and registerSample remain exempt because they are the working paths, with reason strings naming the mechanism. Fixing this issue is what would let those two exemptions go.
Suggested fix direction
Route RegisterClient and RegisterSample through the shared handler in submitIfValid — the same handler the typed invokables use — so the form path leaves the client in the same state the typed path does. Then the typed invokables genuinely do become redundant and can be deleted, closing #287 to zero exemptions.
What would change the verdict
Close it if the form path is intended to be create-only, with attachment always driven separately — in which case SampleView.qml:77's clientId binding and Main.qml's onSampleChanged wiring are the things that need changing, and the intent belongs in examples/lims/README.md.
Related doc drift, already corrected
examples/lims/README.md claimed registerSample "does two dispatches: create on the plain handler, then attach the shared one". The code does one dispatch on the shared handler and relies on BridgeHandler::execute's ResultKeyed branch — gui_lib/sample_presenter.cpp:76-84 says so explicitly. Corrected on the #287 branch, since the exemption text there cites the same mechanism.
Summary
In the shipped lims GUI, registering a client or a sample through the schema-driven form leaves the app in a broken state: the client id is never populated, the sample's shared handler is never attached, and every subsequent dispatch on it fails with
handler not bound.SamplePresenter::submitIfValidroutes bothRegisterClientandRegisterSampleto the plain_creatorhandler, then callsrefresh()on the shared_handler— which nothing ever attached.Found while fixing #304 §A3 (silently swallowed form failures). Reported rather than folded in, per
AGENTS.md: this is a routing defect, not an error-surface one.Reproduction
Driven against the real bridges with a temporary probe (since removed):
The action itself succeeds — the model creates the row and returns a correct payload. What fails is everything downstream of it in the client.
Why the typed invokables were masking it
SampleBridge::registerClientis the only dispatch that emitsclientRegistered, and therefore the only thing that ever sets theclientIdproperty thatSampleView.qml:77binds ("Latest client id: ").SampleBridge::registerSampleis the only dispatch that leaves the shared handler attached.Both look like redundant duplicates of the form path — morph#287 lists them as such — but they are not: they are the only paths that work. The form path is the broken one.
Consequences in the running app
SampleView's "Latest client id" label is permanently-1.sampleproperty, soMain.qml'sonSampleChangedcross-wiring never fires and the result surface never attaches.handler not bounderror.That last one was invisible before. The fix for #304 §A3 binds
replyReceived, so these failures now reach the operator as a red error — correct behaviour surfacing a pre-existing defect, not a regression introduced by that change. Expect the error to become visible the moment that branch lands.Interaction with morph#287
The two exemptions surviving on the #287 branch are keyed to exactly this:
registerClientandregisterSampleremain exempt because they are the working paths, with reason strings naming the mechanism. Fixing this issue is what would let those two exemptions go.Suggested fix direction
Route
RegisterClientandRegisterSamplethrough the shared handler insubmitIfValid— the same handler the typed invokables use — so the form path leaves the client in the same state the typed path does. Then the typed invokables genuinely do become redundant and can be deleted, closing #287 to zero exemptions.What would change the verdict
Close it if the form path is intended to be create-only, with attachment always driven separately — in which case
SampleView.qml:77'sclientIdbinding andMain.qml'sonSampleChangedwiring are the things that need changing, and the intent belongs inexamples/lims/README.md.Related doc drift, already corrected
examples/lims/README.mdclaimedregisterSample"does two dispatches: create on the plain handler, then attach the shared one". The code does one dispatch on the shared handler and relies onBridgeHandler::execute'sResultKeyedbranch —gui_lib/sample_presenter.cpp:76-84says so explicitly. Corrected on the #287 branch, since the exemption text there cites the same mechanism.