Sign-out and open-link actions: stop dropping them to Forms$NoAction - #389
Conversation
`ACTIONBUTTON … (Action: SIGN_OUT)` was refused by the default engine:
client action *pages.SignOutClientAction not yet supported by the
modelsdk engine — rerun with MXCLI_ENGINE=legacy
The refusal was honest. The advice was not. The legacy writer had no case for
the action either, and its default branch is QUIET — it returns Forms$NoAction
for anything unmatched — so the recommended escape hatch produced a button that
rendered, said "Sign out", and did nothing, with `mxcli check`, `exec` and
`mx check` all clean. Measured on Mendix 11.13, `describe page` came back
`actionbutton btnOut (Caption: 'Sign out')` with no action at all, and the
stored BSON held Forms$NoAction.
Both engines now write the same document, and DESCRIBE renders `sign_out` so it
round-trips:
{ "$Type": "Forms$SignOutClientAction", "DisabledDuringExecution": true }
Two keys and no more, pinned against a Studio Pro-authored sign-out button in
ako/TestApp. That reference is provably Studio Pro's rather than mxcli's,
because until this change NEITHER engine could emit the type — which is also
why the shape could not have been guessed from the writers.
Verified with the fix reverted, one engine at a time: modelsdk fails with the
refusal verbatim, legacy fails by writing Forms$NoAction. mx check is 0 errors
on both engines' output, before and after — this was never a build error, which
is exactly what made it dangerous.
The control pins the fallback separately. A test asserting "SIGN_OUT is no
longer NoAction" would also pass if someone had merely softened the default, so
OPEN_LINK — still unimplemented — is asserted to STILL hit Forms$NoAction on
legacy and still be refused on modelsdk.
OPEN_LINK is left unimplemented deliberately: gen calls it OpenLinkClientAction
and its Address is an element rather than a string, so it is a separate job.
`mxcli syntax page.action` listed it as available with no caveat; it now says
it is written by neither engine and points at a nanoflow instead.
Reported as CapTrackV2 FINDINGS §10.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018hifgRSawfaRWXS44YKtSJ
`ACTIONBUTTON … (Action: OPEN_LINK 'https://…')` reached storage on neither
engine. modelsdk refused it; legacy fell through to its QUIET default and wrote
Forms$NoAction, so the button rendered, said "Docs", and did nothing — with
`mxcli check`, `exec` and `mx check` all clean. Same defect as the SIGN_OUT
case in the previous commit, and the syntax help listed the action as available
either way.
Two traps here that a Studio Pro reference settled and reasoning would not.
The STORAGE NAME is Forms$OpenLinkClientAction. The semantic type is
LinkClientAction and the executor stamped "Forms$LinkClientAction", which is
not what Mendix stores — a wrong $Type that never reached disk only because
nothing could write the action at all. And the address is not a string
property: it is a nested Forms$StaticOrDynamicString.
Pinned against 31 Studio Pro-authored link buttons (ako/TestApp,
FeedbackModule) — exactly five keys, LinkType "Web" in all 31:
{ "$Type": "Forms$OpenLinkClientAction",
"Address": { "$Type": "Forms$StaticOrDynamicString",
"AttributeRef": null, "IsDynamic": false,
"Value": "https://www.mendix.com/" },
"DisabledDuringExecution": true,
"LinkType": "Web" }
6 of those 31 are DYNAMIC — the address is read from an attribute at runtime.
MDL cannot author that, so DESCRIBE flags such a button rather than printing
its address as a literal, which would round-trip into a different link.
gen declares a fourth property on Forms$StaticOrDynamicString, `Attribute`,
that not one of the 31 documents carries. It is deliberately left unset:
writing a key Mendix does not store is what produces a document mxbuild accepts
and Studio Pro cannot open.
The previous commit's "still unimplemented" controls named LinkClientAction,
which stops being a valid control the moment this lands — they now name
ShowHomePageClientAction, which has no gen type, no metamodel counterpart and
no MDL statement that builds one, so it is structurally unwritable rather than
merely not yet written.
Both engines emit the same document; mx check 0 errors on each.
Reported as CapTrackV2 FINDINGS §10.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018hifgRSawfaRWXS44YKtSJ
A navigation menu can carry a log-out item. mxcli could neither author one nor
read one back:
authoring MDL's `menu item` took PAGE or MICROFLOW only, so there was no
spelling for it at all.
reading ako/TestApp's sign-out menu item came back as a plain
`menu item 'Item 5';`, so DESCRIBE -> exec turned a working log-out
entry into a dead one — silently, with mx check clean.
Fixing the sign-out BUTTON proved nothing about this, because a menu item's
action reaches storage through four switches that share no code with the button
path — two writers (a standalone menu document and the menu inside a navigation
profile) and two readers. All four had to be wired:
menuActionToGen NoAction default modelsdk, menu document
navMenuAction NoAction default raw BSON, navigation profile
resolveMenuAction raw type name modelsdk read
parseNavMenuItem raw type name legacy read
The readers are the subtler half. Both had a fallback that stored the unmapped
$Type, which LOOKS like it preserves information — ActionType became
"Forms$SignOutClientAction" — while breaking the round trip, because DESCRIBE
and both writers key on "SignOutAction". A round trip closes only when the
reader produces the exact string the writer consumes.
Studio Pro stores the same element a button carries — Forms$SignOutClientAction,
DisabledDuringExecution true, nothing else — which is why SIGN_OUT sits beside
PAGE and MICROFLOW rather than getting a syntax of its own. It names no target,
so the visitor reads it separately from the PAGE/MICROFLOW switch; folding it in
would consume a qualifiedName and mis-assign a trailing ICON. That case is in
the example.
Measured on ako/TestApp (Mendix 11.14), and controlled by neutralising both
readers and re-reading it:
before show navigation menu Responsive -> Item 5
after show navigation menu Responsive -> Item 5 -> sign out
describe -> exec now puts an identical Forms$SignOutClientAction back on disk,
and mx check reports 0 errors on the result.
Reported as CapTrackV2 FINDINGS §10.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018hifgRSawfaRWXS44YKtSJ
|
The job runs GitHub's Copilot autofix agent ( The actual code-scanning analysis passes: There is no fix to port — the failure is in GitHub's own action, not in this repository — so I am standing down on it rather than re-running or working around it. Everything else on the head commit is green except Generated by Claude Code |
Three client actions MDL could spell and neither engine could store. All three failed the same way — the model stayed valid,
mx checkreported 0 errors, and the button or menu entry simply did nothing.CapTrackV2 FINDINGS §10 reported the first of these as "
SIGN_OUTneeds the legacy engine". The refusal was honest; the advice was not.1.
SIGN_OUTon a button (a54e8b40)The default engine refused it —
client action *pages.SignOutClientAction not yet supported by the modelsdk engine — rerun with MXCLI_ENGINE=legacy. The legacy writer had no case for it either and fell through to a quiet default that returnsForms$NoAction, so the recommended escape hatch was the strictly worse path: a button that rendered, said "Sign out", and did nothing.Forms$SignOutClientAction,DisabledDuringExecution: trueForms$NoActiondescribe pageactionbutton btnOut (Caption: 'Sign out')… Action: sign_outmx checkThe two-key shape is pinned against a Studio Pro-authored button in ako/TestApp — a reference provably Studio Pro's rather than mxcli's, since until this change neither engine could emit the type.
2.
OPEN_LINK(3bd242c4)Same defect, plus two traps a reference settled and reasoning would not:
Forms$OpenLinkClientAction. The semantic type isLinkClientActionand the executor stampedForms$LinkClientAction— a wrong$Typethat never reached disk only because nothing could write the action.Forms$StaticOrDynamicString.Pinned against 31 Studio Pro link buttons (ako/TestApp, FeedbackModule) — exactly five keys,
LinkType: "Web"in all 31:{ "$Type": "Forms$OpenLinkClientAction", "Address": { "$Type": "Forms$StaticOrDynamicString", "AttributeRef": null, "IsDynamic": false, "Value": "https://www.mendix.com/" }, "DisabledDuringExecution": true, "LinkType": "Web" }6 of the 31 are dynamic — the address is read from an attribute at runtime. MDL cannot author that, so
DESCRIBEflags such a button rather than printing its address as a literal, which would round-trip into a different link.gendeclares a fourth property onForms$StaticOrDynamicString,Attribute, that none of the 31 documents carry. It is deliberately left unset — writing a key Mendix does not store is what produces a document mxbuild accepts and Studio Pro cannot open.3. A sign-out menu item (
d4d56271)Raised separately: a navigation menu can carry a log-out item too. Fixing the button proved nothing here, because a menu item's action reaches storage through four switches that share no code with the button path:
menuActionToGen(menu document, modelsdk)NoActiondefaultnavMenuAction(navigation profile, raw BSON)NoActiondefaultresolveMenuAction(modelsdk read)parseNavMenuItem(legacy read)The readers are the subtler half. Both had a fallback that stored the unmapped
$Type, which looks like it preserves information —ActionTypebecame"Forms$SignOutClientAction"— while breaking the round trip, because DESCRIBE and both writers key on"SignOutAction". A round trip closes only when the reader produces the exact string the writer consumes.MENU ITEM 'Sign out' SIGN_OUT [ICON …]now works in both a menu document and a navigation profile.SIGN_OUTnames no target, so the visitor reads it separately from the PAGE/MICROFLOW switch — folding it in would consume aqualifiedNameand mis-assign a trailingICON. That case is pinned in the example.Measured on ako/TestApp (Mendix 11.14):
and
describe→execnow puts an identicalForms$SignOutClientActionback on disk,mx check0 errors.Notes on the controls
Two corrections worth recording, both caught rather than shipped:
SIGN_OUTcommit's "still unimplemented" controls namedLinkClientAction. ImplementingOPEN_LINKinvalidated them, and the gate run failed on exactly those two tests. They now nameShowHomePageClientAction— no gen type, no metamodel counterpart, no MDL statement that builds one — so the control pins a structural impossibility rather than a feature with a shelf life.make buildfailed into/dev/null, and I was probing a stale binary that still showed the fix working. Redone properly, neutralising both readers takesItem 5 -> sign outback toItem 5.Validation
go test ./...clean,make vetclean,make check-mdlclean,mx check0 errors on every artefact produced (Mendix 11.13 and 11.14),check-findings907 records OK. New examples:captrack-10-sign-out-action.mdl,captrack-10-open-link-action.mdl,captrack-10-sign-out-menu-item.mdl.Branch restarted from
mainafter #387 merged, so these three commits carry no already-merged history.🤖 Generated with Claude Code
https://claude.ai/code/session_018hifgRSawfaRWXS44YKtSJ
Generated by Claude Code