Skip to content

fix: preserve annotations attached inside loop bodies#331

Open
hjotha wants to merge 5 commits intomendixlabs:mainfrom
hjotha:submit/preserve-loop-body-annotations
Open

fix: preserve annotations attached inside loop bodies#331
hjotha wants to merge 5 commits intomendixlabs:mainfrom
hjotha:submit/preserve-loop-body-annotations

Conversation

@hjotha
Copy link
Copy Markdown
Contributor

@hjotha hjotha commented Apr 27, 2026

Part of #332.

Summary

  • copy loop/while body annotation flows into the parent microflow graph
  • collect annotation captions recursively from nested loop object collections
  • merge loop-local annotation flows into loop body traversal
  • add regressions for annotated nested decisions and annotation flows stored inside loop object collections

Closes #330

Validation

  • go test ./mdl/executor -run 'TestLoopBodyIfAnnotationPromotedToParentFlows|TestTraverseFlow_LoopBodyUsesNestedAnnotationFlows'
  • make build
  • make lint-go
  • make test

@github-actions
Copy link
Copy Markdown

AI Code Review

What Looks Good

  • The PR correctly identifies and fixes the issue where annotations inside loop bodies weren't being preserved in the parent microflow graph
  • The test TestLoopBodyIfAnnotationPromotedToParentFlows properly validates the fix for an annotated nested decision inside a loop body
  • The changes are minimal and focused: adding two lines to propagate annotationFlows in loop/while statement handling, and refactoring annotation collection to be recursive for nested loops
  • The refactoring in cmd_microflows_show_helpers.go cleanly separates concerns with collectAnnotationCaptions and mergeAnnotationsByTarget functions
  • No new MDL syntax is introduced, so syntax design checks don't apply
  • The fix maintains backend abstraction compliance by working entirely within the executor layer
  • Test coverage is added for the specific bug scenario
  • The changes are scoped to a single concern (preserving loop body annotations)

Recommendation

Approve. The PR correctly fixes the annotation preservation issue in loop bodies with minimal, well-targeted changes and appropriate test coverage. All relevant checklist items are satisfied for this bug fix.


Automated review via OpenRouter (Nemotron Super 120B) — workflow source

@hjotha
Copy link
Copy Markdown
Contributor Author

hjotha commented Apr 27, 2026

Updated the branch to also cover annotation flows stored directly inside loop object collections. Validation after the update:

  • go test ./mdl/executor -run 'TestLoopBodyIfAnnotationPromotedToParentFlows|TestTraverseFlow_LoopBodyUsesNestedAnnotationFlows'
  • make build
  • make lint-go
  • make test

@hjotha hjotha force-pushed the submit/preserve-loop-body-annotations branch from 01d88e0 to 15605e0 Compare April 27, 2026 01:01
@github-actions
Copy link
Copy Markdown

AI Code Review

Critical Issues

None found.

Moderate Issues

None found.

Minor Issues

None found.

What Looks Good

  • The PR adds a comprehensive bug test in mdl-examples/bug-tests/330-preserve-loop-body-annotations.mdl that reproduces the issue and validates the fix.
  • The unit tests (TestLoopBodyIfAnnotationPromotedToParentFlows and TestTraverseFlow_LoopBodyUsesNestedAnnotationFlows) properly cover the AST→BSON build path and the describer side.
  • The fix is minimal and focused:
    • In the builder (cmd_microflows_builder_control.go), annotation flows from loop bodies are now propagated to the parent graph for both LoopStmt and WhileStmt.
    • In the show helpers (cmd_microflows_show_helpers.go), annotation caption collection is made recursive to handle nested loop object collections, and loop-local annotations are merged into the body traversal.
  • The changes follow the existing patterns in the codebase (e.g., similar propagation of flows, use of helper functions for annotation handling).
  • No MDL syntax is modified, so full-stack consistency checks for new syntax are not applicable.
  • The PR is scoped to a single bug fix (annotations in loop bodies) with no unrelated changes.
  • Test coverage includes both unit tests and an MDL bug test, avoiding time.Sleep for synchronization.

