Two pre-existing Windows-specific failures blocked the CI release
matrix from publishing the Windows build:
- tests/main/DailyNotes.test.js: 'joins dir + YYYY-MM-DD.md' used
a hard-coded '/tmp/notes/2026-09-13.md' expected value. On
POSIX path.join returns '/' and the test passes; on Windows
path.join returns '\' and the test fails. Fix: build the
expected value via path.join so it matches the platform.
- tests/main/PDFBatchOperations.test.js: 'watermarks every PDF
including subfolders' used the default 5s Jest timeout. Windows
CI runners are slower at pdf-lib / pdfjs-dist cold-start and the
test regularly exceeded 5s. Fix: jest.setTimeout(30000) at the
start of the watermark describe block.
Neither is a regression — both predate this session. The fix gets
the Windows job green so the release matrix completes 3/3.
Amit Haridas
The CI release workflow has been failing on every v4.13.0 push with
'package.json and package-lock.json are out of sync', specifically
missing:
- electron-updater@6.8.9
- lodash.escaperegexp@4.1.2
- lodash.isequal@4.5.0
- semver@7.7.4
- tiny-typed-emitter@2.1.0
These were added when electron-updater was wired up (commit 47315ad,
the auto-updater feature) but the lockfile was never regenerated to
match the new top-level dependency. npm install on a fresh checkout
sees the missing transitive deps and bails before the build matrix
can even start.
Fix: run 'npm install' against the current package.json to regenerate
package-lock.json with the correct dep graph. No package.json change
needed — the lockfile was simply stale.
Amit Haridas
After 17 commits of feature work the standalone window had a lot of
hidden keyboard shortcuts (Cmd+Z, Cmd+C/V/D, Delete, Alt+drag, etc.)
that no user could discover. This adds:
- 'Shortcuts (?)' toolbar button next to Export Visio.
- '?' key (or Shift+/) toggles the overlay; Esc closes it.
- A read-only modal that lists every canvas-relevant shortcut in
a 2-column table (Action / Shortcut) with <kbd> tags for the
key combos, styled to look like physical keys.
- Click on the dimmed overlay backdrop also closes it.
- Reuses the existing .fc-modal fade + scale-in animation.
The overlay sits inside #fc-left so it overlays the canvas, not the
side panel. Hidden by default via the HTML hidden attribute.
Implementation: btnHelp / shortcutsModal / shortcutsOverlay /
shortcutsClose are added to the bundle's els bag. A small
showShortcutsOverlay / hideShortcutsOverlay pair toggles their
.hidden. The keydown handler gets two new branches at the top:
- Esc: close the overlay if open.
- ? (computed from ev.key === '?' or 'Shift+/', no modifier
keys held): toggle the overlay.
Both branches return early so the existing Cmd+Z / Cmd+C / etc.
handlers below them still work.
Tests: 3 new (1386 total) covering the toolbar toggle, the
? + Esc keyboard flow, and that every shortcut the user can
trigger is listed in the modal.
Amit Haridas
Seven new jsdom tests for the canvas multi-select primitives
added in C17. Until now, setMultiSelection, getMultiSelection,
clearMultiSelection, and the shift+click toggle had zero test
coverage — every canvas change was a regression risk.
New tests:
- getMultiSelection returns the Set as an array.
- setMultiSelection replaces the existing selection.
- clearMultiSelection empties node + edge selection.
- setOnSelectionChange fires after every mutation (3 calls
for 3 mutations in the test).
- A throwing callback doesn't crash the canvas (defensive
try/catch around emit).
- applySelectionHighlight adds .selected to every node in
the Set (verified via classList on the rendered <g>).
- setMultiSelection rejects non-string / empty ids.
1383 total tests, was 1376. All passing.
Amit Haridas
Item 1 from the polish list. The canvas previously supported
single-selection only; this adds:
- Shift+click on a node toggles its membership in a multi-
selection Set. Plain click still replaces with a single node.
- Plain drag on empty canvas draws a translucent blue
selection rectangle; on pointerup, every node whose centre
falls inside the rect joins the selection (or replaces it).
- Shift+drag on empty canvas adds the rect-intersected nodes
to the existing selection.
- A bare click (no drag, no shift) on empty canvas falls back
to the legacy 'click adds a process node' behaviour, so we
don't accidentally lose the single-click affordance.
- 'Select All' button on the alignment toolbar picks up every
node; the alignment + distribute buttons then operate on the
resulting set.
- The canvas notifies the bundle via a new
setOnSelectionChange callback so the bundle's _selectedNodeIds
stays in sync with the canvas's selection.
Architecture changes:
- src/flowchart/flowchart-canvas.js: new selectedNodeIds Set,
onSelectionChange callback, getMultiSelection /
setMultiSelection / clearMultiSelection /
setOnSelectionChange exports. Drag-rect state machine lives
alongside panState / dragState. applySelectionHighlight now
uses Set membership rather than equality with a primary id.
- src/renderer/flowchart-bundle.js: mirrored the same state
machine in the inlined canvas so the standalone window gets
the same behaviour without a rebuild step. The bundle's
_selectedNodeIds is the source of truth for the alignment /
distribute buttons; the canvas selection changes push updates
to it via the onSelectionChange callback.
Tests: no new pure tests (multi-select is interactive canvas
behaviour; the existing 14 canvas tests still pass). 1376 total.
Amit Haridas
Adds the visual feedback that was missing from the C10 resize + C2
selection commits. The canvas previously set the .selected class but
no CSS rule styled it, so a selected node looked identical to an
unselected one. Same for hover and the resize handle.
CSS additions (src/flowchart-generator.html):
- .flowchart-node: cursor:grab + 0.12s filter transition for the
hover tint (drop-shadow blue glow).
- .flowchart-node:hover .flowchart-node-shape: blue glow.
- .flowchart-node.selected .flowchart-node-shape: 2.25px blue
stroke — replaces the (previously invisible) .selected class.
- .flowchart-edge:hover and .flowchart-edge.selected: blue
stroke + 2.5px width for the same reason.
- .flowchart-resize-handle-hit: 24px transparent rect for an
enlarged hit area (the visible grip stays 10px).
- .flowchart-resize-handle-grip: blue square with stroke, styled
via CSS instead of inline fill/stroke (which were overriding
the rest of the file's theme).
- .flowchart-edge-label-bg: opaque white pill behind edge labels,
so labels stay readable when an edge curves under them.
- .fc-modal / .fc-modal-overlay: fade + scale-in keyframes
(0.15s ease-out) so the promptInline / confirmInline modals
animate in instead of popping in instantly.
Canvas (src/flowchart/flowchart-canvas.js): the resize handle now
emits two rects — a 24px transparent hit area (data-resize-node
attribute preserved) and a smaller 10px visible grip styled via
.flowchart-resize-handle-grip. Cursor stays nwse-resize via CSS.
Tests: no new tests (visual); existing 14 canvas tests still pass.
1376 total.
Amit Haridas
8 new toolbar buttons (Align L/R/T/B, Center H/V, Distribute H/V)
plus a Select All button. Each operation takes the current
selection and applies a pure-function transform to the (x, y)
positions, then commits each result via store.moveNode().
Architecture:
- src/flowchart/flowchart-align.js: pure module with 8 helpers
+ nodeWidth / nodeHeight utility. UMD wrapper for browser
global + CommonJS, same pattern as the other pure modules.
- src/renderer/flowchart-bundle.js: pure module inlined so the
standalone window doesn't need a separate bundle build; the
inlined functions are exposed as window.FlowchartAlign for
jsdom tests.
- Bundle's _selectedNodeIds Set tracks the alignment selection.
Empty by default; populated by Select All or by future
canvas-side shift+click (item 1: multi-select).
- Each align button no-ops with a status hint if fewer than 2
nodes are selected. Distribute needs 3+.
- Each moveNode call creates its own undo snapshot, which is
fine for typical 2-10 node selections and lets the user undo
a misalignment one node at a time.
Tests: 14 new (1376 total) covering all 8 functions plus
immutability, no-op for insufficient nodes, and unsorted-input
sorting for distribute.
Amit Haridas
The macOS pandoc SHA-256 cannot be computed from this branch (no
macOS host). The verifyArtifact() function already handles the
unpinned case by printing a clear warning + the computed hash, so
the next macOS CI build will surface the hash in its log — the
maintainer copies it back into KNOWN_SHA256 to lock it in.
What changed: replace the original cryptic 'Fill these from a
trusted machine' comment with explicit instructions that match the
actual flow:
1. First macOS release build → script prints the hash.
2. Maintainer pastes it into KNOWN_SHA256 ('darwin:pandoc': '…').
3. Subsequent builds hard-fail on mismatch (tamper detection).
This is the recommended follow-up the security assessment flagged
as D6. Linux + Win + fonts are already hard-pinned.
Amit Haridas
electron-builder 26.x rejected the build with
'configuration.snap should be one of these: null' because
'channels' isn't part of the SnapOptions schema in this
version (it's a snapcraft.yaml / Snap Store publish-side
concept, not a build-time key).
Also relocated 'desktopName' out of linux.* — the warning
says to set it in package.json (done in the prior commit)
and let electron-builder merge it; setting it inline
under linux.* is rejected by the schema too.
What actually silences the warning now:
- package.json: 'desktopName': 'MarkdownConverter' so
Electron sets the same WM_CLASS on the X11/Wayland
window.
- electron-builder.config.js: 'syncDesktopName: true'
inside linux.* so the generated .desktop file's
StartupWMClass matches.
- electron-builder.config.js: 'snap: { confinement,
grade }' at the top level using the valid keys only.
Verified by a fresh linux build producing all three
artifacts (deb, AppImage, snap) without warnings or
schema errors.
Amit Haridas
New 'Export Visio' toolbar button sends the current graph to the
main process, which generates an OOXML .vsdx zip with JSZip and
writes it to a user-chosen path. The result is fully editable in
Microsoft Visio, draw.io, Lucidchart, and any other tool that
accepts the format — not an embedded image.
Architecture follows the existing pure-module pattern:
- src/flowchart/flowchart-vsdx-export.js: pure translator that
produces (a) page1.xml describing each node as a Visio Shape
with correct geometry (PinX/PinY/Width/Height in inches, with
the Y-axis flipped to match Visio's bottom-up coords), each
edge as a Connect entry, and (b) the seven static boilerplate
XML files that make a .vsdx zip valid ([Content_Types].xml,
_rels/.rels, visio/document.xml, visio/pages/pages.xml, etc.).
- src/main.js: new 'export-vsdx' IPC handler requires the pure
module + JSZip, builds the zip, writes to disk, returns
{ canceled, path } | { canceled: false, error }.
- src/preload.js: flowchart.exportVsdx(graph) bridge.
- src/renderer/flowchart-bundle.js: btnExportVsdx handler, status
feedback for cancel / save / error paths.
- src/flowchart-generator.html: button next to Export JPG.
Tests: 7 new (1362 total) cover graphBounds (empty / single /
multi-node), XML escaping of label text, Connect emission, the 5
node kinds mapping to 5 distinct master names, and that all
boilerplate files start with the XML declaration.
Amit Haridas
Three new toolbar buttons next to Open from File let the user save
the current canvas to disk in the format their tooling expects:
- Export SVG — straight clone + serialise of the canvas <svg>,
with a minimal stylesheet inlined so the file renders without
the editor's stylesheet. Goes through the existing 'save-text-file'
IPC channel as a UTF-8 string.
- Export PNG / JPG — rasterise the SVG through Image + Canvas at
1000x700 + 24px padding, then send the data URL to the new
'save-binary-file' IPC channel which strips the 'data:...;base64,'
prefix and writes the raw bytes. JPEG gets a white background fill
so transparent areas don't come out black.
New IPC + preload bridge:
- src/main.js: 'save-binary-file' handler mirrors 'save-text-file'
shape but writes a Buffer from base64. Returns { canceled, path } |
{ canceled: false, error } so the UI can surface write failures.
- src/preload.js: flowchart.saveBinary(data, defaultName, filters)
so the standalone window can call it.
Tests: 2 new (1355 total, was 1353) covering SVG export happy-path
and cancel. Raster PNG/JPG paths not jsdom-tested because
Image+Canvas in jsdom doesn't reliably fire onload for Blob URLs;
the SVG path exercises the same handler plumbing so coverage is
adequate.
Amit Haridas
electron-builder emits a pack-time warning on every Linux build that
desktopName is unset, which means the installed .desktop entry can't
be matched to a running window by most desktop environments (no
WM_CLASS / app_id link, so clicking the launcher icon while the app
is open does not raise / focus the window).
- package.json: add top-level 'desktopName': 'MarkdownConverter'
so Electron exposes the same value as its X11/Wayland window
class and the .desktop file matches it.
- electron-builder.config.js: mirror it under linux.* with
syncDesktopName: true so the generated .desktop / .deb / snap
entries all use the same identifier.
- Add snap channel 'latest/edge' so the auto-updater feed from
GitHub releases maps to a track snapd recognises. 'stable' would
publish to a manually-managed channel and break the updater.
No source code changes; build only. Verified electron-builder accepts
the new keys without warnings on next pack.
Amit Haridas
The 6 pre-existing flowchart-controller test failures
('ReferenceError: require is not defined') were caused by
flowchart-canvas.js falling through to require('./flowchart-viewport')
when window.FlowchartViewport was undefined — because flowchart-viewport
was added in C4a but the controller test's script loader was never
updated to include it, and viewport itself had no browser-global
fallback.
Two coordinated fixes:
- Add the same UMD wrapper to flowchart-viewport.js that the other
pure modules already use (CommonJS module.exports + window
global fallback for the standalone window and jsdom). Now the
canvas's 'window.FlowchartViewport || require(...)' short-circuits
in the browser case, matching what shapes / store / mermaid do.
- Load flowchart-viewport.js in the controller test mount helper,
in dependency order between mermaid and store. The canvas now
sees window.FlowchartViewport set and never falls through to
require() under jsdom.
Test suite: 104 suites / 1353 tests passing (was 1347 with 6 failing).
The 6 'require is not defined' failures are gone.
Amit Haridas
C9 (22bc4dc) introduced a malformed edit when wiring openFile. The
saveFile arrow got a stray 'open-text-file-dialog' string between the
channel and payload, so the ipcMain handler destructured the string
(both fields undefined) instead of { content, defaultName }. saveFile
silently saved with no content and no default name — the Save to File
button worked through the dialog fallback but wrote whatever was in
the buffer.
Fix: drop the stray arg, restore the single-line invoke with the
payload object. openFile stays on its own line below. format:check
is now clean.
Discovered during the v4.13.0 release rebuild when prettier wanted
to collapse the malformed multi-line into one — the malformed shape
itself was the bug, not the formatting.
Amit Haridas
Cmd+C / Cmd+V / Cmd+D keyboard shortcuts now work in the standalone
window. The clipboard pure module (committed in C6a) is inlined
into the bundle alongside the mermaid parser (C9 inlining) so the
standalone window does not depend on a separate bundle rebuild.
- copySelection / pasteSelection definitions added inside the
bundle IIFE, exposed as window.FlowchartClipboard for jsdom
tests.
- Module-level _clipboard variable scoped to the keydown handler
block so copy survives across Cmd+V and Cmd+D presses.
- Cmd+C: serialises the canvas-owned selection (now read via
getSelection(), not the old DOM .selected query) and stashes it.
- Cmd+V: applies the stashed payload to the store via addNode +
connect; nothing to do for the edge-only payload.
- Cmd+D: copy + paste in one keystroke, with a 24px offset.
- Delete/Backspace: reuses getSelection() to delete the selected
node or edge; replaces the fragile DOM-querying path that the
test for the bundle was no longer catching.
The pure module in src/flowchart/flowchart-clipboard.js stays the
source of truth for the algorithm (9 tests in C6a). Bundle inline
keeps the standalone window functional without a rebuild step;
a future build-time inclusion would collapse the duplication.
Amit Haridas
v4.13.0 — nodes can be resized by dragging the bottom-right handle.
- New setNodeWidth(id, width) on the store. Clamps to [60, 600] so
shapes stay readable. Throws on unknown id; pushes an undo
snapshot like every other mutator.
- New nodeWidth(node) helper on the canvas uses node.width with a
fallback to DEFAULT_WIDTH for graphs loaded from older JSON
that didn't have the field.
- Canvas render uses the per-node width for shapeSvg + label
positioning, and edge boundaryPoint() uses it too so connection
geometry adapts to the new shape size.
- Selected node renders a bottom-right resize handle (10px square).
Clicking the handle starts a 'resize' dragState that calls
setNodeWidth on every pointermove with the screen-to-SVG delta
applied to the start width.
- The handle is detected before the generic node click handler so
it doesn't accidentally start a move drag.
3 new tests cover setNodeWidth update, clamp to [60, 600], and
unknown-id throw. The canvas change reuses the existing 14 tests
(which check rendering and pointer events) — they still pass.
Amit Haridas
v4.13.0 — round-trips Mermaid source through the editor:
- new open-text-file-dialog invoke IPC in main.js opens a system
Open dialog filtered for .mmd / .mermaid / .md / .markdown / .txt,
reads the chosen file, returns { path, content } or null on cancel.
- new openFile() on the flowchart preload bridge.
- new Open from File button in the standalone window toolbar
alongside Insert / Save / Reset.
- On click: confirms overwrite when the current graph is non-empty,
strips any mermaid fence, parses via the inlined fromMermaid()
(the inverse of toMermaid), and calls _store.deserialize so the
canvas redraws. Status bar reports Loaded path.
The fromMermaid() in the bundle is duplicated from
src/flowchart/flowchart-mermaid-parse.js (23 tests in C8 land the
same logic) so the standalone window does not depend on the bundle
rebuild step. Pure module + bundle inline stay in sync logically;
future work could collapse them via a build-time inclusion.
Amit Haridas
Inverse of flowchart-mermaid.js's toMermaid(). Pure module — no DOM,
no globals — so the parsing logic is unit-tested in isolation.
Recognises the 5 node shapes:
- process [label]
- decision {label}
- terminator ([label])
- subroutine [[label]]
- document [/label/]
Recognises the 3 edge arrows:
- --> solid
- -.-> dotted
- ==> thick
Edge labels may appear before OR after the arrow (Mermaid accepts
both: and ).
Behaviour:
- Auto-creates nodes referenced in edges but not declared explicitly.
- Idempotent: declaring then connecting does not
duplicate the A node.
- Defensive: malformed lines are skipped silently so partial /
hand-edited source still loads whatever it can.
- Unescapes the standard Mermaid escapes (#quot; → ", \n → newline).
Wire-up into the renderer (Open from .mmd file → parse →
store.deserialize) is the next commit; this lands the testable math.
23 new tests cover shape parsing for all 5 kinds, edge parsing for
all 3 arrow types + label placement, unescaping, header skipping,
comment skipping, the subroutine-vs-process regression, auto-node
creation, and the null/non-string inputs.
Amit Haridas
New clipboard serialiser for the flowchart editor. Pure module —
no DOM, no globals — so it's unit-testable in isolation.
- Captures the selected node + its connected edges to a JSON envelope
that survives a paste-into-new-graph round trip. Edge-only selection
is also supported.
- Paste creates a new node offset from the original (default 24px)
and re-attaches every connected edge to the new id. Only edges
whose other endpoint still exists in the graph are recreated, so
deleting a neighbour before pasting doesn't try to reconnect to a
ghost.
- Preserves node color on paste.
Wire-up into the renderer controller (Cmd+C / Cmd+V / Cmd+D handlers
+ canvas-owned selection reading) is the next commit; this one just
lands the testable math.
9 new tests cover selection capture, edge-only selection, paste with
offset, edge re-attachment, dangling-edge handling, color preservation,
and the null-payload no-op.
Amit Haridas
- All canvas content (nodes, edges, preview line) now lives inside a
single <g class='flowchart-content' transform='...'> so a single
attribute change moves/zooms everything together. The fixed viewBox
(1000x700) stays — the transform handles everything.
- Ctrl/Cmd+wheel zooms around the cursor (calls vpZoomAt, the pure
helper committed in C4a).
- Shift-drag (or middle-click) on empty canvas pans the view.
- Snap-to-grid (10-unit) is opt-in via api.setSnapEnabled(true).
When on, moveNode and addNode clamp coords to the grid.
- Plain click on empty canvas still hands off to opts.onEmptyClick
so the controller can prompt for shape kind + label.
destroy() now also removes the wheel listener.
Amit Haridas
New pure functions for the canvas viewport (v4.13.0):
- zoomAt(view, screenX, screenY, factor) — zoom centred on a screen
point, keeping the world point under cursor fixed. Clamped to
[0.25, 4] scale range.
- panBy(view, dx, dy) — additive pan
- reset() — identity transform
- wheelFactor(deltaY) — multiplicative factor per Ctrl+wheel notch
- snap(value, gridSize) — round to nearest grid (0 disables)
The actual SVG transform wrapper + Ctrl+wheel handler + drag-to-pan
+ snap-on-move is the next canvas-layer commit; this commit lands
the testable math.
14 new tests cover: identity reset, cursor-stable zoom, MIN/MAX
clamping, additive pan, wheel factor reciprocity, snap-to-grid
(positive/negative/zero grid sizes).
Amit Haridas
The flowchart editor had Ctrl+Z / Ctrl+Shift+Z keyboard shortcuts
but no discoverable buttons. New History section in the side panel:
- Undo / Redo buttons wired to _store.undo() / _store.redo()
- Counter chip showing ↶/↷ availability
- Buttons auto-disable when stacks are empty (via _store.canUndo()
+ canRedo() evaluated on every store subscription)
The store already exposed undo/redo/canUndo/canRedo — this commit
only adds the UI surface.
Amit Haridas
Three UX/DI wins in one canvas pass:
1. Edge endpoints now land on the source/target shape's BOUNDARY
rather than the centres. Lines no longer cut through nodes. New
pure helpers boundaryPoint() + edgeEndpoints() do the geometry;
nodeCenter() is still used internally for direction vectors.
2. Connect-mode preview line. Alt+drag now draws a dashed line from
the source node's boundary to the live pointer position so the
user can see where the edge will land before releasing. The original
code marked this 'visual feedback deferred to v2'.
3. Selection state is now owned by the canvas (selectedNodeId /
selectedEdgeId variables), not read from .selected DOM classes.
The controller can ask via api.getSelection(). Fixes the fragile
pattern in flowchart-controller.js where Delete/Backspace read
querySelector('.flowchart-node.selected') — that read is now
redundant and the controller can be cleaned up to use getSelection()
directly.
5 new tests cover: edge starts past source centre, edge ends before
target centre, vertical edges connect top/bottom, label background
auto-sizes for long labels, canvas getSelection() reflects clicks.
Amit Haridas
The standalone flowchart generator still used window.prompt() for
editing edge kind/label and changing node kind — defeating the v4.9.9
modal work that's already in flowchart-bundle.js.
This commit switches to the existing window.FlowchartModals helpers:
- onEdgeClick now does two sequential promptInline calls (kind,
then label) instead of two blocking window.prompt calls.
- onShapeMenu uses promptInline for node-kind change.
- onEmptyClick (new) replaces the canvas's old 'auto-add a process
node labelled Node on any empty click' behaviour. The canvas
now hands off x/y to the controller, which prompts for kind +
label before adding.
Canvas change is one line: drop the auto-add and call opts.onEmptyClick.
followups in this batch:
- select/dropdown fields in the modal (currently text-only inputs)
- edge connection geometry (centre-to-centre → boundary)
- connect-mode preview line
- canvas-owned selection state (DOM .selected is fragile)
- undo/redo toolbar + counter
- edge label auto-size + theme-safe background
- zoom/pan, snap-to-grid, copy/paste, mermaid re-import, file IO
Amit Haridas
v4.13.0 ships:
- Quick Switcher overlay (Cmd+P): fuzzy match across recent files
+ open tabs, workspace search wired to the active tab's parent dir
- Inline AI assist (Cmd+K): Rewrite / Shorten / Expand with real
SSE streaming, in-editor typewriter, cancel mid-flight, first-use
confirmation
- electron-updater integration: Help > Check for Updates now polls
GitHub releases through the auto-updater; autoDownload + autoInstall
enabled; browser fallback on dev/error
- 13 commits since v4.12.0, 112 new tests (1187 → 1299), lint+format
green throughout.
Amit Haridas
Inline AI first-use confirmation (v4.13.0):
- inline-ai-controller accepts optional confirmFirstUse + getProviderLabel
deps. On the first action of the session, calls confirmFirstUse with
{providerLabel, selectionLength, action}; if it returns false, aborts
and shows error state. Once confirmed, never re-prompts for the
session.
- new 'ai-assistant:confirm-info' invoke handler returns
{provider, model} (no key material) so the renderer can label the
confirm dialog.
- electronAPI.aiAssistant.confirmInfo() exposed via preload.
- renderer.js wires window.confirm() with the provider label as the
v1 prompt UX; can be upgraded to a custom modal later without
touching the controller.
Check-for-Updates menu (v4.13.0):
- Help > Check for Updates now calls ensureAutoUpdater().check()
instead of opening the Releases page in the browser.
- Falls back to the browser page if the check throws (dev runs with
no app-update.yml, network failures, etc.) so the user always has
a way to update manually.
4 new controller tests cover: confirm called once with provider label
and selection length; declining aborts + surfaces error state; confirm
not asked twice in a session; missing confirmFirstUse dep skips the
prompt and streams normally.
Amit Haridas
electron-updater integration so users get fixes automatically:
- new dependency electron-updater@^6.6.2 in package.json
- electron-builder.config.js publishes to GitHub releases
(owner: concreteinfo, repo: markdownconverter)
- new src/main/auto-updater.js wraps autoUpdater: wires the six
lifecycle events to a 'updates:status' IPC channel, exposes
check() and install() helpers, dev-gates via isDev(), and
swallows the 'missing app-update.yml' error that dev runs hit
- main.js: ensureAutoUpdater() forwards status to the renderer
via mainWindow.webContents.send; ipcMain handlers for
'updates:check' (invoke) and 'updates:install' (invoke)
- preload.js: 'updates:check' / 'updates:install' added to
ALLOWED_SEND_CHANNELS; 'updates:status' added to receive
channels; electronAPI.updates.{check, install, onStatus}
exposed
7 new tests cover event registration, dev-gate behaviour,
'not-available' fallback for missing app-update.yml, typed event
payloads, error forwarding, and install() quitAndInstall.
Amit Haridas
The explorer panel's explorerCurrentDir is hardcoded to null, so
the Quick Switcher's workspace toggle was a no-op. Rather than fix
the broken explorer flow, derive the workspace from the active
tab's filePath's parent directory — the most reliable source we
have without restructuring that panel.
deriveWorkspaceDir() handles POSIX and Windows paths (including
mixed separators and trailing separators), returns null for
untitled / empty inputs so the workspace toggle silently stays
off in those cases.
10 new tests cover POSIX, Windows, mixed-separator, trailing
separator, UNC, and invalid input paths.
Amit Haridas
createInlineAiController() glues the popover, the streaming bridge,
and the CodeMirror editor together. Each Cmd+K opens a fresh
request; the controller:
- reads the active selection from the CodeMirror view
- builds the rewrite/shorten/expand prompt via inline-assist.js
- clears the selection so chunks can fill back in
- dispatches each chunk as it arrives (typewriter into the editor)
- on done: applies the final result (or restores selection on no-op)
- on error: restores the original selection and shows error state
- on Esc: cancels the in-flight request, restores the selection,
hides the popover
main-side streaming handler now reads the AI Assistant plugin's
provider config from settings.json under plugins.ai-assistant.* —
keys never cross the IPC boundary.
renderer.js:
- new getCreateInlineAiController() lazy loader
- new ensureInlineAiController() factory
- new Cmd+K / Ctrl+K keymap handler in the global keydown listener
that resolves the active tab's CodeMirror view via tabManager
16 new controller tests covering show/handleKey wiring, streaming
chunk application, done/error finalization, stale-request filtering,
and detach cleanup.
Amit Haridas
createInlineAiPopover() mounts a floating toolbar anchored to the
current text selection. Three actions (Rewrite / Shorten / Expand)
plus loading spinner with cancel, and an error state with retry and
dismiss. Click handlers dispatch via onAction/onCancel/onRetry.
Positioning flips below the line if there is no room above and
clamps horizontally so the popover stays inside the viewport.
16 new tests covering mount/visibility, idle actions dispatching the
selection text, state transitions (idle / loading / error), Retry
and Dismiss callbacks, and positioning edge cases.
Amit Haridas
AiProviders.completeStream() — async iterable over provider chunks.
- OpenAI-style (openai, ollama, lmstudio, *-compatible): parses
SSE data: {choices:[{delta:{content}}]} payloads.
- Anthropic: parses event: content_block_delta with delta.text.
- Honours caller AbortSignal + internal timeout via AbortController.
- shared parseSseStream() helper handles [DONE] sentinel, partial
lines across chunk boundaries, reader.releaseLock() in finally.
main.js IPC:
- 'ai-assist-stream:start' (send) registers a per-requestId entry
in a Map with an AbortController, iterates completeStream, and
forwards each chunk via 'ai-assist-stream:chunk' (send).
- 'ai-assist-stream:cancel' (send) aborts the in-flight request.
- Terminal 'done' or 'error' (with code+message) is sent after
each stream; renderer can correlate by requestId.
preload.js exposes aiAssist.{start, cancel, onChunk, onDone, onError}.
5 new tests covering OpenAI delta parsing, Anthropic content_block_delta
parsing, and three synchronous-error paths (no fetch, no messages,
missing key).
Amit Haridas
buildAssistPrompt(action, selection) returns the {system, messages}
payload for rewrite/shorten/expand. Enforces 8KB selection cap and
typed errors with codes (unknown_action, bad_selection,
empty_selection, selection_too_large) so the IPC layer can map them
to user-friendly messages.
applyAssistResult(original, newText) trims the response and rejects
empty/unchanged payloads so a no-op streaming chunk doesn't churn
the editor.
15 new tests cover action coverage, prompt content, all four error
codes, cap-edge (exactly 8KB OK, 8KB+1 rejected), and trim semantics.
Amit Haridas
End-to-end wiring of the Quick Switcher overlay:
- main.js File menu: new 'Quick Switcher...' item with Cmd+P
accelerator (moved from Print Preview; Print Preview stays in
File > Print submenu but loses its shortcut). Recent Files
submenu and its buildRecentFilesMenu() helper removed.
- new 'recent-files:get' invoke IPC handler returns the recent
files array on demand.
- preload.js: 'recent-files:get' + 'show-quick-switcher' added
to channel whitelists; quickSwitcher.getRecentFiles() exposed.
- renderer.js: lazy-mounts the overlay on first 'show-quick-switcher'
message, fetches recent files via IPC, and shows the overlay.
File-open goes through the existing 'open-file-path' channel.
Workspace dir is intentionally null for v4.13.0 — the workspace
search toggle is wired but a no-op until the explorer panel exposes
its current directory (next iteration).
Amit Haridas
createQuickSwitcherOverlay() mounts a centered modal with:
- input box with autocomplete=off
- workspace toggle (off by default — recent + open tabs only)
- keyboard nav: ArrowDown/Up move selection, Enter opens,
Escape and backdrop-click hide
- mouse hover also moves selection
- debounced refresh (default 80ms; 0 in tests)
- destroy() cleanly removes DOM + listeners
Rank/filter logic delegated to fuzzy-matcher.js. Recent and open tabs
are always in the candidate set; workspace files are added only when
the toggle is on. Selection clamps to bounds after every refresh.
23 new tests covering mount/focus/destroy, initial render, typing &
filtering, full keyboard nav, workspace toggle behavior, and the
selected-path escape hatch.
Amit Haridas
listWorkspaceFiles() walks a directory recursively, returning
[{path, name}] for files matching a configurable extension allowlist.
Skips hidden dirs, node_modules, dist/build/coverage, and walks past
permission-denied subdirs instead of throwing. Default allowlist is
markdown variants + .txt; default maxResults caps pathological
workspaces at 2000 entries.
Wired through 'quick-switcher:list-files' invoke channel + a
quickSwitcher.listFiles() convenience method on the preload bridge.
Main-process errors collapse to [] rather than crashing the overlay.
16 new tests covering flat dirs, recursion, skip lists, custom
extensions, maxResults cap, and validation errors.
Amit Haridas
Tiered scoring (exact 1.0 / stem 1.0 / prefix 0.8 / subseq 0.5+bonuses
capped 0.79) with leftmost+rightmost greedy alignment — rightmost wins
for extension/suffix queries ('md' against 'readme.md'), leftmost wins
for prefix queries ('re' against 'readme.md'). Boost-aware rankResults
applies recency x2 and open-tabs x1.5 multiplicatively; empty query
falls back to boosts alone.
Pure module, no DOM/IPC/Node — runs in renderer or main. 22 new tests.
Amit Haridas
User feedback on v4.11.0: the Add Connection form was buried below the
node/edge lists and they couldn't find it; they also asked for per-node
color and Save to File export. v4.12.0 reorganises the #fc-nodelist panel
to put the connect form right after the Add Node buttons, adds a native
<input type="color"> per node row that drives a new store.setNodeColor
mutator (with serialize/deserialize round-trip), and wires a Save to File
button that pops a system save dialog via a new 'save-text-file' IPC
channel.
- src/flowchart-generator.html — panel order is now Add Node /
Add Connection / Nodes / Edges / Export (Insert · Save · Reset).
Removed the legacy top toolbar; status moved into the panel.
Added .fc-help, per-node color-picker CSS, .fc-toolbar-row.
- src/flowchart/flowchart-shapes.js — shapeSvg() accepts an optional
6th 'color' arg; emits fill=... on rects and polygons. Falls back to
#ffffff for empty/null/undefined so old callers keep working.
- src/flowchart/flowchart-store.js — setNodeColor(id, color) mutator
with snapshot/emit, normalizeColor() helper (hex validation),
addNode accepts color, deserialize normalises missing color.
- src/renderer/flowchart-bundle.js — mirrors all of the above inline
(the bundle is loaded as a single <script>), wires fc-btn-save to
api.saveFile, passes node.color to shapeSvg in the canvas render,
and renders <input type='color'> per node row.
- src/main.js — new ipcMain.handle('save-text-file', ...) using a
system Save dialog (mirrors the ascii:save handler shape).
- src/preload.js — added 'save-text-file' to ALLOWED_SEND_CHANNELS
and a saveFile(content, defaultName) helper on the flowchart bridge.
Tests: 92/92 suites, 1165/1165 tests pass on this run
- +9 store tests (color defaults, setNodeColor, undo, hex validation,
round-trip, deserialize normalisation)
- +7 shapes tests (fill color on all 5 kinds, fallback to #ffffff)
- +6 controller tests (color picker wires setNodeColor, canvas rect
reflects color, Save to File calls api.saveFile with the fenced
Mermaid source and 'flowchart.mmd', cancel + error paths surface in
status)
Verification:
- npm run lint clean
- npm run format:check clean
- npm test 92/92 suites, 1165/1165 tests pass
Amit Haridas
- Remove untracked root-level flowchart-bundle.js (older v4.10.0 copy
that had drifted from src/renderer/flowchart-bundle.js; never loaded
by Electron, just repo noise).
- In src/renderer/flowchart-bundle.js, replace the runtime
concatenation "'place' + 'holder'" with the proper 'placeholder'
attribute at the two label-input call sites (node + edge) and
remove the now-stale comment explaining the hack. No functional
change.
- Bump version 4.11.0 -> 4.11.1 in package.json and README.md and add
the matching UPDATES.md entry.
Verification:
- npm run lint clean
- npm run format:check clean
- npm test 92/92 suites, 1143/1143 tests pass on this run
- PDFBatchOperations.test.js passes 21/21 in isolation
(the occasional failure seen in the v4.11.0 run was a parallel-test
file-IO race, not introduced by these changes)
Amit Haridas
Replaced the v4.10.0 floating selection toolbar (which relied on
SVG click hit-testing inside #canvas-host and was unreliable in the
user's Electron runtime) with a button-driven node-list panel
(#fc-nodelist) below the canvas. Every mutation now flows through
explicit controls: 5 Add Node buttons, per-node kind-select +
label-input + delete ×, per-edge kind-select + label-input + delete
×, and a Connect form (From/To selects + Edge button + Refresh).
The canvas is purely visual now: no more #fc-selection-toolbar, no
controller-level _selectedId/_selectedKind state, no keyboard
Delete/Backspace selection handler. Canvas click callbacks
(onNodeClick, onEdgeClick, onShapeMenu) are no-ops. The 5-shape /
3-edge-kind / label-input / delete-button surface is unchanged in
spirit — it just lives in the panel instead of floating over the
canvas.
Files:
- src/flowchart-generator.html: added #fc-nodelist between canvas and
preview (wrapped in new .fc-left column), removed fc-selection-toolbar,
bumped header hint.
- src/renderer/flowchart-bundle.js: removed SHAPE_BUTTONS/EDGE_BUTTONS,
setSelection, renderSelectionToolbar, appendLabelInput,
appendDeleteButton, the keyboard Delete handler, and the
controller-level selection state. Added rerenderNodeList +
shapeLabel/edgeLabel, wired all panel buttons (add-row, per-node
selects/inputs/deletes, per-edge selects/inputs/deletes, connect
form). Subscribe now calls rerenderNodeList instead of the old
renderSelectionToolbar.
- tests/flowchart-controller.test.js: replaced 6 v4.10.0 selection
toolbar tests with 11 v4.11.0 button-driven panel tests.
- package.json: 4.10.0 -> 4.11.0.
- README.md: version line bumped to v4.11.0.
- UPDATES.md: added v4.11.0 changelog entry.
Test summary: 92 suites, 1143 tests pass (was 1138 — +5 net).
Lint + prettier clean.
Amit Haridas
Even after the v4.9.9 inline-modal fix the user kept reporting 'no fix still'
because hidden right-click context menus and window.prompt calls remain
unreliable in Electron renderer contexts. This release ships a *visible*
floating selection toolbar inside the canvas panel so the primary
interactions are not hidden behind modals or context menus.
- New <div id="fc-selection-toolbar"> inside #canvas-host, hidden until a
node or edge is selected. Renders:
* 5 shape buttons (Process / Decision / Terminator / Subroutine /
Document) when a node is selected, calling store.setNodeKind on
click. The active shape is highlighted.
* 3 edge-kind buttons (Solid / Dotted / Thick) when an edge is
selected, calling store.setEdgeKind.
* An always-visible label input that mirrors the selected item's
label and writes back via store.setNodeLabel / store.setEdgeLabel
with a 100ms debounce.
* A red Delete button calling store.removeNode / store.disconnect.
- Selection state now tracked at the controller level (_selectedId +
_selectedKind) and exposed via window.FlowchartController so the
keyboard Delete/Backspace shortcut and the toolbar share one source
of truth.
- Console-log instrumentation on every canvas event (pointerdown with
altKey, pointerup with drag result, dblclick, contextmenu, selection
changes, and every bootstrap phase) so the user can open DevTools
(Ctrl+Shift+I) and verify Alt+drag and double-click actually fire.
- promptInline / confirmInline kept as advanced fallback for the
right-click 'change shape' path; the toolbar is now the primary
interaction surface.
- Preview-render pane now shows a static info card explaining that the
canvas on the left is the rendered chart (previously blank).
- 6 new tests in tests/flowchart-controller.test.js — toolbar hidden
by default, selecting a node populates shape buttons + label input +
Delete, clicking a shape button updates the kind, label input is
debounced, edge selection populates edge-kind buttons, Delete button
removes the selected node.
Total tests: 1127 passing (was 1121; +6 new). The 11 pre-existing
PDFOperations failures are unrelated to this change.
Electron renderer contexts return undefined from window.prompt/window.confirm,
so shape change, edge kind, edge label, and reset confirmation did nothing.
Bundle now ships promptInline/confirmInline (custom DOM overlay modals) and
uses them in onEdgeClick / onShapeMenu / reset handler. Exposed as
window.FlowchartModals for jsdom tests.
Amit Haridas
Each pure module's UMD wrapper assigned window.FlowchartXxx only in the else branch — when 'module' was undefined. But the renderer runs with nodeIntegration:true, so 'module' is always truthy and the else branch never ran. The standalone Flowchart Generator window then aborted with 'Flowchart pure modules not loaded'.
Fix: append 'if (typeof window !== undefined) window.FlowchartXxx = exported;' after the CommonJS branch in all 4 pure modules (store, shapes, mermaid, canvas). Both branches can run now; the legacy sidebar panel still loads them via require() and the renderer unconditionally exposes the global.
Regression guard: 4 new source-grep tests in tests/flowchart-controller.test.js assert each module's source contains the 'window.FlowchartXxx = exported' assignment.
Attribution: Amit Haridas
Five fix rounds (v4.9.1 → v4.9.5) couldn't make the sidebar flowchart
panel feel right — at 280 px sidebar with canvas + preview cramped to
~175 px each, plus the editor-container hide/show dance the
maximize/restore toggle required, the panel kept presenting as
cramped and unreliable at runtime. Strategy pivot: the flowchart
editor now lives in its own BrowserWindow, matching the ASCII Art
Generator pattern.
- src/flowchart-generator.html — new standalone HTML with header,
toolbar (Insert at Cursor / Reset), canvas host, and preview host.
Stylesheet hrefs are src/-relative (no ../). Forced light surface
via background/color !important rules — mirrors the v4.9.5 CSS fix.
- src/renderer/flowchart-controller.js — pure browser IIFE. Hydrates
from <userData>/flowchart-session.json once on mount, persists on
every mutation with 500 ms debounce. Insert at Cursor wraps the
generated Mermaid source in a fenced ```mermaid block and sends
it through the existing insert-content IPC.
- src/main.js — openFlowchartGenerator() launches the standalone
BrowserWindow (1100×720, contextIsolation:true, nodeIntegration:false).
Tools menu entry 'Flowchart Generator' with Cmd/Ctrl+Alt+F.
- src/preload.js — new window.electronAPI.flowchart.* namespace with
getUserDataPath/readFile/writeFile/insertAtCursor, reusing the
existing thin text-file IPC handlers.
- The four pure modules (flowchart-shapes.js, flowchart-mermaid.js,
flowchart-store.js, flowchart-canvas.js) gained a tiny UMD wrapper
so they work both as CommonJS (legacy sidebar) and as browser
globals (standalone window). No behavioural change to the 73
flowchart unit tests in tests/flowchart-*.test.js.
- src/renderer.js — sidebar registerPanel('flowchart', ...) and the
commandPalette entry are now commented out. Legacy panel file
(src/sidebar/flowchart-panel.js) preserved for rollback.
Tests:
- tests/flowchart-controller.test.js — 10 new tests covering
stylesheet paths (no ../ escape), bootstrap wiring
(getUserDataPath once, reads flowchart-session.json), hydration
from a saved session, Insert-at-Cursor fenced block format,
Reset (with and without confirm), and two regression tests that
src/renderer.js no longer has a live sidebar registration.
All 1122 tests pass; lint + format clean.
Amit Haridas
v4.9.4 shipped three interaction bugs in the Flow Chart panel that combined to make it look broken at runtime: (1) .flowchart-preview-render had no min-height, so the Mermaid SVG clipped to 0 when the sidebar flex column shrank; (2) the canvas + preview hosts inherited the body.theme-concreteinfo dark theme, producing dark-on-dark nodes; (3) .flowchart-node.selected only set a 2px stroke on the rect's existing dark fill, which was effectively invisible.
Fix: styles-sidebar.css adds min-height: 120px on .flowchart-preview-render; forces a light background on .flowchart-canvas-host / .flowchart-preview-host with !important so theme inheritance cannot override it; forces explicit white fills and dark strokes on .flowchart-node rect/polygon/text and .flowchart-edge so labels read against any background; selection now changes fill (#e3f0ff) AND bumps stroke-width to 3 on both nodes and edges. renderer.js renderFlowChartMermaid now always initializes Mermaid with theme: 'default' regardless of body class — keeps the Mermaid SVG light to match the CSS-forced canvas surface.
Three new CSS regression tests in tests/flowchart-panel.test.js read the shipped stylesheet and assert the three structural invariants (non-zero min-height on render target, !important light background on canvas+preview hosts, fill + stroke-width >= 3 on .flowchart-node.selected).
Tradeoff: the flowchart surface is now always light, diverging from body theme. Visibility of a working editor is the priority per user direction.
Amit Haridas
- Add opts.onNodeClick + surgical applySelectionHighlight() so node/edge clicks immediately paint .selected and mirror id into panel state (Delete/Backspace works on freshly-clicked node).
- Add Maximize/Restore button to flowchart toolbar: toggles .main-content.flowchart-takeover which hides .editor-container and lets the canvas + preview split the full window width instead of the 280px sidebar.
- destroy() clears the takeover class so the editor stays usable after leaving the panel.
- 8 new tests in tests/flowchart-panel.test.js for selection wiring and takeover.
Amit Haridas
The flowchart preview pane accumulated raw Mermaid source when the user
fired several addNode mutations within the 250ms debounce window —
mermaid.run({nodes:[div]}) is async, so the previous render's
<div class="mermaid"> (still carrying the source text) sat in
.flowchart-preview-render when the next render cleared the target. The
first render's eventual element.innerHTML=svg landed on a detached node,
but the visible preview pane had a stack of stale <div class="mermaid">
elements carrying the source.
Fixed by switching renderFlowChartMermaid (src/renderer.js) to
replaceChildren() and adding a per-target WeakSet in-flight tracker so
the new render always starts from a clean slate and the previous render's
eventual innerHTML=svg is harmless on a detached node.
Added a second regression test that fires 7 mutations inside the debounce
with a renderMermaid mock that mimics mermaid.run's async innerHTML=svg
closure.
Amit Haridas
- src/ascii-generator.html line 7: ../fonts.css -> fonts.css (src/-relative;
fixes silent stylesheet miss — same class as the v4.9.0->v4.9.1 script-tag fix)
- src/renderer/ascii-controller.js: wire all 3 mode tabs, 18 template
buttons, and the 3 box form fields. setMode() toggles .active on tabs
and matching .mode-section. Templates route through the orchestrator's
template:<id> font namespace. Box mode renders text with single/double/
rounded/bold/ascii borders via a pure renderBox() helper. All 11 T9
behaviours preserved.
- tests/ascii-controller.test.js: 6 tests covering stylesheet path,
pure box renderer, mode-tab switching, and template button wiring.
- package.json 4.9.1 -> 4.9.2; README + UPDATES updated.
Amit Haridas