navi

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

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.