Skip to content

Lay domain-model entities out instead of stacking them in one row - #395

Merged
ako merged 2 commits into
mainfrom
claude/mxcli-unit-test-perf-n7ggx8
Sep 4, 2026
Merged

Lay domain-model entities out instead of stacking them in one row#395
ako merged 2 commits into
mainfrom
claude/mxcli-unit-test-perf-n7ggx8

Conversation

@ako

@ako ako commented Sep 4, 2026

Copy link
Copy Markdown
Owner

A generated domain model opens in Studio Pro as a single horizontal line of entities with the boxes touching — unreadable at any zoom. Reported against a 40-entity model (ako/CapTrackV2, Mendix 11.13).

The default position for a CREATE ENTITY with no @Position was one line:

location = model.Point{X: 100 + len(dm.Entities)*150, Y: 100}

Same y for every entity ever created. 40 entities is a 6,950px row — and 150px is narrower than an entity box, so they overlapped as well.

Two changes, because there are two different questions.

The default: a wrapping grid

mdl/dmlayout.GridSlot. It cannot be much better than a grid, and that is the design point: the first entity of a script is placed before the last one exists, so no create-time rule can see the association graph. Slot n stays a function of n alone, so adding an entity never moves one already written. Those same 40 entities now span ~1,400px.

mxcli layout: the real thing, from the whole model

Entities are layered on the association graph — an entity referencing nothing else in the module is a lookup and goes left, everything else one column past the furthest thing it references — so association lines mostly run one way instead of crossing the diagram. Entities with no association at all go in a band underneath rather than among the lookups, which is where the non-persistent helpers belong.

Run against CapTrack's own 01-domain.mdl, the layering falls out of the model with no hints:

column entities
x=180 Department, EmploymentType, MovementReason, PlanType, PlanningYear, Region
x=430 Team, GoalBucket
x=680 GoalChange, GoalRegionValue, PlanScope, CapTrackUser
x=930 Employee, ScopeMonth
x=1180 EmployeeMonth, Movement

mx check 0 errors, and the positions round-trip into MDL as @Position(1180, 157).

It is opt-in and it overwrites hand-arranged positions in the modules it touches, so --dry-run lists the moves first. Marketplace modules and System are skipped — and naming one is an error rather than a silent skip, because a silent skip reports success having done nothing.

Three properties the tests pin, each measured

  • Idempotent. A second run detects nothing to move and never calls the writer: moved 16 of 16, then already laid out (16 entities).
  • Local. Adding one entity with one association moved 3 of 17 — the new entity and the two below it in its column. A layout that reshuffled everything would make every domain-model commit an unreadable diff.
  • Deterministic. An unsorted walk gives a different diagram every run, which rewrites the unit every time — the churn ADR-0008 exists to prevent. Control: dropping the sort fails on run 0.

The controls also cover the old behaviour directly — restoring the single-row formula fails the wrap, width and overlap tests.

Two constraints that shaped the geometry

An entity stores only Location and no Size — Studio Pro derives the box when it draws, and mxcli never sees the result — so spacing is estimated from name length and attribute count, deliberately generous. And a Mendix position is the box's centre, not its top-left, so placement adds half a box.

Not verified

I checked geometry, mx check, the round-trip and idempotence, but I have not opened the result in Studio Pro. If the estimated box sizes are off for unusually wide entity names, boxes could still crowd — that is the one claim I cannot make from here.

Validation

go test ./... clean, make vet clean, make check-mdl clean (422 examples), mx check 0 errors on the laid-out CapTrack model, check-findings 908 records OK.

🤖 Generated with Claude Code

https://claude.ai/code/session_018hifgRSawfaRWXS44YKtSJ


Generated by Claude Code

A generated domain model opened in Studio Pro as a single horizontal line of
entities with the boxes touching — unreadable at any zoom. Reported against a
40-entity model (ako/CapTrackV2, Mendix 11.13).

The default position for a CREATE ENTITY with no @position was:

    location = model.Point{X: 100 + len(dm.Entities)*150, Y: 100}

Same y for every entity ever created. 40 entities is a 6,950px row, and 150px
is narrower than an entity box, so they overlapped as well.

