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
6 changes: 5 additions & 1 deletion .claude/commands/mendix/explore.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ LIST IMPACT OF Module.Customer;
-- Get context for understanding code
DESCRIBE CONTEXT OF Module.MyMicroflow DEPTH 2;

-- Full-text search across strings and source
-- Full-text search across strings, element documentation and source
SEARCH 'validation';
SEARCH 'Customer';
```
Expand All @@ -128,6 +128,10 @@ mxcli search -p app.mpr "error" -q --format names | head -1 | awk '{print $2}' |

# JSON format with jq
mxcli search -p app.mpr "Customer" -q --format json | jq -r '.[].qualifiedName'

# A hit's stringContext says which field matched ("documentation" for the
# documentation of an entity, attribute, constant, page, ...)
mxcli search -p app.mpr "legacy" -q --format json | jq -r '.[] | select(.stringContext == "documentation") | .qualifiedName'
```

## Available Catalog Tables
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"area": "mdl/catalog", "date": "2026-10-13", "symptom": "`mxcli search` / `SEARCH` answers \"No matches found.\" for a word that appears only in an element's documentation (a constant, entity, attribute, page, ...) or only in a microflow's log message, although `select ... from catalog.constants where Description like '%word%'` finds it", "cause": "The strings index was filled by a walk keyed on `Texts$Text` plus a few typed extractions. Documentation and a log message's `StringTemplate.Text` are plain strings, not `Texts$Text`, so the walk could not see them, and the typed extractions listed only microflow and workflow documentation and the log node name", "file": "`mdl/catalog/builder_strings.go` (`buildDocumentationStrings`, `extractLogNodeNames`), `mdl/catalog/tables.go` (`CatalogSchemaVersion` 24 -> 25)", "insight": "A search index fed by walking one stored type misses every field of another type, and nothing reports the gap: the missing field just answers \"No matches found.\". Compare the index against the catalog tables that already hold the field (`objects.Description`, `attributes_data`, ...) instead of walking the model again. Two traps: the `MODULE` row of the `objects` view has `'' as ModuleName`, so a copy keeps no module and a filter on `ModuleName` (excluding Marketplace modules) never matches the module's own documentation - use `CASE WHEN ObjectType = 'MODULE' THEN Name ELSE ModuleName END`; and a typed extraction that already indexes microflow/workflow documentation must be excluded from the generic pass or every hit shows twice. Cached catalogs keep answering \"No matches found.\" until the schema version is bumped. Prove the fix with the pair reverted-vs-restored: both tests fail with the documentation pass and the log_message insert removed", "refs": ["mendixlabs/mxcli#1477"]}
2 changes: 1 addition & 1 deletion .claude/skills/mendix/write-lint-rules/catalog-tables.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ Returned by `strings(language = None)`.
| `qualified_name` | string | The document the text belongs to: `"Sales.Order_Overview"` |
| `object_type` | string | Catalog object type, upper-case: `"PAGE"`, `"SNIPPET"`, `"LAYOUT"`, `"MICROFLOW"`, `"NANOFLOW"`, `"WORKFLOW"`, `"ENUMERATION"`, `"MENU_DOCUMENT"`, … |
| `value` | string | The text itself |
| `context` | string | What the text is. Translatable text names its stored type and property: `"Forms$Page.Title"`, `"Forms$TabPage.Caption"`, `"Enumerations$EnumerationValue.Caption"`; the rest a lower-case label: `"documentation"`, `"page_url"`, `"log_node"`, … |
| `context` | string | What the text is. Translatable text names its stored type and property: `"Forms$Page.Title"`, `"Forms$TabPage.Caption"`, `"Enumerations$EnumerationValue.Caption"`; the rest a lower-case label: `"documentation"` (of an entity, attribute, constant, page, … — `object_type` says which; a module's domain model documentation is `"domain_model_documentation"`), `"page_url"`, `"log_node"`, `"log_message"`, … |
| `language` | string | `"en_US"`; `""` for text that is not translatable (documentation, URLs) |
| `element_id` | string | UUID of the element carrying the text |
| `module_name` | string | `"Sales"` |
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

## [Unreleased]

### Fixed

- **`mxcli search` and `SEARCH` find words that appear only in an element's documentation** (mendixlabs/mxcli#1477) — the strings index held captions, messages, URLs and the documentation of microflows and workflows, but not the documentation of entities, attributes, associations, enumerations, constants, pages and the other documents whose catalog tables carry it, so a word present only there answered "No matches found." (measured: a word in a constant's documentation was found by `select … from catalog.constants where Description like …` and not by `search`). The index now also holds those: each hit names the element (`ObjectType` `ENTITY`, `ATTRIBUTE`, `CONSTANT`, `PAGE`, …; an attribute is `Module.Entity.Attribute`) and the field (`StringContext` `documentation`), the labels microflow documentation already used, plus the parameters of microflows and Java actions and a module's domain model documentation (`domain_model_documentation`). A module's own rows carry the module's name in `ModuleName`, not the empty string the `objects` view holds for a module. They are read from the tables the catalog builder has just filled, not from a second walk of the model; building a catalog of a stock 11.12 app takes the same time (0.5s) and the strings table grows by about 50 rows. Enumeration values are not indexed (Mendix has no documentation field on them), nor is the summary text of a remote service's contract entities. The catalog schema version is bumped to 25, so a cached catalog is rebuilt once. Rules: docs-site "Full-Text Search".
- **`search` finds a microflow's log message** (mendixlabs/mxcli#1477) — only the log node name was indexed. A log message is a plain-text `Microflows$StringTemplate`, not a `Texts$Text`, so the walk that finds every translatable text never saw it, and a word present only in a log message was not found. It is now a `log_message` row beside the `log_node` one (activities inside a loop are not reached, as before for the node name).

