Bläddra i källkod

Stack broadcasts so that not one is lost (#1882)

* Stack broadcasts so that not one is lost
resolves #1608

* fix test and typo

* test

---------

Co-authored-by: Fred Lefévère-Laoide <Fred.Lefevere-Laoide@Taipy.io>
Fred Lefévère-Laoide 7 månader sedan
förälder
incheckning
6e9ed228b8

+ 1 - 0
frontend/taipy-gui/jest.config.js

@@ -23,6 +23,7 @@ module.exports = {
         "./test-config/Canvas.js",
         "./test-config/mockFileUpload.js",
         "./test-config/intersectionObserver.js",
+        "./test-config/nanoid.js",
     ],
     coverageReporters: ["json", "html", "text"],
     modulePathIgnorePatterns: ["<rootDir>/packaging/"],

+ 59 - 79
frontend/taipy-gui/package-lock.json

@@ -34,8 +34,7 @@
         "react-window": "^1.8.6",
         "react-window-infinite-loader": "^1.0.7",
         "socket.io-client": "^4.3.2",
-        "sprintf-js": "^1.1.2",
-        "uuid": "^10.0.0"
+        "sprintf-js": "^1.1.2"
       },
       "devDependencies": {
         "@testing-library/jest-dom": "^6.1.3",
@@ -52,7 +51,6 @@
         "@types/react-window": "^1.8.5",
         "@types/react-window-infinite-loader": "^1.0.5",
         "@types/sprintf-js": "^1.1.2",
-        "@types/uuid": "^10.0.0",
         "@typescript-eslint/eslint-plugin": "^8.5.0",
         "@typescript-eslint/parser": "^8.5.0",
         "add-asset-html-webpack-plugin": "^6.0.0",
@@ -3243,12 +3241,6 @@
       "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
       "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="
     },
-    "node_modules/@types/uuid": {
-      "version": "10.0.0",
-      "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz",
-      "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==",
-      "dev": true
-    },
     "node_modules/@types/yargs": {
       "version": "17.0.33",
       "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz",
@@ -3265,16 +3257,16 @@
       "dev": true
     },
     "node_modules/@typescript-eslint/eslint-plugin": {
-      "version": "8.7.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.7.0.tgz",
-      "integrity": "sha512-RIHOoznhA3CCfSTFiB6kBGLQtB/sox+pJ6jeFu6FxJvqL8qRxq/FfGO/UhsGgQM9oGdXkV4xUgli+dt26biB6A==",
+      "version": "8.8.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.8.0.tgz",
+      "integrity": "sha512-wORFWjU30B2WJ/aXBfOm1LX9v9nyt9D3jsSOxC3cCaTQGCW5k4jNpmjFv3U7p/7s4yvdjHzwtv2Sd2dOyhjS0A==",
       "dev": true,
       "dependencies": {
         "@eslint-community/regexpp": "^4.10.0",
-        "@typescript-eslint/scope-manager": "8.7.0",
-        "@typescript-eslint/type-utils": "8.7.0",
-        "@typescript-eslint/utils": "8.7.0",
-        "@typescript-eslint/visitor-keys": "8.7.0",
+        "@typescript-eslint/scope-manager": "8.8.0",
+        "@typescript-eslint/type-utils": "8.8.0",
+        "@typescript-eslint/utils": "8.8.0",
+        "@typescript-eslint/visitor-keys": "8.8.0",
         "graphemer": "^1.4.0",
         "ignore": "^5.3.1",
         "natural-compare": "^1.4.0",
@@ -3298,15 +3290,15 @@
       }
     },
     "node_modules/@typescript-eslint/parser": {
-      "version": "8.7.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.7.0.tgz",
-      "integrity": "sha512-lN0btVpj2unxHlNYLI//BQ7nzbMJYBVQX5+pbNXvGYazdlgYonMn4AhhHifQ+J4fGRYA/m1DjaQjx+fDetqBOQ==",
+      "version": "8.8.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.8.0.tgz",
+      "integrity": "sha512-uEFUsgR+tl8GmzmLjRqz+VrDv4eoaMqMXW7ruXfgThaAShO9JTciKpEsB+TvnfFfbg5IpujgMXVV36gOJRLtZg==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/scope-manager": "8.7.0",
-        "@typescript-eslint/types": "8.7.0",
-        "@typescript-eslint/typescript-estree": "8.7.0",
-        "@typescript-eslint/visitor-keys": "8.7.0",
+        "@typescript-eslint/scope-manager": "8.8.0",
+        "@typescript-eslint/types": "8.8.0",
+        "@typescript-eslint/typescript-estree": "8.8.0",
+        "@typescript-eslint/visitor-keys": "8.8.0",
         "debug": "^4.3.4"
       },
       "engines": {
@@ -3326,13 +3318,13 @@
       }
     },
     "node_modules/@typescript-eslint/scope-manager": {
-      "version": "8.7.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.7.0.tgz",
-      "integrity": "sha512-87rC0k3ZlDOuz82zzXRtQ7Akv3GKhHs0ti4YcbAJtaomllXoSO8hi7Ix3ccEvCd824dy9aIX+j3d2UMAfCtVpg==",
+      "version": "8.8.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.8.0.tgz",
+      "integrity": "sha512-EL8eaGC6gx3jDd8GwEFEV091210U97J0jeEHrAYvIYosmEGet4wJ+g0SYmLu+oRiAwbSA5AVrt6DxLHfdd+bUg==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "8.7.0",
-        "@typescript-eslint/visitor-keys": "8.7.0"
+        "@typescript-eslint/types": "8.8.0",
+        "@typescript-eslint/visitor-keys": "8.8.0"
       },
       "engines": {
         "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -3343,13 +3335,13 @@
       }
     },
     "node_modules/@typescript-eslint/type-utils": {
-      "version": "8.7.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.7.0.tgz",
-      "integrity": "sha512-tl0N0Mj3hMSkEYhLkjREp54OSb/FI6qyCzfiiclvJvOqre6hsZTGSnHtmFLDU8TIM62G7ygEa1bI08lcuRwEnQ==",
+      "version": "8.8.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.8.0.tgz",
+      "integrity": "sha512-IKwJSS7bCqyCeG4NVGxnOP6lLT9Okc3Zj8hLO96bpMkJab+10HIfJbMouLrlpyOr3yrQ1cA413YPFiGd1mW9/Q==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/typescript-estree": "8.7.0",
-        "@typescript-eslint/utils": "8.7.0",
+        "@typescript-eslint/typescript-estree": "8.8.0",
+        "@typescript-eslint/utils": "8.8.0",
         "debug": "^4.3.4",
         "ts-api-utils": "^1.3.0"
       },
