Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .claude/skills/fix-issue.md
Original file line number Diff line number Diff line change
Expand Up @@ -421,3 +421,6 @@ extracting `OffsetExpression`/`LimitExpression`.
| `create external entities from` a contract that restricts capabilities produces a project that will not build: `'Seasons' is marked Countable=False in the OData service, but True in the app`, `'latitude' is marked Filterable=False …` — one per restricted resource or property | Insert/Update/Delete restrictions were parsed; **Count/Filter/Sort were not**, so the import had nothing to honour and defaulted all three to true — on the one command whose entire job is fidelity to the contract | `mdl/types/edmx.go` (`EdmEntitySet.Countable`, `NonFilterableProperties`, `NonSortableProperties` + the three `applyCapabilityAnnotations` arms), `mdl/executor/cmd_contract.go` | An unannotated set still means countable/filterable/sortable — **silence in a contract is not a restriction**, it is OData's own default, so `nil` and `false` must stay distinguishable (`*bool`, as with the publish-side query options). The generated entity is compared against the contract at *build* time, so anything the contract can say is something the importer must be able to read. Tests `mdl/types/edmx_test.go`. mxcli-formula1 #24 |
| A contract property called `name` is generated as `Stg_Drivername` / `Circuitname` — prefixed with the remote type. A page written against the published `$metadata` then fails with `The selected attribute 'F1Live.Drivers.name' no longer exists`, and the *same* field carries a different name in every module because the remote type names differ | `attrNameForOData` disambiguates any name in `reservedEntityAttrNames`, and `name` was on that list with the comment "Mendix system-managed attribute for the object name". It is not: Mendix builds an external entity with an attribute literally named `name` | `mdl/executor/cmd_contract.go` (`reservedEntityAttrNames` loses one entry; the import now reports the renames it does make) | **Test the whole list at once, not the reported entry.** One contract with a property per listed name, prefixing disabled, then `mx check`: CE7247 "The name 'x' is a reserved word" for `id`/`owner`/`changedBy`/`changedDate`/`createdDate`/`type`/`context`, and silence for `name`. That turns "is the list wrong?" into "which rows are wrong?" for the cost of a single build, and it *earns* the seven entries that stay rather than leaving them as folklore. Two existing tests pinned the old behaviour and had to be corrected — a hand-maintained list of platform rules will accrete guesses unless each row can point at an error code. **Migration**: a re-import renames the attribute back, so references to the prefixed name must follow. Tests `cmd_contract_reserved_test.go`. mxcli-formula1 #28 |
| `MOVE JAVA ACTION …` / `MOVE ODATA SERVICE …` is a parse error (`no viable alternative at input 'MOVEJAVA'`), and neither `CREATE` form takes a folder clause — so those documents can never leave the module root from MDL | The `moveStatement` rule listed seven doctypes and nothing else; the missing ones were never unimplemented, just unlisted | `mdl/grammar/MDLParser.g4` (two alternatives), `mdl/ast/ast.go`, `mdl/visitor/visitor_entity.go` (dispatch **and** the MOVE FOLDER discriminator), `mdl/executor/cmd_move.go`, backend `MoveJavaAction` / `MovePublishedODataService`, `sdk/mpr` exports `MoveUnitByID` | Both reduce to the existing reparent primitive — a top-level document move is one containment row, so a new doctype is a list entry plus a lookup, not new machinery. **Watch the discriminator**: `MOVE FOLDER` is told apart from a document move by the *absence* of a doctype keyword, so every keyword added to the rule must also be added to that condition or a folder move starts parsing as a document move. **Verify placement by differential count, not by reading the model**: run the script with and without the MOVE lines and diff `select ContainmentName, count(*) from Unit` — three new Folders rows (a nested path creates two) and an unchanged Documents count says reparented rather than copied or dropped. Grepping blobs for names is a trap; stock modules are full of the same words. Tests `visitor_move_doctypes_test.go`, example in `18-folder-examples.mdl` — which must sit **before** that script's `drop module`, a mistake the integration gate caught and `mxcli check` did not. mxcli-formula1 #32 |
| `create odata client` with credentials given as constants (`HttpUsername: '@Module.ApiUser'`) still gets HTTP 401 and an empty client, after the fix that made literal credentials work. Sharpened by the same release making a constant `ServiceUrl` mandatory — the shape the tool insists on is the shape whose credentials it will not read | `resolveCredential` trusted the visitor's isLiteral flag. `'@Module.ApiUser'` **is** a STRING_LITERAL, so the flag said "literal" and the previous code sent the fifteen characters `@Module.ApiUser` as the username — and the unresolved-credential note did not fire either, because as far as the code knew nothing was unresolved | `mdl/executor/cmd_odata.go` (`resolveCredential`, `constantReference`, `designTimeConstants`) | **A syntactic classification is not a semantic one.** The visitor can say "this was a quoted string"; only the executor can say "this quoted string names a constant". Any flag of the form isLiteral needs the consumer to ask what the literal *contains* before treating it as a value. **The fix is to resolve, not to refuse**: a constant's design-time default is exactly what Studio Pro sends on the same fetch, so reading it is the value rather than a workaround — and mxcli already has the project open. Three spellings must all work (`'v'`, `@M.C`, `'@M.C'`); a dotted literal like a password containing a dot must not be mistaken for a reference. Tests `cmd_odata_metadata_auth_test.go`. mxcli-formula1 #23 follow-up |
| An app themed dark still shows light-mode drop shadows under the datagrid's filter-operator popover and dropdown filter lists | The generated widget layer re-pointed `.column-selectors` but not the four rules in `_datagrid-filters.scss` that bake the same two-layer shadow. Each already takes its *background* from `--bg-color-secondary`, so Atlas re-colours the panel and leaves the shadow — which is why it reads as a partial fix rather than an untouched widget | `cmd/mxcli/theme/assets/*/files/theme/web/_mxcli-widgets.scss` | **Read the selectors out of the shipped `themesource/`, not the bug report** — the fourth here is `.dropdown-container .dropdown-list`, nested under a parent the report did not name. **Verify in the compiled CSS, never the source** (the §33 rule): apply the theme to a real project, run `mxbuild --target=deploy --java-home=… --java-exe-path=…`, then grep `theme-cache/web/theme.compiled.css` and check the *line number* — the fix must land after the widget module's own rule (30794 vs 27765 here) or the cascade eats it. A theme-cache file older than the SCSS you just wrote is a stale compile, and mtime is the cheapest way to catch it. mxcli-formula1 #33 / suggested issue 4 |
| A `MOVE` cannot be confirmed and a module's layout cannot be reviewed: `SHOW STRUCTURE` groups by document type at every depth and never names a folder, `DESCRIBE` answers for one document at a time, so checking where things ended up means opening the `.mpr` as SQLite | The read side of folders was simply never built. `MOVE`/`DROP FOLDER` write containment; nothing read it back | New `mdl/executor/cmd_list_folders.go` + grammar (`FOLDERS` lexer token, `showOrList FOLDERS (IN …)?` in `MDLCatalog.g4`, `FOLDERS` added to the `keyword` rule so it stays usable as an identifier), `mdl/ast/ast_query.go`, `mdl/visitor/visitor_query.go`, `mdl/executor/executor_query.go` | **A layout listing must show what is *not* there**: empty folders (`[0]`) and documents still at the module root, or it cannot be diffed against an intended layout — that is the whole use. Documents are indexed by `ContainerID` across every list call the backend offers, each best-effort, so a backend that cannot answer one kind yields a listing missing that kind rather than no listing. **Do not stub the hierarchy in the test** — `mkHierarchy` populates `moduleNames` but not `folderNames`, so `BuildFolderPath` returns `""` and every folder silently collapses into the module root, which looks exactly like the bug. Build it from the mock's `ListModules`/`ListUnits`/`ListFolders`, as `getHierarchy` does. Tests `cmd_list_folders_test.go`, example in `18-folder-examples.mdl`. mxcli-formula1 issue #2 |
42 changes: 42 additions & 0 deletions .claude/skills/mendix/organize-project.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,47 @@ begin
end;
```

## Reading the Layout Back

`list folders` shows the folder layout of a module and what is in each folder.
This is the counterpart to `move`: `move` puts a document somewhere, `list
folders` shows where everything actually is.

```sql
-- One module
list folders in MyModule;

