From bfac5a6a89364a7b0d40dfef3e81e4cc6f4e6032 Mon Sep 17 00:00:00 2001 From: delchev Date: Mon, 20 Jul 2026 15:48:07 +0300 Subject: [PATCH] feat(harmonia): adopt Harmonia 2.6.0 - slot-picker toolbar, month/week pickers, upstream skill Harmonia was bumped 2.3.1 -> 2.6.0 (dependabot #6311, pom only). Adapt the generated Harmonia stack to the 2.4-2.6 breaking changes and adopt the new pickers. Slot Picker (2.5.0 breaking): the picker no longer renders its own toolbar, so the `view: slots` page composed only a bare grid with no date navigation. Compose the previous/calendar/next/title/today toolbar from the x-h-slot-picker-* control directives; add the today/chooseDate i18n keys. Month/Week pickers (new in 2.4.0): adopt as two new intent field types. - parser: `month`/`week` accepted field types - EdmIntentGenerator: both map to VARCHAR (len 7/8); the MONTH/WEEK widget type is chosen from the LOGICAL type, not the JDBC dataType (both VARCHAR, so invisible to widgetForType) - mirrors the documentTitle special-case - Harmonia manage form-view + document-view: render x-h-month-picker / x-h-week-picker instead of plain - FormIntentGenerator + template-form-builder-harmonia: input-month / input-week controls and picker blocks - format.js: MONTH/WEEK are plain strings - toPayload passes them through (not ISO-instant-ized), toDateInput slices; fmtDate maps input-week -> WEEK The AngularJS stacks already handle both widget types (verified). Range slider (2.6.0 breaking) and the removed `position-fit` (2.4.0) are not used by any template, so nothing to change there. Docs: remove the in-repo `reference/harmonia/` skill mirror (71 files) and point CLAUDE.md / the migration note at the upstream skill + docs (codbex.com/harmonia) to avoid drift; bump the "Harmonia 2.3.1" version string to 2.6.0; document the two new field types. Tests: EdmIntentGeneratorTest + IntentParserTest cover the month/week mapping and acceptance; IntentEngineIT.month_and_week_fields_generate_the_harmonia_pickers asserts the generated .model widget types and the rendered pickers end-to-end. Co-Authored-By: Claude Opus 4.8 --- CLAUDE.md | 6 +- HARMONIA_2_MIGRATION.md | 4 +- components/engine/engine-intent/CLAUDE.md | 2 +- .../generator/edm/EdmIntentGenerator.java | 42 +- .../generator/form/FormIntentGenerator.java | 4 + .../components/intent/model/FieldIntent.java | 6 +- .../intent/parser/IntentParser.java | 4 +- .../main/resources/intent-assistant-guide.md | 2 +- .../generator/edm/EdmIntentGeneratorTest.java | 27 + .../intent/parser/IntentParserTest.java | 31 + .../shell/js/services/format.js | 14 +- .../reference/harmonia/SKILL.md | 153 ---- .../reference/harmonia/llms.txt | 90 --- .../harmonia/references/accordion.md | 147 ---- .../reference/harmonia/references/alert.md | 107 --- .../reference/harmonia/references/avatar.md | 141 ---- .../reference/harmonia/references/badge.md | 156 ----- .../reference/harmonia/references/bar.md | 109 --- .../harmonia/references/breadcrumb.md | 344 --------- .../references/breakpoint-listener.md | 71 -- .../harmonia/references/button-group.md | 70 -- .../reference/harmonia/references/button.md | 186 ----- .../reference/harmonia/references/calendar.md | 198 ------ .../reference/harmonia/references/card.md | 94 --- .../reference/harmonia/references/checkbox.md | 49 -- .../reference/harmonia/references/chip.md | 155 ---- .../harmonia/references/date-format.md | 83 --- .../harmonia/references/date-picker.md | 181 ----- .../harmonia/references/datetime-picker.md | 125 ---- .../reference/harmonia/references/dialog.md | 62 -- .../reference/harmonia/references/doughnut.md | 83 --- .../harmonia/references/expansion-panel.md | 233 ------ .../reference/harmonia/references/fieldset.md | 317 --------- .../harmonia/references/file-upload.md | 82 --- .../reference/harmonia/references/focus.md | 70 -- .../reference/harmonia/references/i18next.md | 268 ------- .../reference/harmonia/references/icon.md | 136 ---- .../reference/harmonia/references/include.md | 79 --- .../harmonia/references/info-page.md | 94 --- .../harmonia/references/inline-calendar.md | 134 ---- .../harmonia/references/input-group.md | 139 ---- .../harmonia/references/input-number.md | 52 -- .../reference/harmonia/references/input.md | 61 -- .../reference/harmonia/references/label.md | 29 - .../reference/harmonia/references/line.md | 70 -- .../reference/harmonia/references/list.md | 88 --- .../reference/harmonia/references/listbox.md | 54 -- .../reference/harmonia/references/lucide.md | 112 --- .../reference/harmonia/references/menu.md | 174 ----- .../harmonia/references/navigation-menu.md | 249 ------- .../harmonia/references/notifications.md | 629 ----------------- .../harmonia/references/pagination.md | 79 --- .../reference/harmonia/references/pie.md | 104 --- .../reference/harmonia/references/popover.md | 160 ----- .../reference/harmonia/references/progress.md | 71 -- .../reference/harmonia/references/radio.md | 45 -- .../reference/harmonia/references/range.md | 50 -- .../reference/harmonia/references/rating.md | 106 --- .../reference/harmonia/references/scatter.md | 70 -- .../reference/harmonia/references/select.md | 238 ------- .../harmonia/references/separator.md | 50 -- .../reference/harmonia/references/sheet.md | 114 --- .../reference/harmonia/references/sidebar.md | 662 ------------------ .../reference/harmonia/references/skeleton.md | 47 -- .../harmonia/references/slot-picker.md | 266 ------- .../reference/harmonia/references/spinner.md | 28 - .../reference/harmonia/references/split.md | 231 ------ .../harmonia/references/step-indicator.md | 174 ----- .../reference/harmonia/references/switch.md | 48 -- .../reference/harmonia/references/table.md | 548 --------------- .../reference/harmonia/references/tabs.md | 597 ---------------- .../reference/harmonia/references/tag.md | 57 -- .../reference/harmonia/references/template.md | 132 ---- .../reference/harmonia/references/text.md | 61 -- .../reference/harmonia/references/textarea.md | 44 -- .../reference/harmonia/references/theme.md | 85 --- .../reference/harmonia/references/tile.md | 265 ------- .../harmonia/references/time-picker.md | 123 ---- .../reference/harmonia/references/toolbar.md | 194 ----- .../reference/harmonia/references/tooltip.md | 37 - .../reference/harmonia/references/tree.md | 142 ---- .../harmonia/references/utility-classes.md | 47 -- .../document/document-view.html.template | 14 + .../manage/form-view.html.template | 16 +- .../slots/slots-view.html.template | 28 +- .../ui/translations.json.template | 2 + .../ui/form.js.template | 3 +- .../ui/index.html.template | 20 +- .../integration/tests/api/IntentEngineIT.java | 34 + 89 files changed, 233 insertions(+), 10575 deletions(-) delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/SKILL.md delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/llms.txt delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/references/accordion.md delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/references/alert.md delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/references/avatar.md delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/references/badge.md delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/references/bar.md delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/references/breadcrumb.md delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/references/breakpoint-listener.md delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/references/button-group.md delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/references/button.md delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/references/calendar.md delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/references/card.md delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/references/checkbox.md delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/references/chip.md delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/references/date-format.md delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/references/date-picker.md delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/references/datetime-picker.md delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/references/dialog.md delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/references/doughnut.md delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/references/expansion-panel.md delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/references/fieldset.md delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/references/file-upload.md delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/references/focus.md delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/references/i18next.md delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/references/icon.md delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/references/include.md delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/references/info-page.md delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/references/inline-calendar.md delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/references/input-group.md delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/references/input-number.md delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/references/input.md delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/references/label.md delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/references/line.md delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/references/list.md delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/references/listbox.md delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/references/lucide.md delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/references/menu.md delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/references/navigation-menu.md delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/references/notifications.md delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/references/pagination.md delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/references/pie.md delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/references/popover.md delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/references/progress.md delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/references/radio.md delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/references/range.md delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/references/rating.md delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/references/scatter.md delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/references/select.md delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/references/separator.md delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/references/sheet.md delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/references/sidebar.md delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/references/skeleton.md delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/references/slot-picker.md delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/references/spinner.md delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/references/split.md delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/references/step-indicator.md delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/references/switch.md delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/references/table.md delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/references/tabs.md delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/references/tag.md delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/references/template.md delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/references/text.md delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/references/textarea.md delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/references/theme.md delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/references/tile.md delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/references/time-picker.md delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/references/toolbar.md delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/references/tooltip.md delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/references/tree.md delete mode 100644 components/template/template-application-ui-harmonia-java/reference/harmonia/references/utility-classes.md diff --git a/CLAUDE.md b/CLAUDE.md index 93f33a1870a..54af558de5d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -191,9 +191,9 @@ A single `app.intent` YAML file at a project root is the source of truth one alt ## Harmonia runtime UI (`template-application-ui-harmonia-java` + `template-form-builder-harmonia`) -A second runtime UI stack, parallel to the AngularJS/BlimpKit one: generated apps render as a self-contained **Alpine.js + Harmonia SPA** (client-routed by Pinecone in hash mode, no iframes/`postMessage` hubs), served at `/services/web//gen//index.html`, talking to the **reused** generated Java REST controllers over a `fetch` client. The AngularJS IDE is untouched; the two stacks coexist by URL. `template-application-ui-harmonia-java` (registered on `platform-templates` as "Application - UI (Harmonia) - Java") mirrors `template-application-ui-angular-java` and emits the view types (list, manage, setting, master-detail, reports) + built-in **Process Inbox** (`/inbox`) and **Documents** (`/documents`) shell sections + inline process-task surfacing; `template-form-builder-harmonia` ("Harmonia Generator from Form Model", extension `form`) is the runtime form generator. The whole stack — Alpine 3.15.11, Harmonia 2.3.1, Lucide 1.8.0 — is embedded as **webjars** via `components/resources/application-core` (report charts use Harmonia's own native `x-h-chart-*` SVG charts; the `chart.js` webjar remains only for the legacy AngularJS report/dashboard stacks) (incl. Pinecone Router — `org.webjars.npm:pinecone-router`, served version-less at `/webjars/pinecone-router/dist/router.min.js`; it was vendored until the 7.5.2 webjar existed). Developed on PR [#6078](https://github.com/eclipse-dirigible/dirigible/pull/6078). +A second runtime UI stack, parallel to the AngularJS/BlimpKit one: generated apps render as a self-contained **Alpine.js + Harmonia SPA** (client-routed by Pinecone in hash mode, no iframes/`postMessage` hubs), served at `/services/web//gen//index.html`, talking to the **reused** generated Java REST controllers over a `fetch` client. The AngularJS IDE is untouched; the two stacks coexist by URL. `template-application-ui-harmonia-java` (registered on `platform-templates` as "Application - UI (Harmonia) - Java") mirrors `template-application-ui-angular-java` and emits the view types (list, manage, setting, master-detail, reports) + built-in **Process Inbox** (`/inbox`) and **Documents** (`/documents`) shell sections + inline process-task surfacing; `template-form-builder-harmonia` ("Harmonia Generator from Form Model", extension `form`) is the runtime form generator. The whole stack — Alpine 3.15.11, Harmonia 2.6.0, Lucide 1.8.0 — is embedded as **webjars** via `components/resources/application-core` (report charts use Harmonia's own native `x-h-chart-*` SVG charts; the `chart.js` webjar remains only for the legacy AngularJS report/dashboard stacks) (incl. Pinecone Router — `org.webjars.npm:pinecone-router`, served version-less at `/webjars/pinecone-router/dist/router.min.js`; it was vendored until the 7.5.2 webjar existed). Developed on PR [#6078](https://github.com/eclipse-dirigible/dirigible/pull/6078). -**Component reference:** [`components/template/template-application-ui-harmonia-java/reference/harmonia/SKILL.md`](components/template/template-application-ui-harmonia-java/reference/harmonia/SKILL.md) is the full codbex-harmonia directive catalog (all `x-h-*` components incl. the `x-h-select` combobox contract, theming, layout), and [`.../reference/alpinejs/`](components/template/template-application-ui-harmonia-java/reference/alpinejs/) covers the Alpine patterns (routing, page components) — both mirrored from `codbex-athena-app`, the reference app this stack was adopted from. Read them before changing Harmonia markup. +**Component reference:** the full codbex-harmonia directive catalog (all `x-h-*` components incl. the `x-h-select` combobox contract, theming, layout) lives **upstream** — the docs site and the agent-readable skill (formerly mirrored in-repo under `reference/harmonia/`; that copy was removed to avoid drift — always consult the upstream, version-matched to `harmonia.version` in the root `pom.xml`). [`.../reference/alpinejs/`](components/template/template-application-ui-harmonia-java/reference/alpinejs/) still covers the Alpine patterns (routing, page components), mirrored from `codbex-athena-app`, the reference app this stack was adopted from. Read them before changing Harmonia markup. **Detailed guides:** [`components/template/template-application-ui-harmonia-java/README.md`](components/template/template-application-ui-harmonia-java/README.md) (the SPA shell, view-type parity checklist, the master-detail detail registry, process-tasks store) and [`components/template/template-form-builder-harmonia/README.md`](components/template/template-form-builder-harmonia/README.md) (the neutral `formController(ctx)` contract). The repo-root [`HARMONIA_RUNTIME_PLAN.md`](HARMONIA_RUNTIME_PLAN.md) is the design doc + implementation status. **Gotchas that already burned someone — read before changing the templates:** @@ -207,7 +207,7 @@ A second runtime UI stack, parallel to the AngularJS/BlimpKit one: generated app - **Process trigger writes ProcessId via a targeted single-column update (no event, no full row).** Starting a process on `onCreate` writes the instance id back; doing it through the normal `update()` republishes `-updated` and spuriously fires every onUpdate reaction (e.g. the member-email notification fired the instant a loan was created) — and even the silent `updateWithoutEvent()` was a **full-row merge of the trigger's stale snapshot**, which raced concurrent writes (line items recalculating the header totals milliseconds after create, a start-step status set) and silently reverted them. The trigger now uses `repository.updateProperty(id, "ProcessId", processId)` — an SDK `JavaRepository`/`JavaEntityStore` HQL mutation touching only the named column (same for a minted `businessKeyStrategy` field); no audit stamping, no events, nothing else to clobber. `updateProperty` is the sanctioned workflow/system write-back primitive — reserve it for system columns; user data keeps going through the generated repository's normal write path. The trigger guard is `ProcessId != null && !isBlank()` (an empty string from a form must not count as "already started"). - **Documents/CMS path contract (`/services/js/documents/api/documents.js`).** List the **root with NO `?path=`** (a `?path=/` 400s "null has no such function getName"); navigate by path. The CMS query layer does **not** decode an encoded slash, so build `?path=` with **literal slashes** — encode each segment, not the whole path (`p.split('/').map(encodeURIComponent).join('/')`); a blanket `encodeURIComponent` turns `/`→`%2F` and every subfolder/file 400s. Rename is `PUT {path,name}`; delete is `DELETE` with a JSON body of absolute paths; preview/download are `GET /preview|/download?path=`. Matches the dashboard `documents/js/documents.js`. - **Edit forms: match the value to the input shape (dates + comboboxes).** The Java REST controller serializes `java.time` via Jackson as **arrays** (`LocalDate` → `[y,m,d]`, `LocalDateTime` → `[y,m,d,h,mi,s,ns]`) and `Instant` as a **numeric epoch** — NOT ISO strings — so the form's `toDateInput()` handles arrays/numbers/strings (a naive `String(v).slice()` yields garbage → empty date controls on edit). And a relationship FK comes back as a **number** while an option's `data-value` is a string (HTML attribute), so the value must be **stringified on load** (`form.X = String(record.X)`) or the `x-h-select` matches no option and renders empty on edit — the same `String(...)` the codbex-athena-app edit pages use. -- **Combobox = the Harmonia `x-h-select` contract; mirror codbex-athena-app's forms, not the SKILL's toy example.** `x-model` on `x-h-select-input` holds the VALUE (the option `data-value`); the input displays the matched option's label. Use `data-filter="contains"` + an in-dropdown `x-h-select-search` for searchable pickers, options via `x-for` with `:data-value="String(opt.value)"`. **Do NOT add `x-h-select-clear`** — athena's forms omit it (it appears only in the SKILL's standalone example) and it pushes the selected value ~half its height below the field frame. The component reference is `template-application-ui-harmonia-java/reference/harmonia/SKILL.md`. +- **Combobox = the Harmonia `x-h-select` contract; mirror codbex-athena-app's forms, not the SKILL's toy example.** `x-model` on `x-h-select-input` holds the VALUE (the option `data-value`); the input displays the matched option's label. Use `data-filter="contains"` + an in-dropdown `x-h-select-search` for searchable pickers, options via `x-for` with `:data-value="String(opt.value)"`. **Do NOT add `x-h-select-clear`** — athena's forms omit it (it appears only in the SKILL's standalone example) and it pushes the selected value ~half its height below the field frame. The component reference is the upstream Harmonia skill (). - **Theme goes through Harmonia's own colour-scheme API — do not hand-toggle `.dark`.** `harmonia.min.js` manages the `.dark` class itself from `window.Harmonia.get/setColorScheme('light'|'dark'|'auto')`, persisted to localStorage key `codbex.harmonia.colorMode`; its default is `auto` (follow the OS). The shell's top-right switch (`appShell.js toggleTheme()`) calls `Harmonia.setColorScheme`, and a head bootstrap defaults the key to `light` on first run. Because **every** page loads harmonia.min.js and reads the same key, the shell and the standalone form/report iframes all theme consistently — this is why the task form no longer renders dark on a light shell (an earlier custom `dirigible.harmonia.theme` key + manual `.dark` toggle only fixed the shell, not the iframes). Settings is pinned to the **sidebar footer** (bottom-left, like codbex-athena-app); Reports is a single entry **discovered at runtime** (a `reports` store walks the project registry tree for `*/reports/*/index.html`, since intent reports are standalone pages in a separate gen folder the shell can't see at generation time). The intent recipe defaults (`IntentSettings.scaffold`) point model/form/report at the Harmonia templates; glue stays the neutral client-Java template. - **Velocity vs Alpine `$` is the #1 trap in the Harmonia shell templates.** The shell's `index.html.template` and `dashboardPage.js.template` are **generated via Velocity** (they use `$models`/`$entity`/`$appIcon`), and Velocity parses `$`-references **everywhere — including inside HTML comments and JS string bodies**. An Alpine magic like `$store`/`$notifications`/`$nextTick` is fine as a *bare reference* (undefined → Velocity passes it through literally), but the moment it's followed by a **method call with args or trailing statements** Velocity tries to parse it and the generation 500s with `Encountered "..."`. Three real burns: `$store.processTasks.openTask(n.task); n.unread=false` in an `@click` (fixed by moving the logic to an `appShell.openNotification(n)` method and calling that), `$notifications.add({ template:'toast', data:{ message, variant } })` **in an HTML comment** (Velocity choked on `{ message,…}` — reworded the comment), and `$store...filter(r => r.dashboard !== false)` in a getter (escaped via `#set($dollar='$')` + `${dollar}store`, the same trick `report.js.template` uses for `$limit`/`$nextTick`). Rule: in a Velocity-generated file keep `$store.x` to bare refs / no-arg calls, escape with `${dollar}` for anything richer, or move it into a copied (non-Velocity) JS file / an appShell method. diff --git a/HARMONIA_2_MIGRATION.md b/HARMONIA_2_MIGRATION.md index 8438816eb53..34ddbe495de 100644 --- a/HARMONIA_2_MIGRATION.md +++ b/HARMONIA_2_MIGRATION.md @@ -64,7 +64,9 @@ phases land. Harmonia were fixed: `upload` -> built-in `import`; `inbox`/`eye` -> Lucide. (`x-h-info-page-media.icon` still exists in 2.x - left as is.) 3. Refreshed the vendored `template-application-ui-harmonia-java/reference/harmonia/` - skill from the 2.1.0-shipped `skills/harmonia/`. + skill from the 2.1.0-shipped `skills/harmonia/`. (Since removed - the skill is now + consulted upstream at + / to avoid drift.) 4. Synced version strings in `CLAUDE.md` and `HARMONIA_RUNTIME_PLAN.md` (`data-size` split note re-verified - still accurate in 2.1.0). 5. Also fixed a pre-existing bug found during runtime verification: the diff --git a/components/engine/engine-intent/CLAUDE.md b/components/engine/engine-intent/CLAUDE.md index c81bc8e33fe..7150dd023ce 100644 --- a/components/engine/engine-intent/CLAUDE.md +++ b/components/engine/engine-intent/CLAUDE.md @@ -265,7 +265,7 @@ seeds: - { id: 2, name: Albania, code2: AL, code3: ALB, numeric: "008" } ``` -Logical field types (`FieldIntent.type`) are: `string`, `text`, `integer`, `int`, `long`, `decimal`, `double`, `boolean`, `date`, `timestamp`, `uuid`. Generators map them to JDBC + EDM types. **Primary keys must be an integer type (`integer`/`int`/`long`)** - the Dirigible model convention is integer auto-increment identifiers, and a non-integer auto-increment column is invalid SQL (a `uuid`/`VARCHAR` PK produced `AUTO_INCREMENT` on a `VARCHAR(36)` column, which H2 rejects); the parser enforces this and the EDM generator only emits `dataAutoIncrement` for integer columns. `uuid` remains valid for non-PK fields (maps to `VARCHAR(36)`). Relation kinds: `oneToMany`, `manyToOne`, `oneToOne`, `manyToMany`. Step kinds: `userTask`, `serviceTask`, `decision`, `script`, `end`. +Logical field types (`FieldIntent.type`) are: `string`, `text`, `integer`, `int`, `long`, `decimal`, `double`, `boolean`, `date`, `timestamp`, `uuid`, `month`, `week`. Generators map them to JDBC + EDM types. `month` (`YYYY-MM`) and `week` (`YYYY-Www`) are stored as VARCHAR strings and get the `MONTH`/`WEEK` widget types (the Harmonia month/week pickers, `x-h-month-picker`/`x-h-week-picker`); their widget is chosen from the logical type, not the JDBC dataType, since both are VARCHAR. **Primary keys must be an integer type (`integer`/`int`/`long`)** - the Dirigible model convention is integer auto-increment identifiers, and a non-integer auto-increment column is invalid SQL (a `uuid`/`VARCHAR` PK produced `AUTO_INCREMENT` on a `VARCHAR(36)` column, which H2 rejects); the parser enforces this and the EDM generator only emits `dataAutoIncrement` for integer columns. `uuid` remains valid for non-PK fields (maps to `VARCHAR(36)`). Relation kinds: `oneToMany`, `manyToOne`, `oneToOne`, `manyToMany`. Step kinds: `userTask`, `serviceTask`, `decision`, `script`, `end`. Semantics worth knowing: diff --git a/components/engine/engine-intent/src/main/java/org/eclipse/dirigible/components/intent/generator/edm/EdmIntentGenerator.java b/components/engine/engine-intent/src/main/java/org/eclipse/dirigible/components/intent/generator/edm/EdmIntentGenerator.java index 89025bb2d4b..151a541cc5d 100644 --- a/components/engine/engine-intent/src/main/java/org/eclipse/dirigible/components/intent/generator/edm/EdmIntentGenerator.java +++ b/components/engine/engine-intent/src/main/java/org/eclipse/dirigible/components/intent/generator/edm/EdmIntentGenerator.java @@ -899,7 +899,7 @@ private static Map propertyMap(String entityName, FieldIntent fi } p.put("auditType", "NONE"); // Document role: the number/title field renders in the document form's title, not as an input. - p.put("widgetType", field.isDocumentTitle() ? "DOCUMENT_NUMBER" : widgetForType(dataType)); + p.put("widgetType", field.isDocumentTitle() ? "DOCUMENT_NUMBER" : widgetForField(field, dataType)); p.put("widgetSize", field.getSize() == null ? "" : field.getSize() .toString()); @@ -1568,13 +1568,26 @@ private static String labelFieldName(EntityIntent target) { return keyFieldName(target); } - /** Declared length, with type-derived defaults ({@code uuid} -> 36). */ + /** + * Declared length, with type-derived defaults ({@code uuid} -> 36, {@code month} -> 7, {@code week} + * -> 8). + */ private static Integer fieldLength(FieldIntent field) { if (field.getLength() != null) { return field.getLength(); } - if (field.getType() != null && "uuid".equalsIgnoreCase(field.getType())) { - return 36; + if (field.getType() != null) { + switch (field.getType() + .toLowerCase(Locale.ROOT)) { + case "uuid": + return 36; + case "month": // YYYY-MM + return 7; + case "week": // YYYY-Www + return 8; + default: + break; + } } return defaultLength(mapDataType(field.getType())); } @@ -1602,11 +1615,32 @@ private static String mapDataType(String type) { return "CLOB"; case "uuid": case "string": + case "month": // stored as the picker's YYYY-MM string + case "week": // stored as the picker's YYYY-Www ISO-week string default: return "VARCHAR"; } } + /** + * Widget for a field. {@code month}/{@code week} are both stored as {@code VARCHAR}, so they are + * indistinguishable at the JDBC-type level - the picker widget is chosen from the logical type (the + * same reason {@code documentTitle} is special-cased at the call site). + */ + private static String widgetForField(FieldIntent field, String dataType) { + String type = field.getType() == null ? "" + : field.getType() + .toLowerCase(Locale.ROOT); + switch (type) { + case "month": + return "MONTH"; + case "week": + return "WEEK"; + default: + return widgetForType(dataType); + } + } + private static String widgetForType(String dataType) { switch (dataType) { case "INTEGER": diff --git a/components/engine/engine-intent/src/main/java/org/eclipse/dirigible/components/intent/generator/form/FormIntentGenerator.java b/components/engine/engine-intent/src/main/java/org/eclipse/dirigible/components/intent/generator/form/FormIntentGenerator.java index b4dc6dc1089..ff2ff7c20a6 100644 --- a/components/engine/engine-intent/src/main/java/org/eclipse/dirigible/components/intent/generator/form/FormIntentGenerator.java +++ b/components/engine/engine-intent/src/main/java/org/eclipse/dirigible/components/intent/generator/form/FormIntentGenerator.java @@ -545,6 +545,10 @@ private static Control pickControl(FieldIntent field) { return new Control("input-date", "date"); case "timestamp": return new Control("input-datetime-local", "datetime-local"); + case "month": + return new Control("input-month", "month"); + case "week": + return new Control("input-week", "week"); case "uuid": case "string": default: diff --git a/components/engine/engine-intent/src/main/java/org/eclipse/dirigible/components/intent/model/FieldIntent.java b/components/engine/engine-intent/src/main/java/org/eclipse/dirigible/components/intent/model/FieldIntent.java index f939d4d9c18..5a20fde25ee 100644 --- a/components/engine/engine-intent/src/main/java/org/eclipse/dirigible/components/intent/model/FieldIntent.java +++ b/components/engine/engine-intent/src/main/java/org/eclipse/dirigible/components/intent/model/FieldIntent.java @@ -11,8 +11,10 @@ /** * Single attribute on an {@link EntityIntent}. {@link #type} carries a logical type string - * ({@code string}, {@code integer}, {@code decimal}, {@code boolean}, {@code date}, {@code uuid}, - * {@code text}) that the entity generator maps to JDBC and EDM types. + * ({@code string}, {@code integer}, {@code decimal}, {@code boolean}, {@code date}, + * {@code timestamp}, {@code uuid}, {@code text}, {@code month}, {@code week}) that the entity + * generator maps to JDBC and EDM types. {@code month} ({@code YYYY-MM}) and {@code week} + * ({@code YYYY-Www}) are stored as strings and render as the Harmonia month / week pickers. */ public class FieldIntent { diff --git a/components/engine/engine-intent/src/main/java/org/eclipse/dirigible/components/intent/parser/IntentParser.java b/components/engine/engine-intent/src/main/java/org/eclipse/dirigible/components/intent/parser/IntentParser.java index 4524652e5da..eb00c056ed3 100644 --- a/components/engine/engine-intent/src/main/java/org/eclipse/dirigible/components/intent/parser/IntentParser.java +++ b/components/engine/engine-intent/src/main/java/org/eclipse/dirigible/components/intent/parser/IntentParser.java @@ -74,8 +74,8 @@ */ public final class IntentParser { - private static final Set FIELD_TYPES = - Set.of("string", "text", "integer", "int", "long", "decimal", "double", "boolean", "date", "timestamp", "uuid"); + private static final Set FIELD_TYPES = Set.of("string", "text", "integer", "int", "long", "decimal", "double", "boolean", + "date", "timestamp", "uuid", "month", "week"); /** * Primary keys must be an integer type - the Dirigible model convention is integer identifiers * (auto-increment), and a non-integer auto-increment column is invalid SQL on most databases. diff --git a/components/engine/engine-intent/src/main/resources/intent-assistant-guide.md b/components/engine/engine-intent/src/main/resources/intent-assistant-guide.md index 35179b4c443..b00d438ece5 100644 --- a/components/engine/engine-intent/src/main/resources/intent-assistant-guide.md +++ b/components/engine/engine-intent/src/main/resources/intent-assistant-guide.md @@ -1179,7 +1179,7 @@ payment's unallocated balance; entity writes go only through the generated repos | Where | Allowed | |---|---| -| field `type` | `string`, `text`, `integer`, `int`, `long`, `decimal`, `double`, `boolean`, `date`, `timestamp`, `uuid` | +| field `type` | `string`, `text`, `integer`, `int`, `long`, `decimal`, `double`, `boolean`, `date`, `timestamp`, `uuid`, `month` (a `YYYY-MM` string, month picker), `week` (a `YYYY-Www` ISO-week string, week picker) | | primary-key `type` | `integer`, `int`, `long` (integer only) | | relation `kind` | `oneToMany`, `manyToOne`, `oneToOne`, `manyToMany` | | step `kind` | `userTask`, `serviceTask`, `decision`, `script`, `end` | diff --git a/components/engine/engine-intent/src/test/java/org/eclipse/dirigible/components/intent/generator/edm/EdmIntentGeneratorTest.java b/components/engine/engine-intent/src/test/java/org/eclipse/dirigible/components/intent/generator/edm/EdmIntentGeneratorTest.java index f0ce7ddc368..d2cfaf947b5 100644 --- a/components/engine/engine-intent/src/test/java/org/eclipse/dirigible/components/intent/generator/edm/EdmIntentGeneratorTest.java +++ b/components/engine/engine-intent/src/test/java/org/eclipse/dirigible/components/intent/generator/edm/EdmIntentGeneratorTest.java @@ -186,6 +186,33 @@ void dependsOnEmitsWidgetAttributesWithPrimaryKeyDefaults() { assertNull(countryFk.get("widgetDependsOnProperty")); } + @Test + void monthAndWeekMapToVarcharWithTheirPickerWidgets() { + String yaml = """ + name: planning + entities: + - name: Plan + fields: + - { name: id, type: integer, primaryKey: true, generated: true } + - { name: period, type: month } + - { name: sprint, type: week } + """; + Map model = EdmIntentGenerator.buildModelJsonForTest(IntentParser.parse(yaml), "planning"); + Map plan = entityByName(entities(model), "Plan"); + + // Both are stored as VARCHAR (indistinguishable at the JDBC level), so the picker widget is + // chosen from the logical type - the crux of the feature. + Map period = propertyByName(plan, "Period"); + assertEquals("VARCHAR", period.get("dataType")); + assertEquals("MONTH", period.get("widgetType")); + assertEquals("7", period.get("dataLength"), "a month column is sized for YYYY-MM"); + + Map sprint = propertyByName(plan, "Sprint"); + assertEquals("VARCHAR", sprint.get("dataType")); + assertEquals("WEEK", sprint.get("widgetType")); + assertEquals("8", sprint.get("dataLength"), "a week column is sized for YYYY-Www"); + } + @Test void immutableAlwaysEmitsTheAppendOnlyAttribute() { String yaml = """ diff --git a/components/engine/engine-intent/src/test/java/org/eclipse/dirigible/components/intent/parser/IntentParserTest.java b/components/engine/engine-intent/src/test/java/org/eclipse/dirigible/components/intent/parser/IntentParserTest.java index 8484000f3b7..29beb98952b 100644 --- a/components/engine/engine-intent/src/test/java/org/eclipse/dirigible/components/intent/parser/IntentParserTest.java +++ b/components/engine/engine-intent/src/test/java/org/eclipse/dirigible/components/intent/parser/IntentParserTest.java @@ -60,6 +60,37 @@ void bareOneHopRelationFieldRecipientParses() { .getTo()); } + @Test + void monthAndWeekAreAcceptedFieldTypesWhileAnUnknownTypeIsRejected() { + String ok = """ + name: planning + entities: + - name: Plan + fields: + - { name: id, type: integer, primaryKey: true, generated: true } + - { name: period, type: month } + - { name: sprint, type: week } + """; + IntentModel model = IntentParser.parse(ok); + assertEquals("month", model.getEntities() + .get(0) + .getFields() + .get(1) + .getType()); + assertEquals("week", model.getEntities() + .get(0) + .getFields() + .get(2) + .getType()); + + String bad = ok.replace("type: month", "type: quarter"); + IntentValidationException ex = assertThrows(IntentValidationException.class, () -> IntentParser.parse(bad)); + assertTrue(ex.getIssues() + .stream() + .anyMatch(i -> i.contains("unknown type")), + "an unknown field type should still be rejected, got: " + ex.getIssues()); + } + @Test void crossModelRelationParsesWhenModelIsDeclaredInUses() { String yaml = """ diff --git a/components/resources/application-core/src/main/resources/META-INF/dirigible/application-core/shell/js/services/format.js b/components/resources/application-core/src/main/resources/META-INF/dirigible/application-core/shell/js/services/format.js index 8218280aecb..5ccc2596ae2 100644 --- a/components/resources/application-core/src/main/resources/META-INF/dirigible/application-core/shell/js/services/format.js +++ b/components/resources/application-core/src/main/resources/META-INF/dirigible/application-core/shell/js/services/format.js @@ -181,11 +181,14 @@ /** * Convert a date/datetime value to the FIXED shape an HTML requires — NOT pattern-driven. - * `widget` is one of DATE, DATETIME-LOCAL, TIME, MONTH (case-insensitive). Empty -> ''. + * `widget` is one of DATE, DATETIME-LOCAL, TIME, MONTH, WEEK (case-insensitive). Empty -> ''. + * MONTH (YYYY-MM) and WEEK (YYYY-Www) are stored as plain strings, so they slice through unchanged. */ toDateInput(value, widget) { if (value === null || value === undefined || value === '') return ''; const w = String(widget || 'DATE').toUpperCase(); + // WEEK (YYYY-Www) has no calendar-date form, so it is always a stored string. + if (w === 'WEEK') return String(value).slice(0, 8); let y, mo = 1, da = 1, h = 0, mi = 0; if (Array.isArray(value)) { y = value[0]; mo = value[1] || 1; da = value[2] || 1; h = value[3] || 0; mi = value[4] || 0; @@ -213,14 +216,15 @@ /** * Inverse of toDateInput: convert an HTML value to the payload value the backend expects. - * DATE / DATETIME-LOCAL / MONTH -> a full ISO instant (…Z) so a Jackson java.time.* field binds; - * TIME -> passes through unchanged; empty -> null. An unparseable value is returned unchanged. - * `widget` is one of DATE, DATETIME-LOCAL, TIME, MONTH (case-insensitive). + * DATE / DATETIME-LOCAL -> a full ISO instant (…Z) so a Jackson java.time.* field binds; + * TIME / MONTH / WEEK -> pass through unchanged (they are stored as plain strings, not instants); + * empty -> null. An unparseable value is returned unchanged. + * `widget` is one of DATE, DATETIME-LOCAL, TIME, MONTH, WEEK (case-insensitive). */ toPayload(value, widget) { if (value === null || value === undefined || value === '') return null; const w = String(widget || 'DATE').toUpperCase(); - if (w === 'TIME') return value; + if (w === 'TIME' || w === 'MONTH' || w === 'WEEK') return value; const d = new Date(value); return isNaN(d.getTime()) ? value : d.toISOString(); }, diff --git a/components/template/template-application-ui-harmonia-java/reference/harmonia/SKILL.md b/components/template/template-application-ui-harmonia-java/reference/harmonia/SKILL.md deleted file mode 100644 index daa35e03774..00000000000 --- a/components/template/template-application-ui-harmonia-java/reference/harmonia/SKILL.md +++ /dev/null @@ -1,153 +0,0 @@ ---- -name: harmonia -description: How to build UIs with the Harmonia Alpine.js component library (@codbex/harmonia). Use when adding, wiring, or styling Harmonia UI components (x-h-* directives such as buttons, dialogs, selects, tables, date pickers) in a project that depends on @codbex/harmonia. ---- - - - -# Harmonia - -Harmonia is a UI component library for [Alpine.js](https://alpinejs.dev/), built with Tailwind CSS. Components are Alpine directives: you add `x-h-*` attributes to plain HTML elements and the library upgrades them. There is no JSX and no component-tag syntax. - -Full documentation: https://www.codbex.com/harmonia/ - -## How to use this skill - -Find the component in the index below and open its file under `references/`. Each reference lists the directive set, its attributes, whether it binds with `x-model`, and working examples you can adapt. Load only the reference(s) you need. - -## Setup - -Harmonia requires Alpine.js as a peer dependency and ships a CSS file that must be linked. - -### Script tag (auto-registers on `alpine:init`) - -```html - - - -``` - -### ES module (register manually) - -```js -import Alpine from 'alpinejs'; -import registerComponents from '@codbex/harmonia'; - -registerComponents(Alpine.plugin); // register every component -Alpine.start(); -``` - -Import the CSS (`@codbex/harmonia/dist/harmonia.css`) too. For selective registration, import named exports (`import { Button, Card } from '@codbex/harmonia'`) and call `Alpine.plugin(Button)` per component. - -## Conventions that apply to every component - -- **Directive prefix.** Alpine directives registered as `h-` are written as `x-h-` in HTML (for example `x-h-button`, `x-h-date-picker`). -- **Values are Alpine expressions.** A string literal must be quoted inside the attribute value: `x-h-accordion-trigger="'Section title'"`, not `x-h-accordion-trigger="Section title"`. A bare word is read as a variable reference. -- **Compound components nest.** Many components are a set of directives (root plus children). They must be nested as the reference example shows; the library throws a descriptive error at runtime if a required ancestor is missing. -- **Modifiers are dot suffixes.** For example `x-h-accordion.single`, `x-h-accordion-item.default`. -- **Styling is attribute-driven.** Common attributes are `data-size` (for example `sm` / `md`), `data-variant` (for example `primary` / `negative`), and `data-align` for popovers/menus. See each reference for the exact values. -- **Utility classes are a curated subset, NOT all of Tailwind.** Only the classes compiled into `harmonia.css` exist; an arbitrary Tailwind class that is not shipped (for example `h-80`, `gap-20`, `bg-red-450`) silently does nothing. Before using any utility class, confirm it is in the [Utility classes](references/utility-classes.md) reference, and for a one-off value with no matching class use an inline `style`. -- **Form controls use `x-model`.** Inputs, selects, checkboxes, radios, ranges, switches, and the date/time pickers bind their value with Alpine `x-model`. -- **Light and dark modes** are handled automatically. -- **Accessibility.** Components set sensible ARIA roles and a default `aria-label` only when the author has not set one; provide your own labels where the content is not self-describing. - -## Component index - -### Components - -| Name | Description | Reference | -| ---- | ----------- | --------- | -| Accordion | Organizes related content into expandable and collapsible sections, allowing users to reveal or hide information as needed while keeping the interface clean and compact. | [accordion](references/accordion.md) | -| Alert | Communicates important information to the user about a situation or task that requires attention. | [alert](references/alert.md) | -| Avatar | Represents a person, entity, or object using an image, icon, or text, such as a user photo, initials, or symbolic graphic. | [avatar](references/avatar.md) | -| Badge | Displays a short label used to convey the semantic status of an object. | [badge](references/badge.md) | -| Breadcrumb | Displays the current page's location within a navigational hierarchy, helping users understand where they are and navigate back to parent pages. | [breadcrumb](references/breadcrumb.md) | -| Button | Buttons can trigger an action or navigate the user to another location. | [button](references/button.md) | -| Button Group | Groups related buttons into a single container to present them as a unified set of actions. | [button-group](references/button-group.md) | -| Calendar | A full multi-view event calendar with month, week, day, and year views. | [calendar](references/calendar.md) | -| Card | A flexible container that organizes content into distinct sections, typically including a header, main content area, and footer. | [card](references/card.md) | -| Checkbox | Allows users to select or deselect an option, representing a binary choice (true/false). | [checkbox](references/checkbox.md) | -| Chip | A compact, interactive element used to represent an applied filter, a selected item, or a categorization. | [chip](references/chip.md) | -| Date Picker | Allows users to enter a date either by typing it directly or by selecting it from a calendar popover. | [date-picker](references/date-picker.md) | -| Date Time Picker | Lets users pick a date and a time together. | [datetime-picker](references/datetime-picker.md) | -| Dialog | A modal container that appears in response to a user action, temporarily interrupting the current workflow to request information or confirmation. | [dialog](references/dialog.md) | -| Expansion Panel | The Expansion Panel is a container component that manages multiple collapsible panels within a layout. | [expansion-panel](references/expansion-panel.md) | -| Fieldset | A container that groups related form elements, including labels, controls, and helper text, to create accessible and organized input sections. | [fieldset](references/fieldset.md) | -| File Upload | Lets users choose one or more files for upload. | [file-upload](references/file-upload.md) | -| Icon | Renders an SVG graphic, either from a link or a set of built-in icons, that can represent an action, status, or decorative element. | [icon](references/icon.md) | -| Info Page | Provides a structured layout to display instructional content, messages, or status information, such as empty states or error notifications. | [info-page](references/info-page.md) | -| Inline Calendar | A compact calendar for selecting a single date or a date range within a monthly context. | [inline-calendar](references/inline-calendar.md) | -| Input | Provides a single-line field for users to enter text or color values. | [input](references/input.md) | -| Input Group | Combines an input or textarea field with related elements, such as buttons, icons, or labels, to create a cohesive and interactive form control. | [input-group](references/input-group.md) | -| Input Number | Allows users to enter numeric values with built-in validation and step controls. | [input-number](references/input-number.md) | -| Label | Provides an accessible caption for a user interface element, most commonly paired with input controls. | [label](references/label.md) | -| List | A container that displays a collection of related items in a structured format. | [list](references/list.md) | -| Listbox | A single-selection list component with support for grouped options, functionally similar to an HTML `