Commit Graph
114 Commits
Author SHA1 Message Date
Amit Haridas ded17d0b6b feat(flowchart): node resizing (C10)
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
2026-09-30 21:42:35 +05:30
Amit Haridas 4123455863 feat(flowchart): Mermaid source → graph parser (C8)
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
2026-09-30 21:34:53 +05:30
Amit Haridas 7beb3effcf feat(flowchart): clipboard copy/paste/duplicate pure module (C6a)
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
2026-09-30 21:32:03 +05:30
Amit Haridas 98979a2cd2 feat(flowchart): pure viewport math for zoom/pan/snap (C4a)
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
2026-09-30 21:28:36 +05:30
Amit Haridas 0bb1091c47 feat(flowchart): edge geometry at shape boundary + connect preview (C2+C3)
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
2026-09-30 21:23:39 +05:30
Amit Haridas e826c0e624 feat(ai-assist): first-use confirmation + wire Check-for-Updates to updater
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
2026-09-30 21:02:28 +05:30
Amit Haridas 47315add80 feat(updater): electron-updater wiring + IPC bridge
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
2026-09-30 20:30:19 +05:30
Amit Haridas 0d3df32140 feat(quick-switcher): derive workspace dir from active tab
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
2026-09-30 20:28:22 +05:30
Amit Haridas b36d0919a3 feat(ai-assist): renderer controller + Cmd+K wiring (end-to-end)
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
2026-09-30 20:17:57 +05:30
Amit Haridas 72ca2cb46f feat(ai-assist): floating popover UI + state machine
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
2026-09-30 20:14:13 +05:30
Amit Haridas 1a25669967 feat(ai-assist): SSE streaming + IPC handler
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
2026-09-30 20:04:32 +05:30
Amit Haridas cb0b2eac3d feat(ai-assist): pure prompt builder + result applier
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
2026-09-30 20:01:54 +05:30
Amit Haridas 3bd958c064 feat(quick-switcher): modal overlay UI + keyboard nav
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
2026-09-30 19:58:18 +05:30
Amit Haridas 33fa14c11a feat(quick-switcher): IPC handler for workspace file listing
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
2026-09-30 19:56:50 +05:30
Amit Haridas de7ebecad4 feat(quick-switcher): pure fuzzy matcher + ranker
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
2026-09-30 19:53:44 +05:30
amitwh 23046967c3 feat(flowchart): discoverable connect form + per-node color + save-to-file export; v4.12.0
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
2026-09-15 09:23:58 +05:30
amitwh 075e407f3f feat(flowchart): button-based UI replaces click-canvas interaction; v4.11.0
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
2026-09-15 01:33:17 +05:30
amitwh b81119026e feat(flowchart): visible selection toolbar + console-log diagnostics; v4.10.0
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.
2026-09-15 00:40:01 +05:30
amitwh 81e8561403 fix(flowchart): replace window.prompt/confirm with inline modal in standalone window; v4.9.9
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
2026-09-15 00:27:24 +05:30
amitwh 86d4c0d23a fix(flowchart): expose pure modules as window globals for standalone window; v4.9.7
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
2026-09-14 23:59:50 +05:30
amitwh 5f846d1e5f refactor(flowchart): standalone window (Cmd+Alt+F) replaces sidebar panel; v4.9.6
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
2026-09-14 23:50:25 +05:30
amitwh 9557c0af4d fix(flowchart): ensure rendered SVG + selection visibility; force light theme in canvas; v4.9.5
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
2026-09-14 23:09:23 +05:30
amitwh d31ed86fab fix(flowchart): wire selection + layout precedence; v4.9.4
- 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
2026-09-14 23:00:47 +05:30
amitwh e0945b427d fix(flowchart): prevent preview-source duplication; v4.9.3
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
2026-09-14 22:52:13 +05:30
amitwh ac31162d3b fix(ascii-art): wire Box/Templates mode + correct standalone <link href>; v4.9.2
- 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
2026-09-14 22:37:02 +05:30
amitwh 8c708b7abb fix(flowchart): pre-await getUserDataPath to fix persistence; bump v4.9.0 → v4.9.1
Amit Haridas
2026-09-14 22:24:27 +05:30
amitwh 59ee9c609e feat(sidebar): flowchart-panel — canvas + preview + persistence + insert
Amit Haridas
2026-09-14 21:45:47 +05:30
amitwh 0331a08207 feat(flowchart): SVG canvas with drag, double-click label edit, context menu hook
- Adds flowchart-canvas.js with createCanvas(container, store, opts) -> { destroy, getSvg }
- Consumes T1 store (subscribe/moveNode/connect/addNode) and T2 shapeSvg for 5 node kinds
- All 3 edge kinds render: solid (default), dotted (stroke-dasharray 4,4), thick (stroke-width 3)
- Pointer events: drag moves nodes; Alt+drag creates solid edges between nodes
- onEdgeClick fires on edge click; onShapeMenu fires on contextmenu
- Double-click opens inline <input> overlay for label editing
- destroy() unsubscribes from store and removes all listeners/SVG

