fix: preserve rule split describer output and free annotations#266
Conversation
e1c1605 to
d3cbb30
Compare
AI Code ReviewCritical IssuesNone found. Moderate IssuesNone found. Minor Issues
What Looks Good
RecommendationApprove. The PR correctly fixes describer/executor issues for microflow controls with appropriate test coverage and minimal risk. The changes are focused, follow project conventions, and improve roundtrip fidelity for MDL microflows. No blocking issues identified. Automated review via OpenRouter (Nemotron Super 120B) — workflow source |
When an IF, LOOP, or WHILE statement carried an @caption or @annotation and contained other annotated statements in its body, the inner statement's annotations would overwrite fb.pendingAnnotations (shared builder state) before the outer loop in buildFlowGraph attached them to the right object. The outer split/loop then ended up with the wrong caption — or with the inner statement's caption applied to it. The fix snapshots & clears pendingAnnotations at the top of each compound statement handler, re-applies them to the split/loop/while activity right after it's created, and (for IF) moves applyAnnotations above the branch recursion so the split is decorated before the bodies are walked. Also extends applyAnnotations to recognise ExclusiveSplit and InheritanceSplit — on main these were only handled for ActionActivity, so @caption on a split was silently dropped even without nesting. Verified end-to-end against Apps.GetOrCreateMendixVersionFromString: describe -> exec -> describe now produces byte-identical @caption, @annotation, and @position output. Regression tests cover: - nested IFs with explicit @caption on each level - single IF @caption baseline - @annotation attachment targeting the correct split when nested Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- mergeStatementAnnotations: add WhileStmt case (was falling through to default: nil, so @caption on a WHILE was silently dropped). - applyAnnotations: add LoopedActivity case — LOOP and WHILE activities can now carry the captions they already parse. - Add TestLoopCaptionPreserved, TestWhileLoopCaptionPreserved, and TestInheritanceSplitCaptionApplied to cover the gaps ako flagged (loop and InheritanceSplit caption paths were previously untested). - Add `mdl-examples/bug-tests/263-nested-caption-preservation.mdl` reproducer per CLAUDE.md checklist.
An IF whose condition calls a Mendix rule (Module.RuleName(Param = arg)) was always serialized as Microflows$ExpressionSplitCondition, causing Mendix Studio Pro to raise CE0117 "Error(s) in expression" and silently demoting the decision subtype from Rule to Expression on every describe -> exec roundtrip. The flow builder now inspects the IF condition: when it is a qualified function call whose name resolves to a rule (checked via the new MicroflowBackend.IsRule), it emits Microflows$RuleSplitCondition with a nested RuleCall sub-document whose ParameterMappings carry the named arguments. Plain expressions and non-rule function calls still produce ExpressionSplitCondition. The MPR writer gained the corresponding BSON branch so the new split type reaches disk with the shape Studio Pro expects. To make the fix self-contained, this change also extends microflows.RuleSplitCondition with RuleQualifiedName and RuleCallParameterMapping with ParameterName — the two string fields the builder/writer/parser now round-trip through (model.ID-only pointers weren't enough to reconstruct the rule reference after exec). Regression tests cover: - Rule-call IF produces RuleSplitCondition with correct parameter names - Non-rule call IF still produces ExpressionSplitCondition - Flow builder without a backend (syntax-only check) falls back safely - Writer -> BSON -> parser roundtrip preserves RuleSplitCondition shape Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Per PR review — the field was only set by the parser but never read anywhere. The rule call is already identified by RuleQualifiedName, which is what both the writer and describer use.
The microflow describer round-trip was lossy in three ways after the rule-split parser landed: 1. `DESCRIBE MICROFLOW` of an IF whose condition was a RuleSplitCondition silently emitted `if true then`, because the ExclusiveSplit formatter only recognised ExpressionSplitCondition. `describe -> exec` demoted every rule-based decision to `if true` — the MPR still loaded but Studio Pro showed the wrong condition. 2. `@caption` was only emitted for ActionActivity. Splits store their human-readable label (e.g. "Right format?") on the split itself, not on the expression, so round-tripping the describer output produced an ExclusiveSplit with no caption even when Mendix had one. 3. Free-floating Annotation objects (those without an AnnotationFlow pointing to them) were silently dropped by the describer, losing author/date comments on flows that contained no activities. The IF flow builder also emitted a dangling SequenceFlow whenever a nested IF had `thenReturns` in an outer context with `needMerge=false`, because the flow-emission check only looked at `!thenReturns`. Studio Pro rejected the resulting MPR with "Sequence contains no matching element". Guard the three branch-to-merge flows on `needMerge` so the parent handles the continuation. Changes: - `cmd_microflows_format_action.go`: extract `formatSplitCondition` helper that renders ExpressionSplitCondition verbatim and RuleSplitCondition as `Module.RuleName(Param = arg, ...)` using the already-parsed `RuleQualifiedName` and `ParameterName` fields. - `cmd_microflows_show_helpers.go`: emit `@caption` for ExclusiveSplit and InheritanceSplit when a caption is present. - `cmd_microflows_show.go`: prepend free-floating annotations as `@annotation` entries on the first activity in both `describeMicroflow` and `renderMicroflowMDL`. - `cmd_microflows_builder_control.go`: add `&& needMerge` guards to all three branch-to-merge flow emissions so empty mergeID never reaches the BSON writer. - `sdk/mpr/writer_core.go`: document that an empty id in `idToBsonBinary` indicates a caller bug rather than a valid input — the random-UUID fallback produces references to nothing and shows up in Studio Pro as KeyNotFoundException. Depends on `submit/microflow-split-subtype-preservation` for the `RuleQualifiedName` / `ParameterName` fields on RuleSplitCondition / RuleCallParameterMapping and for the RuleCall-aware parser.
d3cbb30 to
27c0dc9
Compare
AI Code ReviewReview SummaryThis PR fixes describer-side issues related to rule split output, split captions, free annotations, and flow builder guards. It's the top of a 3-PR stack that depends on prior parser fixes (#265 for rule subtype preservation, #263 for nested caption preservation). What Looks Good
Minor Issues
RecommendationApprove - The PR correctly fixes the reported issues with comprehensive test coverage, proper pipeline wiring, and clean implementation that follows project conventions. The scope is appropriate for the interconnected nature of the bugs being fixed. Automated review via OpenRouter (Nemotron Super 120B) — workflow source |
PR #266 Review — fix: preserve rule split describer output and free annotationsOverviewDescriber-side counterpart to PRs #263 and #265. Adds What's Good
IssuesLoop caption describer gap (non-blocker, but inconsistent with builder) Suggested addition to if loop, ok := obj.(*microflows.LoopedActivity); ok && loop.Caption != "" {
escapedCaption := strings.ReplaceAll(loop.Caption, "'", "''")
*lines = append(*lines, indentStr+fmt.Sprintf("@caption '%s'", escapedCaption))
}Duplicated free-annotation prefix block func prependFreeAnnotations(lines []string, oc *microflows.MicroflowObjectCollection) []string {
for _, text := range collectFreeAnnotations(oc) {
lines = append([]string{fmt.Sprintf("@annotation '%s'", strings.ReplaceAll(text, "'", "''"))}, lines...)
}
return lines
}(Or just keep it but it's the kind of duplication that drifts.) Minor Notes
VerdictLGTM pending the loop caption describer gap. The |
Summary
Describer-side counterpart to the rule-split subtype fix, plus two related describer bugs that surfaced from the same end-to-end exercise:
RuleSplitCondition, the describer also needs to emit the qualified rule call (Mod.Rule(param=$value)) in theif ...header rather than treating every split condition as an expression. IntroducesformatSplitConditionto dispatch on the condition subtype.@captionfor splits —ExclusiveSplit/InheritanceSplitcarry a caption that the describer was dropping. Now emitted as@caption '...'before theif/inheritkeyword.Annotationobjects with noAnnotationFlow(the "sticky note" style) were ignored by the describer. Now collected and emitted as@annotation '...'at the top of the microflow body.needMergeguard in flow builder — when both branches of an IF terminate inreturn, the builder must not emit a danglingSequenceFlowpointing at an emptyDestinationPointer. Added guard rails in three emit sites.writer_corewarning comment — added a tight warning onidToBsonBinaryexplaining that an empty id here is almost always a bug (produces a random UUID and surfaces asKeyNotFoundExceptionin Studio Pro); the fix belongs in the caller.Part of umbrella #257.
Test plan
go build ./... && go test ./...pass.KeyNotFoundExceptionor CE0117.Stack
This PR is the top of a 3-PR stack. It depends on #265 (rule subtype preservation), which in turn depends on #263 (nested caption preservation). Once the two base PRs merge, this one can be rebased directly onto
main.