Recommendation

approve - The PR correctly addresses the issue with minimal, well-tested changes that follow existing patterns. No blocking concerns identified.


Automated review via OpenRouter (Nemotron Super 120B) — workflow source

This was referenced Apr 27, 2026
Copy link
Copy Markdown
Collaborator

@ako ako left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: fix: preserve annotations attached inside loop bodies

Summary: Clean two-part fix. The builder side is a symmetric two-liner matching the existing sequence-flow promotion pattern; the describer side correctly handles the split between annotation objects (stored in loop ObjectCollections) and annotation flows (now promoted to the parent level).

Builder fix (two lines)

fb.annotationFlows = append(fb.annotationFlows, loopBuilder.annotationFlows...)

Applied symmetrically in both addLoopStatement and addWhileStatement, matching the existing fb.flows = append(fb.flows, loopBuilder.flows...) promotion. The final MicroflowObjectCollection already packages fb.annotationFlows into AnnotationFlows, so no further wiring is needed. ✓

Describer fix

The object-vs-flow split is key: annotation Objects live inside the loop's ObjectCollection; annotation Flows are now promoted to the parent level by the builder. The describer fix handles both sides:

  • collectAnnotationCaptions: Recursively descends into LoopedActivity.ObjectCollection to collect caption IDs from nested annotations. The continue after the Annotation case is redundant (a Go object can only be one type) but harmless.
  • emitLoopBody: Merges parent annotationsByTarget with the loop-local annotation map. This handles the case where annotation flows are stored in the loop's own ObjectCollection (older MPR files that predate the builder fix).

Together these cover both the new MPR layout (flows promoted, captions found recursively) and existing MPR files (flows in loop ObjectCollection, found by the loop-local buildAnnotationsByTarget). ✓

mergeAnnotationsByTarget — returns overlay by reference when base is empty

if len(base) == 0 {
    return overlay
}

This returns the same map as overlay, not a copy. In emitLoopBody the overlay is a freshly built map from buildAnnotationsByTarget, so mutation of the result wouldn't affect anything else. Safe for the current caller. Worth a comment if this function gets additional callers in future.

Test coverage

TestLoopBodyIfAnnotationPromotedToParentFlows: Verifies the builder path — an annotation on a nested IF inside a LOOP ends up in the parent object collection's annotation map. Correctly traces through the promoted flow + recursive caption collection. ✓

TestTraverseFlow_LoopBodyUsesNestedAnnotationFlows: Pre-builds annotationsByTarget from the loop's ObjectCollection and passes it directly to traverseFlow, verifying that @annotation appears in the rendered output. This exercises annotation rendering but not the emitLoopBody merge path itself (the merge logic is tested indirectly by the MDL bug-test script rather than a Go unit test).

A Go-level test that constructs a full LoopedActivity with nested annotations and exercises emitLoopBody end-to-end would close this gap, but the existing coverage is sufficient for the described bug.

Bug test script

Documents the symptom (annotations on IF inside LOOP disappear after roundtrip) and the dependency on PR #327 for full fixpoint roundtrip. The scope note is helpful context. ✓

Verdict

Approve. The fix is correct, minimal, and symmetric between the builder and describer sides.

hjotha pushed a commit to hjotha/mxcli that referenced this pull request Apr 30, 2026
Documents that the empty-base/empty-overlay short-circuits return the
other map by reference. Current callers do not mutate the result, so
aliasing is intentionally safe; the comment warns future callers that
intend to mutate to copy first.

Addresses ako's review on PR mendixlabs#331.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
hjothamendix and others added 3 commits April 30, 2026 10:38
Symptom: annotations attached to statements inside loop or while bodies could disappear after describe/exec because nested annotation flows were not visible from the traversal context.

