From 8c708b7abbdf9cd23e2118238a1ff134022dc530 Mon Sep 17 00:00:00 2001 From: Amit Haridas Date: Mon, 14 Sep 2026 22:24:27 +0530 Subject: [PATCH] =?UTF-8?q?fix(flowchart):=20pre-await=20getUserDataPath?= =?UTF-8?q?=20to=20fix=20persistence;=20bump=20v4.9.0=20=E2=86=92=20v4.9.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Amit Haridas --- README.md | 2 +- UPDATES.md | 5 +++++ package.json | 2 +- src/renderer.js | 16 ++++++++++++---- src/sidebar/flowchart-panel.js | 2 +- tests/flowchart-panel.test.js | 31 +++++++++++++++++++++++++++++++ 6 files changed, 51 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 2f99f4d..040e92b 100644 --- a/README.md +++ b/README.md @@ -257,4 +257,4 @@ Amit Haridas (amit.wh@gmail.com) ## Version -v4.9.0 +v4.9.1 diff --git a/UPDATES.md b/UPDATES.md index d967929..8767dc1 100644 --- a/UPDATES.md +++ b/UPDATES.md @@ -1,5 +1,10 @@ # PanConverter - Updates & Changelog +## Version 4.9.1 (2026-09-14) + +### Fixes +- **Flowchart Panel save failed**: `getUserDataPath()` IPC was not awaited in `src/renderer.js:2439-2449`, so the persistence path was computed as `"[object Promise]/flowchart-session.json"` and rejected by the `write-text-file` userData sandbox. Pre-resolved the path on panel register and cached it; persistence (read and write) now works correctly. + ## Version 4.9.0 (2026-09-14) ### New: Visual Flow Chart Editor (Sidebar panel → "Flow Chart") diff --git a/package.json b/package.json index 881ec0e..552a1b6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "markdown-converter", - "version": "4.9.0", + "version": "4.9.1", "description": "Professional Markdown editor and universal file converter with PDF editing, batch processing, and syntax highlighting", "main": "src/main.js", "scripts": { diff --git a/src/renderer.js b/src/renderer.js index ed03dc4..7fa13e8 100644 --- a/src/renderer.js +++ b/src/renderer.js @@ -2436,16 +2436,24 @@ document.addEventListener('DOMContentLoaded', async () => { .run({ nodes: [div] }) .catch((err) => console.warn('flowchart preview render failed:', err)); }; + // Pre-resolve the userData path on first panel render and cache it. The + // flowchart panel's getUserDataPath() must be sync (it is interpolated into + // the persistence path on mount); calling flowchartIO.getUserDataPath() + // directly would coerce the IPC Promise to "[object Promise]" and break + // every write/read against /flowchart-session.json. + let cachedUserDataPath = null; sidebarManager.registerPanel('flowchart', { title: 'Flow Chart', - render: (container) => - renderFlowChartPanel(container, { - getUserDataPath: flowchartIO.getUserDataPath, + render: async (container) => { + if (!cachedUserDataPath) cachedUserDataPath = await flowchartIO.getUserDataPath(); + return renderFlowChartPanel(container, { + getUserDataPath: () => cachedUserDataPath, readFile: flowchartIO.readFile, writeFile: flowchartIO.writeFile, insertAtCursor: (text) => tabManager.insertAtCursor(text), renderMermaid: renderFlowChartMermaid, - }), + }); + }, }); // Wiki-link navigation: open the target note (creating it on demand after diff --git a/src/sidebar/flowchart-panel.js b/src/sidebar/flowchart-panel.js index 2d2404d..3f0f868 100644 --- a/src/sidebar/flowchart-panel.js +++ b/src/sidebar/flowchart-panel.js @@ -11,7 +11,7 @@ * * @param {HTMLElement} container Mount point inside the sidebar panel * @param {object} deps - * @param {() => string} deps.getUserDataPath Absolute userData directory + * @param {() => string} deps.getUserDataPath Absolute userData directory (renderer pre-resolves and caches this on first mount — must be synchronous and return a real string, not a Promise) * @param {(path:string) => Promise} deps.readFile * @param {(path:string, content:string) => Promise} deps.writeFile * @param {(text:string) => void} deps.insertAtCursor diff --git a/tests/flowchart-panel.test.js b/tests/flowchart-panel.test.js index f0885f1..cbe8011 100644 --- a/tests/flowchart-panel.test.js +++ b/tests/flowchart-panel.test.js @@ -67,6 +67,37 @@ describe('flowchart-panel: persistence', () => { const readFile = jest.fn().mockResolvedValue('{not-json'); expect(() => mount({ readFile })).not.toThrow(); }); + + // Regression: src/renderer.js used to pass flowchartIO.getUserDataPath + // (which returns ipcRenderer.invoke's Promise) directly into the panel. + // The panel then coerced that Promise to "[object Promise]" in the + // template literal, producing a persistence path that fell outside the + // userData sandbox and was rejected by write-text-file. The fix is to + // await the path on the renderer side and cache it, then hand the panel + // a sync getter. This test mirrors that pattern: simulate the renderer's + // pre-resolve + cache, and assert the panel's persistence path is a + // real resolved string on mount. + test('handles an async getUserDataPath via renderer-side pre-resolve + cache', async () => { + const asyncGetUserDataPath = jest.fn().mockResolvedValue('/abs/path/to/userdata'); + // Pre-resolve exactly the way src/renderer.js now does. + let cachedUserDataPath = null; + if (!cachedUserDataPath) cachedUserDataPath = await asyncGetUserDataPath(); + const syncGetUserDataPath = () => cachedUserDataPath; + + const writeFile = jest.fn().mockResolvedValue(undefined); + const { api } = mount({ getUserDataPath: syncGetUserDataPath, writeFile }); + + // read on mount must use the resolved (real) path, not "[object Promise]". + await Promise.resolve(); + expect(asyncGetUserDataPath).toHaveBeenCalledTimes(1); + + // mutate -> debounced write -> assert writeFile is called with the real path. + api.getStore().addNode({ kind: 'process', x: 0, y: 0, label: 'async-cache' }); + jest.advanceTimersByTime(500); + await Promise.resolve(); + expect(writeFile).toHaveBeenCalledTimes(1); + expect(writeFile.mock.calls[0][0]).toBe('/abs/path/to/userdata/flowchart-session.json'); + }); }); describe('flowchart-panel: live preview', () => {