Skip to content

feat: Alpine.js + Harmonia runtime UI template (list + manage)#6078

Open
delchev wants to merge 11 commits into
masterfrom
feat/harmonia-runtime-template
Open

feat: Alpine.js + Harmonia runtime UI template (list + manage)#6078
delchev wants to merge 11 commits into
masterfrom
feat/harmonia-runtime-template

Conversation

@delchev

@delchev delchev commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

What

Adds template-application-ui-harmonia-java — a parallel runtime UI stack that generates a self-contained Alpine.js + Harmonia SPA (client-routed via Pinecone Router, no iframes/postMessage hubs) from the same .model as the existing Angular template, reusing the Java REST/DAO backend unchanged. The AngularJS + BlimpKit IDE is untouched.

This is the first slice of the initiative captured in HARMONIA_RUNTIME_PLAN.md (research + phasing). Both stacks coexist by URL; the choice of UI template is explicit (Angular remains the default everywhere).

What's included

  • New generation template registered on platform-templates — appears in the EDM Generate picker as "Application - UI (Harmonia) - Java". Composes the reused template-application-rest-java sources with a Harmonia UI source set.
  • View types: read-only list, and manage (CRUD list + a shared create/edit form on a baseFormPage with 422 ValidationError → per-field mapping, relationship dropdowns, client-side validation, delete-confirm dialog). Remaining view types (master-detail, report, setting, forms/BPM task forms) are stubbed as a parity checklist.
  • Reusable shell adopted from codbex-athena-app: x-h-split layout, sidebar, route-derived breadcrumb, responsive sidebar↔drawer, a fetch entity client, and a localizable error/i18n catalog.
  • Phase 1 embedding (no CDN): Alpine 3.15.11 + Harmonia 1.24.1 + Lucide 1.8.0 served as webjars via application-core (its harmonia.version bumped 1.4.2 → 1.24.1 — it bundles the webjar but has no Harmonia content); Pinecone Router (no published webjar) vendored under application-core/.../vendor/ (license-excluded in the root pom). The generated index.html references only local /webjars/... and /services/web/application-core/vendor/... URLs.

Verification

Verified end-to-end against a live app from a real model (DependsOnIT/edm.model, Orders MANAGE entity with Country/City dropdowns):

  • Generation via the live service-generate endpoint succeeds; the SPA is served at /services/web/<project>/gen/<model>/index.html.
  • The generated REST path contract matches the controllers (/services/java/<project>/gen/<model>/api/<perspective>/<Entity>Controller); a live CRUD round-trip (create/list/count) + the relationship dropdown sources return 200.
  • All embedded assets resolve locally (Harmonia 1.24.1 confirmed to contain x-h-split/gutterless/breakpoint listener; Lucide UMD exposes createIcons).

Notes / not in scope

  • In-browser render not automated here; all HTTP layers (generation, file correctness, REST contract, CRUD) are verified.
  • Forms/BPM task forms, master-detail, reports, parity Selenide ITs, and an optional harmonia-view platform-links category remain follow-ups (tracked in the module README + plan doc).

🤖 Generated with Claude Code

delchev and others added 11 commits June 24, 2026 00:06
Adds `template-application-ui-harmonia-java`, a parallel runtime UI stack that
generates a self-contained Alpine.js + Harmonia SPA (client-routed via Pinecone,
no iframes/hubs) from the same `.model` as the Angular template, reusing the
Java REST/DAO backend unchanged.

- New generation template registered on `platform-templates` (appears in the EDM
  Generate picker as "Application - UI (Harmonia) - Java"). Composes the reused
  `template-application-rest-java` sources with a Harmonia UI source set.
- View types: read-only `list`, and `manage` (CRUD list + shared create/edit form
  on a `baseFormPage` with 422 ValidationError -> per-field mapping, relationship
  dropdowns, client validation, delete-confirm). Other view types stubbed.
- Reusable shell adopted from codbex-athena-app: x-h-split layout, sidebar,
  breadcrumb, responsive drawer, fetch entity client, error/i18n catalog.
- Phase 1 embedding: Alpine 3.15.11 + Harmonia 1.24.1 + Lucide served as webjars
  via application-core (harmonia.version bumped 1.4.2 -> 1.24.1); Pinecone Router
  vendored (no webjar) under application-core/vendor (license-excluded). The
  generated index.html references only local assets, no CDN.
- HARMONIA_RUNTIME_PLAN.md: research + phasing. IntentSettings: comment noting the
  intent model recipe names the template explicitly (Angular default today).

Verified end-to-end against a live app from a real model (DependsOnIT/edm.model):
generation succeeds, the SPA is served at /services/web/<project>/, and a CRUD
round-trip + relationship dropdowns hit the generated controllers.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Add `setting` view type: SETTING entities (e.g. Country/City) reuse the manage
  CRUD templates against the uiSettingModels collection, grouped under a "Settings"
  sidebar section with their own routes; their derived controller path
  (<restBase>/settings/<Entity>Controller) matches the generated backend.
