Skip to content

Commit 1860a9e

Browse files
committed
Merge remote-tracking branch 'origin/main' into HEAD
# Conflicts: # mdl/grammar/parser/MDLParser.interp # mdl/grammar/parser/mdl_parser.go
2 parents 1e1c681 + 78c42dc commit 1860a9e

15 files changed

Lines changed: 6215 additions & 6002 deletions

.claude/skills/mendix/write-microflows.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,9 @@ $NewProduct = create Test.Product (
378378
change $Product (
379379
Name = $NewName,
380380
ModifiedDate = [%CurrentDateTime%]);
381+
382+
-- Refresh the changed object in the client
383+
change $Product (Name = $NewName) refresh;
381384
```
382385

383386
**Note**: Only specify attributes you want to change. Syntax aligned with CREATE.

docs/01-project/MDL_QUICK_REFERENCE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ authentication basic, session
218218
| List declaration | `declare $list list of Module.Entity = empty;` | |
219219
| Assignment | `set $Var = expression;` | Variable must be declared first |
220220
| Create object | `$Var = create Module.Entity (attr = value);` | |
221-
| Change object | `change $entity (attr = value);` | |
221+
| Change object | `change $entity (attr = value) [refresh];` | `refresh` updates the changed object in the client |
222222
| Commit | `commit $entity [with events] [refresh];` | |
223223
| Delete | `delete $entity;` | |
224224
| Rollback | `rollback $entity [refresh];` | Reverts uncommitted changes |
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Proposal: Microflow CHANGE Refresh Modifier
2+
3+
Status: Draft
4+
5+
## Summary
6+
7+
Allow `change` microflow statements to explicitly preserve the Mendix `RefreshInClient` flag:
8+
9+
```mdl
10+
change $Customer (Name = 'Jane') refresh;
11+
change $Customer refresh;
12+
```
13+
14+
## Motivation
15+
16+
Mendix change-object actions can refresh the changed object in the client independently from committing the object. MDL previously had no syntax for that flag, so a describe/exec round-trip could rewrite `RefreshInClient = true` to `false` for change actions with member assignments.
17+
18+
## Semantics
19+
20+
The `refresh` modifier maps directly to `ChangeObjectAction.RefreshInClient`. Omitting it preserves the existing default behavior. The modifier is accepted both with member assignments and on a memberless change action.
21+
22+
## Tests And Examples
23+
24+
`mdl-examples/doctype-tests/change_refresh_modifier.test.mdl` demonstrates both forms. Go tests cover formatter output, parser behavior, and builder serialization.
25+
26+
## Open Questions
27+
28+
- Should a memberless `change $Object;` continue to infer refresh in separate validity-focused fixes, or should the explicit `refresh` modifier be the only authoring form?

docs/11-proposals/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ BSON schema Registry ◄──── multi-version Support
5050
| [Page Styling Support](page-styling-support.md) | Partial | CSS classes, inline styles, dynamic classes, design properties. Phase 1 (Class/Style) done ||
5151
| [Page Composition](proposal_page_composition.md) | Proposed | Fragment definitions and ALTER PAGE for partial page editing | Page Syntax V2, Page Styling |
5252
| [XPath Gaps](xpath-gaps-proposal.md) | Partial | XPath constraint support gap analysis. ~85% complete, association paths and nested predicates remain ||
53+
| [Microflow CHANGE Refresh Modifier](PROPOSAL_microflow_change_refresh_modifier.md) | Draft | Preserve `RefreshInClient` on change-object actions ||
5354
| [LLM MDL Assistance](PROPOSAL_llm_mdl_assistance.md) | Proposed | Enhanced error messages with examples, reorganized skills by use case ||
5455

5556
### Testing & Evaluation
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
create module ChangeRefreshExample;
2+
3+
create persistent entity ChangeRefreshExample.Customer (
4+
Name: string
5+
);
6+
/
7+
8+
create microflow ChangeRefreshExample.RefreshChangedCustomer (
9+
$Customer: ChangeRefreshExample.Customer
10+
)
11+
returns boolean
12+
begin
13+
change $Customer (Name = 'Jane') refresh;
14+
change $Customer refresh;
15+
return true;
16+
end;
17+
/

mdl/ast/ast_microflow.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -192,9 +192,10 @@ func (s *CreateObjectStmt) isMicroflowStatement() {}
192192

193193
// ChangeObjectStmt represents: CHANGE $Var (assignments)
194194
type ChangeObjectStmt struct {
195-
Variable string // Variable name
196-
Changes []ChangeItem // SET assignments
197-
Annotations *ActivityAnnotations // Optional @position, @caption, @color, @annotation
195+
Variable string // Variable name
196+
Changes []ChangeItem // SET assignments
197+
RefreshInClient bool // Whether to refresh in client
198+
Annotations *ActivityAnnotations // Optional @position, @caption, @color, @annotation
198199
}
199200

200201
func (s *ChangeObjectStmt) isMicroflowStatement() {}

mdl/executor/cmd_microflows_builder_actions.go

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -239,14 +239,13 @@ func (fb *flowBuilder) addRollbackAction(s *ast.RollbackStmt) model.ID {
239239

240240
// addChangeObjectAction creates a CHANGE statement.
241241
func (fb *flowBuilder) addChangeObjectAction(s *ast.ChangeObjectStmt) model.ID {
242+
// Empty non-committing changes need RefreshInClient to satisfy Studio Pro
243+
// consistency checks; explicit `refresh` keeps the same flag for all changes.
242244
action := &microflows.ChangeObjectAction{
243-
BaseElement: model.BaseElement{ID: model.ID(types.GenerateID())},
244-
ChangeVariable: s.Variable,
245-
Commit: microflows.CommitTypeNo,
246-
// Studio Pro rejects an empty non-committing change action unless it
247-
// refreshes in client. The CE0032 message mentions only items/commit,
248-
// but mx check accepts RefreshInClient=true as the third valid escape.
249-
RefreshInClient: len(s.Changes) == 0,
245+
BaseElement: model.BaseElement{ID: model.ID(types.GenerateID())},
246+
ChangeVariable: s.Variable,
247+
Commit: microflows.CommitTypeNo,
248+
RefreshInClient: s.RefreshInClient || len(s.Changes) == 0,
250249
}
251250

252251
// Look up entity type from variable scope
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
3+
package executor
4+
5+
import (
6+
"testing"
7+
8+
"github.com/mendixlabs/mxcli/mdl/ast"
9+
"github.com/mendixlabs/mxcli/sdk/microflows"
10+
)
11+
12+
func TestChangeObjectBuilderWritesRefreshInClient(t *testing.T) {
13+
fb := &flowBuilder{}
14+
15+
fb.addChangeObjectAction(&ast.ChangeObjectStmt{
16+
Variable: "Customer",
17+
RefreshInClient: true,
18+
Changes: []ast.ChangeItem{
19+
{Attribute: "Name", Value: &ast.LiteralExpr{Kind: ast.LiteralString, Value: "Jane"}},
20+
},
21+
})
22+
23+
action := lastChangeObjectAction(t, fb)
24+
if !action.RefreshInClient {
25+
t.Fatal("Expected builder to write RefreshInClient")
26+
}
27+
}
28+
29+
func lastChangeObjectAction(t *testing.T, fb *flowBuilder) *microflows.ChangeObjectAction {
30+
t.Helper()
31+
32+
if len(fb.objects) == 0 {
33+
t.Fatal("Expected builder to create an action activity")
34+
}
35+
activity, ok := fb.objects[len(fb.objects)-1].(*microflows.ActionActivity)
36+
if !ok {
37+
t.Fatalf("Last object = %T, want ActionActivity", fb.objects[len(fb.objects)-1])
38+
}
39+
action, ok := activity.Action.(*microflows.ChangeObjectAction)
40+
if !ok {
41+
t.Fatalf("Action = %T, want ChangeObjectAction", activity.Action)
42+
}
43+
return action
44+
}

mdl/executor/cmd_microflows_format_action.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,8 +245,14 @@ func formatAction(
245245
}
246246
members = append(members, fmt.Sprintf("%s = %s", memberName, escapeExpressionValue(m.Value)))
247247
}
248+
if a.RefreshInClient {
249+
return fmt.Sprintf("change $%s (%s) refresh;", varName, strings.Join(members, ", "))
250+
}
248251
return fmt.Sprintf("change $%s (%s);", varName, strings.Join(members, ", "))
249252
}
253+
if a.RefreshInClient {
254+
return fmt.Sprintf("change $%s refresh;", varName)
255+
}
250256
return fmt.Sprintf("change $%s;", varName)
251257

252258
case *microflows.CommitObjectsAction:

mdl/executor/cmd_microflows_format_action_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,30 @@ func TestFormatAction_ChangeObject_NoChanges(t *testing.T) {
9999
}
100100
}
101101

102+
func TestFormatAction_ChangeObject_WithRefresh(t *testing.T) {
103+
e := newTestExecutor()
104+
action := &microflows.ChangeObjectAction{
105+
ChangeVariable: "Customer",
106+
RefreshInClient: true,
107+
Changes: []*microflows.MemberChange{
108+
{AttributeQualifiedName: "MyModule.Customer.Name", Value: "'Jane'"},
109+
},
110+
}
111+
got := e.formatAction(action, nil, nil)
112+
if got != "change $Customer (Name = 'Jane') refresh;" {
113+
t.Errorf("got %q", got)
114+
}
115+
}
116+
117+
func TestFormatAction_ChangeObject_NoChangesWithRefresh(t *testing.T) {
118+
e := newTestExecutor()
119+
action := &microflows.ChangeObjectAction{ChangeVariable: "Obj", RefreshInClient: true}
120+
got := e.formatAction(action, nil, nil)
121+
if got != "change $Obj refresh;" {
122+
t.Errorf("got %q", got)
123+
}
124+
}
125+
102126
func TestFormatAction_DeleteObject(t *testing.T) {
103127
e := newTestExecutor()
104128
action := &microflows.DeleteObjectAction{DeleteVariable: "Customer"}

0 commit comments

Comments
 (0)