From 3695bd2838782f88e73b96d1997adcc61cf6ea07 Mon Sep 17 00:00:00 2001 From: "Cheng-Chen, Chen" Date: Fri, 11 Sep 2026 05:38:20 +0800 Subject: [PATCH] Fix service creation completion dispatch --- README.md | 19 ++++++++++++------- SECURITY.md | 2 +- app/components/new-container/component.js | 12 ++++++++---- ...ass-replacement.node24-ignore-scripts.json | 4 ++-- package-lock.json | 4 ++-- package.json | 2 +- scripts/check-modernization-blockers | 4 ++-- scripts/check-ui-console-workspace | 2 +- scripts/check-ui-critical-high-dependencies | 2 +- .../new-container-hardware-payload-test.js | 9 ++++++++- 10 files changed, 38 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 8ec3faa28c..a1535e85b0 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ PastureStack is an independent community effort to preserve, audit, and moderniz ## Project status -The current compatibility release is `1.6.107`. It retains the existing Node 24, Ember, Sass, +The current compatibility release is `1.6.108`. It retains the existing Node 24, Ember, Sass, dependency, browser-smoke, terminal, console, and test-harness modernization. It adds a provider-neutral OpenID Connect administration and sign-in flow with PKCE S256, staged configuration validation, a real test login before @@ -16,12 +16,17 @@ activation, and local-authentication recovery. Product-owned names, logos, icons, package metadata, and visible text use PastureStack branding. API models and protocol fields remain compatible. -Release `1.6.107` closes the successful first-create navigation race found by -real-host acceptance testing. Completion now treats the stack query parameter -as immutable route authority and never dereferences the API's mutable saved -resource merely to decide where to navigate. Empty service links still avoid -the redundant action, while non-empty links remain persisted before leaving -the form. +Release `1.6.108` fixes the remaining real-host first-create completion failure. +The create component now invokes and awaits the template's closure callback +directly instead of routing it through deprecated `sendAction` dispatch after +the service has already been persisted. This prevents a successful create from +leaving the form open with an `undefined.get` error and inviting a duplicate +submission. The same callback path is used by create and upgrade completion. + +Release `1.6.107` removed saved-response dereferences from route selection, but +real-host acceptance testing showed that the deprecated callback dispatch still +failed after persistence. It is retained only as a superseded diagnostic step, +not as the current compatibility target. Release `1.6.106` fixes the real first-service creation completion path. An empty service-link set no longer sends a redundant action after the service is diff --git a/SECURITY.md b/SECURITY.md index 9eed387a30..10d0536287 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,7 +2,7 @@ ## Supported state -The maintained compatibility release is the pure numeric `1.6.107` line used +The maintained compatibility release is the pure numeric `1.6.108` line used by the current PastureStack Server release. Earlier branded coordinates are historical records and are not current release or deployment targets. Authentication-provider combinations must still be validated by an diff --git a/app/components/new-container/component.js b/app/components/new-container/component.js index 1c4569088e..e9c674ef3f 100644 --- a/app/components/new-container/component.js +++ b/app/components/new-container/component.js @@ -162,11 +162,11 @@ export default Component.extend(NewOrEdit, SelectTab, { }, done() { - this.sendAction('done'); + return this.invokePassedAction('done'); }, cancel() { - this.sendAction('cancel'); + return this.invokePassedAction('cancel'); }, }, @@ -589,8 +589,12 @@ export default Component.extend(NewOrEdit, SelectTab, { }, doneSaving(savedResource) { - this.sendAction('done', savedResource || this.get('service')); - return savedResource; + let service = savedResource || this.get('service'); + + // Template actions are modern closure functions. Calling the deprecated + // sendAction path against one can persist the service and then fail before + // navigation, leaving the form open and inviting a duplicate submission. + return resolve(this.invokePassedAction('done', service)).then(() => savedResource); }, headerLabel: function() { diff --git a/docs/baselines/npm-package-lock.sass-replacement.node24-ignore-scripts.json b/docs/baselines/npm-package-lock.sass-replacement.node24-ignore-scripts.json index 37d2621e61..8c13c7a877 100644 --- a/docs/baselines/npm-package-lock.sass-replacement.node24-ignore-scripts.json +++ b/docs/baselines/npm-package-lock.sass-replacement.node24-ignore-scripts.json @@ -1,12 +1,12 @@ { "name": "@pasturestack/web-console", - "version": "1.6.107", + "version": "1.6.108", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@pasturestack/web-console", - "version": "1.6.107", + "version": "1.6.108", "license": "Apache-2.0", "dependencies": { "sass": "1.103.1" diff --git a/package-lock.json b/package-lock.json index 37d2621e61..8c13c7a877 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@pasturestack/web-console", - "version": "1.6.107", + "version": "1.6.108", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@pasturestack/web-console", - "version": "1.6.107", + "version": "1.6.108", "license": "Apache-2.0", "dependencies": { "sass": "1.103.1" diff --git a/package.json b/package.json index c7036f2d4b..97e4a6786d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@pasturestack/web-console", - "version": "1.6.107", + "version": "1.6.108", "private": true, "description": "PastureStack browser console for the compatible control platform.", "repository": { diff --git a/scripts/check-modernization-blockers b/scripts/check-modernization-blockers index 47462819f3..ac8daf4856 100755 --- a/scripts/check-modernization-blockers +++ b/scripts/check-modernization-blockers @@ -41,8 +41,8 @@ with open('package.json', encoding='utf-8') as f: print(json.load(f).get('version', '')) PY ) -if [[ "$version" != "1.6.107" ]]; then - echo "UNEXPECTED_UI_ARTIFACT_VERSION version=$version expected=1.6.107" +if [[ "$version" != "1.6.108" ]]; then + echo "UNEXPECTED_UI_ARTIFACT_VERSION version=$version expected=1.6.108" failures=$((failures + 1)) fi diff --git a/scripts/check-ui-console-workspace b/scripts/check-ui-console-workspace index 05134b4134..229c2f6e7a 100755 --- a/scripts/check-ui-console-workspace +++ b/scripts/check-ui-console-workspace @@ -141,4 +141,4 @@ if [[ -n ${PASTURESTACK_PRIVATE_MARKER:-} ]] && grep -RInF -- "$PASTURESTACK_PRI fi printf 'UI_CONSOLE_WORKSPACE_OK version=%s persistence=%s cross_tab=%s\n' \ - 1.6.107 browser-session broker-broadcast + 1.6.108 browser-session broker-broadcast diff --git a/scripts/check-ui-critical-high-dependencies b/scripts/check-ui-critical-high-dependencies index ade46636ba..54f21c9416 100755 --- a/scripts/check-ui-critical-high-dependencies +++ b/scripts/check-ui-critical-high-dependencies @@ -66,7 +66,7 @@ if lock_bytes != baseline_bytes: lock = json.loads(lock_bytes) packages = lock.get("packages", {}) root = packages.get("", {}) -if package.get("version") != "1.6.107": +if package.get("version") != "1.6.108": fail(f"unexpected Web Console version: {package.get('version')}") if root.get("version") != package.get("version"): fail(f"lock root version differs: {root.get('version')}") diff --git a/tests/unit/components/new-container-hardware-payload-test.js b/tests/unit/components/new-container-hardware-payload-test.js index b8f3dae20b..6651c5c9ce 100644 --- a/tests/unit/components/new-container-hardware-payload-test.js +++ b/tests/unit/components/new-container-hardware-payload-test.js @@ -102,21 +102,28 @@ test('first service creation keeps the saved service through links and navigatio return Promise.resolve(); }, }); + let legacyDispatchCount = 0; let component = createComponent(service, launchConfig); run(() => component.setProperties({ serviceLinksArray: A(), + sendAction() { + legacyDispatchCount++; + throw new TypeError("Cannot read properties of undefined (reading 'get')"); + }, done(resource) { navigationResource = resource; + return Promise.resolve(); }, })); let saved = await component.doSave(); let linked = await component.didSave(saved); - component.doneSaving(linked); + await component.doneSaving(linked); assert.equal(linkActionCount, 0, 'an empty link set does not issue a redundant action'); + assert.equal(legacyDispatchCount, 0, 'a closure action bypasses the deprecated sendAction path'); assert.strictEqual(linked, service, 'link action does not discard the persisted service'); assert.strictEqual(navigationResource, service, 'navigation receives the persisted service'); destroyOwned(component);