- Fix: the shell index.html header comment contained a bare `$models` Velocity
  reference, which rendered the entire entity object graph into a comment in every
  generated app. Reworded to prose.

Verified live: generating DependsOnIT/edm.model now produces Country/City CRUD UI
(list + form pages + views), a Settings sidebar group, and /Country|/City routes;
the header no longer dumps the model.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… paths

Master-detail (MANAGE_MASTER/LIST_MASTER + MANAGE_DETAILS/LIST_DETAILS):
- Master entities get a master page at /<Master> — an x-h-split with the master
  record list (left) and, for the selected record, its detail panels (right).
  Masters reuse the manage form for their own routed create/edit.
- Details are decoupled via a runtime registry: each detail emits a registration
  (App.registerDetail(<master>, {...})) so a master renders one generic detailPanel
  per detail without enumerating them at generation time. The panel lists rows
  filtered to the master via the controller's ?<masterEntityId>=<id> query (built
  into the reused rest-java controller), with delete + routed create/edit (FK + a
  returnTo preset; baseFormPage now prefills any form field from a matching query
  param).
- New: detailPanel.js (shell), masterDetail.js collector, master-page/-view +
  detail-register templates; App.details registry; index.html wires master nav,
  routes and detail registration/form scripts.

Fix (surfaced by the hyphenated `sales-order` model): the generated SPA built
its REST base from the raw genFolderName/lowercased perspective, but the Java
backend lives under the *sanitised* package (sales-order -> sales_order). config.js
restBase now uses ${javaGenFolderName} and every page's apiPath uses
${javaPerspectiveName} — matching the DAO/REST templates. Phase 0/1 worked only
because `edm` needed no sanitising.

Verified live against sales-order.model: master Customer + two CustomerPayment
details created through the controllers, and the detail panel's filtered fetch
(?Customer=1) returns exactly that master's payments.

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

Surfaces a process-aware record's BPM user tasks inline, the Alpine/Harmonia
counterpart of the dashboard ProcessTasks module:
- New `processTasks` Alpine store: fetches the current user's inbox tasks
  (/services/inbox/tasks ?type=assignee + ?type=groups), buckets by
  processInstanceId, claims candidate tasks (POST /services/inbox/tasks/{id}
  {action:CLAIM}), and opens the task's formKey in an app-wide dialog (iframe),
  re-fetching on close so completed tasks drop off.
- Generated list / manage / master rows render an inline inbox popover (task
  count + menu) gated on $hasProcess, matching the record via
  entity.ProcessId === task.processInstanceId.
- Shell wires the store script + the task-form dialog into index.html.

Verified: the store + dialog are emitted and wired; gating is correct (a
non-process model emits zero task markup); the inbox endpoints the store calls
respond 200. Full surfacing of a live task needs a process-enabled model
(deferred to combined testing).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
New module — the Alpine.js + Harmonia counterpart of template-form-builder-angularjs.
Given a .form artifact it generates a standalone Harmonia form page
(gen/<genFolder>/forms/<form>/index.html + form.js), for app forms and BPM task
forms (opened by the processTasks store). Registered on platform-templates
(extension "form") so it shows in the Generate picker and can be the intent
recipe's `form` template.

Neutral form-controller contract (the decided replacement for AngularJS $scope/$http):
the .form `code` is the body of formController(ctx), with ctx.{model, params, http,
task:{id,processInstanceId,complete()}, notify, close}. Button `callback`s name
handlers attached to ctx, invoked via the page's run(). BPM task forms complete via
ctx.task.complete() -> POST /services/inbox/tasks/{id}.

v1 renders header/paragraph/line/spacer/link/textfield/textarea/number/checkbox/
date/datetime/time/color/button + one level of container-hbox/vbox; other widgets
fall back to a text input. Reuses the co-generated SPA shell's app/config/api/apiError
(../../js) + the Harmonia/Alpine/Lucide webjars.

Verified live: registers as "Harmonia Generator from Form Model"; generating the
BPMLeaveRequestIT .form produces a Harmonia page (header + textfield + two date
inputs bound to model.*, Approve/Decline buttons with data-variant + run()) and the
formController(ctx) wrapper. Documented follow-ups: migrate existing AngularJS .form
code to the ctx contract and have FormIntentGenerator emit it; feed-driven/complex
widgets.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Report entities (type REPORT) run server-side via the generated Java report
controller (reportFileEntity: GET / rows, POST /search, POST /export). New report.js
collector emits, per report:
- REPORT_TABLE (uiReportTableModels): a <Name>ReportPage + table view — data table over
  the report rows with a Refresh and a client-side CSV Export.
- REPORT_BAR/LINE/PIE/DOUGHNUT/POLARAREA/RADAR (uiReportChartModels): a chart page that
  renders the rows with chart.js (already a bundled webjar, /webjars/chart.js/dist/
  chart.umd.js), labelled by the report's primary-key column with one dataset per other
  column — mirroring the Angular report-chart mapping.

