navi

Obsidian-style interactive graph viewer for org-roam — native window, no Emacs package required.
Log | Files | Refs | README

commit 23ed3fafc5f7ef9cf8591102c9a4a72199056e45
parent 42a70877f4bf7efde19cf20089f90cebe174d259
Author: Matthew Gantenbein <ganten1998@gmail.com>
Date:   Wed, 20 May 2026 22:35:39 -0500

Add macOS .app build script + bundle icon

scripts/build-macos.sh builds the Rust release binary and assembles
Navi.app (Info.plist + CFBundleIcon + ad-hoc codesign), then packages
it as Navi-<version>-<arch>.zip via ditto. Supports ARCH=universal for
x86_64 + arm64. Output goes to dist/ which is now .gitignored.

README updated with the pre-built download instructions and the
build-from-source flow for the .app.

Co-authored-by: Cursor <cursoragent@cursor.com>

Diffstat:
M.gitignore | 3+++
MREADME.md | 25++++++++++++++++++++++---
Aassets/icon.icns | 0
Ascripts/build-macos.sh | 92+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 117 insertions(+), 3 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -1,6 +1,9 @@ # Rust build output /target/ +# Packaging output +/dist/ + # Editor / OS .DS_Store .idea/ diff --git a/README.md b/README.md @@ -8,7 +8,7 @@ Written in Rust on top of [egui](https://github.com/emilk/egui) + [glow](https:/ ## Status -- **macOS** — primary target, fully supported. Tested on Apple Silicon (M-series) at 60 / 120 / 240 Hz. +- **macOS** — primary target, fully supported. Tested on Apple Silicon (M-series) at 60 / 120 / 240 Hz. A pre-built `Navi.app` ships with each release. - **Linux** — builds cleanly; the macOS-only `CADisplayLink` pacer is gated, so a small amount of glue (using `SwapInterval::Wait(1)` or Wayland presentation-time / DRM vblank) is required before it renders. Everything else (egui, winit, glutin, sqlite, emacsclient discovery) is cross-platform. - **Windows** — not supported. The `emacsclient` socket discovery is Unix-only and the macOS pacer would have to be replaced by DXGI waitable swap chains. @@ -16,6 +16,12 @@ Written in Rust on top of [egui](https://github.com/emilk/egui) + [glow](https:/ ## Quick start +### Pre-built macOS app (Apple Silicon) + +Download `Navi-<version>-host.zip` from the [Releases](https://github.com/ganten7/navi/releases) page, unzip, and double-click `Navi.app`. The bundle is ad-hoc signed; on first launch macOS Gatekeeper may flag it — right-click → Open, or run `xattr -cr Navi.app` to clear the quarantine flag. + +### From source + ```bash git clone https://github.com/ganten7/navi.git cd navi @@ -23,13 +29,21 @@ cargo build --release ./target/release/navi ``` +To build the `.app` bundle yourself: + +```bash +./scripts/build-macos.sh # arm64 (host) +ARCH=universal ./scripts/build-macos.sh # arm64 + x86_64 universal +open dist/Navi.app +``` + On first run, Navi auto-detects your `org-roam.db` and creates `~/.config/navi/config.json`. Subsequent launches start in well under a second. --- ## Requirements -- **Rust 1.75+** (stable) — install via [rustup](https://rustup.rs/) +- **Rust 1.75+** (stable) — install via [rustup](https://rustup.rs/) (source builds only) - **org-roam v2** database (`nodes`, `files`, `links`, `tags`, `aliases`) - **emacsclient** + a running Emacs server (`(server-start)` in your init) — for double-click-to-open - A working OpenGL 3.3 context — built into macOS / standard on Linux @@ -156,6 +170,8 @@ navi/ Binary: UI, rendering, event loop src/painter.rs GraphPainter — grid, edges, nodes, labels src/macos_display.rs macOS CADisplayLink + tier control src/theme.rs Colour themes +scripts/build-macos.sh Cargo build + Navi.app + zip for release +assets/icon.icns App bundle icon ``` --- @@ -163,11 +179,14 @@ navi/ Binary: UI, rendering, event loop ## Building ```bash -# Release (recommended) +# Release (recommended for use) cargo build --release # Dev (slower at runtime, faster compile, includes debuginfo) cargo build + +# macOS .app bundle + release zip +./scripts/build-macos.sh ``` The release profile in the workspace has `lto = true` and `opt-level = 3`. Expect a 30–60 s clean release build on a modern laptop. diff --git a/assets/icon.icns b/assets/icon.icns Binary files differ. diff --git a/scripts/build-macos.sh b/scripts/build-macos.sh @@ -0,0 +1,92 @@ +#!/usr/bin/env bash +# Build Navi.app for macOS and package it as Navi-<version>-<arch>.zip. +# +# Usage: +# scripts/build-macos.sh # release build, host arch +# ARCH=universal scripts/build-macos.sh # arm64 + x86_64 universal binary +# +# Output: dist/Navi.app and dist/Navi-<version>-<arch>.zip +# +# Requirements: Rust toolchain (rustup), Xcode CLT for codesign / ditto. +# For ARCH=universal: rustup target add x86_64-apple-darwin aarch64-apple-darwin +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +cd "$ROOT" + +ARCH="${ARCH:-host}" +VERSION="$(grep '^version' navi/Cargo.toml | head -1 | sed -E 's/.*"([^"]+)".*/\1/')" +APP="dist/Navi.app" +ZIP="dist/Navi-${VERSION}-${ARCH}.zip" + +echo "==> Navi $VERSION arch=$ARCH" + +# ── Build ──────────────────────────────────────────────────────────────────── +case "$ARCH" in + host) + cargo build --release -p navi + BIN="target/release/navi" + ;; + universal) + rustup target add x86_64-apple-darwin aarch64-apple-darwin >/dev/null 2>&1 || true + cargo build --release -p navi --target x86_64-apple-darwin + cargo build --release -p navi --target aarch64-apple-darwin + mkdir -p target/universal/release + BIN="target/universal/release/navi" + lipo -create -output "$BIN" \ + target/x86_64-apple-darwin/release/navi \ + target/aarch64-apple-darwin/release/navi + ;; + *) + echo "unknown ARCH=$ARCH (expected host|universal)" >&2 + exit 2 + ;; +esac + +# ── Bundle ─────────────────────────────────────────────────────────────────── +echo "==> Assembling $APP" +rm -rf "$APP" "$ZIP" +mkdir -p "$APP/Contents/MacOS" "$APP/Contents/Resources" + +cp "$BIN" "$APP/Contents/MacOS/navi" +chmod +x "$APP/Contents/MacOS/navi" +strip -x "$APP/Contents/MacOS/navi" 2>/dev/null || true + +if [ -f assets/icon.icns ]; then + cp assets/icon.icns "$APP/Contents/Resources/AppIcon.icns" +fi + +cat > "$APP/Contents/Info.plist" <<PLIST +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleName</key> <string>Navi</string> + <key>CFBundleDisplayName</key> <string>Navi</string> + <key>CFBundleExecutable</key> <string>navi</string> + <key>CFBundleIconFile</key> <string>AppIcon</string> + <key>CFBundleIdentifier</key> <string>com.navi.graph</string> + <key>CFBundleInfoDictionaryVersion</key> <string>6.0</string> + <key>CFBundlePackageType</key> <string>APPL</string> + <key>CFBundleShortVersionString</key> <string>${VERSION}</string> + <key>CFBundleVersion</key> <string>${VERSION}</string> + <key>LSMinimumSystemVersion</key> <string>11.0</string> + <key>NSHighResolutionCapable</key> <true/> + <key>LSApplicationCategoryType</key> <string>public.app-category.utilities</string> + <key>NSPrincipalClass</key> <string>NSApplication</string> +</dict> +</plist> +PLIST + +# Ad-hoc codesign so macOS lets the user open it (still flagged by Gatekeeper +# on first launch — instruct users to right-click → Open or `xattr -cr Navi.app`). +codesign --force --deep --sign - "$APP" >/dev/null 2>&1 || \ + echo "warn: codesign failed (continuing, app will trip Gatekeeper)" + +# ── Package ────────────────────────────────────────────────────────────────── +echo "==> Packaging $ZIP" +ditto -c -k --sequesterRsrc --keepParent "$APP" "$ZIP" + +du -h "$BIN" "$APP/Contents/MacOS/navi" "$ZIP" | sed 's/^/ /' +echo "==> Done. Open with: open $APP" +echo "==> Upload to release: gh release upload v${VERSION} $ZIP --clobber"