-- Every module in the project
list folders;
```

```
MyModule
(module root) [1]
Microflow ACT_Unfiled
Api [0]
Api/Published [1]
ODataService PublicApi
Support [1]
JavaAction Helper

(3 folder(s), 3 document(s))
```

Three things about the output are deliberate:

- **Empty folders are listed** (`Api [0]`), so the listing is the whole layout
and can be diffed against an intended one.
- **Documents still at the module root** appear under `(module root)` — what is
not filed yet is the thing you most want to notice.
- **Ordering is stable**, so a diff between two runs shows only real movement.

Use the CLI's `--json` flag for a row per document (`Module, Folder, Kind, Document`)
when comparing against a checked-in layout.

Do **not** reach for `show structure` here: it groups by document type at every
depth and never shows which folder a document sits in.

## Moving Documents

The `move` command relocates existing documents between folders and modules.
Expand Down Expand Up @@ -252,3 +293,4 @@ drop folder 'Processing' in MyModule;
- [ ] Cross-module moves: checked impact with `show impact of` first
- [ ] Folder naming is consistent across modules
- [ ] DROP FOLDER: verify folder is empty before dropping
- [ ] After a batch of moves: `list folders in MyModule` to confirm the layout
1 change: 1 addition & 0 deletions cmd/mxcli/lsp_completions_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

35 changes: 34 additions & 1 deletion cmd/mxcli/syntax/features_misc.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,40 @@ SHOW IMPACT OF OldModule.CustomerPage;
MOVE PAGE OldModule.CustomerPage TO NewModule;

-- Drop empty folder
DROP FOLDER 'OldFolder' IN Module;`,
DROP FOLDER 'OldFolder' IN Module;