Both resolve <restBase>/reports/<Name>Controller (javaPerspectiveName). index.html adds
report routes, a Reports sidebar group, the page scripts and the chart.js tag.

Verified non-breaking: a non-report model generates cleanly (201) with the chart.js
tag present and no Reports group emitted. Full table/chart runtime needs a
report-bearing model (intent-produced) — covered in combined testing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The old dashboard shell's built-in Process Inbox and Documents perspectives, ported
as always-present sections of every generated Harmonia app (their backends — the
platform inbox + CMS APIs — are reused unchanged):

- Process Inbox (/inbox): lists the user's BPM tasks (assignee + candidate groups)
  via the shared processTasks store (extended with a flat `tasks` list), with
  search and claim-&-open into the app-wide task-form dialog.
- Documents (/documents): a CMS folder browser over /services/js/documents/api —
  navigate folders, download files, create folder, multipart upload, delete, with a
  path breadcrumb. Folders vs files via type === 'cmis:folder'.

Wired as static shell assets: a built-in "Inbox"/"Documents" sidebar group, the
/inbox + /documents routes, and the page scripts — emitted for every app.

Verified live: both views + page components serve; index.html routes/nav/scripts
present; the inbox (/services/inbox/tasks) and documents (/services/js/documents/api)
endpoints respond 200.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ot class-level @Listener/@scheduled

The rollup, notification, integration and schedule glue templates put @Listener /
@scheduled on the class, but those annotations are @target(METHOD) only (the SDK moved
to method-level + self-describing interfaces). javac rejected the generated classes
with "annotation interface not applicable to this kind of declaration", so engine-java
failed to compile gen/events/*.java and the app's client-Java sync broke.

Align them with the working Trigger template's self-describing style:
- Rollup / Notification / Integration: @component implements MessageHandler with
  destination() + kind() instead of class-level @Listener(name=, kind=).
- Job: @component implements JobHandler with cron() instead of class-level
  @scheduled(expression=).
(Webhook's @controller is @target(TYPE) — fine; Resolver is a JavaDelegate — fine.)

Verified live against dirigiblelabs/sample-intent-model: intent generate 200, glue
generate 201, BookLoanCountRollupOnDelete + all gen/events classes now compile clean
(no "annotation interface not applicable"), and the handlers register without
instantiation errors.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…d temporal format

A datetime-local/date/time input value (local, no zone) was POSTed raw, so a
java.time.Instant/Timestamp field failed Jackson binding ("Cannot deserialize
java.time.Instant from String 2026-06-09T16:04") — the form-create 400 the user hit.

form-page now converts both directions (matching the AngularJS stack's new Date(value)):
- toPayload() (create/edit submit): empty date -> null; an HTML date/datetime value ->
  a full ISO instant (…Z) via new Date().toISOString() so Instant/Timestamp parse; a
  bare TIME ("16:04", not a valid Date) passes through so a LocalTime field still parses.
- toDateInput() (edit load): the backend's full ISO value -> the value the HTML widget
  expects (DATE 0..10, DATETIME-LOCAL 0..16, TIME 0..5, MONTH 0..7) so an edited record's
  dates populate the fields.

Both generate per date-type property (uses a per-field temp var to avoid clashes).
Verified at generation against dirigiblelabs/sample-intent-model (Member.JoinedAt):
MemberFormPage emits the correct toPayload + toDateInput.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The detail registration baked an ABSOLUTE apiPath (App.config.restBase + ...), but
detailPanel calls api.get(def.apiPath + q) without a baseUrl override, so the client
prepended restBase again — producing /…/api/services/java/…/api/member/LoanController
(404). Make the detail apiPath relative ('/<perspective>/<Entity>Controller') like the
entity pages, so restBase is prepended exactly once.

Also guard detailPanel.load(): skip the fetch when no master is selected (masterId ==
null) so it no longer fires a wasted ?<fk>=null request before a row is picked.

Verified at generation against sample-intent-model: Loan.detail.js now registers
apiPath '/member/LoanController' (relative) -> resolves to a single
/services/java/lib/gen/library/api/member/LoanController?Member=<id>.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…EADME refresh

Consolidates documentation now that the stack is functional (Loan create/edit,
master-detail, dropdowns, dates all working):

- HARMONIA_RUNTIME_PLAN.md: new "Implementation status (built & verified)" section —
  the two modules, view types + shell sections, the embedding, the runtime contracts
  (REST path sanitisation, {baseUrl:''} semantics, neutral form contract, detail
  registry, processTasks store, date conversion), the fixes surfaced during live
  testing, and follow-ups (next up: intent glue runtime — trigger -> process-start ->
  task -> complete).
- CLAUDE.md: a "Harmonia runtime UI" module section with the gotchas future work must
  keep (Java-sanitised REST paths, {baseUrl:''} = prepend-nothing, two-way date
  conversion, registry-driven master-detail, neutral .form code, self-describing glue
  handlers) + pointers to the module READMEs and the plan doc.
- template README: status -> functional; forms row -> done via template-form-builder-harmonia.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.

1 participant