From da1b1ee7f524382ce1d256d795ad9d554012fc7c Mon Sep 17 00:00:00 2001 From: Russell Standish Date: Thu, 22 Jan 2026 15:16:32 +1100 Subject: [PATCH 1/7] Arrange for Godley tables to refresh all other Godley tables on canvas, for #1466. Also arrange for Godley popups to refresh all other Godley popups on finishing editing. --- RavelCAPI | 2 +- .../apps/minsky-electron/src/app/events/electron.events.ts | 6 ++++++ .../apps/minsky-electron/src/app/managers/WindowManager.ts | 7 +++++++ gui-js/libs/shared/src/lib/constants/constants.ts | 1 + .../lib/godley-widget-view/godley-widget-view.component.ts | 6 +++--- model/godleyIcon.cc | 1 + 6 files changed, 19 insertions(+), 4 deletions(-) diff --git a/RavelCAPI b/RavelCAPI index be1837d64..8282d1b91 160000 --- a/RavelCAPI +++ b/RavelCAPI @@ -1 +1 @@ -Subproject commit be1837d64c34df8febb425520dd962ee4bee9cbe +Subproject commit 8282d1b91ec193f3b0e4d6102eaa9873814d3994 diff --git a/gui-js/apps/minsky-electron/src/app/events/electron.events.ts b/gui-js/apps/minsky-electron/src/app/events/electron.events.ts index 74a64d139..3a7ff615d 100644 --- a/gui-js/apps/minsky-electron/src/app/events/electron.events.ts +++ b/gui-js/apps/minsky-electron/src/app/events/electron.events.ts @@ -150,6 +150,12 @@ ipcMain.handle( } ); +ipcMain.on( + events.REFRESH_ALL_GODLEY_POPUPS,async (event) => { + WindowManager.refreshAllGodleyPopups(); + } +); + ipcMain.on(events.ADD_RECENT_FILE, (event, filePath: string) => { RecentFilesManager.addFileToRecentFiles(filePath); }); diff --git a/gui-js/apps/minsky-electron/src/app/managers/WindowManager.ts b/gui-js/apps/minsky-electron/src/app/managers/WindowManager.ts index d4add7a30..4cff66037 100644 --- a/gui-js/apps/minsky-electron/src/app/managers/WindowManager.ts +++ b/gui-js/apps/minsky-electron/src/app/managers/WindowManager.ts @@ -2,6 +2,7 @@ import { ActiveWindow, AppLayoutPayload, CreateWindowPayload, + events, Functions, minsky, OPEN_DEV_TOOLS_IN_DEV_BUILD, @@ -370,4 +371,10 @@ export class WindowManager { type: 'info', }); } + + static refreshAllGodleyPopups() { + for (const [num, win] of WindowManager.activeWindows) + if (win.context!=WindowManager.getMainWindow()) + win.context?.webContents?.send(events.GODLEY_POPUP_REFRESH); + } } diff --git a/gui-js/libs/shared/src/lib/constants/constants.ts b/gui-js/libs/shared/src/lib/constants/constants.ts index 795aa5d15..f83e7ac6b 100644 --- a/gui-js/libs/shared/src/lib/constants/constants.ts +++ b/gui-js/libs/shared/src/lib/constants/constants.ts @@ -52,6 +52,7 @@ export const events = { RECORD: 'record', RECORDING_REPLAY: 'recording-replay', RECORDING_STATUS_CHANGED: 'recording-status-changed', + REFRESH_ALL_GODLEY_POPUPS: 'refresh-all-godley-popups', REFRESH_CSV_IMPORT: 'refresh-csv-import', REPLAY_RECORDING: 'replay-recording', RESET_ZOOM: 'reset-zoom', diff --git a/gui-js/libs/ui-components/src/lib/godley-widget-view/godley-widget-view.component.ts b/gui-js/libs/ui-components/src/lib/godley-widget-view/godley-widget-view.component.ts index 48ebb05e1..c37714693 100644 --- a/gui-js/libs/ui-components/src/lib/godley-widget-view/godley-widget-view.component.ts +++ b/gui-js/libs/ui-components/src/lib/godley-widget-view/godley-widget-view.component.ts @@ -290,10 +290,10 @@ export class GodleyWidgetViewComponent implements OnDestroy, OnInit, AfterViewIn await this.hardRefresh(); } - async hardRefresh(update = true) { + async hardRefresh() { this.multiEquityAllowed = await this.electronService.minsky.multipleEquities(); - if(update) this.godleyIcon.update(); + this.godleyIcon.update(); const allData: string[][] = await this.godleyIcon.table.getData(); @@ -378,7 +378,7 @@ export class GodleyWidgetViewComponent implements OnDestroy, OnInit, AfterViewIn this.cellEditing[1] = undefined; } - await this.hardRefresh(); + this.electronService.send(events.REFRESH_ALL_GODLEY_POPUPS); } } diff --git a/model/godleyIcon.cc b/model/godleyIcon.cc index ad4def455..20df647c3 100644 --- a/model/godleyIcon.cc +++ b/model/godleyIcon.cc @@ -632,6 +632,7 @@ namespace minsky if (m_editorMode) { editor.mouseMove(-1,-1); + minsky().balanceDuplicateColumns(*this, editor.selectedCol); // May be a bit overzealous, but it solves bug 1273, which is caused by a flow which has not yet fully come into existence.... editor.selectedCol=-1; editor.selectedRow=-1; From 4cfd27bee8effd7001131ae02072753b022ee568 Mon Sep 17 00:00:00 2001 From: Russell Standish Date: Thu, 22 Jan 2026 16:21:45 +1100 Subject: [PATCH 2/7] Potential fix for code scanning alert no. 1587: Unused variable, import, function or class Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- gui-js/apps/minsky-electron/src/app/managers/WindowManager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui-js/apps/minsky-electron/src/app/managers/WindowManager.ts b/gui-js/apps/minsky-electron/src/app/managers/WindowManager.ts index 4cff66037..9e18a2d03 100644 --- a/gui-js/apps/minsky-electron/src/app/managers/WindowManager.ts +++ b/gui-js/apps/minsky-electron/src/app/managers/WindowManager.ts @@ -373,7 +373,7 @@ export class WindowManager { } static refreshAllGodleyPopups() { - for (const [num, win] of WindowManager.activeWindows) + for (const win of WindowManager.activeWindows.values()) if (win.context!=WindowManager.getMainWindow()) win.context?.webContents?.send(events.GODLEY_POPUP_REFRESH); } From cf410f66193b660e7e3a81bbd3094a52a891e619 Mon Sep 17 00:00:00 2001 From: Russell Standish Date: Thu, 22 Jan 2026 16:25:30 +1100 Subject: [PATCH 3/7] Coderabbit nits addressed. --- RavelCAPI | 2 +- gui-js/apps/minsky-electron/src/app/events/electron.events.ts | 2 +- gui-js/apps/minsky-electron/src/app/managers/WindowManager.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/RavelCAPI b/RavelCAPI index 8282d1b91..94e0e7e6b 160000 --- a/RavelCAPI +++ b/RavelCAPI @@ -1 +1 @@ -Subproject commit 8282d1b91ec193f3b0e4d6102eaa9873814d3994 +Subproject commit 94e0e7e6bf98ad975740c1440d3920be73346f3a diff --git a/gui-js/apps/minsky-electron/src/app/events/electron.events.ts b/gui-js/apps/minsky-electron/src/app/events/electron.events.ts index 3a7ff615d..3bcb1f178 100644 --- a/gui-js/apps/minsky-electron/src/app/events/electron.events.ts +++ b/gui-js/apps/minsky-electron/src/app/events/electron.events.ts @@ -151,7 +151,7 @@ ipcMain.handle( ); ipcMain.on( - events.REFRESH_ALL_GODLEY_POPUPS,async (event) => { + events.REFRESH_ALL_GODLEY_POPUPS, async () => { WindowManager.refreshAllGodleyPopups(); } ); diff --git a/gui-js/apps/minsky-electron/src/app/managers/WindowManager.ts b/gui-js/apps/minsky-electron/src/app/managers/WindowManager.ts index 4cff66037..9e18a2d03 100644 --- a/gui-js/apps/minsky-electron/src/app/managers/WindowManager.ts +++ b/gui-js/apps/minsky-electron/src/app/managers/WindowManager.ts @@ -373,7 +373,7 @@ export class WindowManager { } static refreshAllGodleyPopups() { - for (const [num, win] of WindowManager.activeWindows) + for (const win of WindowManager.activeWindows.values()) if (win.context!=WindowManager.getMainWindow()) win.context?.webContents?.send(events.GODLEY_POPUP_REFRESH); } From 233501660928c2a62006fcd2baff75c0a4de6e25 Mon Sep 17 00:00:00 2001 From: Russell Standish Date: Thu, 22 Jan 2026 16:56:40 +1100 Subject: [PATCH 4/7] Update EcoLab ref. --- ecolab | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ecolab b/ecolab index d0fde1555..696284281 160000 --- a/ecolab +++ b/ecolab @@ -1 +1 @@ -Subproject commit d0fde1555fc6fff1ad27b9e02d42144573beaea6 +Subproject commit 69628428104f0ff14ab39fbfb84b5f9e7acd060d From 6230e6e873c496b41dc7eb1eff7ddb69d72a2373 Mon Sep 17 00:00:00 2001 From: Russell Standish Date: Wed, 21 Jan 2026 20:49:25 +1100 Subject: [PATCH 5/7] Needs to be /usr/local/lib64 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 510a2936a..f02b9bd40 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ TK_LIB=$(dir $(shell find $(TCL_PREFIX) -name tk.tcl -path "*/tk$(TCL_VERSION)*" # root directory for ecolab include files and libraries ECOLAB_HOME=$(shell pwd)/ecolab -export LD_LIBRARY_PATH:=$(ECOLAB_HOME)/lib:$(LD_LIBRARY_PATH) +export LD_LIBRARY_PATH:=$(ECOLAB_HOME)/lib:$(LD_LIBRARY_PATH):/usr/local/lib64 ARCH=$(shell arch) HAVE_CLANG=$(shell if which clang++>/dev/null; then echo 1; fi) From 71dddeb445aa9f2fb36eb44f7888782e43f377b8 Mon Sep 17 00:00:00 2001 From: Russell Standish Date: Thu, 22 Jan 2026 17:47:23 +1100 Subject: [PATCH 6/7] Add try/catch around sending event to all windows to prevent windows disappearing throwing. --- .../minsky-electron/src/app/managers/WindowManager.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/gui-js/apps/minsky-electron/src/app/managers/WindowManager.ts b/gui-js/apps/minsky-electron/src/app/managers/WindowManager.ts index 9e18a2d03..f97f76e31 100644 --- a/gui-js/apps/minsky-electron/src/app/managers/WindowManager.ts +++ b/gui-js/apps/minsky-electron/src/app/managers/WindowManager.ts @@ -374,7 +374,11 @@ export class WindowManager { static refreshAllGodleyPopups() { for (const win of WindowManager.activeWindows.values()) - if (win.context!=WindowManager.getMainWindow()) - win.context?.webContents?.send(events.GODLEY_POPUP_REFRESH); + if (win.context!=WindowManager.getMainWindow()) { + try { + win.context?.webContents?.send(events.GODLEY_POPUP_REFRESH); + } + catch (err) {} // absorb any exceptions due to windows disappearing + } } } From 3fc6b0366141ac65f58b6ddcc8fc1ecd0c92816d Mon Sep 17 00:00:00 2001 From: Russell Standish Date: Mon, 15 Dec 2025 17:22:16 +1100 Subject: [PATCH 7/7] Fix regression failures. --- gui-js/libs/shared/src/lib/backend/minsky.ts | 1 - test/testTensorOps.cc | 57 ++++++++++++++------ 2 files changed, 42 insertions(+), 16 deletions(-) diff --git a/gui-js/libs/shared/src/lib/backend/minsky.ts b/gui-js/libs/shared/src/lib/backend/minsky.ts index 3e3468055..49c4621be 100644 --- a/gui-js/libs/shared/src/lib/backend/minsky.ts +++ b/gui-js/libs/shared/src/lib/backend/minsky.ts @@ -1576,7 +1576,6 @@ export class PlotWidget extends Item { this.yvars=new Sequence>(this.$prefix()+'.yvars',Sequence); } async AssignSide(a1: number,a2: string): Promise {return this.$callMethod('AssignSide',a1,a2);} - async Image(...args: any[]): Promise {return this.$callMethod('Image',...args);} async LabelPen(a1: number,a2: ecolab__cairo__Surface): Promise {return this.$callMethod('LabelPen',a1,a2);} async addConstantCurves(): Promise {return this.$callMethod('addConstantCurves');} async addPlotPt(a1: number): Promise {return this.$callMethod('addPlotPt',a1);} diff --git a/test/testTensorOps.cc b/test/testTensorOps.cc index daa02e614..758eb6da0 100644 --- a/test/testTensorOps.cc +++ b/test/testTensorOps.cc @@ -1195,32 +1195,45 @@ TEST_F(TensorValFixture, reduction2dswapped) EXPECT_TRUE(chc.xvectors[1]==ahc.xvectors[0]); EXPECT_EQ(9,chain.back()->size()); vector expected={3,15,27,5,17,29,7,19,31}; - for (size_t _i=0; _i<9; ++_i) EXPECT_EQ(expected[_i], (*chain.back())[_i]); + // above expected was in sorted index order + auto index=chain.back()->index(); + vector sortedIdx(index.begin(),index.end()); sort(sortedIdx.begin(),sortedIdx.end()); + for (size_t _i=0; _i<9; ++_i) EXPECT_EQ(expected[_i], chain.back()->atHCIndex(sortedIdx[_i])); sex->reductionOp=ravel::Op::prod; chain=createRavelChain(state, arg); expected={0,54,180,4,70,208,10,88,238}; - for (size_t _i=0; _i<9; ++_i) EXPECT_EQ(expected[_i], (*chain.back())[_i]); + index=chain.back()->index(); + sortedIdx=vector(index.begin(),index.end()); sort(sortedIdx.begin(),sortedIdx.end()); + for (size_t _i=0; _i<9; ++_i) EXPECT_EQ(expected[_i], chain.back()->atHCIndex(sortedIdx[_i])); sex->reductionOp=ravel::Op::av; chain=createRavelChain(state, arg); expected={1.5,7.5,13.5,2.5,8.5,14.5,3.5,9.5,15.5}; - for (size_t _i=0; _i<9; ++_i) EXPECT_EQ(expected[_i], (*chain.back())[_i]); + index=chain.back()->index(); + sortedIdx=vector(index.begin(),index.end()); sort(sortedIdx.begin(),sortedIdx.end()); + for (size_t _i=0; _i<9; ++_i) EXPECT_EQ(expected[_i], chain.back()->atHCIndex(sortedIdx[_i])); sex->reductionOp=ravel::Op::stddev; chain=createRavelChain(state, arg); expected={2.12132, 2.12132, 2.12132, 2.12132, 2.12132, 2.12132, 2.12132, 2.12132, 2.12132}; - for (size_t _i=0; _i<9; ++_i) EXPECT_NEAR(expected[_i], (*chain.back())[_i], 1e-4); + index=chain.back()->index(); + sortedIdx=vector(index.begin(),index.end()); sort(sortedIdx.begin(),sortedIdx.end()); + for (size_t _i=0; _i<9; ++_i) EXPECT_NEAR(expected[_i], chain.back()->atHCIndex(sortedIdx[_i]), 1e-4); sex->reductionOp=ravel::Op::min; chain=createRavelChain(state, arg); expected={0,6,12,1,7,13,2,8,14}; - for (size_t _i=0; _i<9; ++_i) EXPECT_EQ(expected[_i], (*chain.back())[_i]); + index=chain.back()->index(); + sortedIdx=vector(index.begin(),index.end()); sort(sortedIdx.begin(),sortedIdx.end()); + for (size_t _i=0; _i<9; ++_i) EXPECT_EQ(expected[_i], chain.back()->atHCIndex(sortedIdx[_i])); sex->reductionOp=ravel::Op::max; chain=createRavelChain(state, arg); expected={3,9,15,4,10,16,5,11,17}; - for (size_t _i=0; _i<9; ++_i) EXPECT_EQ(expected[_i], (*chain.back())[_i]); + index=chain.back()->index(); + sortedIdx=vector(index.begin(),index.end()); sort(sortedIdx.begin(),sortedIdx.end()); + for (size_t _i=0; _i<9; ++_i) EXPECT_EQ(expected[_i], chain.back()->atHCIndex(sortedIdx[_i])); } @@ -1234,28 +1247,42 @@ TEST_F(TensorValFixture, sparseSlicedRavel) auto chain=createRavelChain(state, arg); EXPECT_EQ(2, chain.back()->rank()); EXPECT_EQ(3, chain.back()->size()); - vector expectedi{0,5,6}; - for (size_t _i=0; _i<3; ++_i) EXPECT_EQ(expectedi[_i], chain[1]->index()[_i]); + set expectedi{0,5,6}; + set idx(chain[1]->index().begin(), chain[1]->index().end()); + EXPECT_TRUE(idx==expectedi); expectedi={0,2,7}; - for (size_t _i=0; _i<3; ++_i) EXPECT_EQ(expectedi[_i], chain.back()->index()[_i]); + idx=set(chain.back()->index().begin(),chain.back()->index().end()); + EXPECT_TRUE(idx==expectedi); + vector expectedf{0,1,2,3,4}; for (size_t _i=0; _i<3; ++_i) EXPECT_EQ(expectedf[_i], (*arg)[_i]); expectedf={0,2,3}; - for (size_t _i=0; _i<3; ++_i) EXPECT_EQ(expectedf[_i], (*chain[1])[_i]); + auto index=chain[1]->index(); + vector sortedIdx(index.begin(),index.end()); sort(sortedIdx.begin(),sortedIdx.end()); + for (size_t _i=0; _i<3; ++_i) EXPECT_EQ(expectedf[_i], chain[1]->atHCIndex(sortedIdx[_i])); expectedf={0,3,2}; - for (size_t _i=0; _i<3; ++_i) EXPECT_EQ(expectedf[_i], (*chain.back())[_i]); + index=chain.back()->index(); + sortedIdx=vector(index.begin(),index.end()); sort(sortedIdx.begin(),sortedIdx.end()); + for (size_t _i=0; _i<3; ++_i) EXPECT_EQ(expectedf[_i], chain.back()->atHCIndex(sortedIdx[_i])); sex->collapsed=true; chain=createRavelChain(state, arg); EXPECT_EQ(5, chain.back()->size()); expectedi={0,1,5,6,7}; - for (size_t _i=0; _i<3; ++_i) EXPECT_EQ(expectedi[_i], chain[1]->index()[_i]); + idx=set(chain[1]->index().begin(),chain[1]->index().end()); + EXPECT_TRUE(idx==expectedi); + expectedi={0,2,3,5,7}; - for (size_t _i=0; _i<3; ++_i) EXPECT_EQ(expectedi[_i], chain.back()->index()[_i]); + idx=set(chain.back()->index().begin(),chain.back()->index().end()); + EXPECT_TRUE(idx==expectedi); expectedf={0,1,2,3,4}; - for (size_t _i=0; _i<5; ++_i) EXPECT_EQ(expectedf[_i], (*chain[1])[_i]); + index=chain[1]->index(); + sortedIdx=vector(index.begin(),index.end()); sort(sortedIdx.begin(),sortedIdx.end()); + for (size_t _i=0; _i<5; ++_i) EXPECT_EQ(expectedf[_i], chain[1]->atHCIndex(sortedIdx[_i])); expectedf={0,3,1,4,2}; - for (size_t _i=0; _i<5; ++_i) EXPECT_EQ(expectedf[_i], (*chain.back())[_i]); + index=chain.back()->index(); + sortedIdx=vector(index.begin(),index.end()); sort(sortedIdx.begin(),sortedIdx.end()); + for (size_t _i=0; _i<5; ++_i) EXPECT_EQ(expectedf[_i], chain.back()->atHCIndex(sortedIdx[_i])); } TEST_F(TensorValFixture, calipered)