Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/xykong/flux-markdown/llms.txt

Use this file to discover all available pages before exploring further.

FluxMarkdown is a macOS QuickLook extension that renders Markdown files with full support for math (KaTeX), diagrams (Mermaid), and syntax highlighting. This page explains why the project uses a hybrid Swift + TypeScript architecture, how the components fit together, and how data moves from a file on disk to pixels on screen.

Why a hybrid architecture?

A pure-native Swift implementation of GitHub-Flavored Markdown with Mermaid, KaTeX, and syntax highlighting would require reimplementing a significant portion of the JavaScript ecosystem from scratch. That path both increases maintenance burden and risks visual divergence from the reference rendering experience. FluxMarkdown instead uses a native Swift host that embeds a WKWebView running a bundled TypeScript renderer. This approach lets the project reuse battle-tested npm packages directly — the same libraries used by Markdown Preview Enhanced — while keeping the app sandboxed and distributed as a standard macOS extension.
Benefits:
  • Reuses markdown-it, mermaid, katex, and highlight.js without porting them.
  • Rendering output stays visually consistent with the JavaScript ecosystem.
  • The TypeScript layer is independently testable with Jest.
Trade-offs:
  • Slightly higher memory use than a native text view (WKWebView runs in its own process).
  • A shared WKProcessPool is used to cap process count when previewing multiple files simultaneously.
  • Features that require Node.js APIs (fs, child_process) cannot run inside the App Sandbox.

Component overview

Swift host

The Swift side is divided across two targets in project.yml:
  • Sources/Markdown/ — A minimal SwiftUI container (the Host App). It provides the app bundle that embeds the extension and handles auto-update via Sparkle.
  • Sources/MarkdownPreview/ — The QuickLook Extension (AppKit). PreviewViewController implements QLPreviewingController, manages the WKWebView lifecycle, reads the file from disk, and calls into JavaScript to trigger rendering.

TypeScript renderer

The web-renderer/ directory is a standalone Vite project. It is compiled to a single self-contained dist/index.html with all JavaScript, CSS, and fonts inlined (via vite-plugin-singlefile). Xcode includes this file as a bundle resource; Swift loads it with webView.loadFileURL(_:allowingReadAccessTo:). The renderer exposes two globals that Swift calls:
FunctionPurpose
window.renderMarkdown(content, options)Parse and render Markdown to the DOM
window.renderSource(content, theme)Show raw Markdown source with diff marks
window.updateTheme(theme)Switch light/dark/system theme without a full re-render

Directory structure

Sources/Markdown/

Host App (SwiftUI). Minimal container that embeds the QuickLook extension and bundles Sparkle for auto-updates.

Sources/MarkdownPreview/

QuickLook Extension (AppKit). Contains PreviewViewController.swift — the core of the extension.

web-renderer/

TypeScript rendering engine (Vite + markdown-it). Compiled to a single inlined dist/index.html.

scripts/

Versioning, DMG creation, Homebrew cask updates, and GitHub release automation.
PathPurpose
project.ymlXcodeGen configuration. Edit this to add files or targets; run make generate to apply.
MakefileOrchestrates the full build: renderer → XcodeGen → xcodebuild.
.versionSingle source of truth for the version string (e.g. 1.13.149).
Sources/Shared/Code shared between the Host App and the extension targets.
Never edit FluxMarkdown.xcodeproj directly. It is generated by XcodeGen from project.yml and is excluded from version control. Always modify project.yml and run make generate.

Data flow: file preview step by step

When a user presses Space on a .md file in Finder, the following sequence runs:
1

QuickLook invokes the extension

macOS selects the FluxMarkdown extension based on the UTI (net.daringfireball.markdown). QuickLook instantiates PreviewViewController.
2

Swift reads the file

preparePreviewOfFile(at:completionHandler:) is called with the file URL. Swift reads the file contents into a String. Files larger than 500 KB are truncated at a newline boundary to keep the preview responsive.
3

WKWebView loads the HTML bundle

viewDidLoad() loads dist/index.html from the app bundle using webView.loadFileURL(_:allowingReadAccessTo:). The renderer initialises its libraries and signals readiness back to Swift via the logger message handler.
4

Swift calls window.renderMarkdown

Once the handshake is complete, Swift calls webView.evaluateJavaScript("return window.renderMarkdown(content, options)"). The Markdown string and options (theme, base URL, language) are JSON-serialised before being passed across the bridge.
5

TypeScript renders to the DOM

markdown-it parses the Markdown. KaTeX processes math blocks, Mermaid renders diagrams asynchronously, and Highlight.js applies syntax colouring to fenced code blocks. The result is written to the DOM.
6

Local images are served via a custom scheme

The LocalSchemeHandler in Swift handles local-md:// requests, reading image files from the directory containing the previewed Markdown file and returning them to the WebView. This is how images in Markdown are resolved while remaining within the App Sandbox.

JS-to-Swift bridge

The bridge is intentionally narrow. JavaScript logs back to Swift by posting messages to a named handler:
// web-renderer/src/index.ts
window.webkit.messageHandlers.logger.postMessage("render complete");
// Sources/MarkdownPreview/PreviewViewController.swift
userContentController.add(self, name: "logger")

public func userContentController(_ controller: WKUserContentController,
                                  didReceive message: WKScriptMessage) {
    // message.name == "logger"
    // message.body contains the log payload
}
Three message handlers are registered: logger (diagnostics), linkClicked (external link routing), and pinchZoom / gestureZoom (zoom events).

App Sandbox considerations

The extension runs under the macOS App Sandbox. The following constraints apply:
The extension receives read-only, security-scoped access to the specific file QuickLook is previewing. url.startAccessingSecurityScopedResource() is called before reading and balanced with stopAccessingSecurityScopedResource() when the preview is dismissed.
Outbound network access is disabled. The renderer bundle is fully self-contained — all fonts, CSS, and JavaScript are inlined at build time. External images in Markdown are not fetched; only local images (served via the local-md:// scheme handler) are displayed.
The original @shd101wyy/mume rendering engine depends on Node.js APIs (fs, path, child_process) that are unavailable in a sandboxed WebView. FluxMarkdown reconstructs the rendering stack using browser-compatible client-side libraries only.
The following MPE features cannot be supported in this environment due to App Sandbox and Node.js constraints: code chunk execution and local file imports (@import). Note that FluxMarkdown implements its own PDF export using NSPrintOperation — this is distinct from MPE’s server-side PDF generation.

Versioning

The project uses a three-part semantic version stored in the .version file:
1.13.149
│  │   └── Build number (aligns with git commit count)
│  └────── Minor version
└────────── Major version
make generate reads .version, exports MARKETING_VERSION and CURRENT_PROJECT_VERSION as environment variables, and passes them to xcodegen generate. This populates CFBundleShortVersionString and CFBundleVersion in both Info.plist files without manual editing. Use make release [major|minor|patch] to increment the version, update CHANGELOG.md, build a DMG, and create a GitHub release.

Development setup

Prerequisites, build steps, and daily development workflow.

Web renderer overview

Deep dive into the TypeScript rendering pipeline and plugin system.

Renderer plugins

How KaTeX, Mermaid, and Highlight.js are integrated.

Release process

How to cut a new release and update Homebrew casks.

Build docs developers (and LLMs) love