Skip to content

fix(test): read the after-startup setting from the colon-form describe line - #934

Merged
ako merged 75 commits into
mainfrom
claude/exciting-brahmagupta-q0qhgl
Oct 3, 2026
Merged

ako merged 75 commits into
mainfrom
claude/exciting-brahmagupta-q0qhgl

Conversation

@ako

@ako ako commented Oct 2, 2026

Copy link
Copy Markdown
Owner

DESCRIBE SETTINGS now writes AfterStartupMicroflow: 'Mod.Flow',. The test
runner split only on =, kept the whole line as the value, failed to chain the
after-startup microflow, and restored the setting to
'AfterStartupMicroflow: ''Mod.Flow', leaving the project on
MxTest.RegisterEndpoint.

Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01FgupMwfjsUoszFq2kSn2p2

claude added 30 commits October 2, 2026 12:55
…e line

DESCRIBE SETTINGS now writes `AfterStartupMicroflow: 'Mod.Flow',`. The test
runner split only on `=`, kept the whole line as the value, failed to chain the
after-startup microflow, and restored the setting to
`'AfterStartupMicroflow: ''Mod.Flow'`, leaving the project on
MxTest.RegisterEndpoint.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FgupMwfjsUoszFq2kSn2p2
CheckSource closed each test block's wrapper with `END; /`, a `/` the author
never wrote, so every test drew an MDL-V1-SLASH note. Records findings for this
and the after-startup parse.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FgupMwfjsUoszFq2kSn2p2
An association to a System entity reconciled System's domain model, which is
virtual and has no stored unit, so the create failed with "no such file"
after the association was already written.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FgupMwfjsUoszFq2kSn2p2
create or modify workflow printed "Created workflow" on every run, including
in-place updates whose write was elided. It now reports through
ReportMutation like every other document type.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FgupMwfjsUoszFq2kSn2p2
entityMemberName took only IDENTIFIER or a quoted name, so attributes named
Region, Status, Title, Value or Date could be declared bare but not granted.
It now includes keyword, as attributeName does. The keyword-hint tests move to
a workflow activity name, which still rejects bare keywords.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FgupMwfjsUoszFq2kSn2p2
…ntity

CaptionAttribute was qualified with the context entity, so a caption on an
attribute inherited from Administration.Account was stored against the
specialization and mxbuild failed with CE1613.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FgupMwfjsUoszFq2kSn2p2
OnChange routed a combo box into the generic pluggable branch, which emits no
Attribute: or CaptionAttribute:, so describe -> exec dropped the binding. It
now counts only for widgets without a dedicated describe branch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FgupMwfjsUoszFq2kSn2p2
…s balanced

The @font-face pattern stopped at the `}` of the `#{$weight}` interpolation,
leaving each dropped @each block's closing brace behind, so a seeded theme on
any shipped base (console: 27 { vs 29 }) did not compile. The block's end is
now found by counting braces.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FgupMwfjsUoszFq2kSn2p2
rename entity left the DomainModels$ViewEntitySourceDocument under the old
name and persisted the entity's stale SourceDocument pointer, giving CE6784;
recreating the view then orphaned the old document (CE6786). The rename now
carries the pointer and renames the document, found by type and name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FgupMwfjsUoszFq2kSn2p2
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FgupMwfjsUoszFq2kSn2p2
The rule took the entity-level READ row, which the catalog emits when any
member is readable, so a role granted read on FullName alone was reported as
reading Email. It now matches unconstrained MEMBER_READ rows on the PII
attributes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FgupMwfjsUoszFq2kSn2p2
"No access rules found matching …" read as a failed lookup on the
idempotent re-run of a script; it now reads "Unchanged entity access: M.E
(roles) — nothing to revoke".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FgupMwfjsUoszFq2kSn2p2
`create or modify … if not exists` was reported as MDL067, the id the
bare-commit INFO note (a bare `commit $X;` now runs events) also uses. One
id for an error and an unrelated note made it useless for filtering or
looking either one up.