@@ -3367,9 +3359,9 @@
       }
     },
     "node_modules/@typescript-eslint/types": {
-      "version": "8.7.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.7.0.tgz",
-      "integrity": "sha512-LLt4BLHFwSfASHSF2K29SZ+ZCsbQOM+LuarPjRUuHm+Qd09hSe3GCeaQbcCr+Mik+0QFRmep/FyZBO6fJ64U3w==",
+      "version": "8.8.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.8.0.tgz",
+      "integrity": "sha512-QJwc50hRCgBd/k12sTykOJbESe1RrzmX6COk8Y525C9l7oweZ+1lw9JiU56im7Amm8swlz00DRIlxMYLizr2Vw==",
       "dev": true,
       "engines": {
         "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -3380,13 +3372,13 @@
       }
     },
     "node_modules/@typescript-eslint/typescript-estree": {
-      "version": "8.7.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.7.0.tgz",
-      "integrity": "sha512-MC8nmcGHsmfAKxwnluTQpNqceniT8SteVwd2voYlmiSWGOtjvGXdPl17dYu2797GVscK30Z04WRM28CrKS9WOg==",
+      "version": "8.8.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.8.0.tgz",
+      "integrity": "sha512-ZaMJwc/0ckLz5DaAZ+pNLmHv8AMVGtfWxZe/x2JVEkD5LnmhWiQMMcYT7IY7gkdJuzJ9P14fRy28lUrlDSWYdw==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "8.7.0",
-        "@typescript-eslint/visitor-keys": "8.7.0",
+        "@typescript-eslint/types": "8.8.0",
+        "@typescript-eslint/visitor-keys": "8.8.0",
         "debug": "^4.3.4",
         "fast-glob": "^3.3.2",
         "is-glob": "^4.0.3",
@@ -3408,15 +3400,15 @@
       }
     },
     "node_modules/@typescript-eslint/utils": {
-      "version": "8.7.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.7.0.tgz",
-      "integrity": "sha512-ZbdUdwsl2X/s3CiyAu3gOlfQzpbuG3nTWKPoIvAu1pu5r8viiJvv2NPN2AqArL35NCYtw/lrPPfM4gxrMLNLPw==",
+      "version": "8.8.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.8.0.tgz",
+      "integrity": "sha512-QE2MgfOTem00qrlPgyByaCHay9yb1+9BjnMFnSFkUKQfu7adBXDTnCAivURnuPPAG/qiB+kzKkZKmKfaMT0zVg==",
       "dev": true,
       "dependencies": {
         "@eslint-community/eslint-utils": "^4.4.0",
-        "@typescript-eslint/scope-manager": "8.7.0",
-        "@typescript-eslint/types": "8.7.0",
-        "@typescript-eslint/typescript-estree": "8.7.0"
+        "@typescript-eslint/scope-manager": "8.8.0",
+        "@typescript-eslint/types": "8.8.0",
+        "@typescript-eslint/typescript-estree": "8.8.0"
       },
       "engines": {
         "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -3430,12 +3422,12 @@
       }
     },
     "node_modules/@typescript-eslint/visitor-keys": {
-      "version": "8.7.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.7.0.tgz",
-      "integrity": "sha512-b1tx0orFCCh/THWPQa2ZwWzvOeyzzp36vkJYOpVg0u8UVOIsfVrnuC9FqAw9gRKn+rG2VmWQ/zDJZzkxUnj/XQ==",
+      "version": "8.8.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.8.0.tgz",
+      "integrity": "sha512-8mq51Lx6Hpmd7HnA2fcHQo3YgfX1qbccxQOgZcb4tvasu//zXRaA1j5ZRFeCw/VRAdFi4mRM9DnZw0Nu0Q2d1g==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "8.7.0",
+        "@typescript-eslint/types": "8.8.0",
         "eslint-visitor-keys": "^3.4.3"
       },
       "engines": {
@@ -4541,9 +4533,9 @@
       }
     },
     "node_modules/caniuse-lite": {
-      "version": "1.0.30001664",
-      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001664.tgz",
-      "integrity": "sha512-AmE7k4dXiNKQipgn7a2xg558IRqPN3jMQY/rOsbxDhrd0tyChwbITBfiwtnqz8bi2M5mIWbxAYBvk7W7QBUS2g==",
+      "version": "1.0.30001666",
+      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001666.tgz",
+      "integrity": "sha512-gD14ICmoV5ZZM1OdzPWmpx+q4GyefaK06zi8hmfHV5xe4/2nOQX3+Dw5o+fSqOws2xVwL9j+anOPFwHzdEdV4g==",
       "funding": [
         {
           "type": "opencollective",
@@ -5713,11 +5705,11 @@
       }
     },
     "node_modules/date-fns-tz": {
-      "version": "3.1.3",
-      "resolved": "https://registry.npmjs.org/date-fns-tz/-/date-fns-tz-3.1.3.tgz",
-      "integrity": "sha512-ZfbMu+nbzW0mEzC8VZrLiSWvUIaI3aRHeq33mTe7Y38UctKukgqPR4nTDwcwS4d64Gf8GghnVsroBuMY3eiTeA==",
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/date-fns-tz/-/date-fns-tz-3.2.0.tgz",
+      "integrity": "sha512-sg8HqoTEulcbbbVXeg84u5UnlsQa8GS5QXMqjjYIhS4abEVVKIUwe0/l/UhrZdKaL/W5eWZNlbTeEIiOXTcsBQ==",
       "peerDependencies": {
-        "date-fns": "^3.0.0"
+        "date-fns": "^3.0.0 || ^4.0.0"
       }
     },
     "node_modules/debug": {
@@ -6115,9 +6107,9 @@
       }
     },
     "node_modules/electron-to-chromium": {
-      "version": "1.5.29",
-      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.29.tgz",
-      "integrity": "sha512-PF8n2AlIhCKXQ+gTpiJi0VhcHDb69kYX4MtCiivctc2QD3XuNZ/XIOlbGzt7WAjjEev0TtaH6Cu3arZExm5DOw=="
+      "version": "1.5.31",
+      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.31.tgz",
+      "integrity": "sha512-QcDoBbQeYt0+3CWcK/rEbuHvwpbT/8SV9T3OSgs6cX1FlcUAkgrkqbg9zLnDrMM/rLamzQwal4LYFCiWk861Tg=="
     },
     "node_modules/element-size": {
       "version": "1.1.1",
@@ -6569,9 +6561,9 @@
       }
     },
     "node_modules/eslint-plugin-react": {
-      "version": "7.37.0",
-      "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.0.tgz",
-      "integrity": "sha512-IHBePmfWH5lKhJnJ7WB1V+v/GolbB0rjS8XYVCSQCZKaQCAUhMoVoOEn1Ef8Z8Wf0a7l8KTJvuZg5/e4qrZ6nA==",
+      "version": "7.37.1",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.1.tgz",
+      "integrity": "sha512-xwTnwDqzbDRA8uJ7BMxPs/EXRB3i8ZfnOIp8BsxEQkT0nHPp+WWceqGgo6rKb9ctNi8GJLDT4Go5HAWELa/WMg==",
       "dev": true,
       "dependencies": {
         "array-includes": "^3.1.8",
@@ -15732,9 +15724,9 @@
       }
     },
     "node_modules/typedoc-plugin-markdown": {
-      "version": "4.2.8",
-      "resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-4.2.8.tgz",
-      "integrity": "sha512-1EDsc66jaCjZtxdYy+Rl0KDU1WY/iyuCOOPaeFzcYFZ81FNXV8CmgUDOHri20WGmYnkEM5nQ+ooxj1vyuQo0Lg==",
+      "version": "4.2.9",
+      "resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-4.2.9.tgz",
+      "integrity": "sha512-Wqmx+7ezKFgtTklEq/iUhQ5uFeBDhAT6wiS2na9cFLidIpl9jpDHJy/COYh8jUZXgIRIZVQ/bPNjyrnPFoDwzg==",
       "dev": true,
       "engines": {
         "node": ">= 18"
@@ -16010,18 +16002,6 @@
       "integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==",
       "dev": true
     },
-    "node_modules/uuid": {
-      "version": "10.0.0",
-      "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz",
-      "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==",
-      "funding": [
-        "https://github.com/sponsors/broofa",
-        "https://github.com/sponsors/ctavan"
-      ],
-      "bin": {
-        "uuid": "dist/bin/uuid"
-      }
-    },
     "node_modules/v8-to-istanbul": {
       "version": "9.3.0",
       "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz",

+ 1 - 3
frontend/taipy-gui/package.json

@@ -29,8 +29,7 @@
     "react-window": "^1.8.6",
     "react-window-infinite-loader": "^1.0.7",
     "socket.io-client": "^4.3.2",
-    "sprintf-js": "^1.1.2",
-    "uuid": "^10.0.0"
+    "sprintf-js": "^1.1.2"
   },
   "overrides": {
     "react": "$react",
@@ -89,7 +88,6 @@
     "@types/react-window": "^1.8.5",
     "@types/react-window-infinite-loader": "^1.0.5",
     "@types/sprintf-js": "^1.1.2",
-    "@types/uuid": "^10.0.0",
     "@typescript-eslint/eslint-plugin": "^8.5.0",
     "@typescript-eslint/parser": "^8.5.0",
     "add-asset-html-webpack-plugin": "^6.0.0",

+ 20 - 0
frontend/taipy-gui/packaging/taipy-gui.d.ts

@@ -323,6 +323,26 @@ export declare const createRequestUpdateAction: (
     forceRefresh?: boolean,
     stateContext?: Record<string, unknown>
 ) => Action;
+
+/**
+ * Broadcast stack definition.
+ */
+export interface BroadcastDesc {
+    /** Name of the broadcast. */
+    name: string;
+    /** Broadcast stack */
+    stack: Array<unknown>;
+}
+
+/**
+ * Create an *un broadcast* `Action` that will be used to update local state.
+ *
+ * This action will remove a value from a broadcasted stacked variable identified by name.
+ * @param name - The name of the variable identifying the broadcast.
+ * @param values - The values to remove.
+ * @returns The action fed to the reducer.
+ */
+export declare const createUnBroadcastAction: (name: string, ...values: Array<unknown>) => Action;
 /**
  * A column description as received by the backend.
  */

+ 2 - 2
frontend/taipy-gui/src/components/Router.tsx

@@ -30,7 +30,7 @@ import {
     createSetLocationsAction,
     initializeWebSocket,
     INITIAL_STATE,
-    retreiveBlockUi,
+    retrieveBlockUi,
     taipyInitialize,
     taipyReducer,
 } from "../context/taipyReducers";
@@ -79,7 +79,7 @@ const Router = () => {
             .then((result) => {
                 dispatch(createSetLocationsAction(result.data.locations));
                 setRoutes(result.data.locations);
-                result.data.blockUI && dispatch(createBlockAction(retreiveBlockUi()));
+                result.data.blockUI && dispatch(createBlockAction(retrieveBlockUi()));
             })
             .catch((error) => {
                 // Fallback router if there is any error

+ 38 - 6
frontend/taipy-gui/src/context/taipyReducers.spec.ts

@@ -16,6 +16,7 @@ import {
     addRows,
     AlertMessage,
     BlockMessage,
+    BroadcastDesc,
     createAckAction,
     createAlertAction,
     createBlockAction,
@@ -30,6 +31,7 @@ import {
     createRequestUpdateAction,
     createSendActionNameAction,
     createSendUpdateAction,
+    createUnBroadcastAction,
     FileDownloadProps,
     getPayload,
     getWsMessageListener,
@@ -38,7 +40,7 @@ import {
     messageToAction,
     NamePayload,
     NavigateMessage,
-    retreiveBlockUi,
+    retrieveBlockUi,
     storeBlockUi,
     TaipyBaseAction,
     taipyReducer,
@@ -207,6 +209,29 @@ describe("reducer", () => {
         expect(createAlertAction({ atype: "sUc", message: "message" } as AlertMessage).atype).toBe("success");
         expect(createAlertAction({ atype: "  ", message: "message" } as AlertMessage).atype).toBe("");
     });
+    it("creates a broadcast action", () => {
+        expect(
+            (
+                taipyReducer({ ...INITIAL_STATE }, {
+                    type: "BROADCAST",
+                    name: "broadcast",
+                    payload: { value: 1 },
+                } as TaipyBaseAction).data.broadcast as BroadcastDesc
+            ).stack
+        ).toHaveLength(1);
+    });
+    it("un broadcast", () => {
+        const value = { scenario: "scenario id" };
+        const broadcastState = taipyReducer({ ...INITIAL_STATE }, {
+            type: "BROADCAST",
+            name: "broadcast",
+            payload: { value },
+        } as TaipyBaseAction);
+        expect(
+            (taipyReducer(broadcastState, createUnBroadcastAction("broadcast", value)).data.broadcast as BroadcastDesc)
+                .stack
+        ).toHaveLength(0);
+    });
 });
 
 describe("storeBlockUi function", () => {
@@ -321,7 +346,7 @@ describe("createRequestInfiniteTableUpdateAction function", () => {
         const applies = { key: "value" };
         const styles = { styleKey: "styleValue" };
         const tooltips = { tooltipKey: "tooltipValue" };
-        const formats = { formatKey: "formatValue"};
+        const formats = { formatKey: "formatValue" };
         const handleNan = true;
         const compare = "testCompare";
         const compareDatas = "testCompareDatas";
@@ -400,7 +425,14 @@ describe("createRequestTableUpdateAction function", () => {
         const formats = { formatKey: "formatValue" };
         const handleNan = true;
         const filters = [
-            { field: "testField", operator: "testOperator", value: "testValue", col: "testCol", action: "testAction", type: "type" },
+            {
+                field: "testField",
+                operator: "testOperator",
+                value: "testValue",
+                col: "testCol",
+                action: "testAction",
+                type: "type",
+            },
         ];
         const compare = "testCompare";
         const compareDatas = "testCompareDatas";
@@ -777,19 +809,19 @@ describe("retreiveBlockUi function", () => {
     it("should retrieve block message from localStorage", () => {
         const mockBlockMessage = { action: "testAction", noCancel: false, close: false, message: "testMessage" };
         Storage.prototype.getItem = jest.fn(() => JSON.stringify(mockBlockMessage));
-        const result = retreiveBlockUi();
+        const result = retrieveBlockUi();
         expect(result).toEqual(mockBlockMessage);
     });
 
     it("should return an empty object if localStorage is empty", () => {
         Storage.prototype.getItem = jest.fn(() => null);
-        const result = retreiveBlockUi();
+        const result = retrieveBlockUi();
         expect(result).toEqual({});
     });
 
     it("should return an empty object if localStorage contains invalid JSON", () => {
         Storage.prototype.getItem = jest.fn(() => "{ invalid json");
-        const result = retreiveBlockUi();
+        const result = retrieveBlockUi();
         expect(result).toEqual({});
     });
 });

+ 71 - 14
frontend/taipy-gui/src/context/taipyReducers.ts

@@ -46,6 +46,8 @@ export enum Types {
     DownloadFile = "DOWNLOAD_FILE",
     Partial = "PARTIAL",
     Acknowledgement = "ACKNOWLEDGEMENT",
+    Broadcast = "BROADCAST",
+    UnBroadcast = "UNBROADCAST",
 }
 
 /**
@@ -156,6 +158,16 @@ export interface FormatConfig {
     number: string;
 }
 
+/**
+ * Broadcast stack definition.
+ */
+export interface BroadcastDesc {
+    /** Name of the variable identifying the broadcast. */
+    name: string;
+    /** Broadcast stack. */
+    stack: Array<unknown>;
+}
+
 const getUserTheme = (mode: PaletteMode) => {
     const tkTheme = (window.taipyConfig?.stylekit && stylekitTheme) || {};
     const tkModeTheme = (window.taipyConfig?.stylekit && stylekitModeThemes[mode]) || {};
@@ -173,7 +185,7 @@ const getUserTheme = (mode: PaletteMode) => {
                     },
                 },
             },
-        }),
+        })
     );
 };
 
@@ -218,7 +230,7 @@ export const messageToAction = (message: WsMessage) => {
                 (message as unknown as NavigateMessage).to,
                 (message as unknown as NavigateMessage).params,
                 (message as unknown as NavigateMessage).tab,
-                (message as unknown as NavigateMessage).force,
+                (message as unknown as NavigateMessage).force
             );
         } else if (message.type === "ID") {
             return createIdAction((message as unknown as IdMessage).id);
@@ -230,6 +242,8 @@ export const messageToAction = (message: WsMessage) => {
             return createAckAction((message as unknown as IdMessage).id);
         } else if (message.type === "FV") {
             changeFavicon((message.payload as Record<string, string>)?.value);
+        } else if (message.type == "BC") {
+            return createBroadcastAction(message as unknown as NamePayload);
         }
     }
     return {} as TaipyBaseAction;
@@ -300,7 +314,7 @@ export const storeBlockUi = (block?: BlockMessage) => () => {
     }
 };
 
-export const retreiveBlockUi = (): BlockMessage => {
+export const retrieveBlockUi = (): BlockMessage => {
     if (localStorage) {
         const val = localStorage.getItem("TaipyBlockUi");
         if (val) {
@@ -342,6 +356,30 @@ export const taipyReducer = (state: TaipyState, baseAction: TaipyBaseAction): Ta
                         : newValue,
                 },
             };
+        case Types.Broadcast:
+            return {
+                ...state,
+                data: {
+                    ...state.data,
+                    [action.name]: {
+                        name: action.name,
+                        stack: [...((state.data[action.name] || {stack: []}) as BroadcastDesc).stack , action.payload.value],
+                    },
+                },
+            };
+        case Types.UnBroadcast:
+            return {
+                ...state,
+                data: {
+                    ...state.data,
+                    [action.name]: {
+                        name: action.name,
+                        stack: ((state.data[action.name] || {stack: []}) as BroadcastDesc).stack.filter(
+                            (v) => !(action.payload.value as Array<unknown>).includes(v)
+                        ),
+                    },
+                },
+            };
         case Types.SetLocations:
             return { ...state, locations: action.payload.value as Record<string, string> };
         case Types.SetAlert:
@@ -463,7 +501,7 @@ export const taipyReducer = (state: TaipyState, baseAction: TaipyBaseAction): Ta
                 action.payload,
                 state.id,
                 action.context,
-                action.propagate,
+                action.propagate
             );
             break;
         case Types.Action:
@@ -485,6 +523,25 @@ export const createUpdateAction = (payload: NamePayload): TaipyAction => ({
     type: Types.Update,
 });
 
+export const createBroadcastAction = (payload: NamePayload): TaipyAction => ({
+    ...payload,
+    type: Types.Broadcast,
+});
+
+/**
+ * Create an *un broadcast* `Action` that will be used to update local state.
+ *
+ * This action will remove a value from a broadcasted stacked variable identified by name.
+ * @param name - The name of the variable identifying the broadcast.
+ * @param values - The values to remove.
+ * @returns The action fed to the reducer.
+ */
+export const createUnBroadcastAction = (name: string, ...values: Array<unknown>): TaipyAction => ({
+    type: Types.UnBroadcast,
+    name,
+    payload: getPayload(values),
+});
+
 export const createMultipleUpdateAction = (payload: NamePayload[]): TaipyMultipleAction => ({
     type: Types.MultipleUpdate,
     payload: payload,
@@ -513,7 +570,7 @@ export const createSendUpdateAction = (
     context: string | undefined,
     onChange?: string,
     propagate = true,
-    relName?: string,
+    relName?: string
 ): TaipyAction => ({
     type: Types.SendUpdate,
     name: name,
@@ -566,7 +623,7 @@ export const createRequestChartUpdateAction = (
     context: string | undefined,
     columns: string[],
     pageKey: string,
-    decimatorPayload: unknown | undefined,
+    decimatorPayload: unknown | undefined
 ): TaipyAction =>
     createRequestDataUpdateAction(
         name,
@@ -577,7 +634,7 @@ export const createRequestChartUpdateAction = (
         {
             decimatorPayload: decimatorPayload,
         },
-        true,
+        true
     );
 
 export const createRequestTableUpdateAction = (
@@ -599,7 +656,7 @@ export const createRequestTableUpdateAction = (
     filters?: Array<FilterDesc>,
     compare?: string,
     compareDatas?: string,
-    stateContext?: Record<string, unknown>,
+    stateContext?: Record<string, unknown>
 ): TaipyAction =>
     createRequestDataUpdateAction(
         name,
@@ -622,7 +679,7 @@ export const createRequestTableUpdateAction = (
             compare,
             compare_datas: compareDatas,
             state_context: stateContext,
-        }),
+        })
     );
 
 export const createRequestInfiniteTableUpdateAction = (
@@ -645,7 +702,7 @@ export const createRequestInfiniteTableUpdateAction = (
     compare?: string,
     compareDatas?: string,
     stateContext?: Record<string, unknown>,
-    reverse?: boolean,
+    reverse?: boolean
 ): TaipyAction =>
     createRequestDataUpdateAction(
         name,
@@ -670,7 +727,7 @@ export const createRequestInfiniteTableUpdateAction = (
             compare_datas: compareDatas,
             state_context: stateContext,
             reverse: !!reverse,
-        }),
+        })
     );
 
 /**
@@ -701,7 +758,7 @@ export const createRequestDataUpdateAction = (
     pageKey: string,
     payload: Record<string, unknown>,
     allData = false,
-    library?: string,
+    library?: string
 ): TaipyAction => {
     payload = payload || {};
     if (id !== undefined) {
@@ -739,7 +796,7 @@ export const createRequestUpdateAction = (
     context: string | undefined,
     names: string[],
     forceRefresh = false,
-    stateContext?: Record<string, unknown>,
+    stateContext?: Record<string, unknown>
 ): TaipyAction => ({
     type: Types.RequestUpdate,
     name: "",
@@ -812,7 +869,7 @@ export const createNavigateAction = (
     to?: string,
     params?: Record<string, string>,
     tab?: string,
-    force?: boolean,
+    force?: boolean
 ): TaipyNavigateAction => ({
     type: Types.Navigate,
     to,

+ 4 - 3
frontend/taipy-gui/src/context/wsUtils.ts

@@ -1,5 +1,5 @@
 import { Socket } from "socket.io-client";
-import { v4 as uuidv4 } from "uuid";
+import { nanoid } from 'nanoid'
 
 export const TAIPY_CLIENT_ID = "TaipyClientId";
 
@@ -21,7 +21,8 @@ export type WsMessageType =
     | "GDT"
     | "AID"
     | "GR"
-    | "FV";
+    | "FV"
+    | "BC";
 
 export interface WsMessage {
     type: WsMessageType;
@@ -43,7 +44,7 @@ export const sendWsMessage = (
     propagate = true,
     serverAck?: (val: unknown) => void
 ): string => {
-    const ackId = uuidv4();
+    const ackId = nanoid();
     const msg: WsMessage = {
         type: type,
         name: name,

+ 4 - 0
frontend/taipy-gui/src/extensions/exports.ts

@@ -40,6 +40,8 @@ import {
     createSendUpdateAction,
     createRequestDataUpdateAction,
     createRequestUpdateAction,
+    createUnBroadcastAction,
+    BroadcastDesc
 } from "../context/taipyReducers";
 
 export {
@@ -53,6 +55,7 @@ export {
     TableSort,
     Metric,
     TaipyContext as Context,
+    createUnBroadcastAction,
     createRequestDataUpdateAction,
     createRequestUpdateAction,
     createSendActionNameAction,
@@ -68,6 +71,7 @@ export {
 };
 
 export type {
+    BroadcastDesc,
     ColumnDesc,
     FilterDesc,
     LoV,

+ 18 - 0
frontend/taipy-gui/test-config/nanoid.js

@@ -0,0 +1,18 @@
+/*
+ * Copyright 2021-2024 Avaiga Private Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+
+// mock nanoid that is ESM and does not work with jest
+// https://github.com/ai/nanoid/issues/363
+jest.mock("nanoid", () => { return {
+    nanoid : ()=>{}
+  } });

+ 77 - 76
frontend/taipy/package-lock.json

@@ -41,6 +41,7 @@
       }
     },
     "../../taipy/gui/webapp": {
+      "name": "taipy-gui",
       "version": "4.0.0"
     },
     "node_modules/@babel/code-frame": {
@@ -1272,9 +1273,9 @@
       "dev": true
     },
     "node_modules/@types/node": {
-      "version": "22.6.1",
-      "resolved": "https://registry.npmjs.org/@types/node/-/node-22.6.1.tgz",
-      "integrity": "sha512-V48tCfcKb/e6cVUigLAaJDAILdMP0fUW6BidkPK4GpGjXcfbnoHasCZDwz3N3yVt5we2RHm4XTQCpv0KJz9zqw==",
+      "version": "22.7.4",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.4.tgz",
+      "integrity": "sha512-y+NPi1rFzDs1NdQHHToqeiX2TIS79SWEAw9GYhkkx8bD0ChpfqC+n2j5OXOCpzfojBEBt6DnEnnG9MY0zk1XLg==",
       "dev": true,
       "dependencies": {
         "undici-types": "~6.19.2"
@@ -1291,9 +1292,9 @@
       "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA=="
     },
     "node_modules/@types/react": {
-      "version": "18.3.8",
-      "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.8.tgz",
-      "integrity": "sha512-syBUrW3/XpnW4WJ41Pft+I+aPoDVbrBVQGEnbD7NijDGlVC+8gV/XKRY+7vMDlfPpbwYt0l1vd/Sj8bJGMbs9Q==",
+      "version": "18.3.10",
+      "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.10.tgz",
+      "integrity": "sha512-02sAAlBnP39JgXwkAq3PeU9DVaaGpZyF3MGcC0MKgQVkZor5IiiDAipVaxQHtDJAmO4GIy/rVBy/LzVj76Cyqg==",
       "dependencies": {
         "@types/prop-types": "*",
         "csstype": "^3.0.2"
@@ -1323,16 +1324,16 @@
       "dev": true
     },
     "node_modules/@typescript-eslint/eslint-plugin": {
-      "version": "8.7.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.7.0.tgz",
-      "integrity": "sha512-RIHOoznhA3CCfSTFiB6kBGLQtB/sox+pJ6jeFu6FxJvqL8qRxq/FfGO/UhsGgQM9oGdXkV4xUgli+dt26biB6A==",
+      "version": "8.8.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.8.0.tgz",
+      "integrity": "sha512-wORFWjU30B2WJ/aXBfOm1LX9v9nyt9D3jsSOxC3cCaTQGCW5k4jNpmjFv3U7p/7s4yvdjHzwtv2Sd2dOyhjS0A==",
       "dev": true,
       "dependencies": {
         "@eslint-community/regexpp": "^4.10.0",
-        "@typescript-eslint/scope-manager": "8.7.0",
-        "@typescript-eslint/type-utils": "8.7.0",
-        "@typescript-eslint/utils": "8.7.0",
-        "@typescript-eslint/visitor-keys": "8.7.0",
+        "@typescript-eslint/scope-manager": "8.8.0",
+        "@typescript-eslint/type-utils": "8.8.0",
+        "@typescript-eslint/utils": "8.8.0",
+        "@typescript-eslint/visitor-keys": "8.8.0",
         "graphemer": "^1.4.0",
         "ignore": "^5.3.1",
         "natural-compare": "^1.4.0",
@@ -1356,15 +1357,15 @@
       }
     },
     "node_modules/@typescript-eslint/parser": {
-      "version": "8.7.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.7.0.tgz",
-      "integrity": "sha512-lN0btVpj2unxHlNYLI//BQ7nzbMJYBVQX5+pbNXvGYazdlgYonMn4AhhHifQ+J4fGRYA/m1DjaQjx+fDetqBOQ==",
+      "version": "8.8.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.8.0.tgz",
+      "integrity": "sha512-uEFUsgR+tl8GmzmLjRqz+VrDv4eoaMqMXW7ruXfgThaAShO9JTciKpEsB+TvnfFfbg5IpujgMXVV36gOJRLtZg==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/scope-manager": "8.7.0",
-        "@typescript-eslint/types": "8.7.0",
-        "@typescript-eslint/typescript-estree": "8.7.0",
-        "@typescript-eslint/visitor-keys": "8.7.0",
+        "@typescript-eslint/scope-manager": "8.8.0",
+        "@typescript-eslint/types": "8.8.0",
+        "@typescript-eslint/typescript-estree": "8.8.0",
+        "@typescript-eslint/visitor-keys": "8.8.0",
         "debug": "^4.3.4"
       },
       "engines": {
@@ -1384,13 +1385,13 @@
       }
     },
     "node_modules/@typescript-eslint/scope-manager": {
-      "version": "8.7.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.7.0.tgz",
-      "integrity": "sha512-87rC0k3ZlDOuz82zzXRtQ7Akv3GKhHs0ti4YcbAJtaomllXoSO8hi7Ix3ccEvCd824dy9aIX+j3d2UMAfCtVpg==",
+      "version": "8.8.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.8.0.tgz",
+      "integrity": "sha512-EL8eaGC6gx3jDd8GwEFEV091210U97J0jeEHrAYvIYosmEGet4wJ+g0SYmLu+oRiAwbSA5AVrt6DxLHfdd+bUg==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "8.7.0",
-        "@typescript-eslint/visitor-keys": "8.7.0"
+        "@typescript-eslint/types": "8.8.0",
+        "@typescript-eslint/visitor-keys": "8.8.0"
       },
       "engines": {
         "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1401,13 +1402,13 @@
       }
     },
     "node_modules/@typescript-eslint/type-utils": {
-      "version": "8.7.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.7.0.tgz",
-      "integrity": "sha512-tl0N0Mj3hMSkEYhLkjREp54OSb/FI6qyCzfiiclvJvOqre6hsZTGSnHtmFLDU8TIM62G7ygEa1bI08lcuRwEnQ==",
+      "version": "8.8.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.8.0.tgz",
+      "integrity": "sha512-IKwJSS7bCqyCeG4NVGxnOP6lLT9Okc3Zj8hLO96bpMkJab+10HIfJbMouLrlpyOr3yrQ1cA413YPFiGd1mW9/Q==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/typescript-estree": "8.7.0",
-        "@typescript-eslint/utils": "8.7.0",
+        "@typescript-eslint/typescript-estree": "8.8.0",
+        "@typescript-eslint/utils": "8.8.0",
         "debug": "^4.3.4",
         "ts-api-utils": "^1.3.0"
       },
@@ -1425,9 +1426,9 @@
       }
     },
     "node_modules/@typescript-eslint/types": {
-      "version": "8.7.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.7.0.tgz",
-      "integrity": "sha512-LLt4BLHFwSfASHSF2K29SZ+ZCsbQOM+LuarPjRUuHm+Qd09hSe3GCeaQbcCr+Mik+0QFRmep/FyZBO6fJ64U3w==",
+      "version": "8.8.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.8.0.tgz",
+      "integrity": "sha512-QJwc50hRCgBd/k12sTykOJbESe1RrzmX6COk8Y525C9l7oweZ+1lw9JiU56im7Amm8swlz00DRIlxMYLizr2Vw==",
       "dev": true,
       "engines": {
         "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1438,13 +1439,13 @@
       }
     },
     "node_modules/@typescript-eslint/typescript-estree": {
-      "version": "8.7.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.7.0.tgz",
-      "integrity": "sha512-MC8nmcGHsmfAKxwnluTQpNqceniT8SteVwd2voYlmiSWGOtjvGXdPl17dYu2797GVscK30Z04WRM28CrKS9WOg==",
+      "version": "8.8.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.8.0.tgz",
+      "integrity": "sha512-ZaMJwc/0ckLz5DaAZ+pNLmHv8AMVGtfWxZe/x2JVEkD5LnmhWiQMMcYT7IY7gkdJuzJ9P14fRy28lUrlDSWYdw==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "8.7.0",
-        "@typescript-eslint/visitor-keys": "8.7.0",
+        "@typescript-eslint/types": "8.8.0",
+        "@typescript-eslint/visitor-keys": "8.8.0",
         "debug": "^4.3.4",
         "fast-glob": "^3.3.2",
         "is-glob": "^4.0.3",
@@ -1466,15 +1467,15 @@
       }
     },
     "node_modules/@typescript-eslint/utils": {
-      "version": "8.7.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.7.0.tgz",
-      "integrity": "sha512-ZbdUdwsl2X/s3CiyAu3gOlfQzpbuG3nTWKPoIvAu1pu5r8viiJvv2NPN2AqArL35NCYtw/lrPPfM4gxrMLNLPw==",
+      "version": "8.8.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.8.0.tgz",
+      "integrity": "sha512-QE2MgfOTem00qrlPgyByaCHay9yb1+9BjnMFnSFkUKQfu7adBXDTnCAivURnuPPAG/qiB+kzKkZKmKfaMT0zVg==",
       "dev": true,
       "dependencies": {
         "@eslint-community/eslint-utils": "^4.4.0",
-        "@typescript-eslint/scope-manager": "8.7.0",
-        "@typescript-eslint/types": "8.7.0",
-        "@typescript-eslint/typescript-estree": "8.7.0"
+        "@typescript-eslint/scope-manager": "8.8.0",
+        "@typescript-eslint/types": "8.8.0",
+        "@typescript-eslint/typescript-estree": "8.8.0"
       },
       "engines": {
         "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1488,12 +1489,12 @@
       }
     },
     "node_modules/@typescript-eslint/visitor-keys": {
-      "version": "8.7.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.7.0.tgz",
-      "integrity": "sha512-b1tx0orFCCh/THWPQa2ZwWzvOeyzzp36vkJYOpVg0u8UVOIsfVrnuC9FqAw9gRKn+rG2VmWQ/zDJZzkxUnj/XQ==",
+      "version": "8.8.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.8.0.tgz",
+      "integrity": "sha512-8mq51Lx6Hpmd7HnA2fcHQo3YgfX1qbccxQOgZcb4tvasu//zXRaA1j5ZRFeCw/VRAdFi4mRM9DnZw0Nu0Q2d1g==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "8.7.0",
+        "@typescript-eslint/types": "8.8.0",
         "eslint-visitor-keys": "^3.4.3"
       },
       "engines": {
@@ -2019,9 +2020,9 @@
       }
     },
     "node_modules/browserslist": {
-      "version": "4.23.3",
-      "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz",
-      "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==",
+      "version": "4.24.0",
+      "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.0.tgz",
+      "integrity": "sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A==",
       "dev": true,
       "funding": [
         {
@@ -2038,8 +2039,8 @@
         }
       ],
       "dependencies": {
-        "caniuse-lite": "^1.0.30001646",
-        "electron-to-chromium": "^1.5.4",
+        "caniuse-lite": "^1.0.30001663",
+        "electron-to-chromium": "^1.5.28",
         "node-releases": "^2.0.18",
         "update-browserslist-db": "^1.1.0"
       },
@@ -2084,9 +2085,9 @@
       }
     },
     "node_modules/caniuse-lite": {
-      "version": "1.0.30001663",
-      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001663.tgz",
-      "integrity": "sha512-o9C3X27GLKbLeTYZ6HBOLU1tsAcBZsLis28wrVzddShCS16RujjHp9GDHKZqrB3meE0YjhawvMFsGb/igqiPzA==",
+      "version": "1.0.30001666",
+      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001666.tgz",
+      "integrity": "sha512-gD14ICmoV5ZZM1OdzPWmpx+q4GyefaK06zi8hmfHV5xe4/2nOQX3+Dw5o+fSqOws2xVwL9j+anOPFwHzdEdV4g==",
       "dev": true,
       "funding": [
         {
@@ -2413,9 +2414,9 @@
       }
     },
     "node_modules/electron-to-chromium": {
-      "version": "1.5.28",
-      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.28.tgz",
-      "integrity": "sha512-VufdJl+rzaKZoYVUijN13QcXVF5dWPZANeFTLNy+OSpHdDL5ynXTF35+60RSBbaQYB1ae723lQXHCrf4pyLsMw==",
+      "version": "1.5.31",
+      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.31.tgz",
+      "integrity": "sha512-QcDoBbQeYt0+3CWcK/rEbuHvwpbT/8SV9T3OSgs6cX1FlcUAkgrkqbg9zLnDrMM/rLamzQwal4LYFCiWk861Tg==",
       "dev": true
     },
     "node_modules/enhanced-resolve": {
@@ -2691,9 +2692,9 @@
       }
     },
     "node_modules/eslint-plugin-react": {
-      "version": "7.36.1",
-      "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.36.1.tgz",
-      "integrity": "sha512-/qwbqNXZoq+VP30s1d4Nc1C5GTxjJQjk4Jzs4Wq2qzxFM7dSmuG2UkIjg2USMLh3A/aVcUNrK7v0J5U1XEGGwA==",
+      "version": "7.37.1",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.1.tgz",
+      "integrity": "sha512-xwTnwDqzbDRA8uJ7BMxPs/EXRB3i8ZfnOIp8BsxEQkT0nHPp+WWceqGgo6rKb9ctNi8GJLDT4Go5HAWELa/WMg==",
       "dev": true,
       "dependencies": {
         "array-includes": "^3.1.8",
@@ -3077,9 +3078,9 @@
       "dev": true
     },
     "node_modules/fast-uri": {
-      "version": "3.0.1",
-      "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.1.tgz",
-      "integrity": "sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw==",
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.2.tgz",
+      "integrity": "sha512-GR6f0hD7XXyNJa25Tb9BuIdN0tdr+0BMi6/CJPH3wJO1JjNG3n/VsSw38AwRdKZABm8lGbPfakLRkYzx2V9row==",
       "dev": true
     },
     "node_modules/fastest-levenshtein": {
@@ -5376,9 +5377,9 @@
       }
     },
     "node_modules/terser": {
-      "version": "5.33.0",
-      "resolved": "https://registry.npmjs.org/terser/-/terser-5.33.0.tgz",
-      "integrity": "sha512-JuPVaB7s1gdFKPKTelwUyRq5Sid2A3Gko2S0PncwdBq7kN9Ti9HPWDQ06MPsEDGsZeVESjKEnyGy68quBk1w6g==",
+      "version": "5.34.1",
+      "resolved": "https://registry.npmjs.org/terser/-/terser-5.34.1.tgz",
+      "integrity": "sha512-FsJZ7iZLd/BXkz+4xrRTGJ26o/6VTjQytUk8b8OxkwcD2I+79VPJlz7qss1+zE7h8GNIScFqXcDyJ/KqBYZFVA==",
       "dev": true,
       "dependencies": {
         "@jridgewell/source-map": "^0.3.3",
@@ -5762,9 +5763,9 @@
       "dev": true
     },
     "node_modules/update-browserslist-db": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz",
-      "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==",
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz",
+      "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==",
       "dev": true,
       "funding": [
         {
@@ -5781,8 +5782,8 @@
         }
       ],
       "dependencies": {
-        "escalade": "^3.1.2",
-        "picocolors": "^1.0.1"
+        "escalade": "^3.2.0",
+        "picocolors": "^1.1.0"
       },
       "bin": {
         "update-browserslist-db": "cli.js"
@@ -5814,9 +5815,9 @@
       }
     },
     "node_modules/webpack": {
-      "version": "5.94.0",
-      "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz",
-      "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==",
+      "version": "5.95.0",
+      "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.95.0.tgz",
+      "integrity": "sha512-2t3XstrKULz41MNMBF+cJ97TyHdyQ8HCt//pqErqDvNjU9YQBnZxIHa11VXsi7F3mb5/aO2tuDxdeTPdU7xu9Q==",
       "dev": true,
       "dependencies": {
         "@types/estree": "^1.0.5",

+ 17 - 16
frontend/taipy/src/CoreSelector.tsx

@@ -49,6 +49,7 @@ import {
     TableFilter,
     SortDesc,
     TableSort,
+    createUnBroadcastAction,
 } from "taipy-gui";
 
 import { Cycles, Cycle, DataNodes, NodeType, Scenarios, Scenario, DataNode, Sequence, Sequences } from "./utils/types";
@@ -62,6 +63,7 @@ import {
     BadgePos,
     BadgeSx,
     BaseTreeViewSx,
+    CoreProps,
     FlagSx,
     ParentItemSx,
     getUpdateVarNames,
@@ -81,25 +83,14 @@ type Entities = Cycles | Scenarios | DataNodes;
 type Entity = Cycle | Scenario | Sequence | DataNode;
 type Pinned = Record<string, boolean>;
 
-interface CoreSelectorProps {
-    id?: string;
-    active?: boolean;
-    defaultActive?: boolean;
-    updateVarName?: string;
+interface CoreSelectorProps extends CoreProps {
     entities?: Entities;
-    coreChanged?: Record<string, unknown>;
-    updateVars: string;
     onChange?: string;
-    error?: string;
     displayCycles?: boolean;
     showPrimaryFlag?: boolean;
-    propagate?: boolean;
     value?: string | string[];
     defaultValue?: string;
     height: string;
-    libClassName?: string;
-    className?: string;
-    dynamicClassName?: string;
     multiple?: boolean;
     lovPropertyName: string;
     leafType: NodeType;
@@ -167,7 +158,7 @@ const CoreItem = (props: {
             data-selectable={nodeType === props.leafType}
             label={
                 <Grid container alignItems="center" direction="row" flexWrap="nowrap" spacing={1}>
-                    <Grid  size="grow" sx={iconLabelSx}>
+                    <Grid size="grow" sx={iconLabelSx}>
                         {nodeType === NodeType.CYCLE ? (
                             <CycleIcon fontSize="small" color="primary" />
                         ) : nodeType === NodeType.SCENARIO ? (
@@ -441,9 +432,18 @@ const CoreSelector = (props: CoreSelectorProps) => {
 
     // Refresh on broadcast
     useEffect(() => {
-        if (coreChanged?.scenario) {
-            const updateVar = getUpdateVar(updateVars, lovPropertyName);
-            updateVar && dispatch(createRequestUpdateAction(id, module, [updateVar], true));
+        if (coreChanged?.name) {
+            const toRemove = [...coreChanged.stack]
+                .map((bc) => {
+                    if ((bc as Record<string, unknown>).scenario) {
+                        const updateVar = getUpdateVar(updateVars, lovPropertyName);
+                        updateVar && dispatch(createRequestUpdateAction(id, module, [updateVar], true));
+                        return bc;
+                    }
+                    return undefined;
+                })
+                .filter((v) => v);
+            toRemove.length && dispatch(createUnBroadcastAction(coreChanged.name, ...toRemove));
         }
     }, [coreChanged, updateVars, module, dispatch, id, lovPropertyName]);
 
@@ -712,6 +712,7 @@ const CoreSelector = (props: CoreSelectorProps) => {
                       )
                     : null}
             </SimpleTreeView>
+            {props.children}
         </>
     );
 };

+ 3 - 3
frontend/taipy/src/DataNodeChart.tsx

@@ -47,7 +47,7 @@ interface DataNodeChartProps {
     columns?: Record<string, ColumnDesc>;
     defaultConfig?: string;
     updateVarName?: string;
-    uniqid: string;
+    uniqId: string;
     chartConfigs?: string;
     onViewTypeChange: (e: MouseEvent, value?: string) => void;
 }
@@ -240,7 +240,7 @@ const getBaseConfig = (defaultConfig?: string, chartConfigs?: string, configId?:
 };
 
 const DataNodeChart = (props: DataNodeChartProps) => {
-    const { defaultConfig = "", uniqid, configId, chartConfigs = "", onViewTypeChange } = props;
+    const { defaultConfig = "", uniqId, configId, chartConfigs = "", onViewTypeChange } = props;
 
     const [config, setConfig] = useState<ChartConfig | undefined>(undefined);
     useEffect(() => {
@@ -406,7 +406,7 @@ const DataNodeChart = (props: DataNodeChartProps) => {
                 <Grid container alignItems="center">
                     {config?.traces && config?.types
                         ? config?.traces.map((tc, idx) => {
-                              const baseLabelId = `${uniqid}-trace${idx}-"`;
+                              const baseLabelId = `${uniqId}-trace${idx}-"`;
                               return (
                                   <Fragment key={idx}>
                                       <Grid size={2} sx={TraceSx}>

+ 7 - 7
frontend/taipy/src/DataNodeTable.tsx

@@ -52,7 +52,7 @@ interface DataNodeTableProps {
     data?: Record<string, TraceValueType>;
     columns?: Record<string, ColumnDesc>;
     updateVarName?: string;
-    uniqid: string;
+    uniqId: string;
     onEdit?: string;
     onViewTypeChange: (e: MouseEvent, value?: string) => void;
     onLock?: string;
@@ -65,7 +65,7 @@ interface DataNodeTableProps {
 const pushRightSx = { ml: "auto" };
 
 const DataNodeTable = (props: DataNodeTableProps) => {
-    const { uniqid, configId, nodeId, columns = "", onViewTypeChange, notEditableReason, updateDnVars = "" } = props;
+    const { uniqId, configId, nodeId, columns = "", onViewTypeChange, notEditableReason, updateDnVars = "" } = props;
 
     const dispatch = useDispatch();
     const module = useModule();
@@ -108,9 +108,9 @@ const DataNodeTable = (props: DataNodeTableProps) => {
     useEffect(() => {
         if (columns) {
             const res = {} as Record<string, ColumnDesc>;
-            const dfids = {} as Record<string, string>;
-            Object.entries(columns).forEach(([k, v]) => (dfids[v.dfid] = k));
-            selectedCols.forEach((c) => dfids[c] && (res[dfids[c]] = columns[dfids[c]]));
+            const dfIds = {} as Record<string, string>;
+            Object.entries(columns).forEach(([k, v]) => (dfIds[v.dfid] = k));
+            selectedCols.forEach((c) => dfIds[c] && (res[dfIds[c]] = columns[dfIds[c]]));
             setTabCols(res);
         }
     }, [columns, selectedCols]);
@@ -164,9 +164,9 @@ const DataNodeTable = (props: DataNodeTableProps) => {
                 </Grid>
                 <Grid>
                     <FormControl sx={selectSx} fullWidth className="taipy-selector">
-                        <InputLabel id={uniqid + "-cols-label"}>Columns</InputLabel>
+                        <InputLabel id={uniqId + "-cols-label"}>Columns</InputLabel>
                         <Select
-                            labelId={uniqid + "-cols-label"}
+                            labelId={uniqId + "-cols-label"}
                             multiple
                             value={selectedCols}
                             onChange={onColsChange}

+ 91 - 83
frontend/taipy/src/DataNodeViewer.tsx

@@ -69,12 +69,14 @@ import {
     useModule,
     Store,
     FileSelector,
+    createUnBroadcastAction,
 } from "taipy-gui";
 
 import { Cycle as CycleIcon, Scenario as ScenarioIcon } from "./icons";
 import {
     AccordionIconSx,
     AccordionSummarySx,
+    CoreProps,
     FieldNoMaxWidth,
     IconPaddingSx,
     MainBoxSx,
@@ -156,19 +158,12 @@ enum DatanodeDataProps {
     error,
 }
 
-interface DataNodeViewerProps {
-    id?: string;
+interface DataNodeViewerProps extends  CoreProps {
     expandable?: boolean;
     expanded?: boolean;
-    updateVarName?: string;
-    updateVars: string;
     defaultDataNode?: string;
     dataNode?: DataNodeFull | Array<DataNodeFull>;
     onEdit?: string;
-    error?: string;
-    coreChanged?: Record<string, unknown>;
-    defaultActive: boolean;
-    active: boolean;
     showConfig?: boolean;
     showOwner?: boolean;
     showEditDate?: boolean;
@@ -177,9 +172,6 @@ interface DataNodeViewerProps {
     showHistory?: boolean;
     showData?: boolean;
     chartConfigs?: string;
-    libClassName?: string;
-    className?: string;
-    dynamicClassName?: string;
     scenarios?: Scenarios;
     history?: Array<[string, string, string]>;
     data?: DatanodeData;
@@ -253,11 +245,12 @@ const DataNodeViewer = (props: DataNodeViewerProps) => {
         fileDownload = false,
         fileUpload = false,
         showOwnerLabel = false,
+        coreChanged,
     } = props;
 
     const { state, dispatch } = useContext<Store>(Context);
     const module = useModule();
-    const uniqid = useUniqueId(id);
+    const uniqId = useUniqueId(id);
     const editorId = (state as { id: string }).id;
     const editLock = useRef(false);
     const [valid, setValid] = useState(false);
@@ -670,11 +663,21 @@ const DataNodeViewer = (props: DataNodeViewerProps) => {
 
     // Refresh on broadcast
     useEffect(() => {
-        const ids = props.coreChanged?.datanode;
-        if ((typeof ids === "string" && ids === dnId) || (Array.isArray(ids) && ids.includes(dnId))) {
-            props.updateVarName && dispatch(createRequestUpdateAction(id, module, [props.updateVarName], true));
+        if (coreChanged?.name) {
+            const toRemove = [...coreChanged.stack]
+                .map((bc) => {
+                    const ids = (bc as Record<string, unknown>).datanode;
+                    if ((typeof ids === "string" && ids === dnId) || (Array.isArray(ids) && ids.includes(dnId))) {
+                        props.updateVarName &&
+                            dispatch(createRequestUpdateAction(id, module, [props.updateVarName], true));
+                        return bc;
+                    }
+                    return undefined;
+                })
+                .filter((v) => v);
+            toRemove.length && dispatch(createUnBroadcastAction(coreChanged.name, ...toRemove));
         }
-    }, [props.coreChanged, props.updateVarName, id, module, dispatch, dnId]);
+    }, [coreChanged, props.updateVarName, id, module, dispatch, dnId]);
 
     return (
         <>
@@ -685,7 +688,7 @@ const DataNodeViewer = (props: DataNodeViewerProps) => {
                         sx={AccordionSummarySx}
                     >
                         <Stack direction="row" spacing={1} alignItems="baseline">
-                            {showOwnerLabel ? <Typography>{dnOwnerLabel} &gt;</Typography>: null}
+                            {showOwnerLabel ? <Typography>{dnOwnerLabel} &gt;</Typography> : null}
                             <Typography>{dnLabel}</Typography>
                             <Typography fontSize="smaller">{dnType}</Typography>
                         </Stack>
@@ -714,19 +717,19 @@ const DataNodeViewer = (props: DataNodeViewerProps) => {
                                                 ) : null}
                                             </Grid>
                                         }
-                                        id={`${uniqid}-data`}
-                                        aria-controls={`${uniqid}-dn-tabpanel-data`}
+                                        id={`${uniqId}-data`}
+                                        aria-controls={`${uniqId}-dn-tabpanel-data`}
                                         style={showData ? undefined : noDisplay}
                                     />
                                     <Tab
                                         label="Properties"
-                                        id={`${uniqid}-properties`}
-                                        aria-controls={`${uniqid}-dn-tabpanel-properties`}
+                                        id={`${uniqId}-properties`}
+                                        aria-controls={`${uniqId}-dn-tabpanel-properties`}
                                     />
                                     <Tab
                                         label="History"
-                                        id={`${uniqid}-history`}
-                                        aria-controls={`${uniqid}-dn-tabpanel-history`}
+                                        id={`${uniqId}-history`}
+                                        aria-controls={`${uniqId}-dn-tabpanel-history`}
                                         style={showHistory ? undefined : noDisplay}
                                     />
                                 </Tabs>
@@ -765,8 +768,8 @@ const DataNodeViewer = (props: DataNodeViewerProps) => {
                         <div
                             role="tabpanel"
                             hidden={tabValue !== TabValues.Properties}
-                            id={`${uniqid}-dn-tabpanel-properties`}
-                            aria-labelledby={`${uniqid}-properties`}
+                            id={`${uniqId}-dn-tabpanel-properties`}
+                            aria-labelledby={`${uniqId}-properties`}
                         >
                             <Grid container rowSpacing={2} sx={gridSx}>
                                 <Grid size={12} container justifyContent="space-between" spacing={1}>
@@ -786,30 +789,32 @@ const DataNodeViewer = (props: DataNodeViewerProps) => {
                                                 sx={FieldNoMaxWidth}
                                                 value={label || ""}
                                                 onChange={onLabelChange}
-                                                slotProps={{input:{
-                                                    endAdornment: (
-                                                        <InputAdornment position="end">
-                                                            <Tooltip title="Apply">
-                                                                <IconButton
-                                                                    sx={IconPaddingSx}
-                                                                    onClick={editLabel}
-                                                                    size="small"
-                                                                >
-                                                                    <CheckCircle color="primary" />
-                                                                </IconButton>
-                                                            </Tooltip>
-                                                            <Tooltip title="Cancel">
-                                                                <IconButton
-                                                                    sx={IconPaddingSx}
-                                                                    onClick={cancelLabel}
-                                                                    size="small"
-                                                                >
-                                                                    <Cancel color="inherit" />
-                                                                </IconButton>
-                                                            </Tooltip>
-                                                        </InputAdornment>
-                                                    ),
-                                                }}}
+                                                slotProps={{
+                                                    input: {
+                                                        endAdornment: (
+                                                            <InputAdornment position="end">
+                                                                <Tooltip title="Apply">
+                                                                    <IconButton
+                                                                        sx={IconPaddingSx}
+                                                                        onClick={editLabel}
+                                                                        size="small"
+                                                                    >
+                                                                        <CheckCircle color="primary" />
+                                                                    </IconButton>
+                                                                </Tooltip>
+                                                                <Tooltip title="Cancel">
+                                                                    <IconButton
+                                                                        sx={IconPaddingSx}
+                                                                        onClick={cancelLabel}
+                                                                        size="small"
+                                                                    >
+                                                                        <Cancel color="inherit" />
+                                                                    </IconButton>
+                                                                </Tooltip>
+                                                            </InputAdornment>
+                                                        ),
+                                                    },
+                                                }}
                                                 disabled={!valid}
                                             />
                                         ) : (
@@ -910,7 +915,7 @@ const DataNodeViewer = (props: DataNodeViewerProps) => {
                                 </Grid>
                                 <PropertiesEditor
                                     entityId={dnId}
-                                    active={active}
+                                    active={!!active}
                                     isDefined={valid}
                                     entProperties={
                                         propertiesRequested && Array.isArray(props.dnProperties)
@@ -923,15 +928,15 @@ const DataNodeViewer = (props: DataNodeViewerProps) => {
                                     onFocus={onFocus}
                                     onEdit={props.onEdit}
                                     notEditableReason={dnNotEditableReason}
-                                    updatePropVars={updateDnVars}
+                                    updateVars={updateDnVars}
                                 />
                             </Grid>
                         </div>
                         <div
                             role="tabpanel"
                             hidden={tabValue !== TabValues.History}
-                            id={`${uniqid}-dn-tabpanel-history`}
-                            aria-labelledby={`${uniqid}-history`}
+                            id={`${uniqId}-dn-tabpanel-history`}
+                            aria-labelledby={`${uniqId}-history`}
                         >
                             {historyRequested && Array.isArray(props.history) ? (
                                 <Grid container spacing={1}>
@@ -973,8 +978,8 @@ const DataNodeViewer = (props: DataNodeViewerProps) => {
                         <div
                             role="tabpanel"
                             hidden={tabValue !== TabValues.Data}
-                            id={`${uniqid}-dn-tabpanel-data`}
-                            aria-labelledby={`${uniqid}-data`}
+                            id={`${uniqId}-dn-tabpanel-data`}
+                            aria-labelledby={`${uniqId}-data`}
                         >
                             {dtValue !== undefined ? (
                                 <Grid container justifyContent="space-between" spacing={1}>
@@ -1067,30 +1072,32 @@ const DataNodeViewer = (props: DataNodeViewerProps) => {
                                                                 ? "number"
                                                                 : undefined
                                                         }
-                                                        slotProps={{input: {
-                                                            endAdornment: (
-                                                                <InputAdornment position="end">
-                                                                    <Tooltip title="Apply">
-                                                                        <IconButton
-                                                                            sx={IconPaddingSx}
-                                                                            onClick={editDataValue}
-                                                                            size="small"
-                                                                        >
-                                                                            <CheckCircle color="primary" />
-                                                                        </IconButton>
-                                                                    </Tooltip>
-                                                                    <Tooltip title="Cancel">
-                                                                        <IconButton
-                                                                            sx={IconPaddingSx}
-                                                                            onClick={cancelDataValue}
-                                                                            size="small"
-                                                                        >
-                                                                            <Cancel color="inherit" />
-                                                                        </IconButton>
-                                                                    </Tooltip>
-                                                                </InputAdornment>
-                                                            ),
-                                                        }}}
+                                                        slotProps={{
+                                                            input: {
+                                                                endAdornment: (
+                                                                    <InputAdornment position="end">
+                                                                        <Tooltip title="Apply">
+                                                                            <IconButton
+                                                                                sx={IconPaddingSx}
+                                                                                onClick={editDataValue}
+                                                                                size="small"
+                                                                            >
+                                                                                <CheckCircle color="primary" />
+                                                                            </IconButton>
+                                                                        </Tooltip>
+                                                                        <Tooltip title="Cancel">
+                                                                            <IconButton
+                                                                                sx={IconPaddingSx}
+                                                                                onClick={cancelDataValue}
+                                                                                size="small"
+                                                                            >
+                                                                                <Cancel color="inherit" />
+                                                                            </IconButton>
+                                                                        </Tooltip>
+                                                                    </InputAdornment>
+                                                                ),
+                                                            },
+                                                        }}
                                                         disabled={!valid}
                                                     />
                                                 )}
@@ -1134,8 +1141,8 @@ const DataNodeViewer = (props: DataNodeViewerProps) => {
                                     <>
                                         {viewType === TableViewType ? (
                                             <DataNodeTable
-                                                active={active}
-                                                uniqid={uniqid}
+                                                active={!!active}
+                                                uniqId={uniqId}
                                                 columns={tabularColumns}
                                                 data={props.tabularData}
                                                 nodeId={dnId}
@@ -1151,8 +1158,8 @@ const DataNodeViewer = (props: DataNodeViewerProps) => {
                                             />
                                         ) : (
                                             <DataNodeChart
-                                                active={active}
-                                                uniqid={uniqid}
+                                                active={!!active}
+                                                uniqId={uniqId}
                                                 columns={tabularColumns}
                                                 tabularData={props.tabularData}
                                                 configId={dnConfig}
@@ -1174,6 +1181,7 @@ const DataNodeViewer = (props: DataNodeViewerProps) => {
                 </Accordion>
                 {props.error ? <Alert severity="error">{props.error}</Alert> : null}
             </Box>
+            {props.children}
         </>
     );
 };

+ 19 - 15
frontend/taipy/src/JobSelector.tsx

@@ -55,6 +55,7 @@ import {
     createRequestUpdateAction,
     createSendActionNameAction,
     createSendUpdateAction,
+    createUnBroadcastAction,
     getUpdateVar,
     useDispatch,
     useDispatchRequestUpdateOnFirstRender,
@@ -68,6 +69,7 @@ import {
     useClassNames,
     EllipsisSx,
     SecondaryEllipsisProps,
+    CoreProps,
 } from "./utils";
 import StatusChip, { Status } from "./StatusChip";
 import JobViewer, { JobDetail } from "./JobViewer";
@@ -81,17 +83,9 @@ const CloseDialogSx = {
 
 const RightButtonSx = { marginLeft: "auto !important" };
 
-interface JobSelectorProps {
-    updateVarName?: string;
-    coreChanged?: Record<string, unknown>;
-    error?: string;
+interface JobSelectorProps extends CoreProps {
     jobs: Jobs;
     onSelect?: string;
-    updateVars: string;
-    id?: string;
-    libClassName?: string;
-    className?: string;
-    dynamicClassName?: string;
     height: string;
     showId?: boolean;
     showSubmittedLabel?: boolean;
@@ -104,7 +98,6 @@ interface JobSelectorProps {
     onChange?: string;
     value?: string;
     defaultValue?: string;
-    propagate?: boolean;
     updateJbVars?: string;
     details?: JobDetail;
     onDetails?: string | boolean;
@@ -501,6 +494,7 @@ const JobSelector = (props: JobSelectorProps) => {
         showDelete = true,
         propagate = true,
         updateJbVars = "",
+        coreChanged,
     } = props;
     const [checked, setChecked] = useState<string[]>([]);
     const [selected, setSelected] = useState<string[]>([]);
@@ -785,11 +779,20 @@ const JobSelector = (props: JobSelectorProps) => {
     }, [props.value, props.defaultValue]);
 
     useEffect(() => {
-        if (props.coreChanged?.jobs) {
-            const updateVar = getUpdateVar(props.updateVars, "jobs");
-            updateVar && dispatch(createRequestUpdateAction(id, module, [updateVar], true));
+        if (coreChanged?.name) {
+            const toRemove = [...coreChanged.stack]
+                .map((bc) => {
+                    if ((bc as Record<string, unknown>).jobs) {
+                        const updateVar = getUpdateVar(props.updateVars, "jobs");
+                        updateVar && dispatch(createRequestUpdateAction(id, module, [updateVar], true));
+                        return bc;
+                    }
+                    return undefined;
+                })
+                .filter((v) => v);
+            toRemove.length && dispatch(createUnBroadcastAction(coreChanged.name, ...toRemove));
         }
-    }, [props.coreChanged, props.updateVars, module, dispatch, id]);
+    }, [coreChanged, props.updateVars, module, dispatch, id]);
 
     const tableHeightSx = useMemo(() => ({ maxHeight: props.height || "50vh" }), [props.height]);
 
@@ -802,7 +805,7 @@ const JobSelector = (props: JobSelectorProps) => {
                         <CloseIcon />
                     </IconButton>
                     <DialogContent dividers>
-                        <JobViewer job={props.details} inDialog={true}></JobViewer>
+                        <JobViewer job={props.details} inDialog={true} updateVars=""></JobViewer>
                     </DialogContent>
                     <DialogActions>
                         <Button variant="outlined" color="primary" onClick={deleteJob} data-id={props.details[0]}>
@@ -902,6 +905,7 @@ const JobSelector = (props: JobSelectorProps) => {
                     </Table>
                 </TableContainer>
             </Paper>
+            {props.children}
         </Box>
     );
 };

+ 20 - 16
frontend/taipy/src/JobViewer.tsx

@@ -22,25 +22,19 @@ import Typography from "@mui/material/Typography";
 import {
     createRequestUpdateAction,
     createSendActionNameAction,
+    createUnBroadcastAction,
     getUpdateVar,
     useDispatch,
     useDispatchRequestUpdateOnFirstRender,
     useModule,
 } from "taipy-gui";
 
-import { useClassNames, EllipsisSx, SecondaryEllipsisProps } from "./utils";
+import { useClassNames, EllipsisSx, SecondaryEllipsisProps, CoreProps } from "./utils";
 import StatusChip from "./StatusChip";
 
-interface JobViewerProps {
-    updateVarName?: string;
-    coreChanged?: Record<string, unknown>;
-    error?: string;
+interface JobViewerProps extends CoreProps {
     job: JobDetail;
     onDelete?: string;
-    id?: string;
-    libClassName?: string;
-    className?: string;
-    dynamicClassName?: string;
     updateJbVars?: string;
     inDialog?: boolean;
     width?: string;
@@ -51,7 +45,7 @@ export type JobDetail = [string, string, string, string, string, string, number,
 const invalidJob: JobDetail = ["", "", "", "", "", "", 0, "", "", []];
 
 const JobViewer = (props: JobViewerProps) => {
-    const { updateVarName = "", id = "", updateJbVars = "", inDialog = false, width = "50vw" } = props;
+    const { updateVarName = "", id = "", updateJbVars = "", inDialog = false, width = "50vw", coreChanged } = props;
 
     const [
         jobId,
@@ -61,7 +55,7 @@ const JobViewer = (props: JobViewerProps) => {
         submissionId,
         creationDate,
         status,
-        notDeleteable,
+        notDeletable,
         executionTime,
         stacktrace,
     ] = props.job || invalidJob;
@@ -92,10 +86,19 @@ const JobViewer = (props: JobViewerProps) => {
     );
 
     useEffect(() => {
-        if (props.coreChanged?.job == jobId) {
-            updateVarName && dispatch(createRequestUpdateAction(id, module, [updateVarName], true));
+        if (coreChanged?.name) {
+            const toRemove = [...coreChanged.stack]
+                .map((bc) => {
+                    if ((bc as Record<string, unknown>).job  == jobId) {
+                        updateVarName && dispatch(createRequestUpdateAction(id, module, [updateVarName], true));
+                        return bc;
+                    }
+                    return undefined;
+                })
+                .filter((v) => v);
+            toRemove.length && dispatch(createUnBroadcastAction(coreChanged.name, ...toRemove));
         }
-    }, [props.coreChanged, updateVarName, jobId, module, dispatch, id]);
+    }, [coreChanged, updateVarName, jobId, module, dispatch, id]);
 
     return (
         <Grid container className={className} sx={{ maxWidth: width }}>
@@ -169,9 +172,9 @@ const JobViewer = (props: JobViewerProps) => {
                 <>
                     <Divider />
                     <Grid size={6}>
-                        <Tooltip title={notDeleteable}>
+                        <Tooltip title={notDeletable}>
                             <span>
-                                <Button variant="outlined" onClick={handleDeleteJob} disabled={!!notDeleteable}>
+                                <Button variant="outlined" onClick={handleDeleteJob} disabled={!!notDeletable}>
                                     Delete
                                 </Button>
                             </span>
@@ -179,6 +182,7 @@ const JobViewer = (props: JobViewerProps) => {
                     </Grid>
                 </>
             ) : null}
+            {props.children}
         </Grid>
     );
 };

+ 2 - 10
frontend/taipy/src/NodeSelector.tsx

@@ -14,27 +14,19 @@
 import React from "react";
 import Box from "@mui/material/Box";
 
-import { MainTreeBoxSx, useClassNames } from "./utils";
+import { CoreProps, MainTreeBoxSx, useClassNames } from "./utils";
 import { Cycles, DataNodes, NodeType, Scenarios } from "./utils/types";
 import CoreSelector from "./CoreSelector";
 
-interface NodeSelectorProps {
-    id?: string;
-    updateVarName?: string;
+interface NodeSelectorProps extends CoreProps {
     innerDatanodes?: Cycles | Scenarios | DataNodes;
-    coreChanged?: Record<string, unknown>;
-    updateVars: string;
     onChange?: string;
     error?: string;
     displayCycles: boolean;
     showPrimaryFlag: boolean;
-    propagate?: boolean;
     value?: string;
     defaultValue?: string;
     height: string;
-    libClassName?: string;
-    className?: string;
-    dynamicClassName?: string;
     showPins?: boolean;
     multiple?: boolean;
     updateDnVars?: string;

+ 5 - 8
frontend/taipy/src/PropertiesEditor.tsx

@@ -22,7 +22,7 @@ import { DeleteOutline, CheckCircle, Cancel } from "@mui/icons-material";
 
 import { createSendActionNameAction, getUpdateVar, useDispatch, useModule } from "taipy-gui";
 
-import { DeleteIconSx, FieldNoMaxWidth, IconPaddingSx, disableColor, hoverSx } from "./utils";
+import { CoreProps, DeleteIconSx, FieldNoMaxWidth, IconPaddingSx, disableColor, hoverSx } from "./utils";
 
 type Property = {
     id: string;
@@ -39,10 +39,8 @@ type PropertiesEditPayload = {
 
 export type DatanodeProperties = Array<[string, string]>;
 
-interface PropertiesEditorProps {
-    id?: string;
+interface PropertiesEditorProps extends CoreProps {
     entityId: string;
-    active: boolean;
     show: boolean;
     entProperties: DatanodeProperties;
     onFocus: (e: MouseEvent<HTMLElement>) => void;
@@ -51,7 +49,6 @@ interface PropertiesEditorProps {
     isDefined: boolean;
     onEdit?: string;
     notEditableReason: string;
-    updatePropVars?: string;
 }
 
 const PropertiesEditor = (props: PropertiesEditorProps) => {
@@ -66,7 +63,7 @@ const PropertiesEditor = (props: PropertiesEditorProps) => {
         setFocusName,
         entProperties,
         notEditableReason,
-        updatePropVars = "",
+        updateVars = "",
     } = props;
 
     const dispatch = useDispatch();
@@ -102,7 +99,7 @@ const PropertiesEditor = (props: PropertiesEditorProps) => {
                     const payload: PropertiesEditPayload = {
                         id: entityId,
                         properties: [property],
-                        error_id: getUpdateVar(updatePropVars, "error_id"),
+                        error_id: getUpdateVar(updateVars, "error_id"),
                     };
                     if (oldId && oldId != property.key) {
                         payload.deleted_properties = [{ key: oldId }];
@@ -113,7 +110,7 @@ const PropertiesEditor = (props: PropertiesEditorProps) => {
                 setFocusName("");
             }
         },
-        [isDefined, props.onEdit, entityId, properties, newProp, id, dispatch, module, setFocusName, updatePropVars]
+        [isDefined, props.onEdit, entityId, properties, newProp, id, dispatch, module, setFocusName, updateVars]
     );
     const cancelProperty = useCallback(
         (e?: MouseEvent<HTMLElement>, dataset?: DOMStringMap) => {

+ 35 - 23
frontend/taipy/src/ScenarioDag.tsx

@@ -23,29 +23,23 @@ import {
     createRequestUpdateAction,
     createSendActionNameAction,
     createSendUpdateAction,
+    createUnBroadcastAction,
     getUpdateVar,
     useDispatch,
     useDynamicProperty,
     useModule,
 } from "taipy-gui";
-import { useClassNames } from "./utils";
+import { CoreProps, useClassNames } from "./utils";
 import { TaipyDiagramModel } from "./projectstorm/models";
 
-interface ScenarioDagProps {
-    id?: string;
+interface ScenarioDagProps extends CoreProps {
     defaultScenario?: string;
     scenario?: DisplayModel | DisplayModel[];
-    coreChanged?: Record<string, unknown>;
-    updateVarName?: string;
     render?: boolean;
     defaultRender?: boolean;
     showToolbar?: boolean;
     width?: string;
     height?: string;
-    updateVars: string;
-    libClassName?: string;
-    className?: string;
-    dynamicClassName?: string;
     onAction?: string;
     onSelect?: string;
 }
@@ -69,17 +63,17 @@ const DagTitle = (props: DagTitleProps) => (
     </AppBar>
 );
 
-const getValidScenario = (scenar: DisplayModel | DisplayModel[]) =>
-    scenar.length == 3 && typeof scenar[0] === "string"
-        ? (scenar as DisplayModel)
-        : scenar.length == 1
-        ? (scenar[0] as DisplayModel)
+const getValidScenario = (scenario: DisplayModel | DisplayModel[]) =>
+    scenario.length == 3 && typeof scenario[0] === "string"
+        ? (scenario as DisplayModel)
+        : scenario.length == 1
+        ? (scenario[0] as DisplayModel)
         : undefined;
 
 const preventWheel = (e: Event) => e.preventDefault();
 
 const ScenarioDag = (props: ScenarioDagProps) => {
-    const { showToolbar = true, onSelect, onAction } = props;
+    const { showToolbar = true, onSelect, onAction, coreChanged } = props;
     const [scenarioId, setScenarioId] = useState("");
     const [engine] = useState(createEngine);
     const [dagreEngine] = useState(createDagreEngine);
@@ -105,15 +99,32 @@ const ScenarioDag = (props: ScenarioDagProps) => {
 
     // Refresh on broadcast
     useEffect(() => {
-        const ids = props.coreChanged?.scenario;
-        if (typeof ids === "string" ? ids === scenarioId : Array.isArray(ids) ? ids.includes(scenarioId) : ids) {
-            props.updateVarName && dispatch(createRequestUpdateAction(props.id, module, [props.updateVarName], true));
+        if (coreChanged?.name) {
+            const toRemove = [...coreChanged.stack]
+                .map((bc) => {
+                    const ids = (bc as Record<string, unknown>).scenario;
+                    if (
+                        typeof ids === "string"
+                            ? ids === scenarioId
+                            : Array.isArray(ids)
+                            ? ids.includes(scenarioId)
+                            : ids
+                    ) {
+                        props.updateVarName &&
+                            dispatch(createRequestUpdateAction(props.id, module, [props.updateVarName], true));
+                        return bc;
+                    }
+                    const tasks = (bc as Record<string, unknown>).tasks;
+                    if (tasks) {
+                        setTaskStatuses(tasks as TaskStatuses);
+                        return bc;
+                    }
+                    return undefined;
+                })
+                .filter((v) => v);
+            toRemove.length && dispatch(createUnBroadcastAction(coreChanged.name, ...toRemove));
         }
-        const tasks = props.coreChanged?.tasks;
-        if (tasks) {
-            setTaskStatuses(tasks as TaskStatuses);
-        }
-    }, [props.coreChanged, props.updateVarName, scenarioId, module, dispatch, props.id]);
+    }, [coreChanged, props.updateVarName, scenarioId, module, dispatch, props.id]);
 
     useEffect(() => {
         let dm: DisplayModel | undefined = undefined;
@@ -184,6 +195,7 @@ const ScenarioDag = (props: ScenarioDagProps) => {
         <Paper sx={sizeSx} id={props.id} className={className}>
             {showToolbar ? <DagTitle zoomToFit={zoomToFit} /> : null}
             <CanvasWidget engine={engine} ref={canvasRef} />
+            {props.children}
         </Paper>
     ) : null;
 };

+ 3 - 13
frontend/taipy/src/ScenarioSelector.tsx

@@ -45,7 +45,7 @@ import {
 } from "taipy-gui";
 
 import ConfirmDialog from "./utils/ConfirmDialog";
-import { MainTreeBoxSx, ScFProps, ScenarioFull, useClassNames, tinyIconButtonSx } from "./utils";
+import { MainTreeBoxSx, ScFProps, ScenarioFull, useClassNames, tinyIconButtonSx, CoreProps } from "./utils";
 import CoreSelector, { EditProps } from "./CoreSelector";
 import { Cycles, NodeType, Scenarios } from "./utils/types";
 
@@ -67,31 +67,20 @@ interface ScenarioDict {
     properties: Array<Property>;
 }
 
-interface ScenarioSelectorProps {
-    id?: string;
-    active?: boolean;
-    defaultActive?: boolean;
+interface ScenarioSelectorProps extends CoreProps {
     showAddButton?: boolean;
     displayCycles?: boolean;
     showPrimaryFlag?: boolean;
-    updateVarName?: string;
-    updateVars: string;
     innerScenarios?: Cycles | Scenarios;
     onScenarioCrud: string;
     onChange?: string;
     onCreation?: string;
-    coreChanged?: Record<string, unknown>;
     configs?: Array<[string, string]>;
-    error?: string;
-    propagate?: boolean;
     scenarioEdit?: ScenarioFull;
     onScenarioSelect: string;
     value?: string;
     defaultValue?: string;
     height: string;
-    libClassName?: string;
-    className?: string;
-    dynamicClassName?: string;
     showPins?: boolean;
     showDialog?: boolean;
     multiple?: boolean;
@@ -569,6 +558,7 @@ const ScenarioSelector = (props: ScenarioSelectorProps) => {
                 scenario={props.scenarioEdit}
                 submit={onSubmit}
             ></ScenarioEditDialog>
+            {props.children}
         </>
     );
 };

+ 79 - 70
frontend/taipy/src/ScenarioViewer.tsx

@@ -40,6 +40,7 @@ import deepEqual from "fast-deep-equal/es6";
 import {
     createRequestUpdateAction,
     createSendActionNameAction,
+    createUnBroadcastAction,
     getUpdateVar,
     useDispatch,
     useDynamicProperty,
@@ -49,6 +50,7 @@ import {
 import {
     AccordionIconSx,
     AccordionSummarySx,
+    CoreProps,
     FieldNoMaxWidth,
     FlagSx,
     IconPaddingSx,
@@ -64,20 +66,14 @@ import ConfirmDialog from "./utils/ConfirmDialog";
 import PropertiesEditor from "./PropertiesEditor";
 import StatusChip, { Status } from "./StatusChip";
 
-interface ScenarioViewerProps {
-    id?: string;
+interface ScenarioViewerProps extends CoreProps {
     expandable?: boolean;
     expanded?: boolean;
-    updateVarName?: string;
     defaultScenario?: string;
     scenario?: ScenarioFull | Array<ScenarioFull>;
     onSubmit?: string;
     onEdit?: string;
     onDelete?: string;
-    error?: string;
-    coreChanged?: Record<string, unknown>;
-    defaultActive: boolean;
-    active: boolean;
     showConfig?: boolean;
     showCreationDate?: boolean;
     showCycle?: boolean;
@@ -87,9 +83,6 @@ interface ScenarioViewerProps {
     showSubmit?: boolean;
     showSubmitSequences?: boolean;
     showTags?: boolean;
-    libClassName?: string;
-    className?: string;
-    dynamicClassName?: string;
     onSubmissionChange?: string;
     updateScVars?: string;
 }
@@ -348,6 +341,7 @@ const ScenarioViewer = (props: ScenarioViewerProps) => {
         showSubmitSequences = true,
         showTags = true,
         updateScVars = "",
+        coreChanged,
     } = props;
 
     const dispatch = useDispatch();
@@ -610,14 +604,25 @@ const ScenarioViewer = (props: ScenarioViewerProps) => {
 
     // Refresh on broadcast
     useEffect(() => {
-        const ids = props.coreChanged?.scenario;
-        if (typeof ids === "string" ? ids === scId : Array.isArray(ids) ? ids.includes(scId) : ids) {
-            if (typeof props.coreChanged?.submission === "number") {
-                setSubmissionStatus(props.coreChanged?.submission as number);
-            }
-            props.updateVarName && dispatch(createRequestUpdateAction(id, module, [props.updateVarName], true));
+        if (coreChanged?.name) {
+            const toRemove = [...coreChanged.stack]
+                .map((bc) => {
+                    const ids = (bc as Record<string, unknown>).scenario;
+                    if (typeof ids === "string" ? ids === scId : Array.isArray(ids) ? ids.includes(scId) : ids) {
+                        const submission = (bc as Record<string, unknown>).submission
+                        if (typeof submission === "number") {
+                            setSubmissionStatus(submission as number);
+                        }
+                        props.updateVarName &&
+                            dispatch(createRequestUpdateAction(id, module, [props.updateVarName], true));
+                        return bc;
+                    }
+                    return undefined;
+                })
+                .filter((v) => v);
+            toRemove.length && dispatch(createUnBroadcastAction(coreChanged.name, ...toRemove));
         }
-    }, [props.coreChanged, props.updateVarName, id, module, dispatch, scId]);
+    }, [coreChanged, props.updateVarName, id, module, dispatch, scId]);
 
     const disabled = !valid || !active || !!scNotSubmittableReason;
 
@@ -712,31 +717,33 @@ const ScenarioViewer = (props: ScenarioViewerProps) => {
                                             sx={FieldNoMaxWidth}
                                             value={label || ""}
                                             onChange={onLabelChange}
-                                            slotProps={{input: {
-                                                onKeyDown: onLabelKeyDown,
-                                                endAdornment: (
-                                                    <InputAdornment position="end">
-                                                        <Tooltip title="Apply">
-                                                            <IconButton
-                                                                sx={IconPaddingSx}
-                                                                onClick={editLabel}
-                                                                size="small"
-                                                            >
-                                                                <CheckCircle color="primary" />
-                                                            </IconButton>
-                                                        </Tooltip>
-                                                        <Tooltip title="Cancel">
-                                                            <IconButton
-                                                                sx={IconPaddingSx}
-                                                                onClick={cancelLabel}
-                                                                size="small"
-                                                            >
-                                                                <Cancel color="inherit" />
-                                                            </IconButton>
-                                                        </Tooltip>
-                                                    </InputAdornment>
-                                                ),
-                                            }}}
+                                            slotProps={{
+                                                input: {
+                                                    onKeyDown: onLabelKeyDown,
+                                                    endAdornment: (
+                                                        <InputAdornment position="end">
+                                                            <Tooltip title="Apply">
+                                                                <IconButton
+                                                                    sx={IconPaddingSx}
+                                                                    onClick={editLabel}
+                                                                    size="small"
+                                                                >
+                                                                    <CheckCircle color="primary" />
+                                                                </IconButton>
+                                                            </Tooltip>
+                                                            <Tooltip title="Cancel">
+                                                                <IconButton
+                                                                    sx={IconPaddingSx}
+                                                                    onClick={cancelLabel}
+                                                                    size="small"
+                                                                >
+                                                                    <Cancel color="inherit" />
+                                                                </IconButton>
+                                                            </Tooltip>
+                                                        </InputAdornment>
+                                                    ),
+                                                },
+                                            }}
                                             disabled={!valid}
                                         />
                                     ) : (
@@ -787,32 +794,34 @@ const ScenarioViewer = (props: ScenarioViewerProps) => {
                                                         label="Tags"
                                                         sx={tagsAutocompleteSx}
                                                         fullWidth
-                                                        slotProps={{input: {
-                                                            ...params.InputProps,
-                                                            onKeyDown: onTagsKeyDown,
-                                                            endAdornment: (
-                                                                <>
-                                                                    <Tooltip title="Apply">
-                                                                        <IconButton
-                                                                            sx={IconPaddingSx}
-                                                                            onClick={editTags}
-                                                                            size="small"
-                                                                        >
-                                                                            <CheckCircle color="primary" />
-                                                                        </IconButton>
-                                                                    </Tooltip>
-                                                                    <Tooltip title="Cancel">
-                                                                        <IconButton
-                                                                            sx={IconPaddingSx}
-                                                                            onClick={cancelTags}
-                                                                            size="small"
-                                                                        >
-                                                                            <Cancel color="inherit" />
-                                                                        </IconButton>
-                                                                    </Tooltip>
-                                                                </>
-                                                            ),
-                                                        }}}
+                                                        slotProps={{
+                                                            input: {
+                                                                ...params.InputProps,
+                                                                onKeyDown: onTagsKeyDown,
+                                                                endAdornment: (
+                                                                    <>
+                                                                        <Tooltip title="Apply">
+                                                                            <IconButton
+                                                                                sx={IconPaddingSx}
+                                                                                onClick={editTags}
+                                                                                size="small"
+                                                                            >
+                                                                                <CheckCircle color="primary" />
+                                                                            </IconButton>
+                                                                        </Tooltip>
+                                                                        <Tooltip title="Cancel">
+                                                                            <IconButton
+                                                                                sx={IconPaddingSx}
+                                                                                onClick={cancelTags}
+                                                                                size="small"
+                                                                            >
+                                                                                <Cancel color="inherit" />
+                                                                            </IconButton>
+                                                                        </Tooltip>
+                                                                    </>
+                                                                ),
+                                                            },
+                                                        }}
                                                     />
                                                 )}
                                                 disabled={!valid}
@@ -847,7 +856,7 @@ const ScenarioViewer = (props: ScenarioViewerProps) => {
                                 onFocus={onFocus}
                                 onEdit={props.onEdit}
                                 notEditableReason={scNotEditableReason}
-                                updatePropVars={updateScVars}
+                                updateVars={updateScVars}
                             />
                             {showSequences ? (
                                 <>
@@ -866,7 +875,7 @@ const ScenarioViewer = (props: ScenarioViewerProps) => {
                                         const [label, taskIds, notSubmittableReason, notEditableReason] = item;
                                         return (
                                             <SequenceRow
-                                                active={active}
+                                                active={!!active}
                                                 number={index}
                                                 label={label}
                                                 taskIds={taskIds}

+ 19 - 2
frontend/taipy/src/utils.ts

@@ -12,8 +12,25 @@
  */
 import { Theme, alpha } from "@mui/material";
 import { PopoverOrigin } from "@mui/material/Popover";
-
-import { getUpdateVar, useDynamicProperty } from "taipy-gui";
+import { ReactNode } from "react";
+
+import { BroadcastDesc, getUpdateVar, useDynamicProperty } from "taipy-gui";
+
+
+export interface CoreProps {
+    id?: string;
+    updateVarName?: string;
+    active?: boolean;
+    defaultActive?: boolean;
+    coreChanged?: BroadcastDesc;
+    error?: string;
+    updateVars: string;
+    libClassName?: string;
+    className?: string;
+    dynamicClassName?: string;
+    propagate?: boolean;
+    children?: ReactNode;
+}
 
 export type ScenarioFull = [
     string, // id

+ 1 - 1
taipy/gui/gui.py

@@ -1377,7 +1377,7 @@ class Gui:
     ):
         self.__broadcast_ws(
             {
-                "type": _WsType.UPDATE.value if message_type is None else message_type.value,
+                "type": _WsType.BROADCAST.value if message_type is None else message_type.value,
                 "name": _get_broadcast_var_name(var_name),
                 "payload": {"value": var_value},
             },

+ 1 - 0
taipy/gui/types.py

@@ -51,6 +51,7 @@ class _WsType(Enum):
     GET_DATA_TREE = "GDT"
     GET_ROUTES = "GR"
     FAVICON = "FV"
+    BROADCAST = "BC"
 
 
 NumberTypes = {"int", "int64", "float", "float64"}

+ 1 - 1
tests/gui/server/ws/test_broadcast.py

@@ -36,4 +36,4 @@ def test_broadcast(gui: Gui, helpers):
     gui._broadcast("broadcast_name", "broadcast_value")
     received_messages = ws_client.get_received()
     assert len(received_messages)
-    helpers.assert_outward_simple_ws_message(received_messages[0], "U", "_bc_broadcast_name", "broadcast_value")
+    helpers.assert_outward_simple_ws_message(received_messages[0], "BC", "_bc_broadcast_name", "broadcast_value")