Tests: 9 new (rendering 5, pointer events 2, subscribe 1, destroy 1) @jest-environment jsdom
Full suite: 89 suites / 1083 tests pass; lint clean; prettier clean.

Amit Haridas
2026-09-14 21:41:58 +05:30
amitwh 216e0ecf51 feat(flowchart): mermaid translator (flowchart TD, all 5 shapes, 3 edge kinds)
Pure Mermaid translator: graph (nodes + edges) -> flowchart TD source.
5 node kinds (process/decision/terminator/subroutine/document) ->
Mermaid syntax ([..], {..}, ([..]), [[..]], [/../]).
3 edge kinds (solid/dotted/thick) -> Mermaid arrows (-->, -.->, ==>).
Label escaping: double quotes become #quot;, newlines become literal \n.

Also moves tests/fixtures per-test dirs to fixtures-epub / fixtures-css
so the shared tests/fixtures/ stays available for snapshot fixtures.

Amit Haridas
2026-09-14 21:36:02 +05:30
amitwh 4a36b7db76 feat(flowchart): 5 SVG shape templates (process/decision/terminator/subroutine/document)
Amit Haridas
2026-09-14 21:25:38 +05:30
amitwh 7869be0ff9 feat(flowchart): pure graph store with undo/redo + injectable IO
Amit Haridas
2026-09-14 21:23:03 +05:30
amitwh ab50183dc0 refactor(ascii-art): delete in-app modal #ascii-art-dialog, controller, dead preload channels
Amit Haridas
2026-09-14 21:06:28 +05:30
amitwh 1d68fc132f test(preload): ascii channel allow-list assertions + extend existing security test
Amit Haridas
2026-09-14 20:55:58 +05:30
amitwh 9751bbc6df feat(ascii-art): pure AsciiArt module with generate/listFonts/getFontMeta
Amit Haridas
2026-09-14 20:46:49 +05:30
amitwh 57fee91e32 feat(ascii-art): pure AsciiArt module with generate/listFonts/getFontMeta
Pure CommonJS orchestrator that unifies hand-coded fonts, figlet adapter,
and templates behind a single public API. Wired to main.js via ipcMain.handle.

Public API:
  - generate({ text, font, options }) - resolves 'template:<name>' /
    'figlet:<font>' / bare id, falls back to standard for unknown fonts.
  - listFonts() - hand-coded first (17), then figlet, then templates (19).
  - getFontMeta(id) - null for unknown id, full meta for hand-coded,
    { kind } for figlet/templates.

Amit Haridas
2026-09-14 20:45:05 +05:30
amitwh fba848ef16 feat(ascii-art): figlet adapter with lazy load, font cache, structured error 2026-09-14 20:40:55 +05:30
amitwh bb99a5c5ee feat(ascii-art): 19 named ASCII art templates with snapshot tests
Per Task 3 of the 2026-09-14 ASCII art upgrade plan.

- New module: src/main/AsciiArt.templates.js exporting ASCII_TEMPLATES
  (19 entries) and getTemplate(name) accessor (returns '' for unknown).
- New test: tests/main/ascii-art.templates.test.js with 19 per-template
  snapshots + unknown-name + keys-match assertions (21 tests total).
