feat(intent): cross-model entity references, n:m, and faithful field attributes - #6089
Merged
Conversation
…attributes Lets a multi-project app (e.g. Billing: uoms, countries, currencies, customers, customer-payments, sales-invoices) be authored as separate .intent models that reference each other cross-model, reusing the proven PROJECTION mechanism. Tranche A - cross-model references: - New top-level `uses:` block (UsesIntent) + optional `model:` on a relation. - Parser allows a to-one relation to target an entity in a declared uses model (forbids cross-model composition; rejects undeclared models). - EdmIntentGenerator emits a PROJECTION entity per cross-model target (owner table + PK resolved from the owner .model when present, else by convention) plus an integer FK + dropdown; projections carry no perspective so they stay out of the consuming app's nav. No template changes (existing parameterUtils / generateUtils / schema template already handle PROJECTION). - workspaceName plumbed through endpoint -> service -> context for the projectionReferencedModel path. Tranche B - n:m via an explicit intermediate entity (composition to one side, cross-model manyToOne to the other, plus bridge fields like Amount). Reuses the composition + cross-model paths; no extra generator logic. Tranche C - faithful field attributes: FieldIntent unique / precision / scale / calculatedOnCreate / calculatedOnUpdate, and entity-level `audit: true` (emits the four standard audit columns). EdmIntentGenerator emits dataUnique, precision/scale, isCalculatedProperty + calculatedPropertyExpression*, and auditType. Includes the 6 Billing example .intent files (test resources), parser + EDM generator unit tests (21 pass), and intent-assistant-guide.md docs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment on lines
+78
to
+80
| LOGGER.info( | ||
| "Cross-model target [{}] of model [{}] not yet generated at [{}] - using convention fallbacks; regenerate after [{}] is generated", | ||
| targetEntity, alias, modelPath, alias); |
| return new TargetInfo(true, perspective, tableDataName, keyField, keyColumn, labelField, fkType); | ||
| } | ||
| LOGGER.warn("Cross-model target entity [{}] not found in owner model [{}] - using convention fallbacks", targetEntity, | ||
| modelPath); |
Comment on lines
+117
to
+118
| LOGGER.warn("Failed to read owner model [{}] for cross-model target [{}] - using convention fallbacks", modelPath, targetEntity, | ||
| e); |
delchev
pushed a commit
to dirigiblelabs/sample-intent-multi-model
that referenced
this pull request
Jun 27, 2026
Tag each domain entity with a `group:` (master-data / sales / payments / settings) so the generated apps contribute grouped perspectives to the platform's shared application shell instead of forcing the user between per-project UIs. Add a `navigation` project that defines the four navigation groups once (getPerspectiveGroup per id, registered to application-perspectives). Requires the intent shared-shell support (eclipse-dirigible/dirigible#6089, #6090). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…6090) Multi-project intent apps generated a standalone Harmonia shell each, forcing the user to jump between per-domain UIs. This makes each project ALSO contribute its entities as perspectives to the platform's shared application shell, grouped, so they appear as one app. The standalone per-project shell is unchanged (still ideal for an all-in-one model and for testing a domain in isolation). - New entity-level `group:` (EntityIntent.group); EdmIntentGenerator sets the perspective's groupId (perspectiveNavId) from it. - Harmonia template emits a per-entity perspective .js + .extension registered to `application-perspectives` (namespaced id, groupId, path = this project's own SPA route in embedded mode), across the list/manage/master/setting collections. - Harmonia shell gains an embedded (chromeless) mode: when loaded with ?embedded (how the shared shell hosts a perspective) it hides its own sidebar so the shared shell provides the single chrome. Standalone mode unaffected. - Navigation groups are defined once (a dedicated navigation-groups project that exports getPerspectiveGroup per id); entities only reference the id - avoids the shell's duplicate-group-id drop. - Billing example models tagged with groups (master-data / sales / payments / settings); guide documents `group:`. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…ab table-name fix (#6091) * feat(intent): document (header-items) layout + aggregate totals + kebab table-name fix - IntentNaming.upperSnake now collapses non-alphanumeric separators (-, space, ., /) to a single underscore, so a kebab-case intent/project name yields a valid SQL identifier (sales-invoices -> SALES_INVOICES, not the invalid SALES-INVOICES that breaks table creation). Pure-identifier entity/field names are unaffected. - Document (header-items) layout: a master that owns a composition child whose name ends in "Item" (SalesInvoice -> SalesInvoiceItem) is emitted with layoutType MANAGE_DOCUMENT + documentItemsEntity, so it renders as a document (header form, inline items table, totals footer) instead of the default master-detail. The items child stays a DEPENDENT detail. generateUtils gains a uiDocumentModels collection. - New field attribute `aggregate: true` -> emits an "aggregate" render hint so a (typically calculated) total shows in the document's totals footer, not the header form. Presentational only. - Billing sample: SalesInvoice totals marked aggregate. Tests cover the document layout, documentItemsEntity, and the aggregate hint. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Add document-layout UI templates + IntentNaming test The document (header-items) view templates and the kebab-fix unit test that were not captured by the earlier diff: document.js manifest, document-page.js + document-view templates, and IntentNamingTest (3 cases). NOTE: document.js is not yet imported in template/ui/template.js and index.html.template has no MANAGE_DOCUMENT route branch, so the document UI does not generate yet - wiring is the remaining step. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
delchev
added a commit
that referenced
this pull request
Jun 28, 2026
…n shell Root CLAUDE.md: the cross-model intent DSL (uses:/model:, PROJECTION FKs, n:m intermediate entity, new field/entity attributes, document layout), the shared model-independent shell runtime under application-core, and the pure-Harmonia application shell (resources-application). PRs #6089-#6094. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
delchev
added a commit
that referenced
this pull request
Jun 28, 2026
… record selection (#6095) * feat(resources): Harmonia application shell (dirigible-components-resources-application) A pure Harmonia (Alpine.js) shell for the application layer, replacing the AngularJS dashboard's role of hosting application perspectives - the app layer is going pure Java + Harmonia (the IDE stays AngularJS + BlimpKit for now). Embedding Harmonia forms inside an AngularJS shell was the wrong layering. Served at /services/web/application/. It aggregates the `application-perspectives` extension point via the platform-core perspectives service (the same point the generated apps already contribute to), renders the grouped sidebar (groups + items + utilities), and hosts the selected perspective in an iframe. Generated Harmonia apps expose their routes in embedded mode, so their own sidebar is hidden and this shell provides the single chrome. Light/dark via Harmonia's colour-scheme API; responsive sidebar-to-drawer; deep-linkable selection via the URL hash. New module wired into components/pom.xml (modules + dependencyManagement) and group-ui. Assets reuse the existing webjars/application-core URLs - no CDN. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(intent): emit perspectives for MANAGE_DOCUMENT entities (shared shell) A document-layout master (e.g. SalesInvoice, layoutType MANAGE_DOCUMENT) was absent from the shared application shell because the Harmonia perspective emission did not include the uiDocumentModels collection. Add it so document masters appear (grouped) alongside list/manage/master/setting entities. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(resources): share the Harmonia shell runtime; application shell reuses it (no AngularJS) The Harmonia application shell was iframing the platform's AngularJS perspectives (e.g. Settings), which break inside a pure-Harmonia shell. Instead, reuse the existing Harmonia shell pages. - Move the model-independent Harmonia shell runtime (stores, services, base/page components, Inbox/Documents/Reports/notfound views, css) into application-core, served at /services/web/application-core/shell/* - a single shared runtime both the generated per-project shell and the application shell can load (Stage A: the application shell consumes it; repointing the generated template follows in Stage B). - Rebuild resources-application to reuse the shared runtime: Dashboard (generic landing) / Inbox / Documents / Reports as native Harmonia routes, and aggregate `application-perspectives` for the domain apps, hosting each in an embedded iframe. Only named perspective groups are shown, so the legacy AngularJS utility perspectives (the old Settings) are excluded. Drops the AngularJS-iframe approach and the local theme store (now shared). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(harmonia): generated shell reuses the shared runtime (Stage B) The generated per-project Harmonia shell no longer bundles its own copy of the model-independent runtime; it loads it once from the shared location /services/web/application-core/shell (the same runtime the Harmonia application shell uses), so there is a single source of truth. - index.html.template: the css link, the Inbox/Documents/Reports/notfound route targets, and the app/services/stores/base-page/appShell script tags now point at /services/web/application-core/shell/*. config.js, dashboardPage.js, the Settings and Dashboard views, and the per-entity pages/views stay generated locally. - shell.js: stop copying the model-independent files; only generate index.html, config.js, _settings.html, dashboardPage.js and _dashboard.html. - Remove the now-duplicated shell .template files from the template module. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Harmonia document calculated fields + totals roll-up Calculated fields are authored once as a neutral arithmetic expression and evaluated identically on the Java server and the JS client: - New SDK org.eclipse.dirigible.sdk.utils.Calc (server evaluator; double math to match JS, returns BigDecimal at the field scale). - template-application-dao-java emits entity.X = Calc.eval("<expr>", entity, scale)[.xxxValue()] for numeric calculated fields (non-numeric stay verbatim). - Harmonia document item dialog previews them live via the mirror harmoniaCalcEval + recalcDraft (detail-register carries calc/scale). - Calculated fields render read-only. Document header totals are summed from the line items by name convention. A single grouped handler per document (template-application-events-java DocumentRollup) recomputes ALL aggregate sums in one read-modify-write, so per-field updates cannot clobber one another; emitted via the documentRollups glue collection (GlueIntentGenerator) on child create/update/delete. The document footer also sums the loaded items client-side so the UI is instant and never races the eventually-consistent server roll-up. ProcessId is hidden from the manage + document forms (kept in the model so it round-trips on edit / stays null on create). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(harmonia): reflect hosted app + record selection in the shell URL Application shell (resources-application): - Host domain apps via a real route /app/<perspective-id>[/<inner-route>] instead of only swapping the iframe src, so opening an app updates the browser URL. - Mirror the embedded app's own hash route into the top address bar via the iframe's pinecone:end/hashchange, keeping the URL in sync and deep-linkable (back/forward work). Generated apps (template-application-ui-harmonia-java): - Add a selection route /<Entity>/:id rendering the manage-list view (MANAGE, SETTING, DOCUMENT layouts). - Row selection now updates /<Entity>/<id> in place via replaceState (no list reload) and notifies the embedding shell; deep-link/reload pre-selects the row. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs: document multi-model intent, shared Harmonia shell + application shell Root CLAUDE.md: the cross-model intent DSL (uses:/model:, PROJECTION FKs, n:m intermediate entity, new field/entity attributes, document layout), the shared model-independent shell runtime under application-core, and the pure-Harmonia application shell (resources-application). PRs #6089-#6094. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Context
Today an
.intentmodel is self-contained: a relation'sto:must target an entity in the same model,manyToManyis parsed but never materialized, and each model prefixes its own tables. There was no way to author the real-world pattern the codbex apps use, where each domain (UoM, Country, Currency, Customer, SalesInvoice, CustomerPayment) is its own project and entities reference each other across projects.The codbex apps solve this with PROJECTION entities: a consumer stores an integer FK to the owner's
Idand renders a dropdown sourced from the owner's REST service; the projection generates no table / DAO / controller locally. The owner model owns the single physical table. That mechanism existed only in the visual EDM editor — this PR lifts it into the.intentDSL, adds n:m via an explicit intermediate entity, and adds the field attributes needed for faithful models.What changed
Tranche A — cross-model references
uses:block (UsesIntent) + optionalmodel:on a relation.usesmodel; rejects undeclared models and forbids cross-model composition.EdmIntentGeneratoremits aPROJECTIONentity per distinct cross-model target plus an integer FK + dropdown on the consuming side.CrossModelSupportreads the owner's already-generated.modelfor the exact perspective / PK / table (convention fallback when the owner isn't generated yet). Projections carry a blank perspective so they stay out of the consuming app's navigation.workspaceNameplumbed through endpoint → service → context for theprojectionReferencedModelpath.parameterUtils.js/generateUtils.jsand the schema template already handletype=PROJECTION(table skipped; cross-model FK resolves to the owner's table in the shared DB).Tranche B — n:m via an explicit intermediate entity (composition to one side, cross-model
manyToOneto the other, plus bridge fields likeAmountfor partial allocations). Reuses the composition + cross-model code paths; no extra generator logic.manyToManystays non-materializing.Tranche C — faithful field attributes:
FieldIntentgainsunique,precision,scale,calculatedOnCreate,calculatedOnUpdate; entities gainaudit: true(emits the four standard audit columns).EdmIntentGeneratoremitsdataUnique, precision/scale,isCalculatedProperty+calculatedPropertyExpression*, andauditType— the exact attribute names the DAO/schema templates consume.Examples + docs + tests
.intentfiles (uoms, countries, currencies, customers, customer-payments, sales-invoices) underengine-intenttest resources, including the cross-modelSalesInvoiceCustomerPaymentn:m intermediate.intent-assistant-guide.mddocumentsuses:, cross-model relations, the n:m intermediate pattern, and the new field attributes.EdmIntentGeneratorTestasserting projection shape, FK metadata, no-perspective-leakage, n:m, calculated, and audit. 21/21 pass.Target runtime
The intent scaffold defaults the code-gen recipe to
template-application-ui-harmonia-java(Java DAO/REST + Harmonia UI), which setsjavaRuntime=true, so cross-model dropdowns generate as/services/java/{ownerProject}/gen/{ownerModel}/api/{Perspective}/{Entity}Controller. The.modelis template-agnostic, so switching a project to the AngularJS/TypeScript recipe emits the/services/ts/...form with no intent changes.Known limitations (documented, not fixed)
NumberGeneratorServicedoes not apply to the Java path; the example uses a compilablejava.util.UUID.randomUUID().toString()and documents the swap.Verification
mvn -pl components/engine/engine-intent test(21 tests pass).CUSTOMERS_CUSTOMERhas integerCUSTOMER_COUNTRY/CUSTOMER_CURRENCY,SALES_INVOICES_SALES_INVOICE_CUSTOMER_PAYMENThas the composition FK + integer..._CUSTOMER_PAYMENT+ decimal..._AMOUNT, no Country/Currency/Customer/CustomerPayment table is created by consuming models, and the cross-model dropdowns + n:m allocation work in the UI.🤖 Generated with Claude Code