The guard error moves to MDL085, a gap no branch has ever used; the commit
note keeps MDL067, which fmt --upgrade, mdl/upgrade and the released
CHANGELOG already name for it. Docs, skills, the syntax help and the
quick reference follow. The guard test now also asserts the old id is
never reported for it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FgupMwfjsUoszFq2kSn2p2
The bare-commit note ("a bare `commit $X;` now writes WITH EVENTS; it used
to write them off") printed on every `exec` of an idempotent script, even
on a re-run reporting the flows unchanged, where the stored commits already
have WithEvents=true. It buried the warnings that apply.

The note is a script-only check and ValidateProgram has no backend, so the
filter runs in execPreflight, which holds the connected executor:
DropSettledCommitNotes asks StoredCommitEvents (built for fmt --upgrade -p)
for the stored flags and drops the note only when every variable committed
bare is stored with the same number of commits with and without events. A
flow not stored yet, a plain create, an added commit or a stored commit
without events keeps it. `check` is unchanged.

Measured on a Verify copy: run 2 of a two-flow script prints no MDL067;
after storing one flow `without events`, run 3 notes only that flow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FgupMwfjsUoszFq2kSn2p2
exec's pre-flight printed every violation of the semantic pass in full, with
no severity filter. On report pages MDL-WIDGET15 alone was ~35 info notes a
run, burying the warnings and errors that matter on a re-run.

exec (and diff, which shares the pre-flight) now prints errors and warnings
in full and the info notes as one line: "N info notes not shown — run
`mxcli check <file>` to see them (or pass --verbose)". The text summary
counts them, marked "(not shown)", via a new TextFormatter.OmittedInfos, so
it no longer reads "0 info" above that line. --verbose on exec and diff
prints them in full. `check` is unchanged and still prints every note;
exec has no structured diagnostics output, so check --format json|sarif
are unaffected.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FgupMwfjsUoszFq2kSn2p2
A conditionally editable widget stores Editable "Conditional" beside its
ConditionalEditabilitySettings, and describe printed both:
`Editable: Conditional, Editable: <expr>`. The key appeared twice and the
first value is not authorable - the expression is what makes it
conditional. Measured on a text box (Mendix 11.14).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FgupMwfjsUoszFq2kSn2p2
The rendered wrappers live in MxTest, which only a test run creates, so
check --references failed with "module not found: MxTest" and never
resolved anything inside a test body. The rendering now declares the module
on the first wrapper's line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FgupMwfjsUoszFq2kSn2p2
A change inside a loop body is refused by both editing modes, as intended
(ADR-0012: neither the create-or-modify splice nor the alter mutator edits
inside a loop). But the advice was circular: create or modify under mdl 1
said "change activities with alter", and alter said "rewrite the loop with
create or modify". An alter of an activity in the loop that reads the
iterator (`replace commit $c with begin commit $c without events; end;`)
failed first on "the fragment uses $c, which is not declared on the path":
the scope check walks only the top-level flows, so it saw no path inside
the loop and reported the wrong reason before the mutator's real one.

- create or modify: the in-loop refusal carries the stored loop's handle
  and names the alter that makes the change:
  `alter microflow M.F { replace loop $c in $Cars with begin loop $c in
  $Cars begin … end loop; end; };` (`end while;` for a while loop).
- alter: an operation aimed at an activity inside a loop (at any depth)
  is refused up front with the same advice, naming the top-level loop to
  replace, before any scope or fragment check runs.
- mfmutator: its own in-loop message advises replacing the loop instead
  of create or modify.

create or modify still refuses rather than replacing the loop itself: that
would renumber and redraw the loop silently, the loss the refusal exists
to prevent; the explicit alter states it.

Measured on a fresh Mendix 11.14 app: the advised alter kept every object
and flow outside the loop at the same $ID and position, renumbered only
the loop and its body, and mx check reported 0 errors.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FgupMwfjsUoszFq2kSn2p2
write-microflows (pitfall 11, linked from the edit-mode paragraph) and
write-nanoflows now say that neither create or modify nor an alter aimed
at an activity in the loop can change a loop body, and show the
`alter … replace loop … with begin loop … end loop; end;` form that does,
including while loops and nested loops.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FgupMwfjsUoszFq2kSn2p2
…tors

The nested-loop hint fired on every loop inside a loop, so intentional
iteration over two independent lists drew it on every run. It now fires when
the inner loop's body compares the inner iterator with an outer one, the
key-lookup shape its message describes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FgupMwfjsUoszFq2kSn2p2
`check --references` passed two page errors only mxbuild reported:
a textbox bound to an enumeration (CE2421) and the classic drop-down
on a React-client project (CE0582).

The project-tier widget walk now also judges built-in input widgets:

- MDL-WIDGET39: textbox/textarea/datepicker/checkbox/radiobuttons/
  dropdown bound to an attribute type the widget does not take, by a
  matrix measured with mxbuild 11.14.0 (every pair built). Inherited
  attributes and association paths are followed; script-declared
  entities and associations count. A bare association bound as the
  attribute (CE1613, which masks every other mx check error) is
  reported too. HashedString and Date are unmeasured and unjudged.
- MDL-WIDGET40: a `dropdown` when WebUI.UseOptimizedClient is "Yes".
  Measured: "No" and "MigrationMode" build it clean, so neither is
  reported.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FgupMwfjsUoszFq2kSn2p2
Document the measured widget/attribute-type matrix and the
MDL-WIDGET39 / MDL-WIDGET40 check rules in the widget reference and
the create-page skill.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FgupMwfjsUoszFq2kSn2p2
The create-page widget reference showed `datasource: microflow M.DS($Param)`
and the pitfalls page `call microflow M.F($v)`; both are parse errors.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FgupMwfjsUoszFq2kSn2p2
exec's pre-flight did not run the flow verdicts check -p runs, so a script with
a change that cannot be spliced was written up to that statement and stopped
there. The pre-flight now refuses it before anything is written; with
--continue-on-error the verdict is printed and the other statements run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FgupMwfjsUoszFq2kSn2p2
Every node was judged as a 120x60 activity, so the 40x40 merges of a nested
if/else that auto-layout places edge to edge were reported as overlapping.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FgupMwfjsUoszFq2kSn2p2
claude added 16 commits October 2, 2026 15:55
writing-java.md listed entity parameters as IMendixObject, lists as
List<IMendixObject> and string templates as MendixObjectReference; the
field mxbuild (and now mxcli) generates is the proxy class, a list of
proxies, and String. Adds the enum, type-parameter and microflow rows,
the generated shape for the basic example, and a pointer from SKILL.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FgupMwfjsUoszFq2kSn2p2
…dule role

A new page, microflow or nanoflow in a module with no module roles is
granted to an auto-created <Module>.User role, silently. Document GRANT is
additive, so a later admin-only grant on a stub page left it open to
<Module>.User too, and nothing on screen said why.

exec now prints, under the create:

  access: granted to auto-created role Shop.User (the module has no other
  roles; a later grant adds to it — revoke it to narrow access)

Only for a document exec creates and grants by default; a module that
manages its own roles gets no default grant and no note. Documented next
to GRANT (reference and language pages) and in the manage-security skill,
including that drop + create across runs loses a flow's roles.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FgupMwfjsUoszFq2kSn2p2
… capital

`sort by CreatedDate` and `sort by M.E.CreatedDate` — the spelling describe
prints for an AutoCreatedDate declaration — passed check and failed mxbuild
11.14 with CE1613 "The selected attribute 'M.E.CreatedDate' no longer
exists"; `sort by createdDate` and `sort by M.E.createdDate` build clean.

check --references now reports a sort column whose last segment matches
createdDate / changedDate case-insensitively but not exactly, where the
entity provably has no attribute of that exact name, and names the right
spelling (keeping the author's qualification).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FgupMwfjsUoszFq2kSn2p2
… (CE6592)

`grant read *, write *` on a System.FileDocument or System.Image
specialization wrote HasContents (and PublicThumbnailPath) ReadWrite, and
mxbuild 11.14 at security level Production reported CE6592 "Attribute
'HasContents' cannot have write rights, because it is a system attribute".

Measured with write * on both specializations: HasContents and
PublicThumbnailPath are refused; Name, DeleteAfterDownload, Contents, Size
and EnableCaching are not. They are flagged WriteForbidden on
meta.SystemAttrDef, and types.WriteRightsForbidden gains the third cause
beside calculated and autonumber. The GRANT downgrades them to ReadOnly as
it writes, and the reconcile's preserve branch — which cannot load System
but can ask meta — downgrades a stored entry, so `update security` repairs
rules written before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FgupMwfjsUoszFq2kSn2p2
…ed under

`revoke write (HasContents) on entity M.Doc from M.Role` on a FileDocument
specialization answered "No access rules found matching M.Role on M.Doc"
and changed nothing while the rule held ReadWrite. The partial revoke
qualified each member with the statement's entity (M.Doc.HasContents), but
a member access is stored against the DECLARING entity
(System.FileDocument.HasContents) — which GRANT already resolves through
EntityMembers. REVOKE now resolves member names the same way, falling back
to the old qualification for a name the walk does not know.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FgupMwfjsUoszFq2kSn2p2
Two branches each took MDL-WIDGET39 for a new rule; the widget/attribute-type
rule keeps 39 and the bare-expression rule becomes 42.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FgupMwfjsUoszFq2kSn2p2
…tion example

The example access rule used `[owner = '[%CurrentUser%]']`, which mxbuild
11.14 rejects with CE0161: owner is an association to System.User and is
addressed as System.owner in XPath. check --references now reports the bare
spelling, so the example would have failed its own validation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FgupMwfjsUoszFq2kSn2p2
…pta-q0qhgl

# Conflicts:
#	mdl/executor/validate.go
Two branches each added a map from a script-declared association to its two
entities; the retrieve-constraint walk now reads the one the widget walk uses.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FgupMwfjsUoszFq2kSn2p2
…agupta-q0qhgl

# Conflicts:
#	.claude/skills/mendix/create-page/reference/widgets.md
#	CHANGELOG.md
#	cmd/mxcli/testrunner/check_source.go
#	docs-site/src/appendixes/error-messages.md
…tate

Once describe printed a pluggable widget's stored Editable, the baseline the
mendixlabs#1247 merge compares against carried it, and a replacement that did not
mention Editable was read as stating the default. Visibility and editability
the statement leaves out are now left out of the baseline as well.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FgupMwfjsUoszFq2kSn2p2

ako commented Oct 2, 2026

Copy link
Copy Markdown
Owner Author

integration (roundtrip) is still red on aaf48bf. The failure comes from this PR, not from flakiness.

TestReplacePluggableKeepsWhatTheStatementDoesNotState passes again since aaf48bf. The remaining failures are four TestApp WorkflowCommons snippets that now break exec in TestTestAppRoundTrip:

  • Snip_TaskDashboard_Header
  • Snip_WorkflowDashboard_Header
  • Snip_UserTask_NameColumnWithIcon
  • Snip_WorkflowUserTaskView_NameColumnWithIcon

Cause: Studio Pro binds these widgets directly to the snippet parameter through SourceVariable.SnippetParameter, with no data view around them:

  • a combo box's Attribute: TimeFrame
  • an image's Visible: CompletionType in (…)

mxcli does not model that binding yet. Before this PR, describe dropped both properties, so the round trip lost them silently. This PR prints them, and exec now refuses them.

Fix in progress: describe will print the binding as $Param.Attr, and the build will write the SourceVariable Studio Pro stores. These snippets will not be added to the allowlist. I'll push once the full roundtrip suite passes locally.


Generated by Claude Code

claude added 8 commits October 2, 2026 20:30
MDL-WIDGET41 refused visibility on a widget whose package declares no
Visibility system property, but Studio Pro stores conditional visibility on a
Datagrid that declares none (TestApp WorkflowCommons.UserTask_Assign), and
mxbuild accepts it, so describe -> exec of that page failed. Only Editable
stays gated. The roundtrip allowlist shrinks for that page and two Rules
pages that round-trip now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FgupMwfjsUoszFq2kSn2p2
The image branch appended the conditional settings and then
appendAppearanceProps, which appends them again, so Visible: (attribute
value or expression) and Editable: printed twice (#721 C, TestApp
WorkflowCommons.Snip_UserTask_NameColumnWithIcon).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FgupMwfjsUoszFq2kSn2p2
Forms$ConditionalEditabilitySettings.Conditions used the default list
marker [3]; Studio Pro writes [2], as for visibility (both editability
settings in TestApp, WorkflowCommons combo boxes with Editable: <expr>).
A describe -> exec of either rewrote the snippet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FgupMwfjsUoszFq2kSn2p2
A widget placed directly in a snippet or page, outside every data
container, binds an attribute of a parameter: Studio Pro stores the
AttributeRef (a combo box's IndirectEntityRef; a visibility setting's
Attribute) beside SourceVariable Forms$PageVariable {SnippetParameter |
PageParameter: name, Widget: ""}. Measured on TestApp WorkflowCommons:
Snip_TaskDashboard_Header / Snip_WorkflowDashboard_Header combo boxes,
Snip_UserTask_NameColumnWithIcon image visibility, and the built-in inputs
of Snip_WorkflowUserTaskView_Details and others.

describe printed the attribute bare, so exec refused it (no enclosing
object) now that describe prints the combo box's attribute and pluggable
Visible is stored. MDL now spells the source, extending the #826 form:

  Attribute: $Param.Attr            (inputs and combo box)
  Attribute: $Param.Module.Assoc    (combo box association)
  Visible: $Param.Attr in (…)

describe prints it from the stored SourceVariable; the builders resolve
$Param against the declared parameter's entity and write the PageVariable
in the slot parameterSlotKind picks (inputs, the pluggable engine via
WidgetObjectBuilder.SetSourceVariable, ConditionalVisibilitySettings).
A name that is neither a parameter nor (on an input) an enclosing data
view is still refused, at exec and at check (MDL-WIDGET34). A bare
attribute at the root stays refused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FgupMwfjsUoszFq2kSn2p2
…ram-binding

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FgupMwfjsUoszFq2kSn2p2
Binding a widget to a snippet parameter lets six snippets round-trip outright
and six more execute; those now break getput instead of exec, and three lose
putget. Entries only lose laws, apart from exec giving way to the getput it
had masked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FgupMwfjsUoszFq2kSn2p2
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FgupMwfjsUoszFq2kSn2p2
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.

2 participants