Root cause: loop body builders copied only sequence flows back to the parent builder, describe collected annotation captions only from top-level objects, and loop-local annotation flows were not merged into the annotation map used while traversing loop bodies.

Fix: promote nested loop annotation flows to the parent graph, collect annotation captions recursively from nested loop object collections, and merge loop-local annotations into loop body traversal.

Tests: add regression coverage for an annotated nested decision built inside a loop body and for annotation flows stored directly in a loop object collection.
Adds an MDL script under mdl-examples/bug-tests/ exercising an
annotated IF nested inside a LOOP. After exec, the describe output
must contain `@annotation 'note on nested if'`, confirming the
builder propagates nested annotation flows to the parent graph and
the describer collects captions recursively.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Documents that the empty-base/empty-overlay short-circuits return the
other map by reference. Current callers do not mutate the result, so
aliasing is intentionally safe; the comment warns future callers that
intend to mutate to copy first.

Addresses ako's review on PR mendixlabs#331.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@hjotha hjotha force-pushed the submit/preserve-loop-body-annotations branch from 12b8890 to bfc8285 Compare April 30, 2026 08:39
@github-actions
Copy link
Copy Markdown

AI Code Review

Critical Issues

None found.

Moderate Issues

None found.

Minor Issues

None found.

What Looks Good

  • Bug test coverage: The PR adds a dedicated MDL bug test script (mdl-examples/bug-tests/330-preserve-loop-body-annotations.mdl) that reproduces the issue and validates the fix via describe/exec roundtrip, satisfying the checklist requirement for bug fixes to include MDL test scripts in bug-tests/.
  • Unit test coverage: Two new Go tests are added:
    • TestLoopBodyIfAnnotationPromotedToParentFlows verifies the builder correctly promotes nested annotation flows from loop bodies to the parent graph.
    • TestTraverseFlow_LoopBodyUsesNestedAnnotationFlows ensures the describer handles annotation flows stored inside loop object collections during traversal.
  • Focused changes: The fix is surgical and addresses the root cause precisely:
    • Builder: Adds fb.annotationFlows = append(fb.annotationFlows, loopBuilder.annotationFlows...) in both addLoopStatement and addWhileStatement to copy annotation flows from loop bodies to parent graph.
    • Describer: Implements recursive annotation caption collection via collectAnnotationCaptions() that walks into nested loop object collections, and uses mergeAnnotationsByTarget() to merge loop-local annotations into per-body traversal.
  • No syntax changes: Since this is a behavioral fix (not new MDL syntax), the PR correctly avoids modifying grammar/AST/LSP, focusing solely on executor/describer logic.
  • Deterministic helper functions: New helpers collectAnnotationCaptions and mergeAnnotationsByTarget are pure, side-effect free, and handle nil safely.
  • Validation: The PR runs the specified regression tests (TestLoopBodyIfAnnotationPromotedToParentFlows|TestTraverseFlow_LoopBodyUsesNestedAnnotationFlows), make build, make lint-go, and make test per the validation section.
  • Atomic scope: All changes relate exclusively to preserving annotations in loop bodies (no unrelated modifications).

Recommendation

Approve. The PR fully addresses bug #330 with appropriate test coverage, follows the project's architecture (backend abstraction compliance is maintained as changes are confined to executor), and meets all checklist criteria for bug fixes. No changes are needed.


Automated review via OpenRouter (Nemotron Super 120B) — workflow source

@hjotha
Copy link
Copy Markdown
Contributor Author

hjotha commented Apr 30, 2026

Review follow-up status on the current head:

  • Documented the mergeAnnotationsByTarget aliasing contract for the empty-base fast path.
  • Kept the existing builder/describer coverage and bug-test fixture unchanged because the review found the behavior correct.

Local validation after the update was green:

make build
make test
make lint-go

@ako ako added this to the v0.8.0 milestone Apr 30, 2026
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.

Loop-body annotations can be lost during microflow describe/exec

3 participants