Add engine-intent: source-of-truth .intent artefact above EDM/BPMN/form#6017
Open
delchev wants to merge 22 commits into
Open
Add engine-intent: source-of-truth .intent artefact above EDM/BPMN/form#6017delchev wants to merge 22 commits into
delchev wants to merge 22 commits into
Conversation
Introduces components/engine/engine-intent as a scaffolding for a new authoring layer: a single .intent YAML file at a project root drives the regeneration of every other model (EDM, DSM, BPMN, form, report, generated TS/Java) under gen/. Developers author only the intent; everything else is "gen" output owned by the regenerator. What's in: - Intent JPA artefact + repository + service (table DIRIGIBLE_INTENTS, artefact type "intent", file extension .intent) - IntentSynchronizer extends BaseSynchronizer; regeneration pass runs in finishing() so the gen/ output is on disk for the next reconciliation cycle (the orchestrator's file walk happens once per cycle). - SynchronizersOrder.INTENT = 5, ahead of every other artefact type so the regenerated files participate in the next cycle before any consumer synchronizer scans. - IntentTargetGenerator SPI + IntentRegenerationService + per-call IntentGenerationContext. Generators are Spring beans, discovered and ordered via @order. Each owns one slice (entities, processes, forms, reports, permissions, controllers) and must be idempotent + scoped to the gen/ subtree. - IntentModel POJOs covering the v1 shape: entities (+ fields, relations), processes (+ steps), forms, reports, permissions. - IntentParser uses SnakeYAML's SafeConstructor (blocks !!type / !!new tags - intent comes from LLM output and human paste, deserialisation must never be a code-execution surface) then round-trips through Gson via JsonHelper so the typed-POJO mapping lives in a single place. Wired into components/pom.xml, modules/pom.xml dependencyManagement, and group/group-engines/pom.xml. No concrete IntentTargetGenerator implementations yet - only the SPI. Concrete generators per slice, the IDE perspective (Mermaid + Claude chat + patch preview), the /custom/ escape-hatch directory, and the same-cycle-visibility orchestrator hook are all flagged as follow-ups in the module's CLAUDE.md. CLAUDE.md in the new module captures the design decisions verbatim from the design conversation: why this exists, the three things any change here must reckon with (expressiveness ceiling, LLM determinism, structured not free text), the chosen format (YAML), the open same-cycle vs next-cycle visibility question, the v1 YAML shape, and the things-not-to-do list. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…tor) Closes the vertical slice for the engine-intent skeleton: - EntityIntentGenerator: first IntentTargetGenerator implementation. Writes <projectRoot>/gen/<EntityName>Entity.ts in the canonical decorator form (@entity / @table / @id / @generated / @column, @onetomany / @manytoone) so the existing EntitySynchronizer (extension Entity.ts) reconciles the regenerated files into the Hibernate dynamic-map store without further wiring. @order(100) - leaves room for the upcoming slice generators (schema 200, process 300, ...). Idempotent by construction; no timestamps in the output. - IntentEndpoint at /services/ide/intent/*: GET /projects list projects with an intent GET /projects/{project} parsed IntentModel JSON GET /projects/{project}/source raw YAML POST /projects/{project}/regenerate force a regen pass ADMINISTRATOR / DEVELOPER / OPERATOR. Constructor-injected, package conventions match engine-listeners + ide-messaging-monitoring. - components/ui/perspective-intent: perspective shell (id=intent, order=1020, three-node graph SVG icon themed via currentColor). Default region 'center' with a single view, intent-mermaid. - components/ui/view-intent-mermaid: read-only Mermaid ER renderer backed by IntentEndpoint. Project picker (bk-select), reload, regenerate, source/diagram toggle. Loads mermaid@11 from cdn.jsdelivr.net (matches the unicons CDN pattern in the rest of the IDE). Server returns parsed IntentModel; client converts to an erDiagram spec (PK marker on primary-key fields, cardinality glyphs per relation kind). mermaid.initialize uses securityLevel: 'strict'. - Registered in components/pom.xml (reactor + dependencyManagement) and components/group/group-ide/pom.xml. - CLAUDE.md updated: notes EntityIntentGenerator as the worked example, documents the perspective + view layout, trims the follow-up list to what is genuinely still pending (Claude bridge, remaining slice generators, /custom/ escape-hatch, read-only gen/ Monaco model, reverse-engineer, same-cycle visibility, schema validation). Build verified: quick-build install + formatter:validate + release-profile javadoc gate all clean for the touched modules. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Every concrete ArtefactRepository must override ArtefactRepository.setRunningToAll with an explicit @query, otherwise Spring Data tries to derive a query from the method name and the context fails to start with: No property 'setRunningToAll' found for type 'Intent' Every other artefact repository (Listener, Table, View, Schema, Entity, Csvim, DataSource, OpenAPI, Camel, ExtensionPoint, Extension, ...) carries the same override. The skeleton missed it because IntentRepository was modelled on the NoRepositoryBean SPI alone, not on a working repo. Same shape as ListenerRepository. This unblocks the integration-test jobs that were failing in the open PR (#6017) - they all blew up at Spring context bootstrap, not in actual test code. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replaces the wrong-direction EntityIntentGenerator with the correct
EdmIntentGenerator and locks in the corrected architecture in the
module CLAUDE.md so future sessions don't repeat the mistake.
The contract:
app.intent (YAML)
-> Intent generators (this engine)
gen/<intent>.edm + gen/<intent>.model (entities)
gen/<process>.bpmn (processes - TODO)
gen/<form>.form (forms - TODO)
gen/<report>.report (reports - TODO)
gen/<intent>.roles + gen/<intent>.access (permissions - TODO)
-> Existing Dirigible template engine
TS / HTML / Java / SQL under gen/<entity>/...
Intent generators stop at the model layer. They do NOT emit Entity.ts,
Controller.ts, *.java or any other code-shaped output - that artefact
belongs to the existing "Generate from EDM/Schema/BPMN" template flow.
Changes:
- Remove EntityIntentGenerator.java (was emitting decorator TS at the
wrong altitude; committed in 9570405, now reverted).
- Add EdmIntentGenerator (@order 200) writing gen/<intent>.edm (XML)
and gen/<intent>.model (JSON twin) from the entities + relations in
the intent. Both files come from a single typed map so they can never
drift. Conservative defaults for icons, menu keys, layout type,
perspective metadata, widget types - derived from the entity / field
name so the produced .edm is openable and editable as-is.
- Add IntentGenerationContext.getProjectName() so generators can derive
project-scoped paths without duplicating the parsing logic.
- Add IntentParser structural validation: duplicate names, dangling
relation / form-entity / report-source targets, unknown field /
relation / step kinds, multiple primary keys per entity. All issues
surface in one IntentValidationException with the complete list.
- Rewrite CLAUDE.md:
* New "Two-stage architecture" section at the top
* New "Wrong turns we already made" section documenting the
EntityIntentGenerator misstep and the related PermissionIntent
one so they aren't repeated
* "Concrete agreements": new top bullet restricting output
extensions to the model layer only
* "Things to not do": new bullets banning code-shaped output and
template-engine path references
* Layout / generator table / follow-ups all aligned with the new
altitude
quick-build + formatter:validate + release-profile javadoc gate all
clean on the touched modules.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Second concrete intent generator (after EdmIntentGenerator). For every
ProcessIntent in the intent, writes one BPMN 2.0 file under gen/ in the
Flowable flavour the existing BpmnSynchronizer consumes.
What it emits:
- One <startEvent>, one <endEvent>, one <process isExecutable="true">.
- userTask -> <userTask flowable:candidateGroups="..." flowable:formKey="..."/>
- serviceTask -> <serviceTask flowable:async="true" flowable:delegateExpression="${JSTask}">
- script -> same shape as serviceTask
- decision -> <exclusiveGateway> with default="flow_<id>_default", plus
a conditioned outgoing flow to args.then carrying
<conditionExpression><![CDATA[${args.if}]]></conditionExpression>
- end -> the canonical <endEvent>; the explicit step's outgoing
flow targets the single shared end event
Sequence flows are emitted linearly between consecutive effective step
IDs (start -> step1 -> ... -> end), with consecutive end-event entries
collapsed so an author-declared `end` step doesn't double-emit.
Path-free references per the CLAUDE.md "no template-engine paths" rule:
flowable:formKey is the bare form name from args.form (a deployment-time
form-key resolver maps it to a generated page), and args.call for
service tasks is passed through verbatim - it should reference a hand-
authored handler under custom/, never a template output.
No BPMN diagram block (bpmndi). Flowable executes without it; the BPMN
editor in the IDE auto-lays out a missing diagram on first open.
Skipping it keeps the generator deterministic and avoids spurious x/y
churn between regenerations.
@order(300), slotted between EdmIntentGenerator (200) and the future
form (400) / report (500) / permissions (600) generators.
CLAUDE.md updated: the generator-table row for processes is now done,
both worked examples are mentioned in the layout block, and the follow-
ups list shrinks accordingly.
quick-build + formatter:validate + release-profile javadoc gate all
clean on the touched modules.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
FormIntentGenerator (@order 400) writes one gen/<form>.form per FormIntent. Output is the JSON shape consumed by the form-builder editor in the IDE - metadata, feeds, scripts, code, plus a form array containing: - a header control with the form name (or description) - one input control per declared field, typed by looking up the field on the bound forEntity: string / uuid -> input-textfield text -> input-textarea integer / long / decimal / double -> input-number boolean -> input-checkbox date -> input-date timestamp -> input-datetime-local - a trailing container-hbox with one button per declared action; button colour is inferred from the action name (approve -> positive, reject/decline/delete/cancel -> negative, save/submit -> emphasized) - a code block with on<Action>Clicked stubs (TODOs); wiring to a backend is the downstream template engine's or a /custom/ override's job Path-free per the CLAUDE.md rule: no template-output URLs in the generated form. Field labels are humanized (orderDate -> "Order Date", from_date -> "From Date"). IntentEngineIT (new) is the worked end-to-end test: - HTTP-only (extends IntegrationTest, no Selenide) - One comprehensive Orders app.intent declares four entities (Customer/Product/Order/OrderItem) with relations in both directions, an OrderApproval process with every step kind (userTask + decision + serviceTask + end), two forms bound to entities (different action sets), one report and three permission roles - Writes the intent through IRepository, forces sync, then asserts: * the Intent JPA artefact is persisted (IntentService) * gen/orders.edm + gen/orders.model exist with every entity name, the right widget types per field type (NUMBER for decimal, DATE for date, CHECKBOX for boolean, TEXTAREA for text), the PRIMARY/DEPENDENT split (Order and OrderItem become DEPENDENT via incoming manyToOne edges), and all three referenced relation targets (Customer / Order / Product) * gen/OrderApproval.bpmn carries startEvent/endEvent, the userTask with candidateGroups + bare-name formKey, the exclusiveGateway, the serviceTask with delegateExpression=${JSTask}, the handler reference and the ${amount > 10000} condition expression * gen/ApproveOrder.form / gen/NewCustomer.form have the right typed controls per field, humanized labels, action buttons in the right colours, and the on<Action>Clicked stubs in the code * GET/POST /services/ide/intent/* endpoints list the project, return the parsed model with the correct sizes (4 entities, 5 process steps, 2 forms, 1 report, 3 permissions), echo the raw YAML source and trigger an explicit regenerate - A second test verifies that removing the .intent file cleans the persisted artefact CLAUDE.md updated: generator table marks .form done, layout block shows the three concrete generators, follow-ups shrinks to .report and .roles/.access. quick-build install, formatter:validate, release-profile javadoc gate, and a clean compile of tests-integrations all pass. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
ReportIntentGenerator (@order 500) writes one gen/<report>.report per ReportIntent. The output is the JSON shape the report editor consumes: outer record with alias / tId / label / baseTable / query plus a columns array. Dimensions become columns with aggregate NONE; measures are parsed by the aggregate(field) convention (count(*) / sum(field) / avg(field) / min(field) / max(field)) into columns with the matching aggregate. Unknown shapes fall back to NONE-aggregate columns carrying the raw text as their name so the editor still loads the file. baseTable is the upper-snake of the source entity name so the .report lines up with the .edm's dataName. query / joins / filters / orders are left empty; the report editor builds the SQL on open from baseTable + columns. PermissionIntentGenerator (@order 600) writes gen/<intent>.roles from the intent's permissions block, deduped by role name with descriptions carried through. It deliberately does NOT emit .access constraints - URL-shaped access rules belong to whichever downstream template materializes the UI for an entity / form / report, because only that template knows the paths it will publish. The can: [Resource:action] tokens on each permission stay as an authoring hint to those downstream generators; the actual <path, method, role> mapping is the downstream template's contract, not intent's. Follow-ups list this trade-off. IntentEngineIT extended: asserts gen/OrdersByCustomer.report carries the intent's alias, the upper-snake baseTable, NONE-aggregate dimensions (customer.country preserved verbatim as a dotted path), and parsed COUNT/SUM aggregates from the measure expressions. Asserts gen/orders.roles contains the Sales/Manager/Administrator role entries with descriptions. CLAUDE.md updated: now lists five concrete generators; the generator table marks every defined intent block as done; layout block shows the new packages; follow-ups shrinks to the .access-from-intent question (documented as deferred) and the lower-priority extensions (DSM/CSVIM/ schema-level entries). quick-build install, formatter:validate, release-profile javadoc gate and a clean compile of tests-integrations all pass. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Country becomes a first-class entity in the IT YAML (id/name/code2/code3/ numeric, shape borrowed from codbex/codbex-countries) and Customer's country drops from a free-text string field to a manyToOne reference relation - matching how partner-style profiles model country lookups in codbex/codbex-partners. The EDM generator already supports manyToOne relations, so the change is purely intent-side; the .edm now carries the new Customer -> Country reference + a CUSTOMER_COUNTRY FK column on Customer. SeedIntent + IntentModel.seeds[] new POJO + collection. Each seed declares a target entity and a list of rows (key/value maps keyed by the intent's field names). Validation rejects unnamed seeds, duplicate seed names, seeds with no entity, seeds targeting unknown entities, and seeds with no rows; surfaces every problem in one IntentValidationException message. CsvimIntentGenerator (@order 700) writes two files per seed: - gen/<seed>.csvim - JSON CSVIM declaration the platform's CsvimSynchronizer consumes. Defaults match the existing platform IT fixtures (header:true, useHeaderNames:true, delimField:",", delimEnclosing:"\"", distinguishEmptyFromNull:true, version:"1.0", schema PUBLIC when the seed doesn't override it). - gen/<seed>.csv - CSV body. Header carries the entity's dataName columns (upper-snake of the field names, prefixed with the entity's dataName, e.g. COUNTRY_ID, COUNTRY_NAME). Row order matches the entity's declared field order so a row author can omit fields without misaligning the columns. Cells containing the delimiter, the quote character, or a newline are quoted and inner quotes doubled. IT YAML expanded to include the Country entity + the Customer -> Country manyToOne + a seeds block shaped after codbex/codbex-countries-data preloading three rows (Afghanistan/Albania/Algeria) into COUNTRY. The test now asserts: - the EDM declares Country and carries the new referenced="Country" link plus the CUSTOMER_COUNTRY FK column on Customer - the parsed-intent REST response carries five entities (Country included), one seeds entry, and three rows on it - gen/countries.csvim declares the COUNTRY table, PUBLIC schema, sibling CSV reference, and the platform-standard CSVIM defaults - gen/countries.csv starts with the expected COUNTRY_ID,COUNTRY_NAME, COUNTRY_CODE2,COUNTRY_CODE3,COUNTRY_NUMERIC header and carries each of the three rows CLAUDE.md updated: layout block shows the new SeedIntent POJO and the csvim/ generator package; the generator table now lists six (.csvim + .csv) as done; the YAML shape sample carries a seeds block; the done list calls out the codbex-countries / codbex-partners / codbex-countries-data inspiration that drove the IT shape. quick-build install, formatter:validate, release-profile javadoc gate and a clean compile of tests-integrations all pass. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
processSynchronizers() silently skips when the runtime is not prepared yet or another synchronization (e.g. the scheduled SynchronizationJob) is mid-run. Callers of the force variant - tests and the IDE publish flow - rely on the registry being reconciled when it returns, so the silent skip was a race: LocalNativeAppLifecycleIT and IntentEngineIT intermittently queried artefacts a skipped pass never persisted. The force variant now retries (100ms steps, bounded at 5 minutes) until the force flag has been consumed by a completed pass and no concurrent run is still in progress. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…form conventions The review of the scaffold found the committed IntentEngineIT could not pass; running it locally confirmed and surfaced three pipeline-killing bugs plus a set of wrong assumptions. All fixed and the IT (now three tests) passes locally: - Registry prefix: artefact locations are registry-relative but IRepository paths are repository-absolute; generated output landed outside /registry/public where neither the IT nor any downstream synchronizer could see it. resolveProjectRoot now prepends IRepositoryStructure.PATH_REGISTRY_PUBLIC. - Empty-model parse: IntentParser mapped the YAML through JsonHelper, whose Gson is configured with excludeFieldsWithoutExposeAnnotation(); every un-annotated POJO field came back null and all six generators silently skipped. The parser now uses a plain Gson with LONG_OR_DOUBLE numbers (seed id: 1 stays "1" in CSV, not "1.0"). - Output location: model files are written at the project root next to app.intent (the layout of real-world codbex application projects and every platform fixture, and the only one the model-to-code template flow is proven to handle) - never under gen/, which the templates wipe on every regeneration. Writes go through the single writeModelFile surface; a post-pass scrub removes model files no longer backed by the intent and cleans them up when the .intent itself is deleted. - Naming: the YAML name: field drives output base names and the physical table prefix (<INTENT>_<ENTITY>, e.g. ORDERS_ORDER) shared via IntentNaming across .edm dataName, .report baseTable and .csvim table - avoiding SQL reserved words and cross-project collisions. - CSVIM file paths are project-qualified (/orders/countries.csv) as CsvimProcessor resolves them against /registry/public. - EDM fidelity: required to-one relations are compositions (DEPENDENT + inherited transitive perspective, relationship* attributes on the FK property), optional ones plain associations; dropdown key/value and referencedProperty derive from the target entity's actual PK and name-like fields; the .model JSON carries perspectives/navigations and no relations array, matching editor-written documents. - Decision steps support else (gateway-default flow target) so the conditioned branch is actually skippable; then/else targets are validated at parse time; declared-but-unconsumed triggers log a warning. - INTENT_CONTENT uses the portable TEXT column definition (CLOB is not valid on PostgreSQL); mermaid is bundled as the org.webjars.npm webjar instead of loading from a CDN. CLAUDE.md is updated to match reality: the corrected path conventions (wrong turn #3), the Gson pitfall, the scrub ownership contract, the naming and decision semantics, the reference project layout, and the .gen descriptor as the future hook for programmatic model-to-code generation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
88247b1 to
ef8aeee
Compare
Adds the engine-intent section (pointer to the module guide, scope, PR reference) plus the general-purpose gotchas this work surfaced: registry-relative locations vs repository-absolute IRepository paths, the JsonHelper @Expose/pretty-print pitfalls, the now-reliable forceProcessSynchronizers semantics, stale H2 state after killed IT runs, and the bidirectional merge-order rule for dirigiblelabs sample repos (including the re-run-vs-update-branch distinction). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
| p.put("dataName", column); | ||
| p.put("dataType", fkType); | ||
| p.put("dataNullable", relation.isRequired() ? "false" : "true"); | ||
| if ("VARCHAR".equals(fkType) && targetPk != null) { |
…act, not a runtime one The platform draws a sharp line this feature initially ignored: authoring artifacts (.edm, .model, .form, .report) get workspace editors plus an explicit Generate step; only runtime artifacts (.roles, .bpmn, .csvim, jobs, ...) are reconciled from the registry by synchronizers. The .edm is the precedent - it has no synchronizer, and neither should the intent. The synchronizer-based first incarnation generated into the registry, where no Projects view, modeler, or "Generate from EDM" flow could use the output, and its UI could only be a registry-reading perspective. Reworked to the editor-first developer flow: 1. create a project in your workspace 2. create app.intent (any *.intent) at the project root 3. double-click opens the new Intent Editor (components/ui/ editor-intent, registered for application/yaml+intent via the platform-editors extension point): editable YAML left, live read-only diagram right (Mermaid ER + one flowchart per process + forms/reports/roles/seeds summaries), validation issues inline, Save with dirty tracking and ctrl+s 4. Generate writes the six model files NEXT TO app.intent IN THE WORKSPACE PROJECT and refreshes the project tree - nothing is published; stale files from removed slices are scrubbed 5. publish ships intent + models together; the per-artefact synchronizers take it from there as for any project Backend: IntentSynchronizer, the Intent JPA artefact (DIRIGIBLE_INTENTS), its repository/service, and the SynchronizersOrder.INTENT constant are removed. IntentGenerationService (ex IntentRegenerationService) runs the unchanged generators against a workspace project and returns written/scrubbed. New endpoints: POST /services/ide/intent/parse (model JSON or 422 with the full issue list - feeds the editor's live diagram) and POST /services/ide/intent/generate?workspace=&project=&path= (resolved via WorkspaceService, inherently user-scoped). The .intent extension is mapped in ContentTypeHelper. The Mermaid perspective (perspective-intent, view-intent-mermaid) is deleted; its rendering moved into the editor. IntentEngineIT is rewritten against the editor services - parse, all-issues-at-once validation, workspace generation with content assertions per artefact, scrub, and 422 on invalid input. No synchronization cycles: the class runs in ~50s instead of ~6 minutes. Verified in a running instance: editor page, mermaid webjar, parse (200/422), generate into /users/admin/workspace/<project> with correct files, scrub on regenerate, registry untouched until publish. Both CLAUDE.md files document the authoring-vs-runtime artifact principle, the removed synchronizer as wrong turn #4, and the .gen descriptor chaining (GenerateService.generateFromModel, the form-builder's Regenerate mechanism) as the follow-up for one-click model-to-code. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
| return ResponseEntity.unprocessableEntity() | ||
| .body(Map.of("issues", e.getIssues())); | ||
| } catch (RuntimeException e) { | ||
| LOGGER.error("Intent generation failed for [{}/{}/{}]", workspace, project, path, e); |
Comment on lines
+84
to
+85
| LOGGER.info("Generating model files for intent [{}] under [{}] via {} generator(s)", IntentNaming.baseName(context), projectRoot, | ||
| generators.size()); |
| try { | ||
| generator.generate(context); | ||
| } catch (RuntimeException e) { | ||
| LOGGER.error("Intent generator [{}] failed for project [{}]", generator.name(), projectName, e); |
| try { | ||
| repository.removeResource(projectRoot + "/" + fileName); | ||
| scrubbed.add(fileName); | ||
| LOGGER.info("Scrubbed stale intent output [{}/{}]", projectRoot, fileName); |
| scrubbed.add(fileName); | ||
| LOGGER.info("Scrubbed stale intent output [{}/{}]", projectRoot, fileName); | ||
| } catch (RuntimeException e) { | ||
| LOGGER.error("Failed to scrub stale intent output [{}/{}]", projectRoot, fileName, e); |
| String fileName = form.getName() + ".form"; | ||
| if (!seenFiles.add(fileName)) { | ||
| LOGGER.warn("Duplicate form [{}] in intent [{}] - keeping the first occurrence", form.getName(), | ||
| IntentNaming.baseName(context)); |
| for (ReportIntent report : model.getReports()) { | ||
| if (report.getName() == null || report.getName() | ||
| .isBlank()) { | ||
| LOGGER.warn("Skipping unnamed report in intent [{}]", IntentNaming.baseName(context)); |
| String fileName = report.getName() + ".report"; | ||
| if (!seenFiles.add(fileName)) { | ||
| LOGGER.warn("Duplicate report [{}] in intent [{}] - keeping the first occurrence", report.getName(), | ||
| IntentNaming.baseName(context)); |
| IntentModel model = IntentParser.parse(yaml); | ||
| return ResponseEntity.ok(model); | ||
| } catch (IntentValidationException e) { | ||
| return ResponseEntity.unprocessableEntity() |
| return ResponseEntity.ok( | ||
| Map.of("workspace", workspace, "project", project, "written", result.written(), "scrubbed", result.scrubbed())); | ||
| } catch (IntentValidationException e) { | ||
| return ResponseEntity.unprocessableEntity() |
Double-clicking a .intent file routed to the editor (content type maps correctly) but the page failed to bootstrap with two errors: - view.js "You must provide one of the following: ... editorData": the page never loaded its own configs/intent-editor.js, so the platform view framework had no editorData to read. - intentEditor $injector:modulerr: the page requested only the ng-view platform-links category, so WorkspaceService / ViewParameters / the workspace hubs (provided by ng-editor) were absent and the Angular module could not satisfy its dependencies. Both are required of every workspace editor; the editor.html now loads configs/intent-editor.js in the head and requests "ng-view,ng-editor", matching the csvim/form editors. Verified against a running instance: the injected HTML now pulls intent-editor.js, view.js, workspace.js and workspace-hub.js. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The HTTP-only IntentEngineIT covers /parse and /generate exhaustively but structurally cannot catch the editor page failing to bootstrap in a browser - which is exactly the regression that slipped through (the editor.html missing the ng-editor platform-links category and its config script left the services green while the page died with $injector:modulerr). This Selenide test (modeled on BpmnEditorLoadsIT, which exists for the same reason) imports a project with an app.intent, opens it, and asserts the editor tab appears, the intentEditor AngularJS module's injector resolves (directly catching the modulerr), the source textarea and the live diagram SVG render inside the iframe, and clicking Generate writes the model files into the workspace project. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nder
The generated .edm and .bpmn opened to an empty canvas. Root cause: both
visual modelers render the diagram ONLY from a layout block - the EDM
editor decodes the .edm's <mxGraphModel> (editor-entity/js/editor.js:
codec.decode(... getElementsByTagName('mxGraphModel')[0] ...)), and the
Flowable/Oryx modeler decodes the .bpmn's <bpmndi:BPMNDiagram>. The
generators emitted only the logical model and omitted both blocks, on the
mistaken assumption that the editors auto-lay-out on open. They do not.
EdmIntentGenerator now emits an <mxGraphModel> with a deterministic grid
layout: a style="entity" vertex per entity carrying an <Entity> value, a
child vertex per property carrying a <Property> value, and an edge per
foreign-key relation wiring the owner's FK property to the target entity's
primary-key property.
BpmnIntentGenerator now emits the omgdc/omgdi/bpmndi namespaces and a
<bpmndi:BPMNDiagram> with a BPMNShape per node (sized by kind) and a
BPMNEdge per sequence flow, laid out left-to-right on a fixed lane. The
sequence-flow emission was refactored to a flow list so the elements and
their edges derive from one source.
Both layouts are deterministic (byte-stable across regenerations); the
modelers re-route on first manual edit.
Tests:
- IntentEngineIT asserts the diagram blocks, a shape/vertex, and an edge
are present in both the .edm and .bpmn.
- IntentEditorLoadsIT now opens the generated library.edm and
LoanApproval.bpmn and asserts the entity box ("Book") and the process
node ("librarianReview") actually render - directly covering the
empty-canvas regression.
CLAUDE.md and the generator javadocs are corrected: the "no diagram block,
the editor auto-lays-out" claim was wrong; the new rule is that any
generator whose target opens in a visual modeler must emit a deterministic
layout.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…Kit theme EDM editor: entity/property label text was invisible on the light theme (light text on the light entity background). The graph font/stroke colors came from --font_color, which styles.css switched via @media (prefers-color-scheme: dark) - the OS setting, not the IDE theme switcher. With the OS in dark mode but the IDE on the light theme, the text stayed light. --font_color/--stroke_color now follow the BlimpKit --foreground token (which the switcher sets, and which the editor body and icons already use), and the OS media override is removed. Both themes are now correct and track the switcher. Intent editor: Mermaid was initialized with the hardcoded 'default' theme, so the diagram ignored the IDE theme. It now uses theme: 'base' with themeVariables read at runtime from the live BlimpKit tokens (--sapBackgroundColor / --sapTextColor / --sapList_Background / --sapList_BorderColor, with --foreground/--background fallbacks), and re-applies + re-renders on ThemingHub.onThemeChange. The diagram now matches light/dark and follows the switch. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The first theming pass mapped lineColor to a divider token (--sapList_BorderColor) and node fill to a surface token (--sapList_Background); both are deliberately low-contrast, so edges and borders were nearly invisible on light and unreadable on dark. It also read raw custom-property values, which do not resolve nested var()/named colors. Now colors are resolved to concrete rgb via a probe element, and the palette is anchored on the two values that guarantee contrast: the theme foreground and background. Lines, borders and all text use the foreground (always contrasts with the canvas in both themes); node/entity fills are the foreground blended into the background at ~10% so panels read as distinct without washing out their text. Re-applied and re-rendered on theme change as before. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…heme switch Two issues remained after the previous theming pass: - Connector lines (ER relationships, BPMN/flowchart edges) were invisible in dark mode. themeVariables.lineColor does not reach those strokes - mermaid derives the relationship/edge/arrowhead styling from its own injected CSS. Added a themeCSS block to mermaid.initialize that forces edge/relationship strokes, arrowheads, node/entity borders and label text to the theme foreground, so connectors are visible in both themes. - Switching the theme produced "Syntax error in text" diagrams. render() drove all sections through Promise.all, i.e. concurrent mermaid.render calls; mermaid 11 shares global parser/DOM state and is not reentrant, so the concurrent renders corrupted each other - reliably right after the theme-change re-initialize. render() now renders sections sequentially (await), with per-section try/catch and the supersede guard preserved. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… build gotcha Hand-off for a clean session. The editor's Mermaid diagram pane still has two unresolved defects (invisible connector lines in dark mode; "Syntax error" bombs on theme switch) after two fix attempts. engine-intent's CLAUDE.md gains an "UNRESOLVED: Intent Editor diagram theming" section recording the exact symptoms, everything already tried (theme:'base' + themeVariables, probe-resolved colors, themeCSS, sequential render), and the prioritized hypotheses to pursue next: verify the running fat jar is actually rebuilt (most likely cause of "no change"), suspect securityLevel:'strict' stripping themeCSS (try 'loose' or post-process the SVG), log the real switch-time error instead of the generic bomb, and strengthen IntentEditorLoadsIT (a "Syntax error" bomb is itself an <svg>, so the current assertion passes on a broken diagram, and it never switches theme). Both CLAUDE.md files also record the platform-wide gotcha that UI module resources are bundled into the build/application fat jar and are not hot-reloaded, so a green IT never proves the user's running jar is current. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The diagram pane's two unresolved Mermaid defects - invisible connector lines in dark mode and a "Syntax error" bomb on every light/dark theme switch - are removed by construction by switching to mxGraph 4.2.2, the engine the EDM/schema/mapping modelers already use. render() now builds one read-only mxGraph per section: an ER-style diagram for the entities (blue HTML-label cards, solid composition vs dashed association edges, mxFastOrganicLayout) and a top-down flowchart per process (slate start/end, blue user-tasks, green service tasks, amber decision rhombus, conditioned/default edges mirroring BpmnIntentGenerator, mxHierarchicalLayout). Cells use fixed brand colours that read on both the light and dark IDE themes painted on a transparent canvas, so the diagram looks identical in either theme and needs no recolour-on-theme-switch step - which is what eliminates both defects rather than patching Mermaid's theming pipeline. editor-intent now depends on resources-mxgraph (like editor-mapping) instead of the mermaid webjar; the orphaned mermaid.version is dropped. IntentEditorLoadsIT asserts the mxGraph SVG renders and the parsed Book entity label appears in the diagram (the old "any <svg> exists" check passed on a Mermaid error bomb). Both CLAUDE.md files updated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Source pane: replace the plain textarea with an embedded Monaco editor (the platform's main code editor engine) with YAML highlighting, reusing the /webjars/monaco-editor webjar editor-monaco already ships - no new dependency. The Monaco theme follows the IDE light/dark theme via ThemingHub (vs-light / blimpkit-dark / classic-dark) exactly as editor-monaco does. $scope.text stays the single source the parse / save / diagram code reads, kept in sync from Monaco's change event, which drives the same dirty-tracking and debounced re-parse the textarea's ng-change used to; the instance is disposed on $destroy. Diagram layout: mxGraph layouts place cells at arbitrary (often negative) coordinates, so translate the view to seat the content at the border (fitIntoView) - previously cells fell off the left/top edge and were clipped. Lay the entities out left-to-right with mxHierarchicalLayout (the same layout the processes use) instead of mxFastOrganicLayout, which collapsed every card onto the origin because they all start at (0,0). IntentEditorLoadsIT now waits for the Monaco editor (.intent-monaco .monaco-editor); the engine-intent guide is updated. Co-Authored-By: Claude Fable 5 <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.
Summary
Scaffolds a new authoring layer above the existing model artefacts: a single
.intentYAML file at a project root drives the regeneration of every other model (EDM, DSM, BPMN, form, report, generated TS / Java) undergen/. Developers author only the intent; everything else is "gen" output owned by the regenerator. One altitude higher than the existing pattern (where the standard models were the source and HTML / SQL / ORM mappings were the gen output).This PR is the engine skeleton only - no concrete
IntentTargetGeneratorimplementations, no IDE perspective. The design context is captured verbatim incomponents/engine/engine-intent/CLAUDE.md.What's in
IntentJPA artefact (DIRIGIBLE_INTENTS, artefact typeintent, file extension.intent) + repository + service.IntentSynchronizer extends BaseSynchronizer; regeneration pass runs infinishing().SynchronizersOrder.INTENT = 5, ahead of every other artefact, so the regeneratedgen/files participate on the next reconciliation cycle.IntentTargetGeneratorSPI +IntentRegenerationService+ per-callIntentGenerationContext. Spring beans discovered via component scan, ordered with@Order. Each generator owns one slice and must be idempotent + scoped to<projectRoot>/gen/.IntentModelPOJOs for the v1 shape: entities (+ fields, relations), processes (+ steps), forms, reports, permissions.IntentParseruses SnakeYAML'sSafeConstructor(blocks!!type/!!newtags - intent arrives from LLM output and human paste, deserialisation must never become a code-execution surface), then round-trips through Gson so the typed-POJO mapping lives in a single place.Wired into
components/pom.xml,modules/pom.xmldependencyManagement, andcomponents/group/group-engines/pom.xml.Design notes (captured in detail in the module CLAUDE.md)
/custom/escape-hatch, patch-shaped LLM edits, structured intent rather than free text).gen/infinishing()are visible only on cycle N+1. Three resolution options are documented; the scaffold picks the do-nothing one for now.Follow-ups (not in this PR)
IntentTargetGeneratorimplementations per slice (entities, processes, forms, reports, permissions, controllers)./custom/escape-hatch directory + per-slice hook points in the generators.reverse-engineer intentcommand for migrating classic projects.Test plan
mvn -pl components/engine/engine-intent -am -P quick-build -DskipTests install- builds cleanmvn -pl components/engine/engine-intent formatter:validate- formatting cleanmvn -pl components/engine/engine-intent -P release -Dgpg.skip=true -DskipTests -Dlicense.skip=true -Dformatter.skip=true install- javadoc gate cleanCo-Authored-By: Claude Opus 4.7 noreply@anthropic.com
🤖 Generated with Claude Code