diff --git a/HARMONIA_RUNTIME_PLAN.md b/HARMONIA_RUNTIME_PLAN.md
index b5466dd877c..f661864d60b 100644
--- a/HARMONIA_RUNTIME_PLAN.md
+++ b/HARMONIA_RUNTIME_PLAN.md
@@ -371,7 +371,7 @@ Process Inbox (`/inbox`) and Documents (`/documents`).
### Embedding (Phase 1 — no CDN)
-Alpine `3.17.4`, Harmonia `3.6.1`, Lucide `1.48.0` and Pinecone Router `7.6.0` are **webjars** (served
+Alpine `3.17.4`, Harmonia `3.6.2`, Lucide `1.48.0` and Pinecone Router `7.6.0` are **webjars** (served
version-less via webjars-locator at `/webjars/...`; the versions are the `*.version` properties in the
root pom). The chart.js webjar was dropped with the AngularJS dashboard shell (reports use Harmonia's
native `x-h-chart-*`), and the once-vendored Pinecone Router copy under `application-core/.../vendor/`
diff --git a/components/template/template-application-ui-harmonia-java/README.md b/components/template/template-application-ui-harmonia-java/README.md
index d7a0519d442..f86acbbb9a0 100644
--- a/components/template/template-application-ui-harmonia-java/README.md
+++ b/components/template/template-application-ui-harmonia-java/README.md
@@ -188,7 +188,7 @@ Three things about it are deliberate and easy to get wrong:
| process tasks | gated on `hasProcess` / `ProcessId` | ✅ processTasks Alpine store (inbox fetch + claim + bucket by processInstanceId) + inline popover in list/manage/master rows + app-wide task-form dialog |
Asset embedding (Phase 1 — DONE, verified end-to-end against a live app):
-- Alpine `3.17.4` + Harmonia `3.6.1` + Lucide `1.48.0` are **webjars** bundled via
+- Alpine `3.17.4` + Harmonia `3.6.2` + Lucide `1.48.0` are **webjars** bundled via
`components/resources/application-core` (`alpinejs.version` / `harmonia.version` / `lucide.version`
in the root pom), served version-less through webjars-locator at `/webjars/...` (public). The
Harmonia rules live in the upstream skill - see `.claude/docs/harmonia-ui.md`.
diff --git a/components/template/template-form-builder-harmonia/src/main/resources/META-INF/dirigible/template-form-builder-harmonia/ui/index.html.template b/components/template/template-form-builder-harmonia/src/main/resources/META-INF/dirigible/template-form-builder-harmonia/ui/index.html.template
index f4c84f03208..60055a434a5 100644
--- a/components/template/template-form-builder-harmonia/src/main/resources/META-INF/dirigible/template-form-builder-harmonia/ui/index.html.template
+++ b/components/template/template-form-builder-harmonia/src/main/resources/META-INF/dirigible/template-form-builder-harmonia/ui/index.html.template
@@ -43,7 +43,7 @@
#elseif($el.controlId == "paragraph")
$!el.label
#elseif($el.controlId == "line")
-
+
#elseif($el.controlId == "spacer")
#elseif($el.controlId == "link")
diff --git a/pom.xml b/pom.xml
index 34a63c92720..eca807d070f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -253,7 +253,7 @@
0.47.0
26.3.6
3.17.4
- 3.6.1
+ 3.6.2
5.3.0
1.48.0
7.6.0
diff --git a/tests/tests-integrations/src/main/java/org/eclipse/dirigible/integration/tests/ui/tests/NumberInputHarmoniaIT.java b/tests/tests-integrations/src/main/java/org/eclipse/dirigible/integration/tests/ui/tests/NumberInputHarmoniaIT.java
new file mode 100644
index 00000000000..5c46f8f6422
--- /dev/null
+++ b/tests/tests-integrations/src/main/java/org/eclipse/dirigible/integration/tests/ui/tests/NumberInputHarmoniaIT.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (c) 2010-2026 Eclipse Dirigible contributors
+ *
+ * All rights reserved. This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v2.0 which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v20.html
+ *
+ * SPDX-FileCopyrightText: Eclipse Dirigible contributors SPDX-License-Identifier: EPL-2.0
+ */
+package org.eclipse.dirigible.integration.tests.ui.tests;
+
+import static io.restassured.RestAssured.given;
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+
+import java.math.BigDecimal;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Predicate;
+
+import org.eclipse.dirigible.components.initializers.synchronizer.SynchronizationProcessor;
+import org.eclipse.dirigible.repository.api.IRepository;
+import org.eclipse.dirigible.repository.api.IRepositoryStructure;
+import org.eclipse.dirigible.tests.base.UserInterfaceIntegrationTest;
+import org.eclipse.dirigible.tests.framework.browser.HtmlElementType;
+import org.eclipse.dirigible.tests.framework.restassured.RestAssuredExecutor;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+import org.openqa.selenium.Keys;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import com.codeborne.selenide.Selenide;
+
+/**
+ * #8115: a generated number field keeps a zero typed after the decimal point.
+ *
+ *
+ * The generated form binds a number input with {@code x-model.number} inside
+ * {@code x-h-input-number}, as the Harmonia reference prescribes. Alpine writes the model back into
+ * the input whenever the model changes, and up to Harmonia 3.6.1 that write landed on the focused
+ * field too: a field reading {@code 1.0} with a model of {@code 1} was rewritten to {@code 1}, the
+ * next digit made {@code 15}, and the record was saved with that number (codbex/harmonia#149, fixed
+ * in 3.6.2). The generated source is byte-correct either way - the defect lives in the running
+ * directive - so this drives a real form and reads the saved record back.
+ *
+ *
+ * The keystrokes matter. Typed straight, {@code 1.05} never changes the model on Linux Chrome (it
+ * is {@code 1} from the first digit on), so nothing is written back and 3.6.1 passes as well.
+ * Typing {@code 10} and inserting the point before the {@code 0} moves the model from {@code 10} to
+ * {@code 1} while the field reads {@code 1.0}: the write-back fires, and only 3.6.2 keeps the zero.
+ */
+class NumberInputHarmoniaIT extends UserInterfaceIntegrationTest {
+
+ private static final long POLL_TIMEOUT_MILLIS = 60_000;
+
+ private static final long POLL_INTERVAL_MILLIS = 250;
+
+ private static final String PROJECT = "decimalit";
+ private static final String WORKSPACE = "workspace";
+ private static final String PROJECT_PATH = IRepositoryStructure.PATH_USERS + "/admin/" + WORKSPACE + "/" + PROJECT;
+ private static final String GENERATE_URL =
+ "/services/ide/intent/generate?workspace=" + WORKSPACE + "&project=" + PROJECT + "&path=app.intent";
+ private static final String APP = "/services/web/" + PROJECT + "/gen/decimalit/index.html#";
+ private static final String API = "/services/java/" + PROJECT + "/gen/decimalit/api/product/ProductController";
+
+ private static final String TYPED = "1.05";
+
+ /** The column's own scale is explicit, so only the input can lose the typed zero. */
+ private static final String INTENT_YAML = """
+ name: decimalit
+ entities:
+ - name: Product
+ fields:
+ - { name: id, type: integer, primaryKey: true, generated: true }
+ - { name: name, type: string, length: 60 }
+ - { name: price, type: decimal, precision: 18, scale: 2 }
+ """;
+
+ /** What the Price input shows and what the form holds for it, as {@code |}. */
+ private static final String PRICE = """
+ var input = document.getElementById('f_Price');
+ if (!input) return 'not-rendered';
+ var form = Alpine.$data(input).form;
+ return input.value + '|' + (form ? JSON.stringify(form.Price) : 'no-form');
+ """;
+
+ @Autowired
+ private IRepository repository;
+
+ @Autowired
+ private SynchronizationProcessor synchronizationProcessor;
+
+ @Autowired
+ private RestAssuredExecutor restAssuredExecutor;
+
+ @Test
+ void a_typed_decimal_keeps_its_zero_and_is_saved_as_typed() {
+ generateAndPublish();
+
+ // Authenticate the browser session first - a bare openPath lands on the sign-in form.
+ ide.openHomePage();
+ browser.openPath(APP + "/Product/create");
+ assertNotEquals("not-rendered", poll(state -> !"not-rendered".equals(state)), "the Product form must render");
+
+ // 10, then the point inserted before the 0: the model moves from 10 to 1 under a field that
+ // reads 1.0 - the write-back that used to rewrite the field.
+ Selenide.$("#f_Price")
+ .sendKeys("10", Keys.ARROW_LEFT, ".");
+ assertEquals("1.0|1", poll("1.0|1"::equals), "the field must keep the typed 1.0 while the model is 1");
+ Selenide.$("#f_Price")
+ .sendKeys(Keys.END, "5");
+ String expected = TYPED + "|" + TYPED;
+ assertEquals(expected, poll(expected::equals), "the field must show the typed decimal and the model must hold it");
+
+ browser.clickOnElementContainingText(HtmlElementType.BUTTON, "Create");
+
+ restAssuredExecutor.execute(() -> {
+ List