mirror of
https://github.com/amitwh/markdown-converter.git
synced 2026-10-01 09:19:34 +05:30
Three new specs for the v4.8.0+ feature wave: * Theme registry: replace hardcoded 25-theme menu in main.js with a pure ThemeRegistry module + per-theme CSS file convention. Add 12 new themes (Catppuccin x4, One Light, Tokyo Night Storm, Synthwave '84, Outrun, Winter is Coming Light+Dark, Solarized Dark HC, Spring Light). * ASCII art upgrade: consolidate dual implementations (standalone window vs dead in-app modal) into a single path; add 12 hand-coded fonts + figlet npm library for 400+ fonts; add copy/save/insert output destinations; comprehensive tests for the previously-zero-coverage textToASCII/createASCIIBox/getASCIITemplate machinery. * Flow chart editor: sidebar panel with hand-rolled SVG canvas, node-graph data model, drag/drop editing, live Mermaid source preview, undo/redo, session persistence. Emits Mermaid which the existing preview pane already renders natively. Amit Haridas
194 lines
16 KiB
Markdown
194 lines
16 KiB
Markdown
# MarkdownConverter — Flow Chart Editor Design
|
||
|
||
**Date:** 2026-09-14
|
||
**Status:** Draft — pending review
|
||
**Author:** Amit Haridas
|
||
|
||
## Overview
|
||
|
||
Add a sidebar-panel flow chart editor that lets users build flowcharts visually (drag nodes, connect edges, edit labels in-place) without writing Mermaid syntax by hand. The editor produces Mermaid `flowchart` source, which the existing preview pane already renders natively. Insert at cursor writes a ```` ```mermaid ```` fenced block. The feature is purely renderer-side — no main-process changes needed because Mermaid is already a bundled dependency.
|
||
|
||
## Goals
|
||
|
||
1. **Visual node editor.** Click to add nodes (5 shapes: process, decision, terminator, subroutine, document). Drag to move. Double-click to edit label inline. Right-click for shape submenu.
|
||
2. **Edge editor.** Drag from node edge handle to another node to connect. Click edge to set type (solid arrow, dotted arrow, thick arrow) and add a label.
|
||
3. **Live Mermaid preview.** Right pane of the sidebar panel shows the generated Mermaid source and re-renders the actual SVG on every change (debounced 250 ms). User can copy the source or insert it at the cursor.
|
||
4. **Undo / redo.** `Ctrl+Z` / `Ctrl+Shift+Z` within the panel. Snapshot-based, bounded depth (50 steps).
|
||
5. **Persistence.** The current flowchart is auto-saved to `<userData>/flowchart-session.json` so reopening the panel restores the user's work.
|
||
6. **Comprehensive tests.** Pure-data module (`flowchart-store.js`) and the Mermaid translator (`flowchart-mermaid.js`) are fully unit-tested. The canvas (`flowchart-canvas.js`) is DOM-tested with jsdom pointer events.
|
||
|
||
## Non-Goals (v1)
|
||
|
||
- No swimlanes / subgraphs (Mermaid supports them; deferring for v2).
|
||
- No collaboration / multi-user editing.
|
||
- No export to PNG/SVG (the preview pane already renders; "Save as SVG" is a thin wrapper that can come later).
|
||
- No import of existing Mermaid source (parse, validate, place into canvas) — defer to v2.
|
||
- No theme sync (the SVG renders in the preview pane, which already respects the active theme).
|
||
- No infinite canvas / zoom-pan — fixed viewport sized to the panel.
|
||
- No copy/paste of nodes between editor instances.
|
||
|
||
## Decisions Locked
|
||
|
||
| Decision | Choice | Reason |
|
||
|---|---|---|
|
||
| Side location | New sidebar panel `flowchart` | Matches the established `src/sidebar/<name>-panel.js` pattern (`search-panel`, `daily-notes-panel`, `git-panel`) |
|
||
| Canvas technology | SVG (no D3, no Konva, no library) | Hand-rolled SVG keeps the bundle small; the canvas is bounded (~100 nodes max); drag/drop is straightforward with pointer events |
|
||
| Graph model | Pure data structure (nodes + edges) stored in a single `flowchart-store.js` module | Matches the codebase's "pure module + injectable IO" pattern; trivially testable |
|
||
| Node shapes | 5 SVG shape templates per type, sized to text width | Mermaid supports many shapes; 5 covers 95% of real flowcharts |
|
||
| Edge routing | Straight lines between node centers (no orthogonal/Manhattan routing) | Simpler; orthogonal routing can come in v2; the visual is still clear |
|
||
| Undo/redo | Snapshot stack with bounded depth 50 | Predictable memory; matches editor undo conventions |
|
||
| Persistence | Auto-save to `<userData>/flowchart-session.json` on every change (debounced 500 ms) | Survives app restart; user doesn't lose work |
|
||
| Mermaid emission | Always `flowchart TD` (top-down) for v1 | Top-down is the most common flowchart direction; horizontal can be a per-node option in v2 |
|
||
| Debouncing | Preview re-render 250 ms; persistence 500 ms | Responsive but not jittery |
|
||
| Testing | Pure store/mermaid unit tests; canvas DOM tests with jsdom | No real-browser testing needed |
|
||
| Accessibility | Keyboard shortcuts for add/delete/undo; visible focus rings on nodes; ARIA labels on the SVG | The panel is mouse-first but keyboard-accessible |
|
||
|
||
## Architecture
|
||
|
||
```
|
||
┌──────────────────────────────────────────┐
|
||
│ src/sidebar/flowchart-panel.js │
|
||
│ - mounts the panel │
|
||
│ - splits left (canvas) | right (preview)│
|
||
│ - wires pointer events to canvas │
|
||
│ - subscribes to store changes │
|
||
└──────────┬────────────────┬───────────────┘
|
||
│ │
|
||
▼ ▼
|
||
┌──────────────────┐ ┌─────────────────────┐
|
||
│ flowchart- │ │ flowchart-mermaid.js│
|
||
│ canvas.js │ │ - toMermaid(graph) │
|
||
│ - renders SVG │ │ - validate(shape) │
|
||
│ - pointer events │ └─────────────────────┘
|
||
│ - hit-testing │
|
||
└────────┬─────────┘
|
||
│ reads/writes
|
||
▼
|
||
┌──────────────────────────────────────────┐
|
||
│ flowchart-store.js (pure module) │
|
||
│ - graph { nodes, edges } │
|
||
│ - addNode / moveNode / removeNode │
|
||
│ - connect / disconnect / setEdgeType │
|
||
│ - undo / redo (snapshot stack) │
|
||
│ - subscribe(fn) → emits on change │
|
||
│ - serialize() / deserialize(json) │
|
||
│ - persistence IO injected │
|
||
└──────────────────────────────────────────┘
|
||
│
|
||
│ debounced 500 ms write
|
||
▼
|
||
┌──────────────────────────────────────────┐
|
||
│ <userData>/flowchart-session.json │
|
||
└──────────────────────────────────────────┘
|
||
```
|
||
|
||
## New Modules
|
||
|
||
| File | Role |
|
||
|---|---|
|
||
| `src/sidebar/flowchart-panel.js` | Renderer-only panel. Mounts the canvas + preview; registers keyboard shortcuts; wires the "Insert at Cursor" button. |
|
||
| `src/flowchart/flowchart-store.js` | Pure data module. The graph is `{ nodes: Node[], edges: Edge[] }`. Node: `{ id, kind, x, y, label }`. Edge: `{ id, fromNodeId, toNodeId, kind: 'solid'\|'dotted'\|'thick', label? }`. Exports `create()`, `addNode`, `moveNode`, `setNodeLabel`, `setNodeKind`, `removeNode`, `connect`, `disconnect`, `setEdgeKind`, `setEdgeLabel`, `undo`, `redo`, `subscribe`, `serialize`, `deserialize`, `toJSON`. Constructor takes injected IO `{ persistencePath, readFile, writeFile, now }`. |
|
||
| `src/flowchart/flowchart-canvas.js` | Renderer-only SVG canvas. Owns an `<svg>` element. Renders nodes as `<g>` containing shape `<path>`/`<rect>`/`<ellipse>` and a `<text>` label. Renders edges as `<line>` or `<polyline>`. Listens for pointer events: drag to move, double-click to edit label, right-click for shape menu. Hit-testing walks the DOM in reverse z-order. |
|
||
| `src/flowchart/flowchart-mermaid.js` | Pure translator. `toMermaid(graph)` returns the Mermaid `flowchart TD` source string. Validates each shape against the 5 supported kinds; throws on unknown kind. Handles label escaping (`"`, newlines → `\n`). |
|
||
| `src/flowchart/flowchart-shapes.js` | Pure module: `shapeSvg(kind, x, y, width, height) → string`. Defines the 5 SVG shape templates (process=rect, decision=diamond, terminator=stadium, subroutine=rect-with-double-border, document=parallelogram-approx). Pure functions; unit-tested. |
|
||
| `tests/flowchart-store.test.js` | Pure store tests: add/move/connect/disconnect/delete; undo/redo round-trip; subscribe fires once per change; serialize/deserialize round-trip; throws on invalid input. |
|
||
| `tests/flowchart-mermaid.test.js` | Translation tests: each node kind emits the correct Mermaid syntax (`[]`, `{}`, `(())`, `[[]]`, `[]/]`); edge kinds (`-->`, `-.->`, `==>`); label escaping. Snapshot tests for representative graphs. |
|
||
| `tests/flowchart-shapes.test.js` | Each shape function returns SVG that matches the expected viewBox and contains the expected primitive. |
|
||
| `tests/flowchart-canvas.test.js` | jsdom tests: mount canvas with a 3-node graph; assert SVG structure; simulate a `pointerdown` + `pointermove` + `pointerup`; assert store updated with new position. |
|
||
| `tests/flowchart-panel.test.js` | jsdom test: mount the panel; assert canvas + preview panes exist; assert preview re-renders on store change. |
|
||
| `tests/preload-flowchart.test.js` | (No new preload channels — feature is renderer-only.) |
|
||
|
||
## Modified Modules
|
||
|
||
| File | Change |
|
||
|---|---|
|
||
| `src/renderer.js:2300` (sidebar registration) | Add `sidebarManager.registerPanel('flowchart', { title: 'Flow Chart', render: renderFlowChartPanel, icon: '<svg>…flowchart icon…</svg>' })`. |
|
||
| `src/index.html` | Add a sidebar rail button with `data-panel="flowchart"` and the matching icon. |
|
||
| `src/styles/sidebar.css` (or wherever sidebar styles live) | Add minimal layout for the panel's split: `display: flex; flex: 1;` with left pane ~70% (canvas) and right pane ~30% (preview). |
|
||
| `README.md:54-70` (Advanced Features) | Add row: "**Visual flow chart editor** — Build Mermaid flowcharts visually; drag nodes, connect edges, live preview. Insert at cursor." |
|
||
| `README.md:120-128` (Keyboard Shortcuts) | Add row: `\| Add Flow Chart Node \| Insert (when panel focused) \|`, `\| Undo \| Ctrl+Z \|`, `\| Redo \| Ctrl+Shift+Z \|` (panel-scoped). |
|
||
|
||
## Node Shapes
|
||
|
||
| kind | Mermaid syntax | SVG shape | Use case |
|
||
|---|---|---|---|
|
||
| `process` | `A[Label]` | `<rect>` | Generic step |
|
||
| `decision` | `A{Label}` | `<polygon points="…">` (diamond) | Yes/no, branch |
|
||
| `terminator` | `A([Label])` | `<rect rx=…>` (stadium) | Start/end |
|
||
| `subroutine` | `A[[Label]]` | `<rect>` with double border | Named subroutine call |
|
||
| `document` | `A[/Label/]` | `<polygon>` (parallelogram-approx) | Document/file reference |
|
||
|
||
Sizes auto-grow to fit the label text (measure with `getComputedTextLength()` on the `<text>` after first render).
|
||
|
||
## Edge Kinds
|
||
|
||
| kind | Mermaid syntax | SVG style |
|
||
|---|---|---|
|
||
| `solid` | `A --> B` | Solid `<line>` with marker arrow |
|
||
| `dotted` | `A -.-> B` | `stroke-dasharray="4,4"` |
|
||
| `thick` | `A ==> B` | `stroke-width="3"` |
|
||
|
||
Edge labels render as `<text>` at the midpoint of the edge with a small white background rect for legibility.
|
||
|
||
## Data Flow
|
||
|
||
1. User opens the panel via sidebar rail button (`data-panel="flowchart"`).
|
||
2. `flowchart-panel.js` constructs a `flowchart-store.js` instance, calls `deserialize()` with whatever is in `<userData>/flowchart-session.json` (empty graph if absent).
|
||
3. Panel mounts the canvas SVG and the preview pane side by side.
|
||
4. Canvas subscribes to store changes; on every change, re-renders the SVG and notifies the panel.
|
||
5. Panel debounces (250 ms) and calls `flowchart-mermaid.toMermaid(graph)`; updates the preview pane.
|
||
6. Panel debounces (500 ms) and calls `store.serialize()` → writes to `<userData>/flowchart-session.json` via injected `writeFile`.
|
||
7. User clicks "Insert at Cursor": panel emits `insert-content` IPC with the Mermaid source wrapped in a fenced code block (same pattern as ASCII art's insert).
|
||
8. User triggers undo/redo: panel sends `Ctrl+Z` / `Ctrl+Shift+Z` to the canvas's keyboard handler when the panel is focused (the editor's existing undo is left alone — undo only fires when the panel has focus).
|
||
|
||
## Error Handling
|
||
|
||
| Scenario | Handling |
|
||
|---|---|
|
||
| Persistence file is corrupt JSON | `deserialize()` catches, logs warning, returns empty graph. User loses prior session but can start fresh. |
|
||
| Persistence write fails (disk full, perms) | `writeFile` rejects; the panel logs the error but continues functioning. The next save attempt retries. |
|
||
| `toMermaid()` encounters unknown node kind | Throws a typed error; the preview pane shows "Invalid graph: <message>"; the canvas still renders. User must fix the kind (e.g. via right-click menu). |
|
||
| `toMermaid()` label contains newlines | Escapes `\n` to literal `\n` (Mermaid's escape). |
|
||
| Canvas hit-test misidentifies a node | Defensive: the canvas uses `data-node-id` attribute on every `<g>`; lookup by id is O(1). |
|
||
| User adds a node with empty label | Allowed; rendered as a single space. Mermaid will accept it. |
|
||
| User tries to connect a node to itself | `connect()` throws `TypeError`; canvas shows toast. |
|
||
| Undo stack overflow (>50 entries) | Oldest snapshot dropped; no memory growth. |
|
||
|
||
## Testing
|
||
|
||
1. **Unit (`tests/flowchart-store.test.js`)** — Pure store operations. 30+ tests across add/move/connect/disconnect/delete/undo/redo/subscribe/serialize/deserialize. Includes invalid-input rejection.
|
||
2. **Unit (`tests/flowchart-mermaid.test.js`)** — Translation for each shape kind × each edge kind × with-label × without-label. Snapshot tests for 5 representative graphs (linear chain, decision diamond, parallel branches, cycle, large 20-node graph).
|
||
3. **Unit (`tests/flowchart-shapes.test.js`)** — `shapeSvg('process', 10, 10, 100, 50)` returns SVG containing a `<rect>` at the right coordinates.
|
||
4. **DOM (`tests/flowchart-canvas.test.js`)** — Mount with 3-node graph; assert `<svg>` contains 3 `<g data-node-id="…">`. Simulate `pointerdown` on node 1's center, `pointermove` +100px, `pointerup`; assert store's `moveNode` was called with the new position.
|
||
5. **DOM (`tests/flowchart-panel.test.js`)** — Mount panel; assert two panes exist; trigger a store change; assert preview re-renders within 300 ms (jest fake timers).
|
||
|
||
## Risks
|
||
|
||
| Risk | Likelihood | Mitigation |
|
||
|---|---|---|
|
||
| Drag/drop in jsdom is fragile (no real layout) | Medium | Use `getBoundingClientRect()` mocks; canvas tests focus on store wiring, not pixel-perfect rendering. |
|
||
| Hand-rolled SVG shapes look amateurish next to Mermaid's rendered output | Medium | Use the same color tokens as the active theme via CSS variables; reuse `--accent`, `--text-primary` from the theme CSS. |
|
||
| Undo/redo memory growth on large graphs | Low | Bounded depth (50 snapshots) + snapshot diff-based compression (only store changed nodes). |
|
||
| Persistence path conflicts on multi-window future | Low | Use `<userData>/flowchart-session.json` — single user, single app instance. |
|
||
| Mermaid preview re-render flickers on every keystroke | Medium | Debounce 250 ms; show a "Rendering…" status indicator during re-render. |
|
||
| Edge routing looks ugly on dense graphs | Medium | Document as a known limitation in the panel tooltip; v2 orthogonal routing is on the roadmap. |
|
||
|
||
## Acceptance Criteria
|
||
|
||
- [ ] `src/sidebar/flowchart-panel.js` exists and is registered via `sidebarManager.registerPanel('flowchart', …)`.
|
||
- [ ] Sidebar rail button with `data-panel="flowchart"` added to `src/index.html`; clicking it opens the panel.
|
||
- [ ] User can add a node by clicking the canvas (default: process shape at click position).
|
||
- [ ] User can drag a node to a new position; the store's `moveNode` is called with correct coordinates.
|
||
- [ ] User can double-click a node to edit its label inline; on blur or Enter, the store is updated.
|
||
- [ ] User can right-click a node to change its shape (5 options: process, decision, terminator, subroutine, document).
|
||
- [ ] User can drag from a node's edge handle to another node to create an edge.
|
||
- [ ] User can click an edge to change its type (3 options: solid, dotted, thick) and add an optional label.
|
||
- [ ] `Ctrl+Z` / `Ctrl+Shift+Z` (when panel is focused) undoes / redoes the last change.
|
||
- [ ] The right preview pane shows the current Mermaid source as text AND renders the SVG via the existing Mermaid render path.
|
||
- [ ] "Insert at Cursor" wraps the Mermaid source in a ```` ```mermaid ```` fenced code block and inserts it at the editor cursor.
|
||
- [ ] Closing and reopening the app restores the last graph from `<userData>/flowchart-session.json`.
|
||
- [ ] `npm test` passes with the new test files added (target +30 new tests across 5 files).
|
||
- [ ] `npm run lint` clean, `npm run format:check` clean.
|
||
- [ ] `npm run build:linux` succeeds.
|
||
- [ ] README updated to mention the visual flow chart editor.
|