From 43c26c6521859569ec67f6a5cfe7031614b1d2d3 Mon Sep 17 00:00:00 2001 From: Amit Haridas Date: Mon, 14 Sep 2026 19:01:09 +0530 Subject: [PATCH] docs(specs): add theme-registry + ascii-art-upgrade + flowchart-editor designs 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 --- .../2026-09-14-ascii-art-upgrade-design.md | 183 +++++++++++++++++ .../2026-09-14-flowchart-editor-design.md | 193 ++++++++++++++++++ .../specs/2026-09-14-theme-registry-design.md | 158 ++++++++++++++ 3 files changed, 534 insertions(+) create mode 100644 docs/superpowers/specs/2026-09-14-ascii-art-upgrade-design.md create mode 100644 docs/superpowers/specs/2026-09-14-flowchart-editor-design.md create mode 100644 docs/superpowers/specs/2026-09-14-theme-registry-design.md diff --git a/docs/superpowers/specs/2026-09-14-ascii-art-upgrade-design.md b/docs/superpowers/specs/2026-09-14-ascii-art-upgrade-design.md new file mode 100644 index 0000000..befc9cb --- /dev/null +++ b/docs/superpowers/specs/2026-09-14-ascii-art-upgrade-design.md @@ -0,0 +1,183 @@ +# MarkdownConverter — ASCII Art Generator Upgrade Design + +**Date:** 2026-09-14 +**Status:** Draft — pending review +**Author:** Amit Haridas + +## Overview + +Promote the ASCII Art Generator from "works but untested, dual-implemented, 5 fonts" to a full-fledged feature with 17 hand-coded fonts + 400+ FIGlet fonts, single source of truth, comprehensive test coverage, and three output paths (insert at cursor, copy to clipboard, save to file). Consolidate the dead-code in-app modal in favor of the existing standalone `BrowserWindow`. + +## Goals + +1. **Single implementation path.** Delete the dead `#ascii-art-dialog` modal markup, its `ModalManager` instance, the `showASCIIGenerator*` dead channels in `preload.js`, and the corresponding renderer controller at `renderer.js:5942-6727`. The standalone `ascii-generator.html` window remains the only path. +2. **Many more fonts.** Add 12 hand-coded fonts (Big, Small, Lean, Slant, Isometric1-4, 3-D, 3x5, ANSI Shadow, Calvin S) on top of the 5 existing (standard, banner, block, bubble, digital). Plus all `figlet` npm fonts (400+) accessible through a searchable picker. +3. **Three output destinations.** Insert at cursor (existing — wraps result in a fenced code block), Copy to clipboard (new), Save to `.txt` file (new). +4. **Comprehensive test coverage.** The current `textToASCII`, `createASCIIBox`, `getASCIITemplate`, and the new `figlet` adapter all have ZERO tests today. Add full coverage — snapshot tests for known outputs across all fonts, integration tests for copy/save/insert. +5. **Pure-module architecture.** Move all algorithm code from `renderer.js` into `src/main/AsciiArt.js` (pure module, IPC-coupled via `main.js`, mirroring the `DocQA`/`DailyNotes` pattern). + +## Non-Goals (v1) + +- No animated ASCII / motion ASCII. +- No color / ANSI escape codes (would break the markdown fenced code block contract). +- No font upload — only shipped fonts (hand-coded + `figlet` standard library). +- No image export (PNG/SVG of ASCII art) — text only. +- No undo history inside the modal — single-shot generation. +- No in-modal text editing of the generated ASCII (the user can paste into the editor instead). + +## Decisions Locked + +| Decision | Choice | Reason | +|---|---|---| +| Library | `figlet` npm package (latest, ~400 KB unpacked) | Industry-standard JS port of the original C library; 400+ fonts; small footprint | +| Hand-coded fonts | Add 12 more, keep existing 5 as `legacy/` set | Hand-coded fonts have a distinct aesthetic (`figlet` doesn't 1:1 replicate all of them) and load with zero I/O | +| Font loading | Hand-coded: synchronous. `figlet`: lazy-loaded on font-picker open (async) | Avoids 400 KB of synchronous font loading at app startup | +| Module split | `src/main/AsciiArt.js` (pure), `src/renderer/ascii-controller.js` (UI), `src/ascii-generator.html` (already exists) | Matches established renderer-controller + main-module split | +| Single source of truth | Standalone window only; in-app modal deleted | Dead code is technical debt; consolidation is required for testability | +| Output destinations | Insert at cursor (existing) + Copy clipboard + Save to file | Covers all "I want this ASCII art in my markdown" use cases | +| Insert wrapper | Result wrapped in `\n\`\`\`\n…\n\`\`\`\n` (existing behaviour) | Preserves current markdown-fence contract | +| Persistence | None — the modal is stateless. Last-used font remembered via `electron-store` `ascii:lastFont` key (new). | Small UX win without adding persistence layer | +| Tests | jsdom + Jest, snapshot tests for known font outputs | Same pattern as existing `DocQA.test.js` | + +## Architecture + +``` + ┌─────────────────────────┐ + Renderer (UI): │ src/renderer/ │ + - ascii-generator │ ascii-controller.js │ + .html │ - font-picker │ + - ascii- │ - input/options │ + controller.js │ - preview │ + │ - 3 action buttons │ + └────────┬────────────────┘ + │ IPC: ascii:generate, ascii:list-fonts, + │ ascii:last-font, ascii:save + ▼ + ┌─────────────────────────┐ + Main (pure): │ src/main/AsciiArt.js │ + │ generate(text, font, │ + │ options) │ + │ listFonts() │ + │ getFontMeta(id) │ + └────────┬────────────────┘ + │ + ┌───────────────┼───────────────┐ + ▼ ▼ ▼ + ┌─────────────┐ ┌──────────────┐ ┌──────────────┐ + │ Hand-coded │ │ figlet │ │ Templates │ + │ (sync) │ │ (lazy async) │ │ (19 existing)│ + │ 17 fonts │ │ 400+ fonts │ │ │ + └─────────────┘ └──────────────┘ └──────────────┘ +``` + +## New Modules + +| File | Role | +|---|---| +| `src/main/AsciiArt.js` | Pure module. `generate({ text, font, options })` returns the ASCII string. `listFonts()` returns `{ id, label, kind: 'hand-coded'\|'figlet'\|'template', sample }[]`. `getFontMeta(id)` returns `{ kind, height, supportedChars }`. Lazy-loads `figlet` on first `figlet:*` font request; caches font list. | +| `src/main/AsciiArt.fonts.js` | Hand-coded font tables (5 existing + 12 new) extracted from `renderer.js:6005-6474`. Exports `HAND_CODED_FONTS` as `{ id → { height, chars: { 'A' → string[height], … } } }`. | +| `src/renderer/ascii-controller.js` | Renderer-side controller. Wires input/options/preview/action-buttons in the standalone window. Calls `window.api.ascii.generate(...)` etc. (new preload bindings). | +| `tests/ascii-art.test.js` | Unit tests for `AsciiArt.generate()` across hand-coded fonts (snapshot), `listFonts()` shape, `getFontMeta()`, figlet adapter (mocked), template adapter. | +| `tests/ascii-art.fonts.test.js` | Per-hand-coded-font known-output snapshot tests for the word "HELLO" (and a few edge cases: empty string, single char, mixed case, digits). | +| `tests/ascii-art.templates.test.js` | Per-template known-output snapshot for `getASCIITemplate('arrow-right', {})` etc. | +| `tests/preload-ascii.test.js` | Asserts the new `ascii:*` IPC channels are declared in `preload.js` allow-list. | + +## Modified Modules + +| File | Change | +|---|---| +| `src/ascii-generator.html` | Add: (a) searchable font picker dropdown (debounced 100 ms input filter over `listFonts()`), (b) two new buttons: "Copy to Clipboard" + "Save to File", (c) call `window.api.ascii.generate(...)` instead of running `textToASCII` inline. Layout otherwise unchanged. | +| `src/main.js:5584-5611` (`openAsciiGenerator`) | No structural change. The IPC channel name stays `open-ascii-generator`. | +| `src/main.js` (new IPC handlers, after line 5611) | Register: `ipcMain.handle('ascii:generate', …)`, `ipcMain.handle('ascii:list-fonts', …)`, `ipcMain.handle('ascii:get-font-meta', …)`, `ipcMain.handle('ascii:save', …)` (writes user-selected path), `ipcMain.handle('ascii:last-font', …)` (read + write via `electron-store`). | +| `src/main.js` (require block) | Add `const AsciiArt = require('./main/AsciiArt')` near `main.js:4005-4010`. | +| `src/preload.js:98-99,294-296` | **Delete** the dead `show-ascii-generator` and `show-ascii-generator-window` channels. Add new `ascii:generate`, `ascii:list-fonts`, `ascii:get-font-meta`, `ascii:save`, `ascii:last-font` to `validInvokeChannels`. Add the `generators.ascii` namespace helpers. | +| `src/renderer.js:2188` | **Delete** the `new ModalManager('#ascii-art-dialog')` instantiation. | +| `src/renderer.js:5942-6727` | **Delete** the entire in-app ASCII modal block: `showASCIIGenerator`, `hideASCIIGenerator`, `generateASCIIPreview`, `insertASCIIArt`, `createASCIIBox`, `getASCIITemplate`, `textToASCII`, all 5 font tables, the 19 template strings. (Total ~786 lines.) | +| `src/index.html:814-…` (`#ascii-art-dialog` markup) | **Delete** the entire modal markup block. | +| `src/styles.css` + other stylesheets | Remove any CSS tied only to the deleted modal. | +| `package.json` dependencies | Add `"figlet": "^1.8.0"` (or current latest). | +| `README.md:55-57` (ASCII Art Generator row) | Update to mention "17 hand-coded fonts + 400+ FIGlet fonts, copy/save/insert". | +| `README.md:124` (shortcut) | No change — `Ctrl+Shift+A` keeps working (existing standalone window path). | +| `electron-builder.config.js` | No change (pure-JS dep). | + +## Hand-Coded Fonts (12 new) + +Each is a `{ height, chars: { 'A': [...], 'B': [...], ... '0'..'9', ' ' } }` table. Heights vary 4–8 rows. + +| id | label | height | source inspiration | +|---|---|---|---| +| `big` | Big | 8 | `figlet` "Big" | +| `small` | Small | 5 | `figlet` "Small" | +| `lean` | Lean | 6 | `figlet` "Lean" | +| `slant` | Slant | 6 | `figlet` "Slant" (skewed) | +| `isometric1` | Isometric 1 | 6 | `figlet` "Isometric1" | +| `isometric2` | Isometric 2 | 6 | `figlet` "Isometric2" | +| `isometric3` | Isometric 3 | 6 | `figlet` "Isometric3" | +| `isometric4` | Isometric 4 | 6 | `figlet` "Isometric4" | +| `three-d` | 3-D | 7 | `figlet` "3-D" | +| `three-x-five` | 3x5 | 5 | `figlet` "3x5" | +| `ansi-shadow` | ANSI Shadow | 8 | `figlet` "ANSI Shadow" | +| `calvin-s` | Calvin S | 7 | `figlet` "Calvin S" | + +Plus the 5 existing (standard, banner, block, bubble, digital). Total 17 hand-coded. + +## New UI Features + +1. **Searchable font picker.** Dropdown with text input. Lists all `listFonts()` results (17 hand-coded + 400+ figlet = ~417). Fuzzy-match on label, debounced 100 ms. Selecting a font renders a 5-char preview (`"HELLO"`) inline. +2. **Copy to Clipboard button.** Copies the rendered ASCII art to the OS clipboard. Uses Electron's `clipboard.writeText()` (already available via `require('electron').clipboard` in main process — exposed as `window.api.ascii.copy(text)` via new preload helper). +3. **Save to File button.** Opens a `dialog.showSaveDialog` with default `.txt` extension, writes the rendered ASCII art. Returns the saved path on success. +4. **Last-used font memory.** New `electron-store` key `ascii:lastFont`. On window open, preselect the last-used font. + +## Data Flow + +1. User triggers `Ctrl+Shift+A` (existing) or Tools → ASCII Art Generator menu (existing). +2. `main.js:5584` `openAsciiGenerator()` creates/ focuses the standalone `BrowserWindow` (existing behaviour). +3. Renderer loads `src/ascii-generator.html`. The page calls `window.api.ascii.listFonts()` on mount, populates the font picker. +4. User types text, selects font, chooses options (box style, padding) → the preview pane debounces (200 ms) and calls `window.api.ascii.generate(...)`. +5. Click "Insert": renderer emits existing `insert-generated-content` IPC → main forwards to `mainWindow.webContents.send('insert-content', …)` → editor's `tabManager.insertAtCursor` runs, wrapped in fenced code block. +6. Click "Copy": renderer calls `window.api.ascii.copy(text)` → main process writes to clipboard via `clipboard.writeText`. +7. Click "Save": renderer calls `window.api.ascii.save(text)` → main shows save dialog → writes file → returns path. UI shows success toast. + +## Error Handling + +| Scenario | Handling | +|---|---| +| `figlet` fails to lazy-load (dep missing, native binding error) | `AsciiArt.listFonts()` returns hand-coded fonts only; logs a warning. Picker shows "FIGlet fonts unavailable" notice. | +| `figlet` font file missing (corrupt install) | `AsciiArt.generate({ font: 'figlet:Big' })` throws a structured error → controller shows toast + falls back to last working font. | +| User picks a font with unsupported characters (e.g. non-ASCII) | `generate()` substitutes `?` per character; controller shows a one-line warning above the preview. | +| Clipboard write fails (rare, OS lock) | `clipboard.writeText` returns synchronously and almost never throws; if it does, the controller shows a toast and falls back to "Save to File". | +| Save dialog cancelled | Returns `{ canceled: true }`; UI shows no error. | +| IPC channel not allowed | `preload.js` is the gate; renderer can only call declared channels. Lint catches new channel declarations missing from allow-list. | + +## Testing + +1. **Unit (`tests/ascii-art.test.js`)** — `generate({ text: 'HELLO', font: 'big', options: {} })` returns the expected ASCII string (snapshot); `listFonts()` returns `{ id, label, kind, sample }[]` with hand-coded first; `getFontMeta('big')` returns `{ kind: 'hand-coded', height: 8 }`. +2. **Snapshot (`tests/ascii-art.fonts.test.js`)** — One snapshot per hand-coded font for `generate({ text: 'HELLO', font: '' })`. Snapshots are checked into `tests/__snapshots__/ascii-art.fonts.test.js.snap`. Edge cases: empty string, single char, mixed case, digits. +3. **Snapshot (`tests/ascii-art.templates.test.js`)** — One snapshot per existing template (`arrow-right`, `flowchart`, etc.). +4. **Adapter (`tests/ascii-art.figlet-adapter.test.js`)** — `figlet.generate` is mocked; assert the adapter calls through with correct options, handles the promise, handles errors. +5. **Integration (`tests/ascii-art.electron.test.js`)** — Spawn the actual `ascii-generator.html` window via `electron` binary in headless mode and assert the window loads without console errors. Skip if no display available. +6. **Preload (`tests/preload-ascii.test.js`)** — Asserts all new `ascii:*` channels are in `validInvokeChannels` allow-list; old `show-ascii-generator*` channels are gone. + +## Risks + +| Risk | Likelihood | Mitigation | +|---|---|---| +| Hand-coded font tables are tedious to author and error-prone | Medium | Snapshot tests catch drift; visual review for first 4; the rest follow the pattern. | +| `figlet` npm package has unexpected transitive deps or native bindings | Low | `figlet` is pure JS in v1.x; no native deps. Verify with `npm ls figlet` after install. | +| Adding `figlet` increases bundle size (~400 KB unpacked, ~150 KB gzipped in asar) | Low | Acceptable: ASCII art is a featured function. Document in `THIRD-PARTY-NOTICES.md` (MIT license). | +| Deleting 786 lines from `renderer.js` might break something unrelated | Low | The deleted block is self-contained. `git grep "showASCIIGenerator\|textToASCII\|createASCIIBox\|getASCIITemplate"` after deletion should return only the new module location. | +| Snapshot tests for fonts become noisy (whitespace, line endings) | Medium | Normalize line endings to `\n`; trim trailing whitespace; commit a stable snapshot per font after manual review. | +| Standalone window cannot share state with the editor (last-used font, theme sync) | Low | The standalone window is its own `BrowserWindow`; use `electron-store` (already used app-wide) for cross-window state. Theme sync is out of scope (standalone window has its own theme via `body.theme-` from Spec 1). | + +## Acceptance Criteria + +- [ ] `src/main/AsciiArt.js` exists with `generate`, `listFonts`, `getFontMeta` API. +- [ ] 17 hand-coded fonts (5 existing + 12 new) registered, each renders the test word "HELLO" correctly. +- [ ] `figlet` library loads lazily on first `figlet:*` font request; lists ~400+ fonts via `listFonts()`. +- [ ] In-app modal (`#ascii-art-dialog`, `showASCIIGenerator*`, `preload.js:294-296`) is fully deleted; `git grep` returns zero hits for these symbols outside the deletion commit. +- [ ] Standalone window has Copy to Clipboard, Save to File, and Insert buttons. All three work end-to-end. +- [ ] Last-used font is remembered across window opens via `electron-store` `ascii:lastFont`. +- [ ] `npm test` passes with the new test files added. +- [ ] `npm run lint` clean, `npm run format:check` clean. +- [ ] `npm run build:linux` succeeds (no native-binding surprises from `figlet`). +- [ ] README updated to mention the expanded font catalog and the new output destinations. diff --git a/docs/superpowers/specs/2026-09-14-flowchart-editor-design.md b/docs/superpowers/specs/2026-09-14-flowchart-editor-design.md new file mode 100644 index 0000000..7dc606a --- /dev/null +++ b/docs/superpowers/specs/2026-09-14-flowchart-editor-design.md @@ -0,0 +1,193 @@ +# 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 `/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/-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 `/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 + ▼ + ┌──────────────────────────────────────────┐ + │ /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 `` element. Renders nodes as `` containing shape ``/``/`` and a `` label. Renders edges as `` or ``. 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: '…flowchart icon…' })`. | +| `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]` | `` | Generic step | +| `decision` | `A{Label}` | `` (diamond) | Yes/no, branch | +| `terminator` | `A([Label])` | `` (stadium) | Start/end | +| `subroutine` | `A[[Label]]` | `` with double border | Named subroutine call | +| `document` | `A[/Label/]` | `` (parallelogram-approx) | Document/file reference | + +Sizes auto-grow to fit the label text (measure with `getComputedTextLength()` on the `` after first render). + +## Edge Kinds + +| kind | Mermaid syntax | SVG style | +|---|---|---| +| `solid` | `A --> B` | Solid `` with marker arrow | +| `dotted` | `A -.-> B` | `stroke-dasharray="4,4"` | +| `thick` | `A ==> B` | `stroke-width="3"` | + +Edge labels render as `` 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 `/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 `/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: "; 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 ``; 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 `` at the right coordinates. +4. **DOM (`tests/flowchart-canvas.test.js`)** — Mount with 3-node graph; assert `` contains 3 ``. 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 `/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 `/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. diff --git a/docs/superpowers/specs/2026-09-14-theme-registry-design.md b/docs/superpowers/specs/2026-09-14-theme-registry-design.md new file mode 100644 index 0000000..3a45f27 --- /dev/null +++ b/docs/superpowers/specs/2026-09-14-theme-registry-design.md @@ -0,0 +1,158 @@ +# MarkdownConverter — Editor Theme Registry Design + +**Date:** 2026-09-14 +**Status:** Draft — pending review +**Author:** Amit Haridas + +## Overview + +Replace the hardcoded editor-theme menu array in `main.js` (25 inline items at `src/main.js:1137-1245`) and the scattered CSS (`body.theme-` selector blocks across `styles.css`, `styles-modern.css`, `styles-concreteinfo.css`) with a single `ThemeRegistry` module and a per-theme CSS file convention. Add 12 new themes spanning popular-requested, high-contrast, and seasonal categories. Future theme additions become a one-line `register()` call plus one CSS file. + +## Goals + +1. New themes can be added by changing one JS file and adding one CSS file — no menu edits. +2. Add 12 new themes spanning popular-requested (Catppuccin palette), high-contrast (Solarized Dark HC), and seasonal categories. +3. Theme application stays sub-100ms on theme switch (current behaviour). +4. Existing 25 themes keep working — pure refactor for them, additive for the 12 new ones. +5. Theme persistence model unchanged — `electron-store` `theme` key, default `atomonelight`. + +## Non-Goals (v1) + +- No user-defined custom themes (theme editor) — only shipped themes. +- No per-syntax-token color customization beyond the per-theme palette. +- No theme auto-switching based on system light/dark mode. +- No export-theme changes — `ExportThemes.js` is a separate system with its own registry (already exists). + +## Decisions Locked + +| Decision | Choice | Reason | +|---|---|---| +| Module location | `src/main/ThemeRegistry.js` (pure, no Electron) | Matches the established `DocQA`/`DailyNotes`/`WorkspaceSearch` pattern | +| Persistence | Existing `electron-store` `theme` key | Already wired; no migration needed | +| CSS organisation | One file per theme at `src/styles/themes/.css` | Convention beats scattered selector blocks | +| CSS loading | Preload ALL theme `` tags in `index.html`; toggle `disabled` on theme switch | Avoids link-swap network roundtrip; < 5 KB per theme × 37 = ~185 KB total CSS overhead | +| Menu generation | `main.js` calls `ThemeRegistry.list()` + `categories()` to build menu items | Removes the 109-line hardcoded block | +| Theme id format | kebab-case, lowercase, no spaces (`catppuccin-mocha`, `winter-is-coming-light`) | Stable contract for storage + CSS file naming | +| New themes | 12 new: 4× Catppuccin, 1× One Light, 1× Tokyo Night Storm, 2× Synthwave/Outrun, 2× Winter is Coming, 1× Solarized Dark HC, 1× Solarized Light (seasonal Spring variant) | Highest demand per GitHub issues + aesthetic completeness | + +## Architecture + +``` + ┌─────────────────────────┐ + electron-store ───► │ src/main/ThemeRegistry │ ◄── register() at startup + (theme = "x") │ .js (pure module) │ + │ list() │ + │ get(id) │ + │ categories() │ + └──────────┬──────────────┘ + │ list() + ▼ + main.js menu builder + │ + ┌──────────┴───────────┐ + │ │ + ▼ ▼ + "Light Themes" "Dark Themes" + "High-Contrast" "Seasonal" + │ │ + └──────────┬───────────┘ + │ IPC: theme-changed + ▼ + renderer.js applyTheme(id) + │ + ▼ + index.html has 37 , removes from others +``` + +## New Modules + +| File | Role | +|---|---| +| `src/main/ThemeRegistry.js` | Pure module: `register(theme)`, `unregister(id)`, `list()`, `get(id)`, `categories()`, `lightThemes()`, `darkThemes()`. A theme is `{ id, label, category: 'light'\|'dark'\|'high-contrast'\|'seasonal', isDark: boolean }`. No Electron imports. | +| `src/main/ThemeRegistry.bootstrap.js` | Calls `register()` for all 37 themes (25 existing + 12 new). Imported by `main.js` at startup. | +| `src/styles/themes/.css` × 37 | Per-theme CSS rules previously scattered. Each file's body selector is `body.theme-`. Naming convention: `.css`. | +| `tests/theme-registry.test.js` | Register / unregister / list / get / categories; lightThemes/darkThemes filtering; throws on duplicate id. | +| `tests/theme-menu-builder.test.js` | Generates MenuItem[] from `list()` + `categories()`: each item has `{ id, label, type: 'radio', checked, click: () => setTheme(id) }`. | + +## Modified Modules + +| File | Change | +|---|---| +| `src/main.js:1137-1245` | Delete the 109-line hardcoded theme menu array. Replace with: `const themeMenu = buildThemeMenu(store, mainWindow)` (new helper, ~25 lines, lives next to the menu builder). | +| `src/main.js:3952-3955` | `setTheme(id)` already validates against a hardcoded set — replace hardcoded list with `ThemeRegistry.get(id) ? id : 'atomonelight'`. | +| `src/main.js` startup | Add `require('./main/ThemeRegistry.bootstrap')` once. | +| `src/index.html` | Replace the bare `` block with: preload ALL theme CSS as `` (one per theme). The base `styles.css` link stays as the structural (non-themed) layer. | +| `src/renderer.js:2900` (`get-theme` IPC) | No change — handler already returns stored theme. | +| `src/renderer.js:3005-3007` (apply theme) | Replace `document.body.className = 'theme-'` with: find the matching ``, set `disabled = false`; find the previously active ``, set `disabled = true`. Keep the `theme-` body className for any rule that depends on it (we keep the convention). | +| `src/styles.css` + `styles-modern.css` + `styles-concreteinfo.css` | Remove the inlined `body.theme- { … }` blocks (one per existing 25 themes). Move them to `src/styles/themes/.css`. Pure code-motion — no rules change. | +| `README.md:129-159` (Themes section) | Update list to 37 themes, mention new categories. | + +## New Themes (12) + +| id | label | category | notes | +|---|---|---|---| +| `catppuccin-latte` | Catppuccin Latte | light | Warm pastel | +| `catppuccin-frappe` | Catppuccin Frappé | dark | Muted pastels | +| `catppuccin-macchiato` | Catppuccin Macchiato | dark | Medium contrast | +| `catppuccin-mocha` | Catppuccin Mocha | dark | High contrast pastels | +| `one-light` | One Light | light | Atom One Light sibling | +| `tokyo-night-storm` | Tokyo Night Storm | dark | Variant of existing Tokyo Night | +| `synthwave-84` | Synthwave '84 | dark | Neon-on-dark | +| `outrun` | Outrun | dark | Magenta/cyan | +| `winter-is-coming-light` | Winter is Coming (Light) | light | Light variant of existing dark | +| `winter-is-coming-dark` | Winter is Coming (Dark) | dark | New dark variant | +| `solarized-dark-hc` | Solarized Dark (High Contrast) | high-contrast | Accessibility-focused | +| `spring-light` | Spring Light | seasonal | First seasonal theme; same palette as Solarized Light but with green accents | + +Categories are independent from `isDark`; e.g. `winter-is-coming-light` has `isDark: false` and `category: 'light'` but conceptually belongs to the "winter is coming" family. Future seasonal themes can share a `family: 'winter-is-coming'` field without changing the public API. + +## Data Flow + +1. App starts → `main.js` requires `ThemeRegistry.bootstrap.js` → calls `register()` for each of the 37 themes. +2. `main.js` menu builder reads `ThemeRegistry.list()` + `categories()`, generates the View → Theme submenu programmatically. +3. Renderer on init sends `get-theme` IPC → `main.js` replies with stored theme id. +4. `renderer.js:3005-3007` `applyTheme(id)` toggles `disabled` on the matching ``; sets `body.className = 'theme-'` for legacy rule compatibility. +5. User picks a new theme in menu → main process `setTheme(id)` → persists via `electron-store` → broadcasts `theme-changed` IPC → renderer `applyTheme(id)` re-toggles. + +## Error Handling + +| Scenario | Handling | +|---|---| +| Stored theme id no longer exists (e.g. after downgrade) | `setTheme()` falls back to `atomonelight`; logs a warning. Renderer also re-applies defensively. | +| Missing theme CSS file (e.g. partial install) | `` simply doesn't activate; no JS error. App continues with previous active theme. | +| `register()` called with duplicate id | Throws synchronously — startup fails loudly rather than silently shadowing an existing theme. | +| Theme CSS file fails to parse (syntax error) | Browser logs error to console; the disabled `` is never activated so no visual breakage. | + +## Testing + +1. **Unit (`tests/theme-registry.test.js`)** — `register` adds to list, `unregister` removes, `get` returns the theme, `categories()` returns unique categories in registration order, `lightThemes()`/`darkThemes()` filter correctly. Throws on duplicate id. Throws on missing id in `get()`. +2. **Unit (`tests/theme-menu-builder.test.js`)** — Given a list of 5 sample themes (2 light, 2 dark, 1 hc), `buildThemeMenu` returns a `MenuItem[]` with the correct structure: submenu labels, accelerators, `type: 'radio'`, `checked` matching current selection, `click` handlers wired. +3. **Snapshot (`tests/theme-registry-bootstrap.test.js`)** — At startup, `ThemeRegistry.list()` returns exactly 37 themes with the expected ids in the expected order. +4. **Existing tests** — `tests/project-meta.test.js` already checks `v${version}` in README (now 4.8.0); unchanged. + +## Risks + +| Risk | Likelihood | Mitigation | +|---|---|---| +| Per-theme CSS files duplicate common rules across 37 files | Medium | Factor the shared editor-surface rules (caret, selection, scrollbar) into a `styles/themes/_base.css`; per-theme files only carry palette tokens. | +| Preloading 37 `` tags delays first paint | Low | Total CSS ≈ 185 KB; gzip ≈ 30 KB; below the 100 ms first-paint budget on Electron's bundled Chromium. Measure with `webContents.getPrintersAsync`-style timing if needed. | +| Body className + disabled-link double-state could drift | Low | Single function `applyTheme(id)` is the only mutation path; unit-tested with both states asserted. | +| README Themes list grows unwieldy at 37 | Low | Group by category in the README; collapse to one-liners per theme with a table. | + +## Acceptance Criteria + +- [ ] `src/main/ThemeRegistry.js` exists with the public API documented. +- [ ] 37 themes registered at startup; `list().length === 37`. +- [ ] `main.js` menu builder produces the View → Theme submenu from the registry (no hardcoded theme names). +- [ ] Renderer switches themes by toggling ``; no full page reload. +- [ ] Theme persists across restarts via `electron-store`. +- [ ] All existing 25 themes look identical to v4.8.0 (visual regression by inspection — themes are CSS, no logic change). +- [ ] `npm test` passes with the new theme-registry + menu-builder test files added. +- [ ] `npm run lint` clean, `npm run format:check` clean. +- [ ] README updated to list all 37 themes by category.