## [0.26.0] - 2026-10-11

Headline: **Documents mxcli writes now survive Studio Pro's merge and diff: every element carries exactly the property names Studio Pro writes for the project's Mendix version.** Before, a key mxcli left out, or wrote for a newer version, made a document unmergeable the first time Studio Pro saved it, although `mx check`, the build and the runtime stayed green. `mxcli fix property-sets` repairs projects an older mxcli wrote. Alongside it: a Claude Code companion mod with a Project pane, `describe` → `exec` round trips that keep loop back-edges, shared error handlers and page design properties, `check -p` resolving script sets and security references the way `exec` does, `mxcli run --local` as a background service, and a run of Windows fixes for the local dev loop. The [language poster](https://www.mxcli.org/poster/mxcli-language-poster.html) puts all of mxcli and MDL on one page.
Expand Down
21 changes: 19 additions & 2 deletions cmd/mxcli/cmd_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,8 +227,25 @@ var searchCmd = &cobra.Command{
Short: "Search the project catalog using full-text search",
Long: `Search the project catalog for matching strings and source code.

Searches across string literals (captions, labels, messages) and MDL source
definitions. Requires at least a FULL catalog build (done automatically).
Searches across string literals (captions, labels, messages), the documentation
of project elements, and MDL source definitions. Requires at least a FULL
catalog build (done automatically).

What is searched, besides captions, labels and messages, is the documentation of:
entities; attributes; associations; enumerations; constants; pages, snippets,
layouts, building blocks and page templates; microflows, nanoflows, rules and
workflows; Java and JavaScript actions; microflow and Java action parameters;
consumed and published REST services and business event services; import and
export mappings, JSON structures and data transformers; scheduled events, task
queues, regular expressions, image and icon collections and menus; agents, AI
models, knowledge bases and consumed MCP services; and a module's domain model
documentation. That is every document type whose catalog table carries a
documentation text.
Not searched: documentation of an enumeration value (Mendix has no such
field) and the summary text of a remote service's contract.
A hit names the element (qualified name, type) and the field: the context
column says "documentation" for a documentation hit.

MDL source is searched only once the source index exists — build it with
mxcli -p app.mpr -c "refresh catalog full source"
Until then a warning on stderr says only string literals were searched.
Expand Down
2 changes: 1 addition & 1 deletion cmd/mxcli/syntax/features_misc.go
Original file line number Diff line number Diff line change
Expand Up @@ -1164,7 +1164,7 @@ mxcli -p app.mpr --json -c "LIST FOLDERS IN MyModule"

Register(SyntaxFeature{
Path: "search",
Summary: "Full-text search across project strings and source definitions",
Summary: "Full-text search across project strings, element documentation and source definitions",
Keywords: []string{
"search", "full-text search", "find", "grep",
"fts", "catalog strings", "catalog source",
Expand Down
11 changes: 10 additions & 1 deletion docs-site/src/internals/catalog-schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,8 @@ CREATE VIRTUAL TABLE STRINGS USING fts5(
StringContext, -- Where it lives. For translatable text this is
-- <owner $Type>.<property>, e.g. Forms$ActionButton.Caption.
-- Non-translatable strings keep a plain label: page_url,
-- log_node, documentation, rest_path, task_name, ...
-- log_node, log_message, documentation, rest_path,
-- task_name, domain_model_documentation, ...
Language, -- Language code, empty for non-translatable strings
ElementId, -- The owning element's $ID — what distinguishes an
-- enumeration's twelve values from each other
Expand All @@ -383,6 +384,14 @@ templates (`PAGE_TEMPLATE`, `BUILDING_BLOCK`), which are roughly 70% of a stock
project's text and never render in a running app — filter them out with
`ObjectType` when you want only the app's own strings.

The documentation of an element (an entity, attribute, constant, page, ...) is a
plain string, not a `Texts$Text`, so it is added from the catalog tables that
already hold it, not by the walk: `StringContext` is `documentation`,
`ObjectType` names the element (`ENTITY`, `ATTRIBUTE`, `CONSTANT`, `PAGE`, ...)
and `QualifiedName` is the element's own (`Sales.Order.Total` for an
attribute). Microflow and workflow documentation is indexed with the same
labels. See [Full-Text Search](../tools/search.md) for the full list.

An empty translation — a text that exists but is not translated yet — is **not**
a row, so a language's presence in this table means it is actually translated
somewhere.
Expand Down
4 changes: 2 additions & 2 deletions docs-site/src/reference/query/search.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@

## Description

Performs a full-text search across all strings, messages, captions, and MDL source in the project. Uses FTS5 (SQLite full-text search) for fast matching. Returns results grouped by document type and location.
Performs a full-text search across all strings, messages, captions, the documentation of project elements (entities, attributes, constants, pages and more), and MDL source in the project. Uses FTS5 (SQLite full-text search) for fast matching. Returns results grouped by document type and location.

This command requires the catalog to be populated. If search returns no results, run `REFRESH CATALOG FULL` first. The search is also available as a CLI subcommand: `mxcli search -p app.mpr "keyword" --format names|json`.

## Parameters

*keyword*
: The search term to look for. Must be enclosed in single quotes. Matches against entity names, attribute names, microflow activity captions, page titles, widget labels, string constants, log messages, and MDL source text.
: The search term to look for. Must be enclosed in single quotes. Matches against entity names, attribute names, microflow activity captions, page titles, widget labels, string constants, log messages, element documentation, and MDL source text.

## Examples

Expand Down
44 changes: 33 additions & 11 deletions docs-site/src/tools/search.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Full-Text Search

The `SEARCH` command performs full-text search across all strings and source code in a Mendix project. It searches element names, documentation, captions, expressions, and other string content.
The `SEARCH` command performs full-text search across all strings and source code in a Mendix project. It searches captions, messages, the documentation of project elements (entities, attributes, constants, pages and more), and MDL source.

## Syntax

Expand Down Expand Up @@ -69,16 +69,38 @@ mxcli search -p app.mpr "error" -q --format names | head -1 | awk '{print $2}' |

## What Gets Searched

The search command looks through:

- Element names (entities, microflows, pages, etc.)
- Documentation comments
- Attribute names and captions
- Expression strings in microflows
- Widget captions and labels
- Enumeration value captions
- Log message strings
- Validation feedback messages
`SEARCH` looks in two indexes, and says which one a hit came from.

**Strings** (a full catalog, built automatically):

- Every translatable text: page titles, widget captions and labels, enumeration value captions, button and menu captions, show-message and validation-feedback texts, in every language
- Log messages and log node names of microflows
- Page URLs and the paths, resources and operation summaries of published REST services
- Workflow names, descriptions, task names and activity captions
- Element names: the qualified name is a searchable column too, so `SEARCH 'Customer'` also returns the rows of elements named Customer that have some indexed text
- **Documentation of project elements.** A hit's `ObjectType` names the element and its `StringContext` is `documentation`:

| Documented element | `ObjectType` |
|--------------------|--------------|
| Entity | `ENTITY` |
| Attribute (qualified `Module.Entity.Attribute`) | `ATTRIBUTE` |
| Association | `ASSOCIATION` |
| Enumeration | `ENUMERATION` |
| Constant | `CONSTANT` |
| Page, snippet, layout, building block, page template | `PAGE`, `SNIPPET`, `LAYOUT`, `BUILDING_BLOCK`, `PAGE_TEMPLATE` |
| Microflow, nanoflow, rule | `MICROFLOW`, `NANOFLOW`, `RULE` |
| Workflow | `WORKFLOW` |
| Java action, JavaScript action | `JAVA_ACTION`, `JAVASCRIPT_ACTION` |
| Microflow parameter, Java action parameter (qualified `Module.Flow.Parameter`) | `MICROFLOW_PARAMETER`, `JAVA_ACTION_PARAMETER` |
| Consumed and published REST service, business event service | `CONSUMED_REST_SERVICE`, `PUBLISHED_REST_SERVICE`, `BUSINESS_EVENT_SERVICE` |
| Import mapping, export mapping, JSON structure, data transformer | `IMPORT_MAPPING`, `EXPORT_MAPPING`, `JSON_STRUCTURE`, `DATA_TRANSFORMER` |
| Scheduled event, task queue, regular expression, image and icon collection, menu | `SCHEDULED_EVENT`, `TASK_QUEUE`, `REGULAR_EXPRESSION`, `IMAGE_COLLECTION`, `ICON_COLLECTION`, `MENU` |
| Agent, AI model, knowledge base, consumed MCP service | `AGENT`, `AI_MODEL`, `KNOWLEDGE_BASE`, `CONSUMED_MCP_SERVICE` |
| A module's domain model documentation | `MODULE`, with `StringContext` `domain_model_documentation` |

This is the complete set: every document type whose catalog table carries a documentation text. Not covered: the documentation of an enumeration value (Mendix has no such field) and the summary text of a remote service's contract entities (written by the remote service, not by the project).

**MDL source** (only after `refresh catalog full source`): microflow, nanoflow and page bodies, expressions and the other definitions as MDL text. Until it is built, a warning on stderr says only strings were searched.

## Comparison with Catalog Queries

Expand Down
46 changes: 46 additions & 0 deletions mdl-examples/bug-tests/1477-search-documentation.mdl
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
mdl 1;
-- ============================================================================
-- mendixlabs/mxcli#1477: SEARCH finds words that appear only in documentation
-- ============================================================================
--
-- Before: the strings index held captions, messages and URLs, but not the
-- documentation of a constant, an entity or an attribute, and only the name of
-- a microflow's log node, not its message. A word present only in one of those
-- fields answered "No matches found.", although the catalog table that holds
-- the documentation found it:
--
-- select QualifiedName from catalog.constants where Description like '%legacy rate%'
--
-- Documentation and the text of a log message are plain strings, not
-- Texts$Text, so the walk that finds every translatable text never saw them.
--
-- After: each of the words below is a SEARCH hit. The hit names the element
-- (ObjectType CONSTANT, ENTITY, ATTRIBUTE, MICROFLOW) and the field
-- (StringContext documentation, log_message).
--
-- Verify (needs a project; `check` only proves the script is valid MDL):
--
-- mxcli exec 1477-search-documentation.mdl -p app.mpr
-- mxcli search -p app.mpr "legacy rate" -- BugTest1477.RateUrl, documentation
-- mxcli search -p app.mpr "settled invoices" -- BugTest1477.Invoice, documentation
-- mxcli search -p app.mpr "gross amount" -- BugTest1477.Invoice.Amount, documentation
-- mxcli search -p app.mpr "rate lookup" -- BugTest1477.ACT_Lookup, log_message
--
-- Then perturb: search for a word that is in none of them and see
-- "No matches found.".

create module BugTest1477;

/** legacy rate source */
create constant BugTest1477.RateUrl (Type: String, DefaultValue: 'https://rates.example.com');

/** Settled invoices only */
create persistent entity BugTest1477.Invoice (
/** The gross amount before discount */
Amount: Decimal
);

create microflow BugTest1477.ACT_Lookup ()
begin
log info node 'Rates' 'Starting rate lookup';
end;
Loading
Loading