README.md (8005B)
1 # Navi 2 3 An interactive graph viewer for [org-roam](https://www.orgroam.com/), running as a native desktop window. No Emacs package required — Navi reads your `org-roam.db` directly and opens nodes in your existing Emacs process via `emacsclient`. 4 5 Written in Rust on top of [egui](https://github.com/emilk/egui) + [glow](https://github.com/grovesNL/glow) (OpenGL). On macOS it ships a hand-rolled vsync-aligned event loop that locks the renderer to the panel's native refresh rate (60, 120, 240 Hz) with sub-millisecond frame time variance, then drops to ~30 fps when idle to save power on laptops. 6 7 --- 8 9 ## Status 10 11 - **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. 12 - **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. 13 - **Windows** — not supported. The `emacsclient` socket discovery is Unix-only and the macOS pacer would have to be replaced by DXGI waitable swap chains. 14 15 --- 16 17 ## Quick start 18 19 ### Pre-built macOS app (Apple Silicon) 20 21 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. 22 23 ### From source 24 25 ```bash 26 git clone https://github.com/ganten7/navi.git 27 cd navi 28 cargo build --release 29 ./target/release/navi 30 ``` 31 32 To build the `.app` bundle yourself: 33 34 ```bash 35 ./scripts/build-macos.sh # arm64 (host) 36 ARCH=universal ./scripts/build-macos.sh # arm64 + x86_64 universal 37 open dist/Navi.app 38 ``` 39 40 On first run, Navi auto-detects your `org-roam.db` and creates `~/.config/navi/config.json`. Subsequent launches start in well under a second. 41 42 --- 43 44 ## Requirements 45 46 - **Rust 1.75+** (stable) — install via [rustup](https://rustup.rs/) (source builds only) 47 - **org-roam v2** database (`nodes`, `files`, `links`, `tags`, `aliases`) 48 - **emacsclient** + a running Emacs server (`(server-start)` in your init) — for double-click-to-open 49 - A working OpenGL 3.3 context — built into macOS / standard on Linux 50 51 --- 52 53 ## Configuration 54 55 Config file: `~/.config/navi/config.json`. Created on first run with auto-detected defaults. 56 57 ```json 58 { 59 "db": "~/.emacs.d/org-roam.db", 60 "emacsclient": "/opt/homebrew/bin/emacsclient", 61 "server_name": "server", 62 "show_fps": true 63 } 64 ``` 65 66 | Key | Description | 67 |---|---| 68 | `db` | Path to `org-roam.db`. Auto-detected from common Emacs / Doom / Spacemacs / XDG locations on first run. | 69 | `emacsclient` | Path to `emacsclient`. Bare names are resolved against Homebrew, MacPorts, `/usr/local/bin`, `/usr/bin`, `~/.local/bin`, `~/.nix-profile/bin`, NixOS, Snap, and `/Applications/Emacs.app`. | 70 | `server_name` | Emacs server name (default `server`). | 71 | `show_fps` | Show FPS counter in the status bar (`F` toggles at runtime). | 72 73 The legacy config path `~/.config/org-roam-graph/config.json` is also read; the next save writes to the new path. 74 75 ### DB auto-detection order 76 77 | Path | Setup | 78 |---|---| 79 | `$ORG_ROAM_DB` | env override | 80 | `$XDG_DATA_HOME/emacs/org-roam.db` | XDG-strict Linux | 81 | `~/.emacs.d/org-roam.db` | vanilla Emacs | 82 | `~/.config/emacs/org-roam.db` | XDG-style Emacs | 83 | `~/.config/doom/.local/etc/org-roam.db` | Doom 3.x | 84 | `~/.config/doom/org-roam.db` | Doom 3.x fallback | 85 | `~/.doom.d/.local/etc/org-roam.db` | Doom 2.x | 86 | `~/.doom.d/org-roam.db` | Doom 2.x fallback | 87 | `~/.spacemacs.d/org-roam.db` | Spacemacs | 88 89 --- 90 91 ## Opening nodes in Emacs 92 93 Double-click a node (or select it and press `Enter` / `Space`). File nodes open the file; **headline nodes jump to the heading** via `goto-char`. 94 95 GUI apps on macOS get a minimal `PATH`, so Navi resolves `emacsclient` to an absolute path and probes the server socket under: 96 97 - `$EMACS_SERVER_SOCKET` / `$EMACS_SERVER_FILE` 98 - `$XDG_RUNTIME_DIR/emacs/` 99 - `$TMPDIR/emacs{uid}/` 100 - `/tmp`, `/private/tmp` 101 - `/var/folders/*/*/T/emacs{uid}/` (macOS GUI vs Terminal `TMPDIR` mismatch) 102 103 If open fails, an error appears in the status bar. Make sure Emacs has `(server-start)` in its init. 104 105 --- 106 107 ## Controls 108 109 | Input | Action | 110 |---|---| 111 | Drag background | Pan view | 112 | Swipe + release | Kinetic pan (momentum) | 113 | Drag node | Move node | 114 | Scroll / trackpad | Zoom toward cursor | 115 | Click node | Select — highlights connections | 116 | Double-click node | Open in Emacs | 117 | `Tab` / `Shift-Tab` | Cycle nodes | 118 | `Enter` / `Space` | Open selected node | 119 | `T` | Cycle colour theme | 120 | `G` | Toggle tag colouring | 121 | `A` | Toggle age / weathering heatmap | 122 | `D` | Toggle daily-notes filter | 123 | `O` | Toggle orphan filter | 124 | `L` | Cycle local-graph mode (1 → 2 → 3 hops → off) | 125 | `V` | Cycle layout algorithm | 126 | `/` | Search by title or alias | 127 | `W` | Reload graph from database | 128 | `F` | Toggle FPS counter | 129 | `P` | Pause / resume physics | 130 | `R` | Reset view | 131 | `H` | Hold to show controls panel | 132 | `Q` / `Escape` | Quit | 133 134 --- 135 136 ## Frame pacing 137 138 On macOS Navi runs a hand-rolled event loop: 139 140 - A dedicated background thread owns a `CADisplayLink` pinned to the panel's max refresh rate via `CAFrameRateRange`. On each vsync it sends a `UserEvent::Vsync` to the main thread via winit's `EventLoopProxy`. 141 - Mouse / keyboard events do **not** trigger paints. Display vsync ticks are the sole paint signal — this eliminates the multi-paint-per-frame burn that input-driven loops produce. 142 - After ~10 s of inactivity (configurable with `NAVI_IDLE_GRACE_SECS`), the link pauses and the OS drops the panel to its lowest tier. The next input or focus event resumes the link within a frame. 143 - OpenGL's `swap_buffers` is called with `SwapInterval::DontWait` — macOS GL caps swap-vsync at ~108 Hz on ProMotion panels regardless of the displayed tier, so the display-link does the pacing instead. 144 145 Result on a 240 Hz display: 4.17 ms average frame interval, worst-case 5–7 ms (no missed vsyncs in steady state), and the application sits near 0 % CPU when idle. 146 147 ### Diagnostics 148 149 | Env var | Effect | 150 |---|---| 151 | `NAVI_FPS_LOG=1` | Print fps + frame stats + display-link metadata to stderr every ~1 s | 152 | `NAVI_PROF=1` | Print per-layer paint timing (grid/edges/nodes/labels/help) every ~1 s | 153 | `NAVI_IDLE_GRACE_SECS=N` | Override the 10 s active-after-idle grace window | 154 | `NAVI_NO_GRID`, `NAVI_NO_EDGES`, `NAVI_NO_NODES`, `NAVI_NO_LABELS` | Disable individual paint layers (perf debugging) | 155 156 --- 157 158 ## Project layout 159 160 ``` 161 Cargo.toml Workspace root 162 navi-core/ Pure-Rust library: config, org-roam DB loader, graph + physics 163 src/lib.rs 164 src/config.rs Config load/save, db detection, path expansion 165 src/emacs.rs emacsclient + socket discovery 166 src/graph.rs Force-directed physics, layouts, hidden/faded sets 167 navi/ Binary: UI, rendering, event loop 168 src/main.rs winit + glutin event loop, paint pacer 169 src/app.rs egui app: input, layout, status bar 170 src/painter.rs GraphPainter — grid, edges, nodes, labels 171 src/macos_display.rs macOS CADisplayLink + tier control 172 src/theme.rs Colour themes 173 scripts/build-macos.sh Cargo build + Navi.app + zip for release 174 assets/icon.icns App bundle icon 175 ``` 176 177 --- 178 179 ## Building 180 181 ```bash 182 # Release (recommended for use) 183 cargo build --release 184 185 # Dev (slower at runtime, faster compile, includes debuginfo) 186 cargo build 187 188 # macOS .app bundle + release zip 189 ./scripts/build-macos.sh 190 ``` 191 192 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. 193 194 --- 195 196 ## License 197 198 Source release. Add a `LICENSE` file if you intend to distribute binaries.