Skip to content

R8: page actions as words, one spelling per keyword, lowercase keywords (#752) - #773

Merged
ako merged 17 commits into
mainfrom
feature/752-page-action-words
Sep 28, 2026
Merged

ako merged 17 commits into
mainfrom
feature/752-page-action-words

Conversation

@ako

@ako ako commented Sep 27, 2026

Copy link
Copy Markdown
Owner

Closes #752 (R8, phase 3.2 of the beta syntax freeze; tracking #714).

What changes

Page actions are the words a microflow uses — show page, save changes, cancel changes, close page, create object … then …, delete, open link, sign out, complete task, call microflow M.F, call nanoflow M.F. The same in Action:, OnClick:, OnChange:, named action slots ('createFileAction': …), alter page … set, and a menu item's sign out. Arguments are untouched (#751 owns them).

One spelling per keyword

Canonical (describe emits it) Old spelling (still parses, warns) Code
page action words above show_page, save_changes, cancel_changes, close_page, create_object, delete_object, open_link, sign_out, complete_task, microflow M.F, nanoflow M.F MDL-DEPR020
error message '…' (after not null / unique / required, in a validation rule, after on delete restrict, disallow concurrent execution) error '…', feedback '…', error_message, errormessage MDL-DEPR021
on delete cascade | restrict | set null delete_behavior … in all its spellings (incl. the three spellings each of delete_and_references etc.), also alter association … set delete_behavior MDL-DEPR022
ReferenceSet reference_set MDL-DEPR023
returns nothing returns none MDL-DEPR024

Every alias is marked in the grammar (/* @alias MDL-DEPRnnn */), registered with Example/CanonicalExample (the registry test proves both build the same statements), and has an fmt --upgrade rewrite that keeps the script's letter case. delete_object close_page, which describe could print but the grammar refused, now parses.

Lowercase keywords are canonical; fmt no longer upper-cases names. formatter.Format upper-cased every word on a keyword list, names included (Issue64.User → Issue64.USER, attribute Title → TITLE) and changed what 374 example scripts build. It now lowercases only tokens the parse tree shows are keywords (visitor.FormatSpans), keeping property keys (a keyword followed by :/=), names (anything reached through keyword/identifierOrKeyword/qualifiedName), CamelCase or digit-bearing values (ButtonStyle: Success, RenderMode: H2), data types and annotations. Text stored verbatim (expressions, XPath, OQL, catalog/SQL queries, template params, multi-line literals) keeps case and layout. describe now writes lowercase folder, workflow default ->, and published-REST get/post.

Design choices the ADRs did not settle

  • Codes start at MDL-DEPR020, not 006, because R3/R4: one argument-binding form (Param = expr) and alter set ( Key: value ) everywhere (3.1) #751/R5: XPath always in [ ], expressions bare, one constant reference (3.3) #753–R6/R7/R9/R10: verbs, session commands, metadata placement, Studio Pro document names (3.5–3.6) #755 add registry entries in parallel; R8's entries live in their own slice (r8Entries) so the merges do not collide. A gap in the numbering means nothing.
  • Two-word actions without their own tokens (save changes, cancel changes, sign out, complete task) are one lexer token admitting both spellings (WS+ or _), like not null; the underscore identifies the alias. Actions whose words are already tokens (show page, close page, create object, open link, call microflow) are token sequences in actionExprV3 — a lexer-level show page would have broken show pages (longest match).
  • returns nothing wins over returns none: it is what describe already emitted (as Nothing, now lowercase) and what the docs use.
  • ReferenceSet (not reference set): it is the Mendix type name describe writes, a value like Reference, not a keyword phrase.
  • fmt case rule: a CamelCase word is left as written, because that is how Mendix writes names and enumeration values; an all-caps or all-lowercase word is a keyword. This makes fmt(describe(x)) a fixed point on PedApp and TestApp.
  • Not changed, deliberately:
  • alter settings now stores the section lowercase in the AST (the executor already lowercased it), so keyword case no longer changes the AST.

Test plan (what I ran)

  • make build, make lint (Go + TS) — pass.
  • go test ./mdl/visitor/ ./mdl/executor/ ./mdl/upgrade/ ./mdl/formatter/ ./mdl/grammar/ ./mdl/deprecation/ ./mdl/langver/ ./cmd/mxcli/... — pass.
  • go test -tags integration ./mdl/roundtrip/ (PedApp + TestApp round trip, allowlists, execute-both upgrade property test, describe: end every statement with ';', never print '/' (#744) #745 terminator test) — pass; no allowlist entries added.
  • New tests: TestR8OldSpellingsAreAliases / TestR8PageActionWordsBuildTheirAction (visitor: 30 old/canonical pairs build identical statements, only the old records its code, each with a fix), TestUpgrade_R8Spellings (exact rewrites, case kept, idempotent), TestFormatterKeepsMeaning (replaces TestFormatterChangesMeaning: every mdl-examples script formats to one that builds the same statements, idempotently), TestPedAppDescribeUsesCanonicalSpellings / TestTestAppDescribeUsesCanonicalSpellings (integration: describe emits no R8 alias and no upper-case keyword).
  • Revert checks:
    • ExitActionExprV3 stubbed → TestR8OldSpellingsAreAliases fails 9 subtests ("recorded nothing").
    • old formatter/format.go restored → TestFormatterKeepsMeaning fails on 374 scripts.
    • describe folder → FOLDER and save changes → save_changes → TestPedAppDescribeUsesCanonicalSpellings fails (9 folder lines, 4 MDL-DEPR020 uses).
    • The registry test (TestRegistryExamplesRecordTheirCode) failed for all five new codes before the grammar/visitor change.
  • Manual: mxcli fmt over a describe dump of every PedApp and TestApp document changes only whitespace; mxcli check / fmt --upgrade on a scratch script with named action slots and SHOW_PAGE.
  • Not verified in Studio Pro: every change is a respelling that builds the identical AST (so the identical model); the execute-both property test covers the model side.

Follow-ups

🤖 Generated with Claude Code

ako and others added 17 commits September 27, 2026 18:43
…vision (#768)

`/` shares a precedence level with `*`, `div` and `mod`, so `$a/X * $b/Y`
parsed as `(($a/X) * $b) / Y` and was stored as `$a/X * $b / Y` (and, in a
list filter, `$b / $currentObject/Y`). When a `/ Member` step cannot
attach to the whole left side, attach it to the last operand of the
preceding multiplicative expression. Mendix has no `/` division.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… ({n} = e)) (#751)

Every call site binds an argument as `Param = expression`: call
microflow/nanoflow/java/javascript/external action, web service, execute
database query, send rest request, show page, and page/button actions and
flow data sources. A workflow's call microflow / call workflow takes its
arguments right after the callee as bare expressions. show message and
validation feedback take `with ({1} = e)`.

The old spellings stay as registered deprecated aliases with fmt --upgrade
rewrites computed from the parse tree:
  MDL-DEPR006  $Param = e
  MDL-DEPR007  Param: e
  MDL-DEPR008  workflow `with (Param = '<expr>')` (same meaning: the
               string's content is the expression)
  MDL-DEPR009  objects [..] / parameters [..]

describe writes the canonical forms, and now also writes a validation
feedback's template arguments, which it dropped. A stored workflow
expression that does not read back as itself when bare keeps the string
form in describe.

Skills, docs-site, the quick reference and `mxcli syntax` are migrated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Page actions use the words a microflow uses: show page, save changes,
cancel changes, close page, create object, delete, open link, sign out,
complete task, call microflow, call nanoflow. A rule's message is
`error message '…'` everywhere, an association's delete behaviour is
`on delete …`, the many-to-many type is ReferenceSet and a REST call
that ignores its response `returns nothing`. describe writes these.

Every old spelling keeps parsing with the same meaning and is a
registered deprecated alias with an fmt --upgrade rewrite:
MDL-DEPR020 (snake-case page actions, a flow call without `call`, a
menu item's sign_out), MDL-DEPR021 (error / feedback / error_message),
MDL-DEPR022 (delete_behavior), MDL-DEPR023 (reference_set) and
MDL-DEPR024 (returns none). `delete_object close_page`, which describe
printed but the grammar refused, now parses.

Refs #752

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
formatter.Format upper-cased every word on a keyword list, names
included: Issue64.User became Issue64.USER and an attribute Title
became TITLE, so fmt changed what 374 of the example scripts build.
It now lowercases only the tokens the parse tree shows are keywords,
and keeps a property key, a name, a CamelCase or digit-bearing value,
a data type and an annotation as written. Text the model stores as
written (expressions, XPath, OQL, catalog and SQL queries, template
parameters, multi-line literals) keeps its case and its layout.
TestFormatterKeepsMeaning replaces TestFormatterChangesMeaning: every
example script formats to one that builds the same statements.

describe writes lowercase keywords where it did not: an enumeration's
`folder`, a workflow's `default ->` outcome and a published REST
operation's method. alter settings keeps the section lowercase in the
AST, so its keyword case no longer changes what a script builds.
TestPedApp/TestAppDescribeUsesCanonicalSpellings pin describe to the
R8 spellings and lowercase keywords.

Refs #752

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… (R8)

Skills, docs-site, mxcli syntax and the quick reference write the R8
spellings: page actions as words, `call microflow`, `error message`,
`on delete`, ReferenceSet, `returns nothing`. The catalog's
`show_page` reference kind is a data value and is left alone.

Refs #752

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…in/end while under mdl 1 (#754)

R2 (ADR-0010): braces hold declarative children, imperative flow is
`begin … end <keyword>`. The custom error handler was the only brace block
inside a microflow:

- grammar: `on error [without rollback] begin … end error` is canonical;
  the brace form stays as an alias marked MDL-DEPR540, which builds the
  identical handler and has a structural fmt --upgrade rewrite
  (`{` -> begin, `}` -> end error), nested handlers included.
- describe microflow/nanoflow, the commented-out handler fallback, handle
  targets and the generated `mxcli test` flows emit the canonical form.
- `while` without `begin` or ending in a bare `end` is a new rejection
  gated on `mdl 1` (MDL-V1-WHILE); under mdl 0 it keeps parsing, warns,
  and `fmt --upgrade --header` inserts the missing words.
- The PedApp describe check now also refuses any deprecated spelling in
  describe output (control: with the old emitter it reports MDL-DEPR540
  on the Studio Pro-authored handlers).
- mdl-examples, skills, docs-site, quick reference and `mxcli syntax`
  migrated to the canonical spellings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… phrases read as nouns (#751)

The E001 hint's Right example, the control-bar selection suggestion, the
microflow help text and the vega-charts skill pack still wrote an argument
as `$Param = e` / `Param: e`, the spellings MDL-DEPR006/007 now warn on.
The four new registry Structural texts were verb phrases, so check printed
"Rewrite the drop the `$` ...".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MDL-WIDGET28's suggestion, the under-specified keyword hints, the open link /
show page argument errors, the fragment action-parameter error and the named
action slot error still told the author to write SAVE_CHANGES, SHOW_PAGE,
OPEN_LINK, MICROFLOW M.F, ... -- the spellings #752 deprecates. They now name
the words. `complete task` without an outcome gets the same named hint as
`complete_task`. The page.action syntax entry no longer claims `Action: OPEN
LINK` without a URL is MDL-WIDGET28: the word form is a syntax error.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
#	.claude/skills/mendix/write-microflows/SKILL.md
#	CHANGELOG.md
#	mdl/deprecation/deprecation.go
# Conflicts:
#	.claude/skills/mendix/cheatsheet-errors/SKILL.md
#	.claude/skills/mendix/create-page/reference/widgets.md
#	.claude/skills/mendix/overview-pages/SKILL.md
#	.claude/skills/mendix/write-microflows/SKILL.md
#	CHANGELOG.md
#	cmd/mxcli/syntax/features_page.go
#	docs-site/src/appendixes/quick-reference.md
#	docs-site/src/language/data-binding.md
#	docs-site/src/language/widget-types.md
#	docs-site/src/reference/page/create-page.md
#	docs-site/src/tutorial/create-page.md
#	docs/01-project/MDL_QUICK_REFERENCE.md
#	mdl/deprecation/deprecation.go
#	mdl/executor/cmd_pages_describe_pageparams_test.go
… workflow args after the callee)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.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.

R8: words not SCREAMING_SNAKE; one spelling per keyword; error message (3.2)

1 participant