mirror of
https://github.com/amitwh/markdown-converter.git
synced 2026-10-02 01:29:32 +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
16 KiB
16 KiB
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
- Single implementation path. Delete the dead
#ascii-art-dialogmodal markup, itsModalManagerinstance, theshowASCIIGenerator*dead channels inpreload.js, and the corresponding renderer controller atrenderer.js:5942-6727. The standaloneascii-generator.htmlwindow remains the only path. - 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
figletnpm fonts (400+) accessible through a searchable picker. - Three output destinations. Insert at cursor (existing — wraps result in a fenced code block), Copy to clipboard (new), Save to
.txtfile (new). - Comprehensive test coverage. The current
textToASCII,createASCIIBox,getASCIITemplate, and the newfigletadapter all have ZERO tests today. Add full coverage — snapshot tests for known outputs across all fonts, integration tests for copy/save/insert. - Pure-module architecture. Move all algorithm code from
renderer.jsintosrc/main/AsciiArt.js(pure module, IPC-coupled viamain.js, mirroring theDocQA/DailyNotespattern).
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 +
figletstandard 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
- 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. - Copy to Clipboard button. Copies the rendered ASCII art to the OS clipboard. Uses Electron's
clipboard.writeText()(already available viarequire('electron').clipboardin main process — exposed aswindow.api.ascii.copy(text)via new preload helper). - Save to File button. Opens a
dialog.showSaveDialogwith default.txtextension, writes the rendered ASCII art. Returns the saved path on success. - Last-used font memory. New
electron-storekeyascii:lastFont. On window open, preselect the last-used font.
Data Flow
- User triggers
Ctrl+Shift+A(existing) or Tools → ASCII Art Generator menu (existing). main.js:5584openAsciiGenerator()creates/ focuses the standaloneBrowserWindow(existing behaviour).- Renderer loads
src/ascii-generator.html. The page callswindow.api.ascii.listFonts()on mount, populates the font picker. - User types text, selects font, chooses options (box style, padding) → the preview pane debounces (200 ms) and calls
window.api.ascii.generate(...). - Click "Insert": renderer emits existing
insert-generated-contentIPC → main forwards tomainWindow.webContents.send('insert-content', …)→ editor'stabManager.insertAtCursorruns, wrapped in fenced code block. - Click "Copy": renderer calls
window.api.ascii.copy(text)→ main process writes to clipboard viaclipboard.writeText. - 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
- 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 }. - Snapshot (
tests/ascii-art.fonts.test.js) — One snapshot per hand-coded font forgenerate({ text: 'HELLO', font: '<id>' }). Snapshots are checked intotests/__snapshots__/ascii-art.fonts.test.js.snap. Edge cases: empty string, single char, mixed case, digits. - Snapshot (
tests/ascii-art.templates.test.js) — One snapshot per existing template (arrow-right,flowchart, etc.). - Adapter (
tests/ascii-art.figlet-adapter.test.js) —figlet.generateis mocked; assert the adapter calls through with correct options, handles the promise, handles errors. - Integration (
tests/ascii-art.electron.test.js) — Spawn the actualascii-generator.htmlwindow viaelectronbinary in headless mode and assert the window loads without console errors. Skip if no display available. - Preload (
tests/preload-ascii.test.js) — Asserts all newascii:*channels are invalidInvokeChannelsallow-list; oldshow-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-<id> from Spec 1). |
Acceptance Criteria
src/main/AsciiArt.jsexists withgenerate,listFonts,getFontMetaAPI.- 17 hand-coded fonts (5 existing + 12 new) registered, each renders the test word "HELLO" correctly.
figletlibrary loads lazily on firstfiglet:*font request; lists ~400+ fonts vialistFonts().- In-app modal (
#ascii-art-dialog,showASCIIGenerator*,preload.js:294-296) is fully deleted;git grepreturns 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-storeascii:lastFont. npm testpasses with the new test files added.npm run lintclean,npm run format:checkclean.npm run build:linuxsucceeds (no native-binding surprises fromfiglet).- README updated to mention the expanded font catalog and the new output destinations.