Skip to content

Commit 486db9a

Browse files
delchevclaude
andauthored
feat(harmonia): adopt Harmonia 2.6.0 - slot-picker toolbar, month/week pickers, upstream skill (#6312)
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 <input type="month|week"> - 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 <noreply@anthropic.com>
1 parent 82e3023 commit 486db9a

89 files changed

Lines changed: 233 additions & 10575 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CLAUDE.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -191,9 +191,9 @@ A single `app.intent` YAML file at a project root is the source of truth one alt
191191

192192
## Harmonia runtime UI (`template-application-ui-harmonia-java` + `template-form-builder-harmonia`)
193193

194-
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/<project>/gen/<model>/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).
194+
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/<project>/gen/<model>/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).
195195

196-
**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.
196+
**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 <https://codbex.com/harmonia/> and the agent-readable skill <https://github.com/codbex/harmonia/blob/main/skills/harmonia/SKILL.md> (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.
197197

198198
**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:**
199199

@@ -207,7 +207,7 @@ A second runtime UI stack, parallel to the AngularJS/BlimpKit one: generated app
207207
- **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 `<entity>-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").
208208
- **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`.
209209
- **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.
210-
- **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`.
210+
- **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 (<https://github.com/codbex/harmonia/blob/main/skills/harmonia/SKILL.md>).
211211
- **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.
212212

213213
- **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.

HARMONIA_2_MIGRATION.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,9 @@ phases land.
6464
Harmonia were fixed: `upload` -> built-in `import`; `inbox`/`eye` -> Lucide.
6565
(`x-h-info-page-media.icon` still exists in 2.x - left as is.)
6666
3. Refreshed the vendored `template-application-ui-harmonia-java/reference/harmonia/`
67-
skill from the 2.1.0-shipped `skills/harmonia/`.
67+
skill from the 2.1.0-shipped `skills/harmonia/`. (Since removed - the skill is now
68+
consulted upstream at <https://github.com/codbex/harmonia/blob/main/skills/harmonia/SKILL.md>
69+
/ <https://codbex.com/harmonia/> to avoid drift.)
6870
4. Synced version strings in `CLAUDE.md` and `HARMONIA_RUNTIME_PLAN.md`
6971
(`data-size` split note re-verified - still accurate in 2.1.0).
7072
5. Also fixed a pre-existing bug found during runtime verification: the

components/engine/engine-intent/CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,7 @@ seeds:
265265
- { id: 2, name: Albania, code2: AL, code3: ALB, numeric: "008" }
266266
```
267267
268-
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`.
268+
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`.
269269
270270
Semantics worth knowing:
271271

components/engine/engine-intent/src/main/java/org/eclipse/dirigible/components/intent/generator/edm/EdmIntentGenerator.java

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -899,7 +899,7 @@ private static Map<String, Object> propertyMap(String entityName, FieldIntent fi
899899
}
900900
p.put("auditType", "NONE");
901901
// Document role: the number/title field renders in the document form's title, not as an input.
902-
p.put("widgetType", field.isDocumentTitle() ? "DOCUMENT_NUMBER" : widgetForType(dataType));
902+
p.put("widgetType", field.isDocumentTitle() ? "DOCUMENT_NUMBER" : widgetForField(field, dataType));
903903
p.put("widgetSize", field.getSize() == null ? ""
904904
: field.getSize()
905905
.toString());
@@ -1568,13 +1568,26 @@ private static String labelFieldName(EntityIntent target) {
15681568
return keyFieldName(target);
15691569
}
15701570

1571-
/** Declared length, with type-derived defaults ({@code uuid} -> 36). */
1571+
/**
1572+
* Declared length, with type-derived defaults ({@code uuid} -> 36, {@code month} -> 7, {@code week}
1573+
* -> 8).
1574+
*/
15721575
private static Integer fieldLength(FieldIntent field) {
15731576
if (field.getLength() != null) {
15741577
return field.getLength();
15751578
}
1576-
if (field.getType() != null && "uuid".equalsIgnoreCase(field.getType())) {
1577-
return 36;
1579+
if (field.getType() != null) {
1580+
switch (field.getType()
1581+
.toLowerCase(Locale.ROOT)) {
1582+
case "uuid":
1583+
return 36;
1584+
case "month": // YYYY-MM
1585+
return 7;
1586+
case "week": // YYYY-Www
1587+
return 8;
1588+
default:
1589+
break;
1590+
}
15781591
}
15791592
return defaultLength(mapDataType(field.getType()));
15801593
}
@@ -1602,11 +1615,32 @@ private static String mapDataType(String type) {
16021615
return "CLOB";
16031616
case "uuid":
16041617
case "string":
1618+
case "month": // stored as the picker's YYYY-MM string
1619+
case "week": // stored as the picker's YYYY-Www ISO-week string
16051620
default:
16061621
return "VARCHAR";
16071622
}
16081623
}
16091624

