Swift + SwiftUI port of the app, Quick Look extension, and render helper - #16
Open
santiagobarros wants to merge 14 commits into
Open
Swift + SwiftUI port of the app, Quick Look extension, and render helper#16santiagobarros wants to merge 14 commits into
santiagobarros wants to merge 14 commits into
Conversation
A stale copy merged over by ditto keeps files the new bundle no longer ships, which breaks the code-signature seal and makes Gatekeeper reject the app.
The README promises no network calls, but the app contacted the GitHub releases API on every launch. The check now runs only from a new "Check for Updates…" menu item, with explicit feedback for up-to-date and unreachable cases.
New Settings window (Cmd+,) with three document fonts: the existing serif, GitHub's system sans stack, and Geist (vendored from the geist npm package with a pinned SHA-256 like the other libraries). The choice persists in NSUserDefaults and is applied live to all open windows as a data-font attribute driven by an injected user script.
A data-based QLPreviewProvider app extension built into Contents/PlugIns by build.sh — no Xcode project required. Markdown renders with the bundled marked.umd.js under JavaScriptCore; LaTeX math is extracted from the source (code-fence aware, so $a_i$ survives emphasis parsing) and rendered with katex.renderToString, which needs no DOM — Quick Look executes no JavaScript in data-based previews. KaTeX CSS ships with its woff2 fonts inlined as data URIs. Local images referenced by the markdown are embedded as cid: attachments on the QLPreviewReply, since data-based previews have no base URL; a read-only filesystem exception lets the sandboxed extension read them (macOS additionally asks once for privacy-protected folders like Desktop). Mermaid fences degrade to a styled source block for now. The appex is signed with its entitlements before the outer app — codesign --deep would strip them. Note for testing: qlmanage -p crashes on macOS 26 for all third-party Quick Look extensions; use a QLPreviewPanel harness or Finder itself.
The Quick Look sandbox cannot host a browser engine (WebContent processes are terminated immediately, and view-based preview extensions are never launched by quicklookd — both verified on macOS 26), so Mermaid is solved in two layers: 1. The app caches every SVG it renders, keyed by sha256 of the trimmed diagram source, in both light and dark themes (viewer.js renders the non-displayed theme in the background after the visible pass). 2. On cache miss the extension asks an optional on-demand launchd helper over XPC. launchd registers no running process at rest, spawns the helper when the extension connects, and it exits after 45 seconds idle. It is opt-in (./install.sh --with-mermaid-helper) because launchd agents surface as Login Items. The extension prefers the SVG matching the current system appearance, then a live helper render, then a stale-theme SVG; unseen diagrams with no helper degrade to the styled source fallback. A permissions section in the README documents the model.
./build.sh installer builds a standard macOS installer whose customize pane offers two optional selections: setting Markdown Viewer as the default .md handler (pre-selected) and installing the Mermaid Quick Look helper (off by default, since it registers a Login Items entry). The choices run per-user postinstall scripts that call the same set-default-handler.py and register-mermaid-helper.sh now shipped in the app's Resources; install.sh reuses them instead of embedding its own copies. The app package removes any previous install before laying down the payload so stale files cannot break the signature seal.
build.sh auto-selects a signing identity — CODESIGN_IDENTITY override, then Developer ID Application (with hardened runtime and secure timestamp, the notarization prerequisites), then Apple Development, then ad-hoc (unchanged default for contributors without certificates) — and reports what it used. New ./build.sh notarize submits the release zip via notarytool and staples the ticket; the installer is signed when a Developer ID Installer certificate exists. The render helper derives its own team identifier from its signature and rejects XPC callers signed by any other team (macOS 13+ setCodeSigningRequirement). Ad-hoc builds carry no team, so local development skips the check.
A Swift Package under swift/ (buildable with Command Line Tools alone, no Xcode project) with four targets mirroring the ObjC architecture: a SwiftUI DocumentGroup app, the data-based Quick Look extension, the on-demand mermaid render helper, and a shared RenderHelperKit whose @objc XPC protocol keeps Swift and ObjC components interoperable. swift/build.sh reuses the root build.sh for bundle layout, vendored libraries, plists, entitlements, and signing, then swaps in the three Swift binaries — viewer.js/viewer.css remain the shared rendering pipeline. The Swift app writes mermaid cache entries with hashes identical to the ObjC build, so either implementation can read the other's cache. Parity gaps are listed in swift/README.md.
santiagobarros
force-pushed
the
pr/swift-port
branch
from
July 10, 2026 17:01
7a7c858 to
ffe6f24
Compare
Author
On the first launch where another app (usually Xcode on a developer Mac) owns Markdown files, ask once whether to make Markdown Viewer the default, using NSWorkspace's modern default-application API. Declining is remembered; if the app is already the default (e.g. the installer checkbox or install.sh set it), the prompt never appears.
…sions New optional, pre-selected choice: elects Markdown Viewer's Quick Look extension over any other Markdown previewer already registered (e.g. QLMarkdown), via pluginkit -e use, mirroring the existing default .md handler choice. install.sh now does the same unconditionally, matching its existing unconditional default-handler behavior.
…ages Installer.app renders custom welcome/conclusion resources via the legacy WebKit1 WebView class, which is deprecated (since macOS 10.14) and crashes on current macOS when that code path is exercised — this is what actually caused 'the installer says it failed' even though the payload had already been applied. RTF resources are rendered by TextKit directly, with no WebKit involved at all; confirmed via a minimal repro that the HTML path invokes WebView while the RTF path does not.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this is
A port of MDviewer's three native components to Swift + SwiftUI, Apple's recommended stack for new macOS apps — offered as a possible future direction for the project.
Stacked on #15 — this branch contains all of that PR's commits plus the
swift/directory; review #15 first, then this one shrinks to the Swift additions.Structure
A Swift Package under
swift/(buildable with Command Line Tools alone — no Xcode project required, preserving this repo's script-driven ethos):MarkdownViewerAppsrc/main.mDocumentGrouplifecycle: open panel, recents, and window-per-document come from the framework;PreviewModelowns the WKWebView, renderer pipeline, live reload with scroll restore, and all menu commandsQuickLookPreviewsrc/quicklook.mRenderHelpersrc/render-helper.mRenderHelperKitsrc/render-helper.h@objcnames match the ObjC build, so Swift and ObjC components interoperate during a transition./swift/build.shcompiles the targets and swaps the binaries into the bundle produced by the rootbuild.sh, which continues to own resources, vendored libraries, plists, entitlements, and signing.viewer.js/viewer.cssare shared unchanged — the rendering pipeline is identical by construction.Verification
swift buildandxcodebuild(Xcode 26).QLPreviewPanelsessions and reached the Swift helper over XPC for a never-before-seen diagram; no crash reports.Known gaps (also in
swift/README.md)NSWindow.tabbingMode, so the tab-by-default preference is lost (manual tabbing still works).