- 17 templates verbatim from src/ascii-generator.html:596-626
  (arrow-right, arrow-down, decision, process, flowchart, sequence,
  network, hierarchy, header, note, warning, info, divider, separator,
  banner, checklist + the brief's own TEMPLATE_NAMES order).
- 2 templates verbatim from src/renderer.js:6542-6697
  (progress-bar, table-simple).
- 1 newly authored: arrow-up (completes the arrow triplet; does not
  exist in either source).

Amit Haridas
2026-09-14 20:35:39 +05:30
amitwh f71aa2b097 feat(ascii-art): 17 hand-coded font tables (5 existing + 12 new) with snapshot tests
Widen height bound to 3-12 so the spec-named figlet fonts fit:
- Isometric1, Isometric2, Isometric3, Isometric4 (h=11)
- Calvin S (h=3)

Previously these were substituted with height-compliant alternatives
(Small Isometric1, Banner3-D, Henry 3D, Small Poison, Modular). Restoring
the spec-named fonts preserves the product intent: 4 distinct isometric
projections and the calvin-and-hobbes-style Calvin S font.

Amit Haridas
2026-09-14 20:30:04 +05:30
amitwh 5385e159ed feat(ascii-art): 17 hand-coded font tables (5 existing + 12 new) with snapshot tests
Amit Haridas
2026-09-14 20:27:03 +05:30
amitwh 0bc50c7d54 feat(renderer): toggle <link disabled> on theme-changed instead of page reload
Replace the page-reload IPC handling in the theme-changed listener with an
in-place toggle of the preloaded <link id="theme-*"> tags. The helper scans
for a matching link first and only mutates disabled states when one exists,
so an unknown id (or main-process miss) short-circuits and preserves the
currently active theme. body.className is always set to the requested id so
legacy selectors keep matching.

Add tests/theme-renderer-apply.test.js with three cases covering: switching
to a fresh theme, idempotent re-switch, and the unknown-id short-circuit.
The test's local helper mirrors the renderer implementation so future drift
is caught at test time.

Amit Haridas
2026-09-14 20:11:44 +05:30
amitwh a2b4a856f3 feat(theme-registry): buildThemeMenu converts registry → Electron MenuItem[]
Adds src/main/themeMenuBuilder.js — a pure module that consumes
ThemeRegistry.list() + ThemeRegistry.categories() and the injected
setTheme/getCurrentThemeId callbacks to produce the View → Theme
submenu's MenuItemTemplate[] in the same shape as the previous
hardcoded block in src/main.js:1137-1245.

- Radio-style items with checked=true on the active theme id
- Grouped by category in registry order with separators between
  non-empty categories
- No Electron / electron-store imports — keeps the module pure and
  unit-testable under @jest-environment node
- Tests use jest.resetModules() + per-test require to avoid the
  module-cache leakage the bootstrap test surfaced in T2

Amit Haridas
2026-09-14 19:46:15 +05:30
amitwh eaad62d02e feat(theme-registry): bootstrap with 25 existing + 12 new themes
Registers all 37 editor themes at startup via ThemeRegistry.bootstrap.js.
Side-effect module: requiring it populates the registry from a static
THEMES array (25 existing menu themes refactored into the registry +
12 new: Catppuccin x4, one-light, tokyo-night-storm, synthwave-84,
outrun, winter-is-coming x2, solarized-dark-hc, spring-light).

Categories split: 13 light / 22 dark / 1 high-contrast / 1 seasonal
(spring-light per spec).

Snapshot test asserts exact id order, shape validity, and category
counts. Tests use jest.resetModules() + per-test requires so the
bootstrap module re-evaluates its registration loop each run.

Amit Haridas
2026-09-14 19:43:48 +05:30
Amit Haridas 1e7e332c24 feat(theme-registry): pure ThemeRegistry module with full API
- 8 exports: register/unregister/list/get/categories/lightThemes/darkThemes/clear
- kebab-case id validator; category whitelist (light/dark/high-contrast/seasonal)
- duplicate-id and shape errors with descriptive messages
- pure CommonJS, no IO, no Electron deps — T2 bootstrap will register all 37 themes
- 10 Jest tests covering register/unregister/get/categories/light+dark filters
- full suite: 77 suites, 924 tests passing; lint + Prettier clean

Amit Haridas
2026-09-14 19:39:52 +05:30
amitwh b1b72f9a60 chore(release): bump v4.7.1 → v4.8.0; Prettier pass on overnight-session files
12 commits on master since v4.7.1 baseline, taking tests 524 → 914 across
27 new suites. This commit:
- Bumps package.json + README version to v4.8.0 (additive features → semver minor)
- Applies Prettier formatting to all files touched during the overnight
  session so release build is reproducible from a clean repo

New in v4.8.0:
- PDF encryption close-out (D1)
- KaTeX rendering test coverage
- Autosave buffer + crash recovery banner
- Daily notes + workspace search + doc-aware Q&A
- Search sidebar panel + Ask mode
- Status bar: word count, reading time, Flesch-Kincaid grade
- Footnote hover preview
- Smart paste (URL → link, CSV/TSV → table)
- Daily notes menu item + sidebar panel + Q&A deep-link
- Daily templates gallery
- Pluggable DocQA engine (TF-idF default + lazy neural embeddings)

Amit Haridas
2026-09-14 14:56:05 +05:30
amitwh c3fe72bcae feat(templates,qa): template gallery + pluggable DocQA engine
Two more features from the deferred menu:

Daily-note template gallery:
- src/main/DailyNotesTemplates.js — pure module: listTemplates() /
  saveTemplate() / deleteTemplate() / labelFor() with injectable IO.
- src/main/DailyNotes.js — openOrCreate() now accepts seedContent so a
  non-default template can seed a NEW note (existing notes never get
  clobbered).
- src/main.js — IPC channels daily-templates:list / save / delete /
  apply. apply renders the chosen template (with {date}/{weekday}
  substitution) and pipes through DailyNotes.openOrCreate.
- src/sidebar/daily-templates-panel.js — gallery UI: list, +New
  (prompt for name + content), Use (applies to today's note),
  delete (refuses to remove the last template so the default survives).
- src/renderer.js — registers the panel.
- src/index.html — icon (already added).

Pluggable DocQA engine (semantic search hook):
- src/main/SemanticEngine.js — engine interface with defaultEngine() (TF-idF,
  always available) and neuralEngine() (lazy @xenova/transformers,
  falls back gracefully when the dep is missing). getEngine(name)
  resolves either.
- src/main/DocQA.js — ask() is now async and accepts an engine arg.
  TF-idF path unchanged; neural path calls engine.rank(question, chunks)
  directly. The chunk corpus is built up front regardless of engine so
  ranking is consistent.
- src/main.js — doc-qa:ask IPC resolves the engine via SemanticEngine.getEngine(name)
  before calling DocQA.ask. The renderer can pass {engine: 'transformers'}
  to opt in once @xenova/transformers is installed.

Tests (51 new across this batch):
- tests/main/DailyNotesTemplates.test.js (18): labelFor separators /
  edge cases / non-string safety, listTemplates empty / present / sort,
  saveTemplate nested dir + .md extension + validation + null content,
  deleteTemplate success / missing / validation.
- tests/daily-templates-panel.test.js (12): mount + empty state + list +
  XSS safety, Use button (apply + error path), Delete button (success +
  last-template guard), New template (save + cancel), refresh.
- tests/main/SemanticEngine.test.js (8): default engine shape + rank
  matches WorkspaceSearch, getEngine for tf-idf / unknown / transformers
  (graceful fallback when @xenova/transformers missing), parity check.
- DocQA: 5 new tests for engine arg (custom engine.rank called, default
  fallback, neural hit shape translation); existing tests updated to
  await the now-async ask().

Full suite: 76 suites, 914 tests, lint+format clean.

Activation for the neural engine:
  npm install @xenova/transformers
  (heavy; ~50 MiB with deps) — then 'transformers' is selectable in
  doc-qa:ask. Until then, all calls use TF-idF transparently.

Amit Haridas
2026-09-14 11:57:32 +05:30
amitwh 2aa72738f4 feat(sidebar): daily-notes panel + Q&A deep-link to file offset
Daily notes panel:
- src/sidebar/daily-notes-panel.js — new sidebar panel that lists every
  YYYY-MM-DD.md in the daily-notes dir (newest first), with a Today
  button that creates/opens today's entry. Refresh button reloads.
  Clicking a row calls onOpenFile(path). DOM is built with textContent
  for dynamic fields — no XSS surface from a hostile filename.
- src/main.js — daily-notes:list IPC now returns absolute paths (joined
  with the daily-notes dir) so the renderer can pass them straight to
  open-file-path without re-synthesizing.
- src/renderer.js — registers the panel; icon for the daily-notes
  button (calendar icon).
- src/index.html — calendar SVG icon for the daily-notes sidebar entry.

Q&A deep-link:
- src/main.js — open-file-path accepts either a string (legacy) or an
  object {path, offset}. The offset is forwarded to the renderer via
  file-opened.
- src/renderer.js — file-opened handler scrolls the editor to the
  offset using EditorView.scrollIntoView(y: 'center') so the matching
  passage lands in the middle of the visible area.
- search panel now passes {path, offset} to open-file-path so Q&A
  results with chunk offsets jump straight to the passage.

Tests (12 new, tests/daily-notes-panel.test.js):
  - mount, empty state, list rendering
  - Today button → onOpenFile, 'Created today' / 'Today already exists' status
  - error paths (openToday reject, listExisting reject, listExisting missing)
  - click + Enter/Space on a row open the right path
  - refresh() re-fetches, keeps status when keepStatus:true
  - non-md entries filtered out
  - XSS-safe textContent for dynamic data

Full suite: 73 suites, 873 tests, lint+format clean.

Amit Haridas
2026-09-14 11:53:45 +05:30
amitwh 1b85bc47a4 feat(paste): CSV/TSV → markdown table on tabular paste
- src/utils/csv-to-table.js — pure module. Auto-detects the delimiter
  (tab wins over comma; comma wins over semicolon). RFC 4180-style
  parsing handles quoted fields, escaped quotes (""), and CRLF. Cells
  are escaped for markdown tables (\\ for backslashes, \| for pipes,
  newlines stripped). Alignment: a column is right-aligned when every
  non-empty cell matches a numeric pattern AND there are ≥2 rows or
  ≥1 multi-character cell (single-char numbers like "1" alone are
  too ambiguous to call as numeric — could be labels). Single-row input
  produces a header-only table (no fabricated "Column N" labels, no
  body).
- src/editor/smart-paste.js — paste handler now tries CSV first (it
  needs no async), falls back to the existing URL → title flow.

Tests (30 new, tests/csv-to-table.test.js):
  - detectDelimiter: tab > comma > semicolon, empty/non-string safe
  - parseRows: simple, RFC 4180 quoted, escaped quotes, CRLF, multi-line
  - escapeCell: pipes, backslash-first escaping, newline strip, null/number
  - csvToTable: simple CSV, single-row (header-only), TSV, empty input
  - alignment: numeric detection (≥2 rows OR ≥1 multi-char cell), currency,
    percentages, text columns stay left
  - escaping inside cells (pipes, newlines)
  - ragged-row padding
  - looksLikeCsv: true for tab/comma multi-row, false for single row,
    column-count mismatch, prose, very short input

Full suite: 72 suites, 861 tests, lint+format clean.

Amit Haridas
2026-09-14 09:01:07 +05:30
amitwh bd86748c47 feat(paste): smart URL → markdown-link on URL-only pastes
- src/main/UrlTitle.js — fetch a URL, return its <title>. Pure module
  with injectable fetch for tests. Decodes named + numeric + hex entities
  (AT&amp;T, Caf&#233;, &#x2014;), strips the <title> tags, collapses
  whitespace, caps the label at 200 chars. 5s timeout via AbortController,
  2 MiB body cap to avoid OOM on big downloads, streaming reader with
  overflow cancellation. Rejects non-http(s) URLs up front.
- src/main.js — IPC url-title:fetch proxies to fetchTitle.
- src/editor/smart-paste.js — CodeMirror 6 extension that detects
  URL-only pastes (one URL, surrounded only by whitespace), inserts the
  URL immediately, then async-rewrites the insertion range to
  [Title](url) once the title arrives. Multi-line / prose pastes pass
  through untouched.
- src/editor/codemirror-setup.js — accepts smartPasteFetcher option and
  pushes the extension when provided.
- src/renderer.js — passes ipcRenderer.invoke('url-title:fetch') as
  the fetcher.
- src/preload.js — url-title:fetch added to ALLOWED_SEND_CHANNELS.
- eslint.config.js — AbortController / TextDecoder / TextEncoder added
  to globals (available in Node 20+ and Chromium).

Tests (34 new):
- tests/main/UrlTitle.test.js (24): isHttpUrl scheme filter, decodeTitle
  named/numeric/hex entities + whitespace + non-string, extractTitleFromHtml
  first match + case-insensitive + null fallback, fetchTitle success +
  long-title cap + streaming body, all failure paths (non-http,
  no-fetch, non-OK, wrong content-type, no <title>, network error,
  body over maxBytes — including streaming overflow cancellation).
- tests/smart-paste.test.js (10): URL_ONLY_RE detection (bare URL,
  path/query/fragment, whitespace padding, scheme rejection, embedded
  rejection, empty/malformed), replacement format ([T](url), brackets
  in title survive, query strings preserved).

Full suite: 71 suites, 831 tests, lint+format clean.

Amit Haridas
2026-09-14 08:58:32 +05:30
amitwh 6d08c138d8 feat(preview): footnote hover preview
Hovering a footnote reference (rendered by marked-footnote as
<a data-footnote-ref href="#footnote-N">) now shows a small popover
with the footnote body text, matching the UX of Typora and Obsidian.

- src/renderer/footnote-preview.js — pure DOM module. Mounts a single
  popover once per preview pane; mouseover delegates via Element.closest()
  to the ref, lookups the matching <li id="footnote-N"> in the same pane,
  strips the backref ↩, and positions above/below the cursor with edge
  clamping. CSS.escape() fallback for non-browser environments.
- src/renderer.js — _renderPreview calls mountFootnotePreview once per
  pane (gated by a dataset marker so re-renders don't accumulate
  listeners).
- eslint.config.js — CSS + Element added to browser globals.

Tests (8 new, tests/footnote-preview.test.js): mount/unmount, delay +
timer behavior, mouseover/mouseout show/hide, dangling ref graceful
no-op, backref ↩ stripped from the displayed text, cancel-pending-show
when a new ref is hovered, idempotent remount guard.

Full suite: 69 suites, 797 tests, lint+format clean.

Amit Haridas
2026-09-14 08:56:31 +05:30
amitwh 7397e7f618 feat(statusbar): word count + reading time + Flesch-Kincaid grade
- src/utils/writing-stats.js — pure module: stripMarkdown() drops fenced
  code blocks, inline code, image URLs, link URLs, headings, blockquote
  markers, list bullets, and emphasis markers so the word count reflects
  the prose a reader actually consumes (the convention used by Hemingway,
  iA Writer). splitSentences / countSyllables use Flesch's standard
  heuristics. computeStats returns wordCount, sentenceCount,
  syllableCount, readingTimeMinutes (default 220 wpm),
  fleschKincaidGrade, charCount.
- src/renderer.js — _updateStatusBar now calls computeStats and updates
  three new status items: ~X min read, Grade N.N, and the existing Words
  + Chars counters use the stripped count.
- src/index.html — two new status items: #reading-time and #grade-level.

Tests (23 new, tests/writing-stats.test.js): markdown stripping (fenced,
inline, images, links, wikilinks, headings, lists, emphasis, HTML),
sentence splitter edge cases, syllable heuristic (short words, silent e,
empty), and computeStats with custom wpm + null-safe inputs.

Full suite: 68 suites, 789 tests, lint+format clean.

Amit Haridas
2026-09-14 08:54:28 +05:30