1625+
/**
1626+
* Widget for a field. {@code month}/{@code week} are both stored as {@code VARCHAR}, so they are
1627+
* indistinguishable at the JDBC-type level - the picker widget is chosen from the logical type (the
1628+
* same reason {@code documentTitle} is special-cased at the call site).
1629+
*/
1630+
private static String widgetForField(FieldIntent field, String dataType) {
1631+
String type = field.getType() == null ? ""
1632+
: field.getType()
1633+
.toLowerCase(Locale.ROOT);
1634+
switch (type) {
1635+
case "month":
1636+
return "MONTH";
1637+
case "week":
1638+
return "WEEK";
1639+
default:
1640+
return widgetForType(dataType);
1641+
}
1642+
}
1643+
16101644
private static String widgetForType(String dataType) {
16111645
switch (dataType) {
16121646
case "INTEGER":

components/engine/engine-intent/src/main/java/org/eclipse/dirigible/components/intent/generator/form/FormIntentGenerator.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -545,6 +545,10 @@ private static Control pickControl(FieldIntent field) {
545545
return new Control("input-date", "date");
546546
case "timestamp":
547547
return new Control("input-datetime-local", "datetime-local");
548+
case "month":
549+
return new Control("input-month", "month");
550+
case "week":
551+
return new Control("input-week", "week");
548552
case "uuid":
549553
case "string":
550554
default:

components/engine/engine-intent/src/main/java/org/eclipse/dirigible/components/intent/model/FieldIntent.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@
1111

1212
/**
1313
* Single attribute on an {@link EntityIntent}. {@link #type} carries a logical type string
14-
* ({@code string}, {@code integer}, {@code decimal}, {@code boolean}, {@code date}, {@code uuid},
15-
* {@code text}) that the entity generator maps to JDBC and EDM types.
14+
* ({@code string}, {@code integer}, {@code decimal}, {@code boolean}, {@code date},
15+
* {@code timestamp}, {@code uuid}, {@code text}, {@code month}, {@code week}) that the entity
16+
* generator maps to JDBC and EDM types. {@code month} ({@code YYYY-MM}) and {@code week}
17+
* ({@code YYYY-Www}) are stored as strings and render as the Harmonia month / week pickers.
1618
*/
1719
public class FieldIntent {
1820

components/engine/engine-intent/src/main/java/org/eclipse/dirigible/components/intent/parser/IntentParser.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,8 @@
7474
*/
7575
public final class IntentParser {
7676

77-
private static final Set<String> FIELD_TYPES =
78-
Set.of("string", "text", "integer", "int", "long", "decimal", "double", "boolean", "date", "timestamp", "uuid");
77+
private static final Set<String> FIELD_TYPES = Set.of("string", "text", "integer", "int", "long", "decimal", "double", "boolean",
78+
"date", "timestamp", "uuid", "month", "week");
7979
/**
8080
* Primary keys must be an integer type - the Dirigible model convention is integer identifiers
8181
* (auto-increment), and a non-integer auto-increment column is invalid SQL on most databases.

components/engine/engine-intent/src/main/resources/intent-assistant-guide.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1179,7 +1179,7 @@ payment's unallocated balance; entity writes go only through the generated repos
11791179

11801180
| Where | Allowed |
11811181
|---|---|
1182-
| field `type` | `string`, `text`, `integer`, `int`, `long`, `decimal`, `double`, `boolean`, `date`, `timestamp`, `uuid` |
1182+
| 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) |
11831183
| primary-key `type` | `integer`, `int`, `long` (integer only) |
11841184
| relation `kind` | `oneToMany`, `manyToOne`, `oneToOne`, `manyToMany` |
11851185
| step `kind` | `userTask`, `serviceTask`, `decision`, `script`, `end` |

0 commit comments

Comments
 (0)