Skip to content

R2 part 3: menus, property maps and database connections take ( ) properties and { } children (#754) - #783

Merged
ako merged 22 commits into
mainfrom
feature/754-navigation-maps-rest
Sep 28, 2026
Merged

ako merged 22 commits into
mainfrom
feature/754-navigation-maps-rest

Conversation

@ako

@ako ako commented Sep 28, 2026

Copy link
Copy Markdown
Owner

Closes #754 (phase 3.4, R2). This is the third and last PR for the issue, after #771 (flow blocks) and #776 (integration documents). It covers what #776 listed as the remainder:

What changes

Construct Canonical (new) Old spelling, still parses Code
REST operation headers Headers: ('Accept': 'application/json') ('Accept' = 'application/json') MDL-DEPR120
Menu children navigation P home page M.H { menu item … menu 'X' { … } }, create menu M.M { … } menu ( item; … ), create menu M.M ( … ), menu 'X' ( … );, and a ; after an item MDL-DEPR121
Menu item action and icon menu item 'Home' ( OnClick: show page M.P, Icon: I ), ( OnClick: call microflow M.F ), ( OnClick: sign out ), menu 'X' ( Icon: I ) { … } menu item 'Home' page M.P icon I, microflow M.F, sign out, menu 'X' icon I ( MDL-DEPR122
Page and snippet header maps Params: ( $Order: M.Order ), Variables: ( $show: Boolean = 'true' ) { … } MDL-DEPR123
Text-template parameters ContentParams: ({1} = Name), also CaptionParams and <Name>Params [{1} = Name] MDL-DEPR124
Design properties DesignProperties: ('Spacing': ('margin-top': 'Large'), 'Full width': on) ['Spacing': ['margin-top': 'Large'], …] MDL-DEPR125
Snippet call arguments Params: (Asset = $Asset) {$Asset: $Asset} / {Asset: $Asset} MDL-DEPR126
Database connection create database connection M.Db ( Type: 'PostgreSQL', ConnectionString: @M.Url, Username: @M.U, Password: @M.P ) { query Q ( Sql: $$…$$, Parameters: ( p: Integer default '0', q: String null ), Returns: M.E, Map: ( Attr = column ) ) } type '…' connection string @… username … password … begin query Q sql … parameter … returns … map (column as Attr); end MDL-DEPR127
  • The two spellings build the same AST. This is checked in three ways:

    • the registry's Example/CanonicalExample equality test;
    • a per-case reflect.DeepEqual test;
    • a check that applying the recorded rewrite reaches the canonical form.

    No BSON writer changed.

  • Snippet call parameter name. The one visible AST normalisation is that a snippet call's parameter name is now stored without its $ for both spellings. $Asset and Asset used to be stored as written, and the page builder stripped the $ anyway, so written BSON is unchanged.

  • Warnings and rewrites. Each old form is a registered alias marked /* @alias MDL-DEPR12x */ in the grammar. It warns under both mdl 0 and mdl 1. fmt --upgrade rewrites it structurally from the parse tree, replacing clause words and inserting punctuation, so every value, string and SQL body stays where it is. That means the gated string-escape rewrite can never overlap it.

  • describe prints the canonical forms. That covers navigation, menus, page and snippet headers, widget template parameters and design properties, REST headers and database connections. describe navigation now puts the { … } menu block after on sync error and sync ( … ). The clauses are order-free, so the old output and the new one build the same statement.

  • New syntax is strict. The database connection's property list and the query properties refuse an unknown key or a value of the wrong kind. A menu item's OnClick: takes only the three actions an item can carry.

  • Docs are moved to the canonical forms:

    The .mdl files and the parseable markdown blocks were converted by applying only the visitor's own DEPR120–127 rewrites. Syntax patterns and prose were edited by hand.

Design choices the ADRs did not settle

  • A navigation profile's menu items are its own { } children, as in the proposal's after-example, not menu { … }.
    • The block is parsed anywhere among the clauses, which are order-free. That is what makes the rewrite a local one: menu ( becomes {, and the block is not moved. describe writes it last.
    • The profile's clauses stay clauses: home page … [for Role], login page, not found page, on sync error and sync ( sync M.E all; … ). The proposal's ( HomePage: … ) header needs a design for role-based home pages. The sync rules would need a child shape, sync M.E ( Mode: … ). Both are left out (see follow-ups).
  • The separator comes from R3/R4, not from the bracket. The proposal's "maps become ( key: value )" reads against R3 for two of the maps:
    • Params, Variables, DesignProperties and Headers set properties with :.
    • Text-template parameters bind values with =, as with ({1} = …) does (ADR-0010 R4). So ContentParams: ({1} = e) is a bracket swap only.
    • A snippet call is a call site, so its arguments are (P = $v), without the $ (R4).
  • Database queries.
    • Parameters: ( name: Type [default '…' | null] ) keeps the old test-value tail as written. There is no $, because the SQL references {name}.
    • The column map is Map: ( Attr = column ). It binds the attribute the way a REST mapping side does (Attr = jsonField), which puts the two in the opposite order to the old column as Attr.
    • Type: stays a string ('PostgreSQL'), since the stored value is written verbatim and 'BYOD' is also valid. The proposal's example wrote a bare postgresql.
    • The old clause form does not mix with the new one. A connection is one form or the other.
  • Design-property toggle values stay on/off. The proposal's R3 example writes 'Full width': true. That is a value change, not a bracket change, so it is not part of this PR.
  • A ; after an item is still read inside either block, with a MDL-DEPR121 warning, so a half-converted menu parses.
  • Codes 120–127 are from the block assigned to this item.

Test plan (what I ran)

  • make build
  • go test ./mdl/... ./api/... ./sdk/... ./cmd/mxcli/...: all ok.
  • New tests:
    • mdl/visitor/r2_rest_test.go: 10 cases, one old spelling each: REST headers, navigation menu block, menu document, menu item clauses, page header maps, snippet header maps, template parameters, design properties (nested and empty), snippet call arguments, and database connection (two queries, default and null parameters, map). Each checks:

      • the canonical form under mdl 0 and mdl 1 with no warning;
      • the old form records its code once, only that code, and builds a DeepEqual statement;
      • the recorded rewrite reaches a script that records nothing and builds the same statement.

      Also covered: exact rewrite text for mixed menu, navigation, snippet call and database-connection forms; unknown and mistyped database-connection keys; and a menu item refusing OnClick: save changes.

    • mdl/upgrade/r2_rest_test.go: TestUpgrade_R2NavigationMapsAndDatabaseConnection. It runs one script with every old spelling mixed in single statements, in both letter cases, with a comment inside the menu. It checks the exact output, the rewrite counts, and that a second upgrade is a no-op.

    • mdl/executor/r2_rest_describe_test.go: describe output for a navigation profile, a menu, a database connection, REST headers, widget maps and a page header (mock backend). Each output re-parses with zero deprecations, and the navigation and database-connection statements round-trip field by field. Controls: a profile with no menu gains no block, and a connection with no queries ends at its property list.

    • Registry examples for DEPR120–127 are checked by TestRegistryExamplesRecordTheirCode and TestUpgrade_EveryRegistryExample.

  • make test-integration-roundtrip (PedApp + TestApp): ok. No allowlist entry changed; the menu entries are Round-trip harness: untracked describe → exec losses on the Studio Pro fixture (microflows, entities, pages, snippets, menus, roles, JS actions) #721's nested-items class and still fail.
  • make test-integration-upgrade (execute-both property test over the converted mdl-examples): ok.
  • make check-skill-mdl: all checkable blocks pass.
  • make lint: passed.

Revert checks

  • Walk disabled (r2RestUse(n, use) commented out). These fail:
    • TestR2Rest_OldFormIsADeprecatedAlias
    • TestR2Rest_RewriteText
    • TestRegistryExamplesRecordTheirCode
    • TestUpgrade_R2NavigationMapsAndDatabaseConnection
    • TestUpgrade_EveryRegistryExample
  • The six describe emitters restored from origin/main: all seven new describe tests fail, each reporting the matching code (DEPR121/122 for navigation and menu, 127 for both connection tests, 120 for headers, 124/125 for widget maps, 123 for the page header).

Not verified in Studio Pro: no BSON writer changed, and both spellings build the same statement. The equality tests and the execute-both property test show that.

Follow-ups (not needed to close #754's bracket rule)

🤖 Generated with Claude Code

ako and others added 22 commits September 28, 2026 15:23
`if not exists` was accepted on create entity and create association only
(and parsed-then-ignored on view entities). ADR-0010 R1 makes it the
separate "leave it alone" operation beside create or modify, on every type.

- grammar: `ifNotExists?` after the kind's keywords, before the name, in
  every create rule that names one element. Not on annotation, index,
  validation rule, navigation, translations, external entities.
- ast: CreateGuard embedded in every named create statement.
- visitor: the guard is applied once, in ExitCreateStatement, to whichever
  statement the create rule built; a type that cannot carry it is an error.
- executor: Registry.Dispatch probes existence and skips before the handler
  (entity/association keep their in-handler check); stmtCreateInfo counts
  the guard as idempotent for every kind; MDL067 (or modify + if not
  exists) covers every kind.
- docs: mxcli syntax create-if-not-exists, basics.md, quick reference,
  check-syntax skill; finding recorded.

describe never emits the guard, so the round trip is unaffected.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ets (#754)

Menu items are { } children with no ';' separators, and an item's action
and icon are its properties: menu item 'Home' ( OnClick: show page M.P,
Icon: I ). A navigation profile's items are its own { } children. The
property maps are in ( ): a page or snippet header's Params/Variables,
ContentParams/CaptionParams/<Name>Params ({1} = e), DesignProperties,
a snippet call's arguments (P = $v, R4) and a REST operation's Headers
('Name': value). A database connection is ( Type: ..., ConnectionString:
@M.C, ... ) { query Q ( Sql: ..., Parameters: ( ... ), Returns: M.E,
Map: ( Attr = column ) ) }.

Every old spelling keeps parsing under both language versions, builds
the same statement, warns (MDL-DEPR120..127) and carries a structural
fmt --upgrade rewrite.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… brackets (#754)

describe navigation/menu, describe page/snippet (Params, Variables,
ContentParams, CaptionParams, <Name>Params, DesignProperties), describe
consumed rest service (Headers) and describe database connection emit
the canonical forms, so their output re-parses without recording any
deprecated spelling. Hints in validator messages use the same forms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…#754)

Skills (synced), docs-site, the quick reference, the language
reference, mxcli syntax entries, mdl-examples and the layout scaffold
use the canonical spellings. The .mdl files and parseable markdown
blocks were converted by applying only the visitor's own MDL-DEPR120..127
rewrites; syntax patterns and prose were edited by hand.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…#731)

exec skips `create … if not exists` when the element is already there and
leaves it untouched, but diff compared the script's definition against the
stored one and reported the element as modified — a pre-apply view that
disagreed with what exec does. The stored definition is now what the guarded
statement leaves behind. Tests: existing -> unchanged; controls for the
unguarded form (modified) and an absent element (new).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…h no Sql (#754)

Review of #783. The new menu-item, database-connection and query property
lists were described as strict but took a duplicated key silently: a menu
item with two OnClick actions was written with whichever the builder
checked first. A query's Sql became optional by accident (the clause form
required it), and a sub-menu accepted an OnClick that the old spelling
could not write and describe never prints. All three are now errors in the
new syntax only; the old spellings are unchanged.

Also: a half-converted sub-menu, `menu 'X' icon I { … }`, parses as the
MDL-DEPR122 alias and upgrades; and MDL058's suggestion writes the
canonical `Username: @M.C` rather than the deprecated clause.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rop } (#712)

alter workflow M.W { set (Key: value) [on <activity>]; insert before|after
<activity> { ... } insert into <activity> { outcomes | path | boundary event }
replace <activity> with { ... } drop <activity> [member]; }. The old
per-action forms build the same operations and are registered aliases
MDL-DEPR140-149 with fmt --upgrade rewrites.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…712)

WorkflowMutator embeds AlterTargetResolver (shared rule in
backend.ResolveWorkflowActivityTarget); the executor resolves every target
before applying anything, and checks an inserted path's number.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#765)

`refresh catalog full source` describes every document from a goroutine
pool that shares one ExecContext.Cache. describeEntity -> findModule filled
executorCache.modules lazily with no synchronisation, and preWarmCache did
not warm it, so workers each listed the modules and published the slice
while others read the field: two DATA RACE reports per refresh under -race.

The module list is now filled and invalidated under executorCache.modulesMu
(cachedModules); the page builder's getModules goes through the same path.

Tests: TestModuleCache_ConcurrentFillListsOnce (detector-free: 8 concurrent
lookups on a slow mock list once; 8 without the fix) and
TestParallelEntityDescribes_NoDataRace (-race over PedApp's entity
describes on the catalog's parallel path).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The start activity is an addressable member of the flow (`start1`, caption
'Start'), and the new `insert before` put activities ahead of it: exec said
"Altered workflow" and mx check (11.14.0) answered CE9526 "Main process in
workflow should start with a start event". The shared activity-kind guard
now refuses it for both passes and both backends, pointing at
`insert after <start>`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…otchas (#712)

Two gotchas still named the old `set activity` / `replace activity` forms,
and the start-activity refusal for `insert before` is now documented.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The full describe (layout flows, ELK, diff, with handles, create or
modify's diff) misrouted the merge-to-split flow in CxLayout.CX_VAL_Factory
and CX_VAL_EmailTemplate: when a decision's merge wraps onto a new row the
flow leaves the merge's bottom, but a merge has no statement, so describe
wrote only the next split's `to: top` and the rebuild drew the flow out of
the merge's right side.

`from:` on an if's @anchor is now that exit side, the same slot `from:` has
on any statement (the flow leaving it). Describe emits it for the merge the
@merge line already names; the builder hands it to the merge-out flow at
the top level (it already honoured it inside branch bodies), and case /
type-split branches clear a nested if's leftover exit anchor so it cannot
land on the flow out of the enclosing merge.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…header templates and images (#707)

The part of #707 that #728 left open. Each item was a property describe
printed as a `--` comment or a side file because the grammar had no slot
for it, so a replay lost it:

- User role properties are a ( Key: value ) list: ModuleRoles,
  Description, ManageAllRoles, ManageableRoles, ManageUsersWithoutRoles,
  CheckSecurity. The list is optional, so a role with no module roles
  describes as `create or modify user role X;` and parses. The positional
  form is the deprecated alias MDL-DEPR710 with an fmt --upgrade rewrite.
  New backend method SetUserRoleProperties; `create or modify` sets only
  the stated properties.
- Workflow notes: `@annotation '...'` before an activity or event
  sub-process, and an `annotation '...'` header clause for the workflow.
- A consumed REST header value is a template (`'Bearer {Token}'`).
  `'Bearer ' + $Token` stored only "Bearer "; it now builds the template
  and is the deprecated alias MDL-DEPR711 with an fmt --upgrade rewrite.
- Image collection describe writes `Data: '<base64>'` (Format only when
  the bytes do not show it) instead of /tmp/mxcli-preview paths; the TUI
  preview decodes it; File: paths resolve script-relative.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… image Data (#707)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
#	mdl/grammar/domains/MDLSecurity.g4
# Conflicts:
#	mdl/ast/ast_page_v3.go
#	mdl/grammar/MDLParser.g4
#	mdl/grammar/domains/MDLService.g4
…abase connections and REST headers (#754)

describe prints a database connection as ( Type: …, ConnectionString: … ) with
a query's Map: ( Attr = column ), and a REST header as 'Name': 'value'; the
integration round trips still looked for the keyword forms. The header inputs
use ':' too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ako added a commit that referenced this pull request Sep 28, 2026
The examples #790 migrated conflicted with #783's edits; each takes the
chain's side and is re-upgraded with the chain's fmt --upgrade, which also
knows wave 4's new aliases (MDL-DEPR130..149, 710) — so the examples that
merged cleanly but used those spellings are upgraded too. The
deprecated-aliases corpus is left verbatim.

The conformance allowlist follows #787's docs renames (show-* -> list-*)
and shrinks by what wave 4 fixed; two new fragments are made conformant
(the #767 anchor example as a whole microflow, #783's header template in a
text fence with ':').

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ako
ako merged commit ee5134f into main Sep 28, 2026
29 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.

R2: three brackets, three meanings — integration documents, navigation/menus, on error, while (3.4)

1 participant