-- Read the placement back
LIST FOLDERS IN MyModule;`,
SeeAlso: []string{"folders"},
})

// ── Folders ─────────────────────────────────────────────────────────

Register(SyntaxFeature{
Path: "folders",
Summary: "LIST FOLDERS — the folder layout of a module, with what is in each folder",
Keywords: []string{
"folders", "list folders", "show folders", "layout",
"folder tree", "where is this document", "unfiled",
},
Syntax: "LIST FOLDERS [IN <module>];",
Example: `-- Layout of one module
LIST FOLDERS IN MyModule;

-- Every module in the project
LIST FOLDERS;

-- As rows, to diff against an intended layout
mxcli -p app.mpr --json -c "LIST FOLDERS IN MyModule"

-- Complements MOVE: MOVE places a document in a folder, LIST FOLDERS reads
-- the placement back. SHOW STRUCTURE is organised by document type at every
-- depth, so it never shows which folder a document sits in.
--
-- Empty folders are listed too (with [0]), and documents still at the module
-- root appear under "(module root)" — so the output is the whole layout and
-- can be diffed against an intended one.`,
SeeAlso: []string{"move", "structure"},
})

// ── Search ──────────────────────────────────────────────────────────
Expand Down
19 changes: 19 additions & 0 deletions cmd/mxcli/theme/assets/console/files/theme/web/_mxcli-widgets.scss
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,25 @@
box-shadow: var(--mxt-shadow);
}

// _datagrid-filters.scss:71, 143, 153 and 206 — the same two-layer light-mode
// shadow, baked four times:
//
// box-shadow: 0 2px 20px 1px rgba(5, 15, 129, .05),
// 0 2px 16px 0 rgba(33, 43, 54, .08);
//
// These are the filter-operator popover, the dropdown filter's list in both its
// standalone and contained forms, and the list inside a dropdown container. Each
// already takes its background from --bg-color-secondary, so Atlas re-colours the
// panel and leaves the shadow behind — elevation drawn for a light ground,
// floating over a dark one. Same treatment as .column-selectors above.
.filter-selectors,
.dropdown-content,
:not(.dropdown-content) > .dropdown-list,
.dropdown-container .dropdown-list {
border-color: var(--mxt-line);
box-shadow: var(--mxt-shadow);
}

// _three-state-checkbox.scss — the row-select boxes down the left of every
// grid. Nine baked literals: #e7e7e9 borders and #f8f8f8 disabled fills that
// vanish on a dark surface, #ffffff checkmarks, and #264ae5 for the checked and
Expand Down
19 changes: 19 additions & 0 deletions cmd/mxcli/theme/assets/ledger/files/theme/web/_mxcli-widgets.scss
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,25 @@
box-shadow: var(--mxt-shadow);
}

// _datagrid-filters.scss:71, 143, 153 and 206 — the same two-layer light-mode
// shadow, baked four times:
//
// box-shadow: 0 2px 20px 1px rgba(5, 15, 129, .05),
// 0 2px 16px 0 rgba(33, 43, 54, .08);
//
// These are the filter-operator popover, the dropdown filter's list in both its
// standalone and contained forms, and the list inside a dropdown container. Each
// already takes its background from --bg-color-secondary, so Atlas re-colours the
// panel and leaves the shadow behind — elevation drawn for a light ground,
// floating over a dark one. Same treatment as .column-selectors above.
.filter-selectors,
.dropdown-content,
:not(.dropdown-content) > .dropdown-list,
.dropdown-container .dropdown-list {
border-color: var(--mxt-line);
box-shadow: var(--mxt-shadow);
}

// _three-state-checkbox.scss — the row-select boxes down the left of every
// grid. Nine baked literals: #e7e7e9 borders and #f8f8f8 disabled fills that
// vanish on a dark surface, #ffffff checkmarks, and #264ae5 for the checked and
Expand Down
19 changes: 19 additions & 0 deletions cmd/mxcli/theme/assets/signal/files/theme/web/_mxcli-widgets.scss
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,25 @@
box-shadow: var(--mxt-shadow);
}

// _datagrid-filters.scss:71, 143, 153 and 206 — the same two-layer light-mode
// shadow, baked four times:
//
// box-shadow: 0 2px 20px 1px rgba(5, 15, 129, .05),
// 0 2px 16px 0 rgba(33, 43, 54, .08);
//
// These are the filter-operator popover, the dropdown filter's list in both its
// standalone and contained forms, and the list inside a dropdown container. Each
// already takes its background from --bg-color-secondary, so Atlas re-colours the
// panel and leaves the shadow behind — elevation drawn for a light ground,
// floating over a dark one. Same treatment as .column-selectors above.
.filter-selectors,
.dropdown-content,
:not(.dropdown-content) > .dropdown-list,
.dropdown-container .dropdown-list {
border-color: var(--mxt-line);
box-shadow: var(--mxt-shadow);
}

// _three-state-checkbox.scss — the row-select boxes down the left of every
// grid. Nine baked literals: #e7e7e9 borders and #f8f8f8 disabled fills that
// vanish on a dark surface, #ffffff checkmarks, and #264ae5 for the checked and
Expand Down
2 changes: 2 additions & 0 deletions docs/01-project/MDL_QUICK_REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,7 @@ it is for pages.

| Statement | Syntax | Notes |
|-----------|--------|-------|
| List folders | `list folders [in module];` | The folder layout, with the documents in each folder |
| Microflow folder | `folder 'path'` (before BEGIN) | `create microflow ... folder 'ACT' begin ... end;` |
| Page folder | `folder: 'path'` (in properties) | `create page ... (folder: 'pages/Detail') { ... }` |
| Drop folder | `drop folder 'path' in module;` | Folder must be empty |
Expand Down Expand Up @@ -485,6 +486,7 @@ alter workflow Module.OrderApproval
| Full types | `show structure depth 3;` | Typed attributes, named parameters |
| Filter by module | `show structure in ModuleName;` | Single module only |
| Include all modules | `show structure depth 1 all;` | Include system/marketplace modules |
| Folder layout | `list folders [in module];` | `show structure` is by document type at every depth and never shows folders — use this to read back where a `move` put something |

## Navigation

Expand Down
18 changes: 18 additions & 0 deletions mdl-examples/doctype-tests/18-folder-examples.mdl
Original file line number Diff line number Diff line change
Expand Up @@ -132,4 +132,22 @@ create odata service FolderTest.PublicApi (

move odata service FolderTest.PublicApi to folder 'Api/Published';

-- ============================================================================
-- Level 7: Read the layout back
-- ============================================================================

/**
* LIST FOLDERS is the counterpart to MOVE: it shows the folder layout and what
* sits in each folder, including empty folders and anything still at the module
* root. SHOW STRUCTURE groups by document type at every depth and never shows
* which folder a document is in.
*/
list folders in FolderTest;

-- SHOW is accepted as the legacy verb for the same statement.
show folders in FolderTest;

-- With no IN clause, every module in the project.
list folders;

drop module FolderTest;
3 changes: 3 additions & 0 deletions mdl/ast/ast_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ type ShowObjectType int
const (
ShowModules ShowObjectType = iota
ShowEnumerations
ShowFolders
ShowConstants
ShowEntities
ShowEntity
Expand Down Expand Up @@ -110,6 +111,8 @@ func (t ShowObjectType) String() string {
return "MODULES"
case ShowEnumerations:
return "ENUMERATIONS"
case ShowFolders:
return "FOLDERS"
case ShowConstants:
return "CONSTANTS"
case ShowEntities:
Expand Down
Loading
Loading