md-preview-app-macos
Native macOS Markdown viewer app with Quick Look extension, Mermaid diagrams, KaTeX math, document outline, and editor integration
What this skill does
# MD Preview App — macOS Markdown Viewer > Skill by [ara.so](https://ara.so) — Daily 2026 Skills collection. A fast, native macOS app (AppKit + WKWebView) for reading `.md` files. No Electron, no browser tab. Features include a document outline sidebar, Mermaid diagram rendering, KaTeX math, Quick Look extension, in-document search, and one-click "Open With" for popular editors. --- ## Installation ### Pre-built (recommended) Download the signed and notarized DMG from the [Releases page](https://github.com/pluk-inc/md-preview.app/releases) and drag to `/Applications`. ### Build from source ```sh git clone [email protected]:pluk-inc/md-preview.app.git cd md-preview.app open md-preview.xcodeproj # Build and run the `md-preview` scheme # SPM resolves Sparkle + swift-markdown on first build ``` **Requirements:** macOS 15+, Xcode with Swift 6.0, Apple Silicon or Intel. --- ## Project Layout ``` md-preview/ # Main app target (AppKit, WKWebView) quick-look/ # Quick Look extension (.appex) scripts/ # Release & rollback automation Version.xcconfig # MARKETING_VERSION + CURRENT_PROJECT_VERSION appcast.xml # Sparkle update feed ``` --- ## Key Dependencies (Swift Package Manager) | Package | Purpose | |---|---| | `swift-markdown` (Apple) | Markdown parsing via cmark-gfm | | `Sparkle` | Auto-update framework | | Mermaid (bundled JS) | Fenced `mermaid` block rendering | | KaTeX (bundled JS) | LaTeX math rendering | --- ## Core Architecture ### Markdown → HTML Pipeline ```swift import Markdown // Parse a markdown string into a Document let source = try String(contentsOf: fileURL, encoding: .utf8) let document = Document(parsing: source) // Walk the document tree to build HTML + extract headings for TOC struct HTMLRenderer: MarkupVisitor { typealias Result = String mutating func visitDocument(_ document: Document) -> String { document.children.map { visit($0) }.joined() } mutating func visitHeading(_ heading: Heading) -> String { let text = heading.plainText let anchor = text.lowercased() .replacingOccurrences(of: " ", with: "-") .filter { $0.isLetter || $0.isNumber || $0 == "-" } let level = heading.level return "<h\(level) id=\"\(anchor)\">\(text)</h\(level)>\n" } mutating func visitParagraph(_ paragraph: Paragraph) -> String { "<p>\(paragraph.children.map { visit($0) }.joined())</p>\n" } mutating func visitCodeBlock(_ codeBlock: CodeBlock) -> String { let lang = codeBlock.language ?? "" if lang == "mermaid" { // Render as Mermaid diagram via bundled mermaid.min.js return "<div class=\"mermaid\">\(codeBlock.code)</div>\n" } if lang == "math" { // Render as KaTeX display math return "<div class=\"math-display\">$$\(codeBlock.code)$$</div>\n" } return "<pre><code class=\"language-\(lang)\">\(codeBlock.code)</code></pre>\n" } } ``` ### Loading HTML into WKWebView ```swift import WebKit class PreviewViewController: NSViewController, WKNavigationDelegate { let webView = WKWebView() func loadMarkdown(from url: URL) { let source = try! String(contentsOf: url, encoding: .utf8) var renderer = HTMLRenderer() let body = renderer.visit(Document(parsing: source)) let html = wrapInTemplate(body) // Load with base URL so bundled assets (mermaid, KaTeX) resolve let resourceURL = Bundle.main.resourceURL! webView.loadHTMLString(html, baseURL: resourceURL) } // Handle navigation: open external links in default browser func webView(_ webView: WKWebView, decidePolicyFor action: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { if action.navigationType == .linkActivated, let url = action.request.url, url.scheme == "https" || url.scheme == "http" { NSWorkspace.shared.open(url) decisionHandler(.cancel) } else { decisionHandler(.allow) } } } ``` ### Document Outline (TOC) Sidebar ```swift struct HeadingItem: Identifiable { let id = UUID() let level: Int let text: String let anchor: String } // Extract headings while parsing func extractHeadings(from document: Document) -> [HeadingItem] { var headings: [HeadingItem] = [] for child in document.children { if let heading = child as? Heading { let text = heading.plainText let anchor = text.lowercased() .replacingOccurrences(of: " ", with: "-") headings.append(HeadingItem(level: heading.level, text: text, anchor: anchor)) } } return headings } // Jump to heading via JavaScript func scrollTo(anchor: String) { let js = "document.getElementById('\(anchor)')?.scrollIntoView({behavior:'smooth'})" webView.evaluateJavaScript(js, completionHandler: nil) } ``` --- ## Quick Look Extension The extension lives in `quick-look/` and is a separate `.appex` target. It reuses the same HTML rendering pipeline so Mermaid diagrams and math work offline in Finder spacebar previews. ```swift // quick-look/PreviewViewController.swift (skeleton) import Quartz class PreviewViewController: NSViewController, QLPreviewingController { func preparePreviewOfFile(at url: URL, completionHandler: @escaping (Error?) -> Void) { let source = try! String(contentsOf: url, encoding: .utf8) var renderer = HTMLRenderer() let html = wrapInTemplate(renderer.visit(Document(parsing: source))) let base = Bundle.main.resourceURL! webView.loadHTMLString(html, baseURL: base) completionHandler(nil) } } ``` --- ## Mermaid Diagrams Fenced `mermaid` blocks are detected during parsing and emitted as `<div class="mermaid">` elements. The bundled `mermaid.min.js` initializes on page load — no CDN required. **Markdown input:** ````markdown ```mermaid flowchart TD A[Input .md] --> B[swift-markdown] B --> C[WKWebView] ``` ```` **HTML template snippet (how it's wired):** ```html <script src="mermaid.min.js"></script> <script>mermaid.initialize({ startOnLoad: true, theme: 'default' });</script> ``` --- ## KaTeX Math | Syntax | Usage | |---|---| | `$x^2 + y^2$` | Inline math | | `$$\int_0^1 f(x)\,dx$$` | Display math | | ` ```math ` block | Fenced display math | Copying a rendered formula pastes the original LaTeX source (via the bundled `copy-tex` KaTeX extension). --- ## "Open With" Editor Integration The app queries Launch Services for apps that declare an editor role for Markdown UTIs, filters to known editors, and remembers your pick. ```swift let mdUTI = UTType("net.daringfireball.markdown")! let editors = NSWorkspace.shared.urlsForApplications( toOpen: fileURL // or query by UTI ).filter { url in let knownEditors = ["com.microsoft.VSCode", "com.todesktop.230313mzl4w4u92", // Cursor "dev.zed.zed", "com.sublimetext.4", "com.barebones.bbedit", "com.panic.Nova", "com.coteditor.CotEditor", "com.macromates.TextMate", "org.vim.MacVim", "com.apple.dt.Xcode", "com.apple.TextEdit"] let bundleID = Bundle(url: url)?.bundleIdentifier ?? "" return knownEditors.contains(bundleID) } // Open the file in chosen editor NSWorkspace.shared.open([fileURL], withApplicationAt: editorURL, configuration: .init(), completionHandler: nil) ``` --- ## Share / Copy Source The Share toolbar item feeds the Markdown *text* (not a file URL) to `NSSharingServicePicker`, so **Copy** writes raw Markdown to the cl
Related in Writing & Docs
jax-development
IncludedUse this skill when the user is writing, debugging, profiling, refactoring, reviewing, benchmarking, parallelising, exporting, or explaining JAX code, or when they mention JAX, jax.numpy, jit, grad, value_and_grad, vmap, scan, lax, random keys, pytrees, jax.Array, sharding, Mesh, PartitionSpec, NamedSharding, pmap, shard_map, Pallas, XLA, StableHLO, checkify, profiler, or the JAX repo. It helps turn NumPy or PyTorch-style code into pure functional JAX, fix tracer/control-flow/shape/PRNG bugs, remove recompiles and host-device syncs, choose transforms and sharding strategies, inspect jaxpr/lowering/IR, and benchmark compiled code correctly.
nature-article-writer
IncludedDrafts, rewrites, diagnostically critiques, and style-calibrates primary research manuscripts for Nature and Nature Portfolio journals. Use when the user wants a Nature-style title, summary paragraph or abstract, introduction, results, discussion, methods, figure legends, presubmission enquiry, cover letter, reviewer response, or when a scientific draft sounds generic, jargon-heavy, structurally weak, or AI-ish and needs precise, broad-reader-friendly prose without inventing data, analyses, or references. Best for primary research articles and letters rather than reviews or press releases unless explicitly adapting one.
deckrd
IncludedDocument-driven framework that derives requirements, specifications, implementation plans, and executable tasks from goals through structured AI dialogue. Use when user says "write requirements", "create spec", "plan implementation", "derive tasks", "structure this feature", "break down into tasks", or "document this module". Also use for reverse engineering existing code into docs (/deckrd rev). Do NOT use for direct code writing — use /deckrd-coder after tasks are generated. Do NOT use when the user only wants to run or fix existing code without planning.
clinical-decision-support
IncludedGenerate professional clinical decision support (CDS) documents for pharmaceutical and clinical research settings, including patient cohort analyses (biomarker-stratified with outcomes) and treatment recommendation reports (evidence-based guidelines with decision algorithms). Supports GRADE evidence grading, statistical analysis (hazard ratios, survival curves, waterfall plots), biomarker integration, and regulatory compliance. Outputs publication-ready LaTeX/PDF format optimized for drug development, clinical research, and evidence synthesis.
handling-sf-data
IncludedSalesforce data operations with 130-point scoring. Use this skill to create, update, delete, bulk import/export, generate test data, and clean up org records using sf CLI and anonymous Apex. TRIGGER when: user creates test data, performs bulk import/export, uses sf data CLI commands, needs data factory patterns for Apex tests, or needs to seed/clean records in a Salesforce org. DO NOT TRIGGER when: SOQL query writing only (use querying-soql), Apex test execution (use running-apex-tests), or metadata deployment (use deploying-metadata).
accelint-ac-to-playwright
IncludedConvert and validate acceptance criteria for Playwright test automation. Use when user asks to (1) review/evaluate/check if AC are ready for automation, (2) assess if AC can be converted as-is, (3) validate AC quality for Playwright, (4) turn AC into tests, (5) generate tests from acceptance criteria, (6) convert .md bullets or .feature Gherkin files to Playwright specs, (7) create test automation from requirements. Handles both bullet-style markdown and Gherkin syntax with JSON test plan generation and validation.