feat: Alpine.js + Harmonia runtime UI template (list + manage)#6078
Open
delchev wants to merge 51 commits into
Open
feat: Alpine.js + Harmonia runtime UI template (list + manage)#6078delchev wants to merge 51 commits into
delchev wants to merge 51 commits into
Conversation
delchev
added a commit
that referenced
this pull request
Jun 24, 2026
Follow-up to the glue handlers switching from class-level @Listener/@scheduled to self-describing @component beans (destination()/kind()/cron()). Update the IntentEngineIT assertions accordingly: notification/integration/rollup triggers check @component + the destination() return value; the job checks @component + implements JobHandler + the cron() return value. Fixes the PR #6078 CI failure. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
Follow-up to the glue handlers switching from class-level @Listener/@scheduled to self-describing @component beans (destination()/kind()/cron()). Update the IntentEngineIT assertions accordingly: notification/integration/rollup triggers check @component + the destination() return value; the job checks @component + implements JobHandler + the cron() return value. Fixes the PR #6078 CI failure. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… just null A new record's process never started when the form submitted ProcessId as an empty string: the trigger guard checked `ProcessId != null`, and "" != null, so it bailed before Process.start. Two complementary fixes: - Trigger.java.template: treat a blank ProcessId as "not started yet" (`ProcessId != null && !ProcessId.isBlank()`), robust to any client. - Harmonia form-page toPayload(): convert empty-string fields to null (matching the AngularJS contract of omitting untouched fields), so a system-managed back-reference like ProcessId is sent as null on create and the trigger fires; a populated value still round-trips on edit. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Starting a process on onCreate writes the new process-instance id back onto the entity via repository.update(), which republished "<entity>-updated" and spuriously fired every onUpdate reaction — e.g. the notification emailing the member the instant the loan was created, before any review happened. The back-reference is a system write, not a user edit, so it must not emit a lifecycle event. Generate a no-event update on the DAO (updateWithoutEvent, persists via super.update without the Producer.sendToTopic) and have the process trigger use it for the ProcessId write-back. Update IntentEngineIT to assert the silent update. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… + AngularJS-compat) Opening a BPM task form in Harmonia hit three bugs: 1. Claim failed with "Unexpected end of JSON input": the claim endpoint returns a 200 with an empty body, and api.request() called r.json() unconditionally. Read the body as text first and only parse when non-empty (the claim actually succeeded — hence it showed claimed after a refresh). 2. The form 404'd on the SPA shell assets (js/app.js, config.js, services/api.js) and left "App is not defined": the standalone form page referenced the shell via fragile relative paths, but a task form is opened directly (iframe/dialog), not inside the shell. Make the form self-contained — it now loads only form.js (which carries its own minimal fetch client, harmoniaHttp) plus the Harmonia/Alpine/Lucide webjars, and no longer depends on window.App. 3. "no handler for callback onApproveClicked()": intent-generated .form code is AngularJS idiom ($scope/$http/NotificationHub/DialogHub); inlined into formController(ctx), $scope was undefined so attaching the handler threw and was swallowed. Add compatibility shims in formController that map those legacy globals onto ctx, so the existing .form code runs unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Inbox: rebuild the built-in /inbox as an Outlook-style master-detail (mirrors the dashboard redesign #6064/#6068) instead of a flat table — a resizable x-h-split with the task list on the left and, on the right, the selected task's claim action and its form rendered inline in an <iframe> (the form is self-contained, so it embeds cleanly). Adds an auto-refresh toggle, last-updated stamp, and clears the selection + re-fetches when a form completes (harmonia.form.close postMessage). Documents: it did not work at all — every operation went through a root listing of GET <base>?path=/, which the CMS backend rejects with 400 ("null has no such function getName"). Align with the dashboard js/documents.js contract: - list the root with NO ?path= param (folder.path comes back as "/"); - navigate/download/delete by each child's absolute .path; - DELETE sends a JSON body of paths (not ?path=). So New folder, Upload, Delete, navigation and download now work. Also wire the harmonia.form.close postMessage into the processTasks store so the app-wide task-form dialog (list/manage/master row popovers) closes + refreshes on completion (previously only the X button closed it). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Scaffold the model and form recipes to the Alpine.js + Harmonia templates (template-application-ui-harmonia-java + template-form-builder-harmonia) instead of AngularJS + BlimpKit; glue stays the framework-neutral client-Java template. The standalone report-file UI stays the AngularJS perspective for now (there is no Harmonia report-file template yet) — its Java backend is framework-neutral, so the recipe flips to Harmonia once that template lands (TODO noted in code). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ngs/Reports nav Theme: Harmonia is light by default (its :root tokens) and dark via a `dark` class on the document root. Hardcode the default to light with a FOUC-free head bootstrap, and add an Appearance switcher in the sidebar (the counterpart of the dashboard's BlimpKit theme menu) that toggles light/dark and persists the choice to localStorage. Navigation: Settings and Reports are now SINGLE sidebar entries (not one item per setting/report entity, which flooded the sidebar — there can be tens). Each opens a generated landing page (_settings.html / _reports.html) listing its entities, which still have their own CRUD/report routes. Only the most-used PRIMARY entities remain as direct nav items — matching the dashboard's organization. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…e it the default
Adds the Harmonia counterpart of the AngularJS report-file template, so a standalone
.report generates a Harmonia report UI:
- template/template-report-file.js ("Application Report - Table - Harmonia") — clones
the AngularJS -java entry (same model processing + generateGeneric, same reportModels
/ generateReportModels collections) but points at the Harmonia UI sources.
- template/ui/reportFile.js — reuses the framework-neutral Java backend verbatim
(reportFileEntity Repository + Controller + default roles + project.json) and swaps the
AngularJS perspective for a self-contained Harmonia page.
- ui/perspective/report-file/{index.html,report.js} — a standalone Alpine + Harmonia page
(its own fetch client; light-themed like the shell) under gen/<genFolder>/reports/<name>/:
queries the Java report controller (GET list with $limit/$offset, GET /count, POST
/export), renders a table with pagination + CSV export.
- template-report-file.extension registers it on platform-templates.
With the template in place, flip the intent `report` recipe default to it
(IntentSettings.scaffold) — so model, form and report all default to Harmonia; glue stays
the framework-neutral client-Java template.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ark page on a light shell) A BPM task form opened from the inbox rendered dark on the light shell. The form/report pages open standalone in an iframe, and Harmonia themes `body` (not `html`); the form's body is only as tall as its content, so the uncovered `html` area showed the browser default — dark on a dark-mode OS. Two fixes on the standalone form + report pages: - theme the `html` element (background/foreground from the Harmonia tokens) so the whole page surface follows the theme, and give the body a min-height so it fills the iframe; - apply the shell's theme bootstrap (light by default, restoring the localStorage choice) so the iframe'd page matches the shell — Harmonia is light at :root, dark via a `dark` class, and a standalone document must set the class itself. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…create the tables) The Harmonia model template merged only the REST-java backend + Harmonia UI, omitting the schema layer. But nothing else creates the physical tables: the client-Java JavaEntityManager only *registers* an @entity against an existing table (the log shows "Registered ... -> table", never "Created table") — TableCreateProcessor creates tables from the schema's .table artefacts. So a freshly generated Harmonia app had no tables; CRUD and CSVIM seeds failed ("Table metadata was not found for table [LIBRARY_GENRE]"). It only appeared to work when a prior AngularJS/schema generation had already created the tables (kept in place across restarts). Merge template-application-schema sources (as the AngularJS full-stack template does) and dedupe the shared parameters. Now the Harmonia stack generates the .table/.schema artefacts and the tables are created on sync. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
dbb767a to
db95e4c
Compare
…ail + File Preview)
Rebuild the built-in /documents view from a basic browser into the full Document Storage
experience of the dashboard's AngularJS documents perspective, as an Outlook-style
master-detail:
- Left: file/folder list — file-type icons, multi-select checkboxes (select-all), row
overflow menu (copy link, rename, download, delete), drag-and-drop upload, empty state.
- Right: File Preview pane — CSV rendered as a table via PapaParse (added to the shell as a
webjar), other types in an <iframe> over the /preview endpoint, with a can-preview check
that excludes model/code artifacts; loading + "cannot be previewed" states.
- Toolbar: back/forward history, breadcrumbs, search-in-folder toggle, new folder, delete
selected, download folder as zip, upload menu (files / unpack zip contents).
- Dialogs: new folder, rename, delete (single + multi).
All operations go through the CMS REST API exactly as the dashboard does
(/services/js/documents/api/documents.js): root listed with no ?path=, navigate by path,
POST /folder, PUT {path,name} rename, DELETE with a JSON body of paths, multipart upload
(+ /zip for unpack), GET /download + /zip + /preview.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… ?path= (subfolders 400'd)
Drilling into a subfolder (and download/preview/zip/upload within one) failed with HTTP 400
"null has no such function getName". The CMS query layer does not decode an encoded slash —
GET ?path=/__EXPORTS returns 200 but ?path=%2F__EXPORTS returns 400 — and the previous code
ran the whole path through encodeURIComponent, turning every "/" into "%2F".
Add pathParam(): encode each path SEGMENT but keep the slashes literal
(`p.split('/').map(encodeURIComponent).join('/')`), and route all ?path= URLs (list, download,
zip, preview, upload, copy link) through it. Root still lists with no ?path=. Matches the
dashboard, which sends raw paths.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…theme, ProcessId, form shims
Update the Harmonia section after the recent work:
- .form now runs AngularJS code via compat shims and is self-contained (no migration needed).
- Full-stack model template MUST merge template-application-schema (JavaEntityManager registers
entities but never creates tables; the schema sync does) — else a fresh app has no tables.
- Process trigger persists ProcessId via updateWithoutEvent() (no spurious onUpdate); guard is
!= null && !isBlank().
- Documents/CMS path contract: root with no ?path=, encode segments not slashes (%2F 400s),
PUT {path,name} rename, DELETE body of paths.
- Theme: light default, class-based; harmonia.min.js auto-applies OS dark, so standalone iframe
pages must re-assert; Settings/Reports are single nav entries; intent recipe defaults to Harmonia.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ight switch; Settings in sidebar footer; runtime Reports nav Aligns with codbex-athena-app and fixes the lingering issues: Theme — delegate to Harmonia's native colour-scheme API (window.Harmonia.get/setColorScheme, key codbex.harmonia.colorMode) instead of a custom localStorage key + manual `.dark` toggle. The switch moves to the top-right of the toolbar (VitePress-style sun/moon). Because every page — including the standalone form/report iframes that each load harmonia.min.js — reads the same colour-scheme key, they now all honour the shell's choice; this also fixes the task form / report rendering dark on a light shell (harmonia.min.js otherwise auto-applies the OS scheme). The head bootstrap just defaults the scheme to light on first run. Settings — pinned to the sidebar footer (bottom-left), like athena, instead of a content group. Reports nav — discovered at RUNTIME: a new `reports` store walks the project's registry tree for `*/reports/*/index.html` pages, so intent reports (standalone pages in a separate gen folder the shell can't see at generation time) finally surface. The sidebar Reports entry shows when the store is non-empty; the Reports landing lists them and opens each report page in a new tab. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… footer; runtime reports Update the theme gotcha: the theme is now driven through window.Harmonia.get/setColorScheme (key codbex.harmonia.colorMode) — never hand-toggle .dark — so the shell and the standalone form/report iframes theme consistently (fixes the iframe-dark issue). Settings sits in the sidebar footer; Reports is discovered at runtime via the reports store. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…s empty) The report controller returns rows keyed by the report's result-set aliases (e.g. "Member Name", with spaces), but the page looped `$properties` for headers/cells — which is empty for a standalone .report — so the table generated no columns and showed nothing even though the backend returned a row. Derive the columns at runtime from the data (Object.keys of the first row) and render cells via row[col] (bracket access, so aliased keys with spaces work). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ntent, per-entity icons Three polish items: - Humanized display names: the Reports nav/landing and the standalone report page show "Members With Loans Due" instead of "MembersWithLoansDue" (camelCase -> spaced Title Case), via a small humanize in the reports store + report page (display only; URLs/ids unchanged). - Meaningful app title/caption: EdmIntentGenerator now writes a model-level `title` (the intent's humanised `name`, e.g. "Library") + `description` into the .model. The Harmonia template exposes them as appTitle/appDescription; the shell uses the title for the document title + sidebar header and the description as a subtitle. Falls back to the project name when absent (non-intent apps). - Per-entity icons: a new optional `icon` on an intent entity (a Lucide icon name, e.g. book/user). EdmIntentGenerator stores it as `iconName` (Harmonia sidebar, Lucide) and maps it to a unicons SVG URL for `icon`/`perspectiveIcon` (AngularJS perspective); the Harmonia sidebar renders it, falling back to "list". Documented in the intent assistant guide. (Both new model keys are ignored by tooling that only reads entities/perspectives.) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…monia skill references Edit forms rendered empty controls because the values weren't in the shapes the inputs expect: - Dates: the Java REST controller serializes java.time via Jackson as ARRAYS ([y,m,d] / [y,m,d,h,mi,s,ns]) and Instant as a numeric epoch, not ISO strings — so toDateInput()'s String(value).slice() produced garbage and the date/time inputs showed empty. Rewrote it to handle arrays + epoch numbers + ISO strings, emitting the exact value each widget needs. - Relationship combobox: the FK comes back as a number, but an option's data-value (an HTML attribute) is a string, so the value never matched an option and the x-h-select rendered empty on edit. Stringify dropdown values on load (the pattern codbex-athena-app's edit pages use), and align the combobox markup with the Harmonia select contract — data-filter="contains" + an in-dropdown x-h-select-search, data-value coerced to String. Also copy the Harmonia + Alpine.js reference skills from codbex-athena-app into .claude/skills/ (annotated with provenance) and point CLAUDE.md at them, so future Harmonia markup follows the library's documented contracts. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…odule
.claude/* is gitignored, so the skills copied from codbex-athena-app could not live there. Move
them to components/template/template-application-ui-harmonia-java/reference/{harmonia,alpinejs}/
(tracked) and repoint CLAUDE.md, so the codbex-harmonia directive catalog (x-h-select combobox
contract, theming, layout) + Alpine patterns are versioned references for the Harmonia templates.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…d offset below the frame) The relationship combobox showed the selected value pushed ~half its height below the field frame. The only deviation from codbex-athena-app's (visually correct) forms was the x-h-select-clear button — which athena never uses (it appears only in the standalone SKILL example). Removing it matches the reference and fixes the layout; the x-h-select-input already offers clear-on-empty via typing. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…chas Capture the lessons from the edit-form/combobox fixes: - Java REST serializes java.time as Jackson arrays / Instant as epoch (not ISO), so toDateInput must handle arrays/numbers; FK values must be stringified on load to match an option's string data-value or x-h-select renders empty on edit. - Combobox follows the x-h-select contract as used in codbex-athena-app's forms (data-filter + x-h-select-search, String(data-value)); do NOT add x-h-select-clear — it offsets the value below the field frame. Points at the in-repo Harmonia SKILL reference. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
IntentSettings.scaffold now defaults model/form/report to the Harmonia templates, but IntentEngineIT still asserted the AngularJS templateIds — failing generate_writes_all_model_files_into_the_workspace_project (codeGenerations 'orders.model' templateId) and the assertSettings recipe check. Update both: - orders.model -> template-application-ui-harmonia-java/template/template.js - OrdersByCustomer.report -> template-application-ui-harmonia-java/template/template-report-file.js - scaffolded .settings contains template-application-ui-harmonia-java Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ariables
The Loan Approval (and any BPM task) form opened empty — its fields bind to model.<X> but nothing
loaded the task context, so LoanedOn/DueOn etc. were blank. The data is available at
GET /services/inbox/tasks/{id}/variables ({"variables": {LoanedOn, DueOn, Member, Book, ...}}).
The Harmonia form runtime now fetches it on init (when a taskId is present) and merges the
variables into the reactive model, so the fields show the current values. Runs after the .form
controller so the live values win. Documented in form.js + the README ctx contract.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Form layout (template-application-ui-harmonia-java): - Lay form fields on a responsive 12-column grid; each field's width comes from the model's widgetSize, so consecutive narrow fields share a row. Wide widgets (textarea/checkbox) span full width. - Use widgetLabel for labels, mark required with text-negative, add :data-invalid per field, and give the form more horizontal padding. widgetSize is now a plain column count (out of 12) instead of a Fundamental CSS class string: - editor-entity: store 3/4/6/12 (Small/Medium/Large/XLarge). - Harmonia template maps it to grid-column span (12 -> col-span-full). - All AngularJS UI template stacks (angular, angular-v2, angular-java) now emit fd-col-md--<widgetSize> so they keep working. Reports and Settings open content inline (master-detail), not in a new tab or the main pane: - Reports: report list on the left, selected report embedded in an iframe. - Settings: setting entities on the left, the selected entity's manage-list loaded inline via x-html (new settingsPage component). Split panes use data-size (data-default-size is a no-op in harmonia 1.24.1); Reports/Settings/Inbox/Documents default to a 20/80 split. Stop entity-name page headings from truncating to "Bo..."/"Mem..." by adding shrink-0 (x-h-toolbar-title bakes in overflow-hidden + ellipsis). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Harmonia runtime UI (template-application-ui-harmonia-java): - List & Manage views are now master-detail: the table shows the major columns; selecting a row reveals that record's FULL property list in a right-hand pane (an entity may have many properties but only 2-3 columns). - Master view's right pane now also shows the master's own fields (Details card) alongside its detail collections. - Process user tasks surface as inline buttons (not a dropdown) wherever a record appears: list/manage detail pane, master header, and master detail rows (e.g. a Loan's "Librarian Review" task). - Relationship (lookup) FKs render the referenced label instead of the raw id across all tables and panes — each page/detail loads the reference rows from the relationship controller (the same source the form uses) and resolves FK -> label. - java.time values display readably: Jackson arrays (LocalDate/LocalDateTime) and epoch numbers (Instant/Timestamp, via the property's isDateType hint). - Reports/Settings/Inbox/Documents split panes default to 20/80 (data-size; data-default-size is a no-op in harmonia 1.24.1). IDE editors — auto-close when the backing file is gone (e.g. the workspace was cleaned by a Maven rebuild), matching the Monaco editor's behavior. On a 404 while loading content the editor now calls layoutHub.closeEditor: intent, bpm, form-builder, jobs, listeners, websockets, csv, csvim, report, entity, schema, mapping, and the extensions/security/data-structures sub-editors. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sidebar (template-application-ui-harmonia-java):
- Split the sidebar into labeled sections: "Application" (Inbox, Documents),
"Entities" (the model entities), and "Reports".
- Reports are now listed individually in the sidebar (one entry per report,
runtime-discovered) instead of a single landing link; selecting one sets
$store.reports.selected and opens it embedded on /reports. The report's
active highlight clears when navigating away (gated on isActive('/reports')).
- The /reports page just embeds the selected report (no own toolbar title, so
the report's own header isn't duplicated).
- Sidebar header shows only the app name; the description moved to a hover
title tooltip instead of an always-visible second line.
Navigation labels come from the model:
- The sidebar renders each entity's menuLabel (falling back to the name)
through a navLabel() helper that humanizes PascalCase/camelCase names so
multi-word names read as words (SalesInvoice -> "Sales Invoice"); it is
idempotent and does not pluralize.
- The intent EDM generator now emits a humanized + pluralized menuLabel
(new IntentNaming.pluralize), so generated models carry natural plural nav
labels (Book -> "Books", SalesInvoice -> "Sales Invoices",
Category -> "Categories"). Pluralization lives in the model, not the UI.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…stone Pinned JDT Language Server milestone tarballs on download.eclipse.org are pruned over time, so the pinned URL started returning 404 and broke the build. Switch the download to the rolling 'jdt-language-server-latest.tar.gz' snapshot (never removed) and drop the sha256 pin (the snapshot's contents change), both in the root version properties and the ide-java-lsp download-plugin config. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
In the master, manage-list and list views the right-hand detail pane is now hidden (0%, gutter included) while nothing is selected — the table fills the full width — and appears at ~40% once a record is selected, via :data-hidden bound to the no-selection state (the same mechanism the sidebar panel uses). Also fixed the master view's list panel to use data-size (it had the no-op data-default-size). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The top-toolbar breadcrumb (next to the Home icon) was inert — its sections /
routeTitles maps were empty placeholders, so only the Home icon showed. It now
builds a live trail from the current path: Home › <shell section>, Home ›
Reports › <selected report>, or Home › <Entity>[ › Create | › Edit] for entity
routes (earlier crumbs link back). The entity crumb is labeled from a generated
route -> menuLabel map (window.__harmoniaNav in index.html) through the same
navLabel helper, so it reads exactly like the sidebar ("Book" -> "Books").
pageTitle now derives from the last crumb; the dead nav/routeTitles/sections
placeholders are replaced by the model-driven navLabels.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a Home dashboard (routed at '/' and /dashboard, reached via the Home icon): - One KPI tile per top-level entity, generated from the model — shows the entity's label and its live record count (fetched from the controller's GET /count, skeleton while loading), click-through to the entity list. - One tile per runtime-discovered report (opens it embedded on /reports); report count + description is a follow-up (needs the intent generator). New generated dashboardPage component (bakes the entity widget list from the model — label/icon/route/count apiPath) + a generic _dashboard.html view that iterates it; counts load independently so a slow/erroring controller can't block the rest. KPI-tile style adopted from the codbex dashboards. Wired the routes, the page-component script tag, and the shell.js manifest entries. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Report tiles now embed a compact preview of the report instead of a count: the report page gains a preview mode (?preview=1) that hides the toolbar (refresh/export) and pagination and shows only the first 5 rows, and the dashboard report tile iframes the report in that mode with an "open" affordance to the full report. Reuses the same report model/page — no separate renderer. (Settings entities are already excluded from the dashboard: the entity tiles only include type == PRIMARY.) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Entities: a new intent `dashboard: false` on an entity excludes its dashboard tile. EntityIntent.dashboard -> EdmIntentGenerator emits dashboardWidget="false" on the .model entity -> the Harmonia dashboard skips it. (Setting entities are already excluded by their type.) Reports: report tiles are now model-driven. ReportIntent gains `dashboard` (default shown) and the existing `description`; ReportIntentGenerator writes both into the .report. The Harmonia reports store enriches each discovered report from its .report file (label, description, dashboard flag); the dashboard shows the description on the report tile and omits reports with dashboard:false (they remain in the sidebar). No separate widget file — the .report is already valid JSON read directly by the store. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
dashboardPage.js is Velocity-generated (entity tiles), so Alpine's $store
collided with Velocity's $: the new reports getter's
$store.reports.items.filter(r => r.dashboard !== false) made Velocity try to
parse that method call and fail ("Encountered ="). Escape $ via
#set($dollar='$') and use ${dollar}store (the same pattern report.js uses for
$limit/$nextTick).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a "Dashboard" entry at the top of the sidebar's Application group (navigates to /dashboard, active on the home route), so the home dashboard is reachable from the sidebar as well as the Home icon. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The shell header now shows a brand icon before the title, configurable like the
title/description:
- Template parameter `appIcon` (Generate dialog) wins, else the model icon, else
a default ('blocks'). A value with '/' or '.' is rendered as an <img> (custom
SVG/PNG/data URI); a bare word is a Lucide icon name.
- Intent-level `icon:` (IntentModel.icon) -> .model -> appIcon.
- .settings `branding` block (title / description / icon): developer-owned and
preserved across regenerations, so one model (e.g. "Library") can be
rebranded per deployment (each library) without editing the intent. Scaffolded
from the model so it's visible/editable; the generator prefers branding over
the intent's own name/description/icon, which win over defaults.
Verified end-to-end: editing .settings branding (title "Sofia City Library",
icon "book") rebrands the generated shell header.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…e toggle Rework the shell's top-right toolbar (left to right): a visible Lucide sun/moon theme switch (the previous x-h-icon button rendered blank), a notifications bell with an unread-count badge and a dropdown list, and a user avatar (Lucide) whose dropdown shows the logged-in user and Log out. - New currentUser store: loads the platform user name (/services/js/platform-core/services/user-name.js) and logs out via /logout. - New notifications store: in-app list behind the bell (unread count, add, markAllRead, remove); app code/glue pushes entries. Starts empty (no fake data); subscribing to a server-side source is a follow-up. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…notifications Theme fix: standalone report and form pages (embedded as iframes — dashboard report tiles, /reports, task forms) only applied the colour scheme at load, so toggling the shell theme left them stale until a full refresh. They now listen for the localStorage `storage` event (fired in the iframe when the parent shell changes the shared codbex.harmonia.colorMode key) and re-apply via Harmonia's colour-scheme API (guarded against a set/echo loop). Notifications (Phase 1): the user's actionable BPM tasks now surface in the top-right bell — processTasks.load() calls notifications.syncTasks(), which adds a "New task: ..." item per task (preserving read state, dropping completed ones); clicking it opens the task form (appShell.openNotification). Added the app-wide Harmonia notification overlay (toast template) so components/forms/glue can raise transient toasts. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Theme as a global store: move the light/dark state from the app component to
an Alpine store ($store.theme). The toolbar toggle referenced `theme` which
errored on load ("theme is not defined") from eager x-show evaluation; a store
resolves from any scope at any time. Removed the now-dead app theme code.
- Bell badge: the unread count is now a small chip tucked into the bell's
bottom-right corner (absolute, ~15px) instead of an x-h-badge pill that
covered/off-centred the icon and widened the button into its neighbours.
- Live task refresh: the processTasks store now re-checks the inbox on
navigation (pinecone:end) and on a 30s poll, so the notification bell AND the
in-record task buttons update on their own when a task is raised (e.g. a new
Loan starting a process), not only after a full refresh or an Inbox visit.
- Embedded report/form iframes re-theme correctly on shell theme switch: the
storage handler can't gate on Harmonia.getColorScheme() (reads the same key,
already holding the new value) — track the last applied scheme and re-apply.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The detail pane is toggled with x-show, which only hides the element — Alpine still evaluates the inner x-text bindings. Before a row is selected, `selected` is null, so displayValue(selected.Email, ...) / lookupText(..., selected.Field) threw "Cannot read properties of null" on page load. Use optional chaining (selected?.<field>) in the list / manage / master property panes so the value resolves to '—' until a row is selected. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…AUDE.md Root CLAUDE.md (Harmonia section): the Velocity-vs-Alpine `$` trap in velocity-generated shell files (incl. inside HTML comments), put cross-scope state in Alpine stores not the app component (theme), iframe re-theming via the storage event (don't gate on getColorScheme), x-show still evaluates inner bindings (use selected?.), data-size (not data-default-size) for split panes + :data-hidden to collapse, x-h-toolbar-title truncation needs shrink-0, the richer shell (dashboard/sections/breadcrumb/user-bell-theme/notifications), and widgetSize as an integer column count. engine-intent CLAUDE.md (Done): model-driven dashboard + per-deployment branding (.settings branding > intent icon/name/desc > defaults; appIcon param) + dashboard exclude flags (entity dashboardWidget / report dashboard + description) + widgetSize integer + IntentNaming.pluralize. 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.
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.modelas 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
platform-templates— appears in the EDM Generate picker as "Application - UI (Harmonia) - Java". Composes the reusedtemplate-application-rest-javasources with a Harmonia UI source set.list, andmanage(CRUD list + a shared create/edit form on abaseFormPagewith 422ValidationError→ 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.codbex-athena-app:x-h-splitlayout, sidebar, route-derived breadcrumb, responsive sidebar↔drawer, afetchentity client, and a localizable error/i18n catalog.3.15.11+ Harmonia1.24.1+ Lucide1.8.0served as webjars viaapplication-core(itsharmonia.versionbumped 1.4.2 → 1.24.1 — it bundles the webjar but has no Harmonia content); Pinecone Router (no published webjar) vendored underapplication-core/.../vendor/(license-excluded in the root pom). The generatedindex.htmlreferences 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,OrdersMANAGE entity with Country/City dropdowns):service-generateendpoint succeeds; the SPA is served at/services/web/<project>/gen/<model>/index.html./services/java/<project>/gen/<model>/api/<perspective>/<Entity>Controller); a live CRUD round-trip (create/list/count) + the relationship dropdown sources return 200.x-h-split/gutterless/breakpoint listener; Lucide UMD exposescreateIcons).Notes / not in scope
harmonia-viewplatform-links category remain follow-ups (tracked in the module README + plan doc).🤖 Generated with Claude Code