Development
Prerequisites
- Node.js 20+
- Rust (latest stable)
- Tauri prerequisites
Setup
# Clone the repo
git clone https://github.com/sorenwacker/nodus.git
cd nodus
# Install dependencies
npm install
# Start development server
make dev
Commands
| Command | Description |
|---|---|
make dev |
Start development server |
npm test |
Run tests |
npm run lint |
Lint code |
npm run build |
Build frontend |
npm run tauri build |
Build for production |
cargo test |
Run Rust tests (in src-tauri/) |
cargo clippy --all-targets -- -D warnings |
Lint Rust code, test code included (in src-tauri/) |
npm ci && npm run build |
Install and type-check the MCP server (in packages/nodus-mcp-server/) |
scripts/setup-hooks.sh |
Install the pre-commit hook, which runs the CI checks for the parts of the tree a commit touches |
scripts/ci-check.sh |
Run every check CI runs, on the whole tree |
Project Structure
nodus/
├── src/ # Vue frontend
│ ├── canvas/ # Canvas rendering (DOM, SVG, Canvas 2D)
│ ├── components/ # Vue components
│ ├── composables/ # Vue composables
│ ├── stores/ # Pinia state management
│ ├── llm/ # LLM integration
│ └── lib/ # Utility libraries
├── src-tauri/ # Rust backend
│ └── src/
│ ├── database.rs # SQLite operations
│ ├── watcher.rs # File system watcher
│ └── commands.rs # Tauri commands
└── docs/ # Documentation
Tech Stack
| Layer | Technology |
|---|---|
| Desktop | Tauri v2 |
| Frontend | Vue 3 + TypeScript |
| Canvas | DOM cards, SVG edges, and a 2D canvas above the level-of-detail threshold |
| Database | SQLite via sqlx |
| Math | Typst WASM |
Logging
The application uses a structured logging system with namespace prefixes.
Log Levels
| Environment | Default Level | Shows |
|---|---|---|
| Development | info |
info, warn, error |
| Production | warn |
warn, error |
Namespaces
| Logger | Prefix | Usage |
|---|---|---|
appLogger |
[Nodus] |
General application logs |
storeLogger |
[Store] |
Pinia store operations |
canvasLogger |
[Canvas] |
Canvas rendering |
agentLogger |
[Agent] |
LLM agent operations |
Usage
import { storeLogger } from '../lib/logger'
storeLogger.debug('Verbose debugging info') // Only in development
storeLogger.info('General information') // Development only
storeLogger.warn('Warning message') // Always shown
storeLogger.error('Error occurred', error) // Always shown
Measuring canvas performance
Canvas cost on a large workspace is dominated by graphics memory, not by the JS heap, so the browser devtools memory panel does not show it. The build carries the devtools feature: press Ctrl+Shift+I, open Performance, record about five seconds of panning, and compare Paint against Style/Layout and against JS.
On Linux, watch shared memory rather than resident size. A runaway canvas shows as shmem growth in journalctl -k after an out-of-memory kill, attributed to no process's RSS, because the surfaces belong to the i915 GEM allocator. The WebProcess's very large total-vm is not evidence of a leak: JSC's gigacage reserves that address space on every page.
To reproduce a suspected out-of-memory condition without taking the machine down, cap the process:
Two levers already reduce cost by zoom rather than by graph size; see "Detail also drops by zoom" in the product design document. One known lever remains untried: the edge SVG is width:100%;height:100% inside a 1px by 1px parent with overflow: visible, which gives the browser no real viewport to cull paint against. Sizing it to the graph's bounding box is worth testing if more is needed.
Known dependency advisory
glib 0.18 carries a medium-severity unsoundness advisory (VariantStrIter). It
cannot be upgraded from this repository: Tauri 2.11.5, the latest release, pins
it transitively through gtk 0.18 and muda. Nodus contains no direct glib
usage and never calls the affected API, and the issue is Linux-only. The alert is
dismissed as tolerable risk and should be revisited when Tauri's gtk stack moves
to glib 0.20.
Release signing
Two independent signatures matter, and neither is configured in this repository because both require credentials the maintainer holds.
Update signatures (required for auto-update)
The updater refuses any payload whose signature does not verify, so the release pipeline needs a keypair:
npm run tauri signer generate -- -w ~/.nodus-updater.key- Put the public key in
src-tauri/tauri.conf.jsonunderplugins.updater.pubkey, replacing the placeholder. - Add the private key and its password as the repository secrets
TAURI_SIGNING_PRIVATE_KEYandTAURI_SIGNING_PRIVATE_KEY_PASSWORD.
Until the keypair exists, bundle.createUpdaterArtifacts stays false and
plugins.updater.pubkey stays empty. A placeholder public key is worse than no
key: Tauri finds it, demands the matching private key, and fails the build.
Set both when you add the key:
bundle.createUpdaterArtifactstotrueplugins.updater.pubkeyto the generated public key
The release then publishes a signed latest.json; without them it publishes
installers only, and the in-app update check stays silent.
Distribution signatures (removes the install warnings)
Unsigned builds are the largest avoidable loss of users: macOS reports an unidentified developer and Windows SmartScreen warns before the first run.
- macOS needs an Apple Developer account. Add
APPLE_CERTIFICATE,APPLE_CERTIFICATE_PASSWORD,APPLE_SIGNING_IDENTITY,APPLE_ID,APPLE_PASSWORDandAPPLE_TEAM_IDas repository secrets, then add the same names to theenvblock of the release job. Add them together: declaring the variables while the secrets are unset makestauri-actionattempt a certificate import with an empty value, which fails the macOS build. - Windows needs a code-signing certificate; set
bundle.windows.certificateThumbprintintauri.conf.json, or supply the certificate to the signing step.