Two changes, because there are two different questions.

**The default** (mdl/dmlayout.GridSlot) is now a wrapping grid. It cannot be
better than that: the first entity of a script is placed before the last one
exists, so no create-time rule can see the association graph. Slot n stays a
function of n alone, so adding an entity never moves one already written. Those
same 40 entities now span ~1,400px.

**mxcli layout** does the real thing, from the whole model once it exists.
Entities are layered on the association graph — an entity referencing nothing
else in the module is a lookup and goes left, everything else one column past
the furthest thing it references — so association lines mostly run one way
instead of crossing the diagram. Entities with no association at all go in a
band underneath rather than among the lookups, which is where the
non-persistent helpers belong.

Run against CapTrack's own 01-domain.mdl, the layering falls out of the model:
six lookups in one column, then Team/GoalBucket, then PlanScope's band, out to
EmployeeMonth/Movement. mx check 0 errors, and the positions round-trip into
MDL as @position.

It is opt-in and it overwrites hand-arranged positions in the modules it
touches, so --dry-run lists the moves first. Marketplace modules and System are
skipped, and NAMING one is an error rather than a silent skip — a silent skip
reports success having done nothing.

Three properties the tests pin, each measured rather than assumed:

  - idempotent: a second run detects nothing to move and never calls the
    writer (16 of 16 moved, then "already laid out").
  - local: adding one entity with one association moved 3 of 17, not the model.
    A layout that reshuffled everything would make each domain-model commit an
    unreadable diff.
  - deterministic: an unsorted walk gives a different diagram every run, which
    rewrites the unit every time — the churn ADR-0008 exists to prevent. The
    control fails on run 0.

Two constraints that shaped the geometry: an entity stores only Location and NO
Size (Studio Pro derives the box when it draws), so spacing is estimated from
name length and attribute count; and a Mendix position is the box's CENTRE, so
placement adds half a box rather than none.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018hifgRSawfaRWXS44YKtSJ
The layout change landed with CLAUDE.md and the Cobra help updated and nothing
else. The three that were missed are the user-facing ones.

`mxcli syntax domain-model.entity.alter` did not document SET POSITION AT ALL —
a gap that predates this work — so the topic now lists it, says the coordinate
is the box's CENTRE rather than its top-left, and points at `mxcli layout` for
arranging a whole module.

The generate-domain-model skill was actively wrong:

    IMPORTANT: All entities MUST have @position annotation
    Without it, entities appear at (0,0) or random locations.

Neither half held. An entity without a position took the next slot in a
deterministic row — now a grid — and never (0,0). The "MUST" was advice nothing
enforced, and the model that prompted this work ignored it, which is part of how
it came out as a 6,000px line. The section now says positions are optional, that
a grid is a default rather than a layout, and that a generated domain model is
better served by writing none and running `mxcli layout` afterwards.

New docs-site page tools/domain-model-layout.md, linked from SUMMARY.md: what
the layering does, the measured column breakdown, the flags, the fact that it
replaces hand-placed positions, and the idempotence/locality properties that
make it safe to leave in a build script.

make check-skill-mdl passes (205 blocks).

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

ako commented Sep 4, 2026

Copy link
Copy Markdown
Owner Author

github-advanced-security is red on fc77d745 and it is not this PR's failure.

The job runs GitHub's Copilot autofix agent (COPILOT_AGENT_MODEL: sweagent-capi:gpt-5.3-codex) and exits 1 inside its own runtime — the log ends in that agent's cleanup call to api.individual.githubcopilot.com and its orphan-process teardown (npm exec @playw), with no reference to any file this PR changes. It failed identically on the previous head of this PR, and on #387 and #389 before that; #389 merged with it red.

The real code scanning is unaffected: CodeQL and the Analyze jobs report normally, and Analyze (python) / Analyze (actions) are already green on this commit with the rest still running.

There is no fix to port — the failure is inside GitHub's own action, not in this repository — so I am standing down on it rather than re-running or working around it. I'll follow up on build-and-test, which is still in progress.


Generated by Claude Code

@ako
ako merged commit aa728ab into main Sep 4, 2026
15 of 16 checks passed
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