A lightweight, clean desktop app for opening, reading, editing, and exporting Markdown files.
Built with Electron, React, TypeScript, and electron-vite.
Current version: v1.0.7
Download: Windows · macOS (DMG) · Linux (AppImage) · Linux (DEB)
Moji (文字) literally means "letter", "character", or "writing" in Japanese. Short and easy to remember, it evokes characters and writing. The name fits its purpose: opening, editing, previewing, and exporting Markdown smoothly—without distractions.
- Open Markdown files: supports
.mdand.markdownthrough file dialog, drag and drop, CLI/file association entry points, and single-instance forwarding. - Multi-document workspace: horizontal tabs, dirty markers, close buttons, duplicate-file detection, and unsaved-change confirmation with clear action icons.
- Tab management: close other tabs, tabs to the right, saved tabs, or all tabs from the document tab menu.
- Preview mode: sanitized Markdown rendering with heading anchors, outline navigation, tables, task lists, footnotes, definition lists, subscript/superscript, highlight/insert marks, emoji shortcodes, LaTeX math via KaTeX (
$…$and$$…$$), linkify, typographer, syntax-highlighted code, and copy buttons for code blocks. - Graphics and Mermaid diagrams: every valid fenced
mermaidblock supported by bundled Mermaid renders as a responsive diagram, including flowcharts, sequence, Gantt, class, ER, state, and journey diagrams. Click any rendered SVG or Markdown image to inspect it in a modal with zoom, drag navigation, a minimap, and individual PNG export; malformed Mermaid blocks remain readable code blocks. - Outline navigation: collapsible heading tree available in Preview and Editor modes. Preview uses scroll-spy; clicking any heading scrolls preview or moves editor cursor to its Markdown source.
- Search and replace: top-bar search finds visible Markdown text even across inline formatting, distinguishes the active match, and shows the active/total occurrence count. Preview offers previous/next navigation; Editor separates navigation from replace-one/replace-all controls.
- Editor mode: CodeMirror 6 Markdown editor with line numbers, history, wrapping, localized untitled document names, Markdown formatting shortcuts, and save/save as flows.
- Live preview: while editing, toggle a resizable split view from the top bar (or
Ctrl+\) to keep the rendered preview beside the source editor. Scrolling either pane moves the other to the matching part of the document, and the pane ratio is remembered. The toggle is disabled in view mode and when the workspace is too narrow for two readable panes. - Untitled document recovery: documents without a filesystem path are saved as internal recovery drafts and reopened after restarting Moji. Saving as a real file or closing the tab removes the recovery draft.
- Large documents: files are streamed from the main process in UTF-8 chunks and decoded incrementally, Markdown is parsed and highlighted in a Web Worker, and documents above 20 MB switch the preview to block virtualization so only the visible part stays in the DOM.
- Batch open with progress: selecting many files opens them with bounded concurrency, a progress banner, and a cancel action that keeps whatever already opened.
- Export mode: export the active document as HTML, PDF, or PNG. PDF supports A4, Letter, Legal, portrait, and landscape; long code lines wrap in PDF and PNG exports.
- Export progress: HTML, PDF, and PNG exports report their current phase and can be cancelled; a cancelled or failed export never leaves a partial file behind.
- Diagram exports: rendered Mermaid diagrams are embedded as self-contained SVG in HTML, PDF, and PNG exports.
- Local images: images referenced relative to the document are served through an authorized
moji-asset://protocol, restricted to directories of documents you actually opened, loaded lazily and cached in memory. - Settings view: centered in-workspace panel for language, untitled-document recovery, preview typography, editor and preview font sizes, reading width, and a localized shortcut reference.
- About view: in-workspace panel showing app name, version (from
package.json), author, repository link, and the story behind the name. - Markdown guide: bundled localized reference documents (
samples/markdown-guide.<locale>.md) opened from the status bar in Editor mode with split view enabled, showing the read-only Markdown source beside its rendered preview. You can select and copy examples; narrow workspaces show the source until there is room for both panes. - Recent files: Welcome screen shows recently opened Markdown files and lets you reopen or remove entries.
- Remembered app state: window size/position, recent files, last used folder, language, preview typography, editor and preview font sizes, reading width, Markdown preview theme, live preview split and pane ratio, and untitled-document recovery preference are persisted in user settings.
- Update checks: installed Windows NSIS and Linux AppImage builds check GitHub Releases and link to the release page when a newer version is available, so you can choose the correct artifact.
- Markdown themes: dark/light toggle for rendered Markdown. App chrome remains dark; exports always use the light theme.
- Internationalization: English (United States and United Kingdom), Portuguese (Brazil and Portugal), Spanish, French, German, Italian, Dutch, Arabic, Hindi, Japanese, Chinese (Simplified and Traditional, Taiwan), and Russian. Initial language follows the OS when possible and user choice is persisted.
- Security: sandboxed renderer, context isolation,
nodeIntegration: false, DOMPurify sanitization, and external links opened in the OS browser. - Keyboard shortcuts: common file, search (Ctrl+G / Ctrl+Shift+G for next and previous match), replace, tab, preview, split view, export, fullscreen, and font-size (Ctrl+Plus / Ctrl+Minus / Ctrl+0, in Editor mode too) actions, plus Ctrl+M to leave editor focus and Ctrl+Q to quit through the unsaved-changes guard; Settings lists every available shortcut.
Windows and Linux install from the downloads above with no extra steps.
macOS refuses to open Moji the first time, saying it is damaged or that Apple cannot check it for malicious software. The app is fine. Signing an app requires a paid Apple Developer account, which Moji does not have yet, so macOS treats it as coming from an unidentified developer.
- Drag Moji from the DMG into your Applications folder.
- Double-click it. macOS blocks it. Dismiss the dialog.
- Open System Settings > Privacy & Security, scroll to the Security section, and click Open Anyway next to the message about Moji.
- Confirm. macOS remembers the choice, so this is a one-time step per version.
Control-clicking the app and choosing Open no longer works on macOS 15 (Sequoia) and later: Apple removed that override. System Settings is the only route through the interface.
If you prefer the terminal, clearing the quarantine flag skips the prompts entirely:
xattr -dr com.apple.quarantine /Applications/Moji.appUpdate from GitHub Releases by downloading a new DMG. Signing and notarizing future macOS builds removes Gatekeeper warnings.
- Node.js
^20.19.0 || >=22.12.0(required by Vite 7 and electron-vite 5; packaging also needsrequire()of ES modules, unflagged since Node 22.12) - npm
npm install
npm run dev
npm run typecheck
npm run buildUseful scripts:
npm run dev: launch Electron with hot reload.npm run dev:update: launch development mode and simulate an available99.0.0update without network access.npm run typecheck: run TypeScript checks without emitting files.npm run verify: the gate thedist*scripts run first. Currently it istypecheckalone: this repository has no automated test suite.npm run build: build main, preload, and renderer intoout/.npm run preview: run the built app preview.npm run benchmark:corpus: generate a local 1/5/20/50 MB Markdown corpus under.tmp/benchmark-corpus/.npm run benchmark:ipc: measure the cost of moving a document across the process boundary.npm run benchmark:record: build, generate the corpus, and record a run intodocs/baseline-v1.json.npm run benchmark:compare: compare a run against the recorded baseline and the budgets indocs/performance-budget.md.
npm run dist
npm run dist:win
npm run dist:linux
npm run dist:macArtifacts are written to release/.
Current packaging targets:
- Windows: NSIS installer, x64, with GitHub Release checks.
- Linux: AppImage with GitHub Release checks, plus deb for manual installation.
- macOS: universal (Apple Silicon + Intel) DMG and ZIP.
File associations for .md and .markdown are declared in electron-builder.yml.
macOS releases are not code-signed or notarized, because that requires a paid Apple Developer account. Consequences:
- Gatekeeper blocks the app when the DMG is downloaded from the web. Users open it through System Settings > Privacy & Security > Open Anyway, or by clearing the quarantine flag with
xattr -dr com.apple.quarantine /Applications/Moji.app. Control-clicking the app and choosing Open stopped working in macOS 15 (Sequoia), where Apple removed that override. The user-facing steps live under Installation. - Update checks stay disabled on macOS. Users update by downloading a new DMG from GitHub Releases.
To sign locally, install an Apple Developer ID certificate in the keychain and drop the CSC_IDENTITY_AUTO_DISCOVERY=false override; build/entitlements.mac.plist and hardenedRuntime are already configured for notarization.
Releases are built and published by hand. There is no CI workflow in this repository.
- Update
versioninpackage.jsonandpackage-lock.json. - Run
npm run verify(typecheck). Thedist*scripts already runverifyfirst, so a release cannot be produced from a failing tree. - Build each platform on that platform:
npm run dist:win,npm run dist:linux,npm run dist:mac. Cross-building is not set up, andelectron-builder.ymlpublishes to a draft GitHub Release. - Commit the version bump, then create and push the matching tag, such as
v1.0.5. - Upload the artifacts to the draft release and publish it once Windows and Linux are in place: NSIS, AppImage, DEB, and the
latest.yml/latest-linux.ymlupdate metadata thatelectron-updaterreads.
macOS is unsigned and secondary. A missing or broken DMG should not hold back a good Windows and Linux release, so publish without it rather than waiting.
electron-updater checks GitHub Releases only in packaged Windows NSIS builds and Linux AppImages. Development, deb, and macOS builds do not check for updates. When a newer version is found, Moji opens GitHub Releases for the user to choose and install the correct artifact. Windows production releases should use an Authenticode certificate through electron-builder signing environment variables; never store certificate credentials in repository.
electron/
main.ts Window lifecycle, persisted bounds, file opening, single-instance flow, close guard, macOS application menu, IPC registration
preload.ts Safe renderer API exposed through contextBridge, including the chunked document read
shared.ts Shared IPC names, settings, export types, languages, recent-file limits, supported extensions
ipcInput.ts Normalization of every value the renderer sends over IPC
fileCapabilities.ts Paths the user actually chose; the only paths writes and asset reads are allowed on
assetPaths.ts Authorization for the moji-asset:// protocol, resolved through realpath
assetCache.ts Bounded in-memory LRU cache for served images
documentStream.ts Chunked file reads, paired with documentDecoder.ts for streaming UTF-8 decoding
openPool.ts Bounded-concurrency map used by the multi-file open session
settings.ts User settings persistence, window bounds, recent files, preview theme, and last dialog directory
drafts.ts Recovery storage for untitled documents (draftStore/draftJournal/draftCapacity)
export.ts HTML/PDF/PNG export implementation with progress, cancellation, and remembered output directory
png.ts Streaming PNG encoder (pngWorker/pngScanlines) that keeps tall-document exports within memory
updater.ts GitHub release checks for packaged NSIS and AppImage builds
performance.ts Local numeric measurements, plus the benchmark.ts runner
src/
App.tsx Renderer state, document actions, close guard wiring, mode switching, split-view scroll sync
components/ Top bar, tabs, sidebar, outline tree, preview, split view, Mermaid viewer, editor, export/settings/about dialogs, progress banners, confirm dialog, welcome view
hooks/ Grouped app state and the observed workspace width
workers/ Markdown parse/highlight/render worker
lib/ Markdown and Mermaid rendering, outline extraction, preview scroll/search/virtualization, split-view scroll mapping, draft journaling, export HTML, metrics
locales/ en, pt-BR, es, ja, zh, ru translation files
styles/ Theme tokens, app shell CSS, Markdown preview CSS
types/ Preload API typings and Vite environment types
assets/ App logo used by the welcome and about views
samples/ Bundled Markdown documents (full Markdown guide)
scripts/ electron-vite runner, Electron binary install, benchmark corpus/run/compare
docs/ README screenshots, performance budget, recorded benchmark baseline
.ai-framework/RULES.md: project rules for AI-assisted changes..ai-framework/DESIGN.md: visual system, tokens, layout, and component rules.openspec/specs/: current behavior specs (app-shell, appearance, automatic-updates, document-export, document-translation, internationalization, markdown-editing, markdown-viewing).docs/performance-budget.md: performance budgets the benchmark scripts compare against.
MIT © Alex Ishida








