Sync ako/mxcli: Mendix 11.14 + Java 25, navigation profiles, mapping fidelity, clearer errors - #983
Merged
Conversation
…efinition2 Two halves of the same problem: a rebuilt mapping did not carry the same property SET as the document it replaced, so no export mapping mxcli wrote matched its Studio Pro original and no 11.10+ document round-tripped — which kept both in #260's silent-loss set no matter what else was fixed. #277 — the export writers hardcoded three properties the IMPORT twin already read off the element: object root MinOccurs 1 hardcoded 0 value element MaxLength 0 / -1 hardcoded 0 value element IsKey false not written at all MaxLength is the one that cannot be a constant: Studio Pro stores 0 for a string element and -1 for a numeric one, mirroring the bound schema element exactly as MaxOccurs already did (FeedbackModule.EXM_PostFeedback has both — screenWidth and screenHeight are the -1s). The export element model carried none of these fields and the reader populated none of them, so this is model, reader, builder and three writers, not a writer patch; patching only the writer would have written zeros from an empty model. #279 — MessageDefinition2 was never read or written. gen records it as Introduced 11.10.0 and generates no accessor at all, so the read goes to raw BSON, the route parameterEntityFromRaw takes. It is CARRIED, not derived. nil means the key is absent, which is not the same as present-and-empty — hence the pointer. Writing it onto a pre-11.10 document is the overlay-rule mistake CLAUDE.md warns about: mxbuild tolerates the unknown property and Studio Pro refuses to open the document. The executor decides — carry the stored value on an update, apply the version gate only on a CREATE where there is nothing to read it off. A plain version gate in the writer looks equivalent and is not: it re-adds the key to every older document a rewrite touches, which turned four previously-clean fixtures red when I tried it. MappingSourceReference is the same family and deliberately NOT carried. Its gate (10.16) predates every project in the field, and the codec emits it through a package-level TypeDefaults registration, so making it conditional would touch every mapping write to preserve one pre-10.16 fixture. Recorded on the remaining knownLossy entry. Three pinned Studio Pro documents came off knownLossy and now round-trip byte-identically: FeedbackModule.IMM_PostResponse, FeedbackModule.EXM_PostFeedback and MxGenAIConnector.EM_CohereEmbed_Request. knownLossyFrom goes with them — its only entry was IMM_PostResponse. Validated: mxbuild 11.13 reports 0 errors; control (hardcodes restored) fails the new test with the reported values; unit suite, executor integration suite (417s), 417 MDL scripts and vet all green. Closes #277 Closes #279 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… the config ako/mxcli-ledger #146. `mxcli run --local` died on every 11.14 app: bundling web client: no rollup.config.mjs in …/deployment/web (run a serve Deploy build first) and the advice was not actionable, because a serve Deploy build had just run. Under 11.14 it does not produce that file. BuildWebClient exists to close a gap in Mendix 11.13 and earlier: the serve Deploy target wrote the client source and a self-contained rollup config but never ran the bundler, so a standalone-served app 404'd on /dist/index.js and rendered blank. 11.14 closes that gap upstream — it writes web/dist/ itself and no longer emits a config, because there is nothing left to configure. The gate tested for the CONFIG, so it failed on the absence of a file whose purpose had been served. Both call sites are fatal, so this is every 11.14 app rather than one project. Measured on a blank app, same build target, with the versions as the only variable: 11.13.0 rollup.config.mjs PRESENT dist/index.js ABSENT 11.14.0 rollup.config.mjs ABSENT dist/index.js PRESENT Exactly inverted, which is why a gate written against one is wrong on the other. Verified against those two real deployment directories: 11.14 returns nil with a skip line, 11.13 still proceeds into the rollup step. The fix gates on the gap the step closes rather than on the shape one version happened to leave behind. Two things it deliberately does NOT do: - It does not become an unconditional no-op when dist/ exists. runlocal.go:1091 re-bundles when the bundle is PRESENT but not SERVED, and a blanket skip would silently break that recovery path. - It does not gate on the Mendix version. The condition is about the bundle, and a version check would need updating for every release. Still an error when there is neither a bundle nor a config, because then the build genuinely produced no client and continuing would serve a blank page — with a message that names both halves, so a user on 11.14 does not read the old text and go looking for a build that already ran. The doc comment called this "a no-op-safe prerequisite". It was not no-op-safe; it is now. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ako/mxcli-ledger #143. Replaying one `create or replace snippet` reset a translated label to its source language in all six languages of a project. Silently: the run reported "Created snippet", mx check reported 0 errors, and the app rendered in one language. The replace path was an unconditional delete-then-create. Nothing canon.Reconcile does can survive that — by the time the create runs there is no stored document left to transplant identities from or to compare against, so both the preservation and the unchanged-elision have nothing to work from, and the translations go with the deleted unit along with everything else MDL cannot express. The verb was the tell. A path that prints "Created" on a replace of an existing document is not going through the write choke point that every other document type uses. The control is free and lands in the same run: put a page and a snippet carrying the same shape of content in one script and replay it. page "Unchanged page" translations survive snippet "Created snippet" translation destroyed Same project, same statement shape, one run — which is what makes this a snippet defect rather than a fact about replacing documents. The page path 40 lines above already did it correctly, so the fix is to do what it does: reuse the stored unit's ID, call UpdateSnippet (which already existed and was called from nowhere), and delete only the genuine duplicates. Reporting follows the same rule, so a replay of an unchanged snippet now says "Unchanged" instead of claiming a creation every time. That also fixes the second consequence the page path documents: a delete+add churns the file in git and crashes Studio Pro's RevStatusCache. This is the live half of ledger #144, which reported duplicate GUIDs that only `mx convert` notices. That symptom does NOT reproduce on current main — measured over a simple page, a rich page with seven translations and an inserted widget shifting the element sequence, and the ledger project's own committed model (0 duplicates across 81,374 elements with an $ID, with a planted duplicate proving the scanner detects them). #143 is its stated cause, so this is where the fix belongs. Verified end to end: after the fix a replay reports Unchanged for both documents and all three nl_NL translations remain. Reverting the change fails the regression test with the reported call pattern. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
fix(mappings): mirror the schema facets on export, and carry MessageDefinition2
fix(run): start a Mendix 11.14 app — gate the bundler on the gap, not the config
fix(snippets): a replace keeps the document's translations
ako/mxcli-ledger #131, open across six consecutive builds. `mxcli check -p app.mpr` reported every string literal containing a doubled apostrophe as malformed: Unexpected token after expression — the expression appears incomplete or malformed (possible missing space between keywords). Nothing was glued and no keyword was involved. `''` is how the Mendix expression language escapes an apostrophe inside a string literal; CLAUDE.md states the rule, exec writes it, DESCRIBE round-trips it byte-for-byte, and mx check reports 0 errors. Only exprcheck disagreed. exprcheck's lexer scanned to the next quote with no notion of an escape, so 'a''b' lexed as TWO strings: parseOr consumed the first and the second was reported as a leftover token. The message is a red herring by construction — the leftover reporter cannot know WHY a token is left over, so any lexer gap surfaces as "glued keywords", which is what made this cost a hand bisect of a 600-line file. A doubled quote is now consumed as part of the literal; a single one ends it. Unterminated input still lexes to TokError, and that control is asserted at the lexer rather than the parser: a lexer that simply swallowed to end-of-input would satisfy every escape case above while silently accepting a broken literal. One thing worth recording about the reproduction. It only happens WITH `-p` — exprcheck runs when a project is supplied, and `make check-mdl` runs without one. So this repo's own MDL gate is blind to the whole class, and the mdl-examples script added here documents the defect but would NOT have caught it; the regression guard is the unit test. Verified end to end: both the minimal 'a''b' and the reporter's real OData filter now pass check with a project, describe returns them unchanged, and the package's existing leftover/glued-keyword tests still pass. Reverting the lexer arm fails the new tests with the reported message. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`describe {import,export} mapping` over the 327 demo-app documents: 83
parsed and were faithful, 112 parsed but rebuilt a DIFFERENT mapping, 132
did not parse. Four independent describer/grammar defects, all fixed here
except where noted.
1. An OBJECT element could not take a nested member path. Studio Pro maps
an object several levels down with nothing mapped in between —
`= meta/pagination` — and mendixlabs#927 gave that to VALUE elements only.
An object path may step THROUGH an array where a value may not: one
object per item is legal, while a value pulled through many items is
CE0256. So the occurrence guard is now keyed on the element having an
entity, and intermediate markers are dropped when printing and stepped
through when resolving.
2. An Array or Wrapper printed as an EMPTY value binding with its whole
subtree dropped, because the printer branched on `Kind == "Object"`
alone — the export half was fixed under #268, the import half was not.
Fixing the dispatch was not enough: the two-level import array, where
container and item each carry their own entity and association, is now
honoured as written, detected the same way #262 detects it on export.
The item is built by hand, because the generic builder would try to
resolve the stored marker as a member — and building by hand means
remembering what the generic path does, which the first attempt did
not: it dropped the item's own custom handler and its CE0281 value
elements.
3. `create ./Entity = x` becomes the bare entity, which the grammar now
accepts. Both printers were also dropping the `by` and backup clauses
on that branch, so a Custom-handled element rebuilt as a plain Create.
4. Members MDL cannot spell bare — `$type` (43 occurrences),
`research%3Aread`, `https%3A//…` — were emitted raw. This needed no
grammar at all: identifierOrKeyword already accepts a quoted
identifier, so only the describer had to quote.
The entity-less IMPORT container is deliberately NOT given syntax. There
is no such element in any pinned Studio Pro document (0 of 12, both
directions), and the 10 the census found are all EXPORT, which `group as`
already covers. Inventing a spelling whose stored shape cannot be checked
against a real document is how a mapping ends up valid to mxbuild and
unopenable in Studio Pro, so DESCRIBE emits a comment naming it instead.
I wrote that syntax first and reverted it.
Two more pinned documents now round-trip byte-identically:
KrogerAPI.IM_ProductList and MxGenAIConnector.IM_CohereEmbed_Response.
IM_Collection_RetrieveNearestNeighbors keeps a knownLossy entry for one
value element's data type — stored Integer, attribute Decimal — which
this structure records no schema type for, so the rule cannot be settled
from it.
Grammar-ordering trap worth knowing: `group` is a keyword qualifiedName
accepts, so the new bare-entity alternative placed before `GROUP AS`
swallowed `group as x` and stored an entity named "Module.group". No
parse error; only TestExportGroupElement caught it.
Validated: mxbuild 11.13 reports 0 errors on the repro, which also
round-trips as "Unchanged"; unit suite, executor integration suite (374s),
418 MDL scripts and vet all green.
Refs #260
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
fix(check): accept Mendix's own apostrophe escape in expressions
fix(mappings): make DESCRIBE emit MDL its own grammar accepts
…urs 1 Two of #272's three measured differences from Studio Pro. Both leak into every mapping built over the structure, since a mapping element mirrors the schema element it resolves against. 1. resolveExposedName only CAPITALISED a member name, never sanitised it, so any character outside [A-Za-z0-9_] reached the stored ExposedName and Mendix refused to build: CE9524 "JSON element '(Object)/$type' has an invalid custom name '$type'. Custom name should start with a letter or underscore followed by either letters, digits or underscores." Unlike the reserved-name case in #300, that message is accurate. `$type` occurs 43 times in the demo apps, alongside `research%3Aread` and `https%3A//sws.siemens.com/sam/claims/tenantId`. The rule is Studio Pro's, read off CRS_ModelSimulator's structures: every character outside the set becomes `_`, then the initial is capitalised — confidence(No) -> Confidence_No_, first(Importance)_ Payload (kg) -> First_Importance__Payload__kg_. A LEADING DIGIT is prefixed rather than replaced, since the name must start with a letter or underscore and `1st` -> `_st` would lose a character. This also completes #260's item 4: such a member can now be CREATED as well as described. 2. The root carried MinOccurs 0; Studio Pro writes 1, uniformly across all nine Studio Pro-authored structures in the round-trip fixture. It was pinned to 0 with a documented reason — writing 1 used to fail every bound mapping with CE5015 "Attribute 'MinOccurs' does not match schema element", because the mapping serializers hardcoded 0. That blocker was removed by #277/#279, which made every mapping element mirror the bound schema element. Both sides now agree, so the root can carry the 1. The code and the test both say the two must move together. NOT reproduced, and the issue's premise does not hold: #272 also asks for Studio Pro's array-item naming (Data -> Datum) instead of mxcli's `Item` suffix. Across the nine pinned structures Studio Pro produces BOTH real singularisation (Datum, Department, Category, Image, Size, Choice) and a flat JsonObject / JsonObject_2 / JsonObject_3 (Chunks, Metadata, Labels, Embeddings) — and it is not a collision fallback: nothing named `Embedding` exists in the structure whose item is `JsonObject`. The two families come from different modules, so the behaviour varies by Studio Pro version or creation path, and picking either would match some real documents while breaking others. Evidence recorded on the issue. Validated: mxbuild 11.13 reports 0 errors on a structure whose members previously made the project unbuildable, and both mappings bound to it round-trip as "Unchanged"; unit suite, executor integration suite (352s), 421 MDL scripts and vet all green. Refs #272 Closes #260 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ako/mxcli-maintenance #1 and #3. `mxcli new --version 11.14.0` produced a project whose very first build failed: Execution failed for task ':compile' > Java compilation initialization error error: release version 25 not supported Mendix 11.14's mx create-project writes JavaVersion = 25. mxcli builds and launches the runtime on JDK 21 and validates it — resolveJDK21 refuses a JAVA_HOME that is anything else — so a blank 11.14 app could not build, and could not boot either. Installing a JDK 25 is the fix that looks obvious and is wrong. It gets past the compile and then fails at boot with UnsupportedClassVersionError … class file version 69.0, because the runtime is still launched on 21. Compiling for 25 and running on 21 cannot work in either direction. The maintenance repo checked the other half directly: 11.14's own runtime bundles are still Java 21 class files (major version 65), so 25 buys nothing today. So the project is lowered, not the JDK raised. Three things about how: - It lowers ONLY when the project asks for more than the runtime launcher uses. On 11.13, whose blank app is already 21, the step does not fire — and it disappears by itself the day the launcher becomes version-aware, rather than becoming a second thing to remember. - It runs BEFORE SettleGeneratedSources. That step is a build, so with the order reversed it is the thing that fails, and the fix would arrive one step too late to matter. - A version it cannot parse is left alone. Overwriting a setting that is merely unfamiliar would replace a value the user can see with one they did not choose. Mendix then warns "Java versions below 25 are deprecated for deployment". That is a deprecation notice on a project that builds and runs, which beats an error on one that does neither. runtimeMaxJavaVersion and resolveJDK21 have to move together, so there is a test asserting it — raising one without the other would lower projects to a version the launcher no longer uses, which is worse than not lowering them at all. Verified end to end on a real 11.14 project, with the same mxbuild and the same JDK as the failing run: `mxcli new --version 11.14.0` now reports the change, stores Java 21, and `mxbuild --target=deploy` reports BUILD SUCCEEDED where it previously refused the compile. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
fix(json): sanitise exposed names, and give the structure root MinOccurs 1
…ava 25
Mendix Studio Pro 11.14 supports Java 25, and its blank app is created
with JavaVersion = 25. mxcli resolved a JDK 21 unconditionally and
refused any other JAVA_HOME, so such a project could not be built:
error: release version 25 not supported
and, if built elsewhere, could not boot — compiled for 25, launched on
21, UnsupportedClassVersionError … class file version 69.0.
The Java release is now a per-project variable rather than a constant.
resolveJDK takes the major, ProjectJavaMajor reads it from the project's
own Settings > Model > JavaVersion, and the four places that launch a
toolchain — build, settle, mxbuild --serve and the runtime — all resolve
from it. Using the SAME major for the build and the runtime is the point:
class files from a newer release cannot be loaded by an older JVM, so
resolving them independently would reintroduce the boot failure.
Measured end to end on a real 11.14 project left exactly as
mx create-project wrote it, with a Temurin 25 present:
mxbuild --target=deploy BUILD SUCCEEDED, no deprecation warning
mxcli run --local Runtime started; app serving at :8080
curl / and /dist/index.js 200 and 200
the runtime process /tmp/jdk25/bin/java
The no-regression control is the stronger half, and it is deliberately
adversarial: a Java 21 project booted with JAVA_HOME pointing at the JDK
25 must IGNORE it and launch on the 21. It does — verified per process
(/usr/lib/jvm/temurin-21-jdk-arm64), not from the model setting. Worth
recording that a stale JVM from the previous run made `ps | head -1`
report the wrong answer first; only the per-pid mapping settles it.
Two traps, both covered by tests:
- The major is matched against `java -version` TEXT, which has never had
a machine-readable form. It must be followed by a dot, whitespace or
the closing quote, or a request for 21 also matches a future 210.
- Zero means "the caller did not say" and resolves to 21, so an options
struct that forgets the new field behaves exactly as before rather than
silently changing which JDK is used.
When the required JDK is missing the error names the version, what was
searched, and the second way out that a bare "install a JDK" hides: the
requirement is a model setting, and a project can be built for a release
the machine already has. That advice is only shown when the requirement
is not the default, where it would otherwise be noise.
mxcli run --help no longer calls version-aware JDK selection a follow-up.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
fix(new): a Mendix 11.14 project that builds as generated
feat(run): build and run on the JDK the project asks for, including Java 25
ako/mxcli-maintenance §7. `create or replace navigation Phone` failed:
navigation profile not found: Phone (available: Responsive)
and nothing else in mxcli added one. Mendix routes a phone to a Phone
PROFILE on the User-Agent, not on viewport width, so this put a whole
class of app out of reach: phone-specific pages could be written but no
phone browser could land on them, and a layout naming the missing profile
failed CE1613.
A profile is now created when the name is one of Mendix's fixed web kinds
— Responsive, Phone, Tablet — and re-running the statement updates it as
before, so a script stays re-runnable.
THE SHAPE IS PINNED AGAINST A REAL DOCUMENT, and that is the whole of
this change. Read over PED from a Studio Pro 11.14 project carrying all
three web profiles:
Responsive Phone Tablet
ProgressiveWebAppSettings null {Precaching, InstallPrompt} null
AppIcon Atlas icon "" ""
Name / Kind Responsive Phone Tablet
All three store the same fourteen keys. Only Phone carries PWA settings.
So the obvious implementation — clone the Responsive profile already in
the project and change its name, which is how this was first going to be
written — produces a Phone profile with no PWA settings, and mx check
reports 0 errors on it. That is the failure mode CLAUDE.md warns about,
and the reason the reference was worth asking for rather than working
around.
Three boundaries, each with a control:
- The kind set is CLOSED. Creating "Phone" adds the profile the platform
routes to; creating "Mobile" would add one that never can, so an
unknown name is still an error.
- Offline (PhoneOffline, …) and native profiles are excluded. Both are
real Mendix values, but no reference document was available for either
and a guessed profile is exactly what this commit exists to avoid.
- The legacy engine refuses rather than approximating, and MCP refuses
because nothing was verified on that path.
The kind table lives in mdl/types because both sides need it and the
executor must not import a backend implementation (ADR-0002).
Verified end to end: Phone and Tablet created on a blank app produce the
same 13 keys + $ID as the Studio Pro-authored Responsive profile beside
them, with the PWA asymmetry reproduced; mx check reports 0 errors; and a
layout with `menubar (profile: 'Phone')` — the CE1613 the reporter had to
comment out — now builds clean. Reverting the PWA rule fails the test.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…t build it #311 and #313 both merged, and together they contradict each other on main: the JDK is now resolved from the project (so Java 25 builds and runs), while `mxcli new` still lowered every 11.14 project to 21. Measured on main with a JDK 25 installed and JAVA_HOME pointing at it: $ mxcli new X14 --version 11.14.0 Java 25 → 21: mxcli builds and runs on JDK 21, and Mendix 11.14+'s runtime bundles are still Java 21 class files. Both clauses of that message are false as of #313. mxcli runs on whatever the project asks for, and the runtime bundles' class-file version was never evidence that 25 was unusable — a JVM 25 loads Java 21 class files perfectly well. I retracted that argument on #311 and should have closed it rather than leaving it to merge. The cost is not cosmetic: lowering downgrades the project against the platform's direction and earns Mendix's "Java versions below 25 are deprecated for deployment" warning, when leaving it at 25 builds clean. So the step now asks the question that matters — can THIS machine build what the project asks for — and lowers only when the answer is no. That keeps the reason `mxcli new` had for touching the version at all (a generated project whose first build fails is a bad starting point) while dropping the assumption that only 21 is runnable. Measured both ways with the same command: JDK 25 visible stored Java: 25, nothing printed no JDK 25 "Java 25 → 21: no JDK 25 on this machine, and the project would not build", stored Java: 21 Three cases are deliberately left alone rather than lowered: an unreadable or unparseable version (overwriting a setting the user can see with one they did not choose), a project already at the fallback, and a machine with neither JDK — where lowering would swap one missing JDK for another and hide that none is installed. The version parsing moves to the docker package beside the resolver that answers the question, so `new` and the launcher cannot drift on what "Java25" means; a test guards that. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…file
MDL-OFFLINE01. Mendix rejects an attribute bound across two or more
associations on any page an offline navigation profile can reach:
CE6206 Attribute paths with multiple steps cannot be used on pages
that are accessible through an offline-based navigation
It bites at a distance. The pages were valid; adding the PROFILE is what
invalidated them, so the error surfaces nowhere near the statement that
wrote the path — and mxcli writes such paths happily. Verified: a
datagrid column bound Req_Asset/Asset_Site/SiteName stores an
IndirectEntityRef with two steps and mx check reports 0 errors, which is
correct, because that project has no offline profile.
THE THRESHOLD IS TWO HOPS, NOT ONE, and the reference page proved it by
carrying both with only one flagged:
MaintenanceRequest_Asset → AssetName 1 step accepted
MaintenanceRequest_Asset → Asset_Site → SiteName 2 steps CE6206
Read over MCP from the project that hit it. Without that control the
obvious reading of "multiple steps" is "any indirect reference", which
would flag every association-bound column in an offline app.
In MDL a hop is a slash, so the count is strings.Count(path, "/") and no
BSON walk is needed. Two traps the tests pin:
- An XPath constraint is also full of slashes and is NOT an attribute
path, so only attribute bindings are scanned.
- A dynamic text binds through TEMPLATE PARAMETERS rather than
`Attribute:`, which is how the second reported error was written — a
scan of `Attribute:` alone would have found one of the two.
It is a WARNING, not an error, and that is the design rather than a
shortcut. Deciding whether a page is reachable from an offline profile
means walking the whole page graph — home pages, menu items, and every
page those open. Studio Pro does that; `check` cannot, because it does
not build the catalog that holds the edges. The entry points alone would
have caught NEITHER reported error, since that profile's menu had one
item. So the diagnostic names the profile and states the condition
instead of asserting a reachability it has not established: being wrong
in the confident direction is how a checker teaches people to ignore it.
A project with no offline profile pays one navigation read and sees
nothing, which is the overwhelmingly common case.
Not covered: an end-to-end run against a project that HAS an offline
profile — mxcli cannot create one yet (#314 excludes offline kinds), so
the project-reading half is exercised by the negative case and the
decision logic by unit tests.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
#260 took DESCRIBE from 195 of 327 real mappings parsing to 317. This closes the remaining 10, which reduce to two causes across four distinct documents — and the corpus now round-trips at 327/327. 1. An entity-less export ROOT printed as `. {`. `group as` covers a nested grouping node (#262), but a root has no member name, so it needed its own spelling: `group {`. The real document is CapitalConnector.EM_AttachedDataRequest (OneHarness), whose root is et=Object ent='' assoc='' oh=Parameter. 2. A custom handler's value-path parameter printed Mendix's storage markers raw — `Suggestion: (Value)`. The stored path is "…|suggestions|(Wrapper)|(Value)": an array of PRIMITIVES, whose value is named by the element's exposed name, `Value` (#268). Printing the member is only half of (2). Emitting `Value` while the builder concatenates it back as "…|(Wrapper)|Value" gives a path that resolves to nothing, so the parameter path is now resolved THROUGH the schema index, the way a member reference is — which is also how it steps through the array to reach the wrapper. Two things only a real build caught, both invisible to `mxcli check`: - The entity-less root fell through to the VALUE branch, because the builder decides object-vs-value on `def.Entity != ""`. That produced a project mxbuild cannot even LOAD: "Type ExportValueMappingElement does not contain a constructor with a parameter of type ExportMapping." - An element with NO association cannot be Find — mxbuild says so with CE0224 "No association selected for obtaining objects." It is Parameter, which is what the real document stores on both its elements. I would have guessed Find from the surrounding code. Validated: 327/327 of the demo-app mappings describe into MDL that parses (from 317); mxbuild 11.13 reports 0 errors on the repro, which round-trips as "Unchanged"; unit suite, executor integration suite (337s), 422 MDL scripts and vet all green. Refs #260 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…s PWA settings
Each of Mendix's web profile kinds has an offline twin, and an offline
profile is not a different document: measured over PED on Studio Pro 11.14,
a freshly created PhoneOffline stores exactly the same fourteen keys as a
Phone and differs only in Kind and Name. So the six kinds share one builder
rather than needing a second, guessed shape.
The correction in the same change is the more important half.
ProgressiveWebAppSettings was written as {Precaching: true, InstallPrompt:
true} for Phone, copied from the reference project's Phone profile. Those
are the USER'S settings, not the platform's: the schema defaults Precaching
to false and makes the whole element optional, and a profile added through
Studio Pro's own model API materialises null for every kind — Phone and the
offline kinds included. mxcli would have turned precaching silently on for
every profile it created, with mx check reporting 0 errors either way.
The general lesson, recorded in the file and the symptom table: measure what
Studio Pro CREATES, by adding a profile over PED and reading back what it
filled in. Reading a profile the project already has measures the user.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…dated An offline navigation profile restricts every page it can reach: an attribute may be bound across at most ONE association hop, and a longer path is CE6206. This bites at a distance. The pages were valid, the statement that broke them never mentions them, and mxcli writes multi-step paths happily — so `create or replace navigation TabletOffline` printed one cheerful line and the next build failed somewhere with no visible connection to it. That is the finding from ako/mxcli-maintenance. Creating an offline profile now scans the project's stored pages and snippets and names the bindings that exceed the limit. The scan keys on the stored DomainModels$AttributeRef rather than on a table of widget property names, which is what makes it cover pluggable widgets: a DataGrid2 column's binding sits several levels inside a CustomWidgets$WidgetValue, at a key path no property table would have listed. It deliberately does NOT match every IndirectEntityRef — a data source that navigates an association is a different element and CE6206 does not reject it. It is a warning, not a refusal: whether a page is actually REACHABLE from the new profile takes the whole page graph, which mxcli does not walk here. Verified end to end on an 11.13 project — mxbuild reports 0 errors while the flagged page is unreachable, and exactly the flagged column (Req_Asset/Asset_Site/SiteName, 2 of 2) as CE6206 once the profile's home page points at it. The sibling one-hop column in the same grid is accepted throughout, which is the threshold control, confirmed by mxbuild rather than by the rule that predicts it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…t through
`create import mapping X with json structure Module.NoSuchThing` was
accepted and written verbatim. mxbuild reports the dangling reference as
CE1613 "… no longer exists", but only at build time — `mxcli check` and
even `check --references` passed.
The second failure is the one with teeth. The schema index is empty
whenever the structure cannot be loaded FOR ANY REASON, and resolvable()
reads an empty index as "there is nothing to validate against" — its
correct meaning for a schema-less mapping. So one typo in the source name
switched off every member check in that mapping, and this went through
with no complaint at all:
create import mapping M.IM with json structure M.NoSuchThing
{ create M.Loc { LocId = nonsense } }; -- both wrong, both accepted
So the refusal is at the SOURCE rather than in resolvable(): a NAMED but
unresolvable source is an error, while NO source at all — XML schema,
message definition, the tests that create a schema-less mapping — must
keep working. The error names the structures that would have worked, the
shape mendixlabs#882 established for members.
Also: `null values` is an enum, not free text.
ExportMappings$NullValueOption is {LeaveOutElement, SendAsNil}, and mxcli
wrote whatever identifier it was given, so `null values Banana` reached
the stored document verbatim.
An EMPTY project and a backend that CANNOT LIST look identical from here.
Refusing is right for the first and wrong for the second, so the refusal
only fires when the listing succeeds. That distinction has teeth: two
pre-existing mock tests named a structure their mock did not provide and
started failing. The right fix was to give the mock the structure, not to
weaken the check — a mapping whose named source does not exist is an
inconsistent model to be asserting ID preservation against.
Still written through, and out of scope: `with xml schema
Module.NoSuchThing`. A gen type exists (modelsdk/gen/xmlschemas) so it is
verifiable in principle, but it needs a new ListXmlSchemas method across
the backend interface, both engines and the mock — a lot of plumbing for
a dangling-reference check on a document type mxcli cannot create. The
silent-disabling half does not apply there: an XML mapping never had
member validation, because mxcli cannot read XML schemas at all.
Validated: mxbuild 11.13 reports 0 errors on the repro; the three bad
forms are refused with the structures that would have worked; unit suite,
executor integration suite (317s), 421 MDL scripts and vet all green.
Refs #259
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ee it `cmd/mxcli/docker/build_integration_test.go` still called `resolveJDK21`, which #311/#313 renamed to `resolveJDK(major)` when the Java release became a per-project setting. The file is behind `//go:build integration`, so neither `go build ./...` nor `go test ./...` compiles it: the break landed on main with a green gate, and every open PR inherited it. It read as three separate PR failures. Two changes. The call site now resolves the JDK the SCAFFOLDED PROJECT asks for, which means moving the skip guard below `mx create-project`. Passing the old constant back in as `resolveJDK(21)` would compile and would reinstate exactly the assumption the rename removed — on an 11.14 row the guard would look for a JDK 21 while the build needs 25. `make vet` now vets once per build-tag set, `""` and `integration`. Vet only type-checks, so this costs seconds and needs no mx, no JDK and no Docker — against a CI job that spent half an hour reaching a compile error the type-checker finds immediately. Verified in both directions: reintroducing the old call fails `make vet` with the same message CI produced. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
fix(ci): restore the integration build, and make `make vet` able to see it
feat(navigation): create a navigation profile — online and offline kinds
fix(new): only lower a project's Java version when this machine cannot build it
feat(check): warn on a multi-step attribute path under an offline profile
fix(mappings): describe the last two shapes that did not parse
All three passed every gate and were wrong only in what they told the reader,
which is the class of defect that costs the most and shows up in no test.
1. A reserved word now says whether QUOTING is the fix.
`Title: String(100)` is accepted in `create entity`; `write (Title)` in a
grant list is a parse error, and mxcli answered with three renames. Quoting
works — `write ("Title")` executes with the name intact — and
QUOTED_IDENTIFIER is in the expectation set the raw message already printed.
The maintenance app renamed a stored attribute to RequestTitle on that
advice, which is a schema change where quoting costs nothing.
The hint now branches on whether the platform reserves the name, not just
the parser, because the two have opposite remedies: a platform-reserved name
is rejected after the quotes are stripped (CE7247), so recommending quoting
there produces something that parses and then fails the build. Measured
against Mendix's own list: 38 of 41 hinted keywords are rescued by quoting,
and exactly three — Type, Default, Owner — are not. The old advice was wrong
for the 38 and right for the 3 by accident.
The reserved-word lists move to mdl/types so the validator (mdl/executor)
and the hinting (mdl/visitor) share one copy; neither may import the other.
2. `grant … on System.User` explains itself.
It failed with a UUID and a missing .mxunit path, which reads like a corrupt
project. It is not: the System module's domain model is not a stored unit in
ANY Mendix project. Refused up front by name, which also avoids the
half-applied state a late failure leaves — the roles are validated by then.
The message states that the limitation is universal, and names the
consequence otherwise rediscovered the hard way: a constraint that traverses
System is equally unusable.
3. `mxcli test` says why it found nothing.
"Found 0 test(s) in 1 file(s)" counted the DIRECTORY as a file, so it read
as though something had been opened and found wanting. The count is now of
files actually read, and an empty suite names the .mdl/.md files skipped for
their name — a .mdl in a test directory is almost always the intended test
misnamed, so listing it turns a dead end into a rename.
Each has a control: that the quoted form actually parses (without which the
first test passes against advice that does not work), that an ordinary
module is not caught by the System guard, and that FilesRead is not passing
by never being incremented.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
fix(mappings): resolve a mapping's schema source instead of writing it through
fix(errors): three messages that sent the maintenance app the wrong way
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Seventeen commits since the last sync. Most of it comes from building two real apps against mxcli —
ako/mxcli-maintenanceandako/mxcli-ledger— and fixing what they hit.Mendix 11.14 and Java 25
11.14 ships a blank app with
JavaVersion = 25, and mxcli resolved a JDK 21 unconditionally