Thanks for your interest in contributing to NeverWrite. This guide covers everything you need to get started.
| Tool | Version | Notes |
|---|---|---|
| Node.js | 22+ | Required for desktop app and CI |
| npm | 11+ | Package manager for apps/desktop |
| pnpm | 10.33+ | Package manager for apps/web-clipper |
| Rust | 1.94+ | Edition 2021 across all crates |
- macOS: Xcode Command Line Tools (
xcode-select --install) - Windows: MSVC Build Tools, WebView2
- Linux: Standard build essentials for Rust and Electron packaging (
build-essential,pkg-config,curl,wget)
apps/
desktop/ Electron + React desktop app (npm)
web-clipper/ WXT browser extension (pnpm)
crates/
types/ Shared DTOs and domain models
vault/ Vault scanning, parsing, filesystem watching
index/ Search, link resolution, indexing
diff/ Diff engine + WASM bindings
ai/ Shared AI domain types
vendor/ Vendored ACP runtimes
scripts/ Automation utilities
cd apps/desktop
npm install
# Frontend only (Vite dev server)
npm run renderer:dev
# Full Electron app with Rust sidecar
npm run devcd apps/web-clipper
pnpm install
pnpm dev# From the repo root
cargo build
cargo testgit clone <your-fork-url>
cd <your-clone-directory>git checkout -b my-changeFollow the code style guidelines below, then verify:
# Desktop
cd apps/desktop
npm run lint # ESLint
npm run build # TypeScript check (tsc -b) + Vite build
npm test # Vitest
# Web clipper
cd apps/web-clipper
pnpm run check # TypeScript + tests + build (all-in-one)
# Rust
cargo testgit add <files>
git commit -m "fix(editor): resolve cursor jump on live preview toggle"
git push origin my-changeOpen a PR against main. Describe what changed and why. Link related issues if applicable.
We use a lightweight conventional format:
type(scope): short description
Types: fix, feat, refactor, chore, docs, test, perf
Scope is optional but encouraged for targeted changes (e.g., editor, vault, clipper, ai).
Examples:
fix(editor): resolve cursor jump on live preview toggle
feat(clipper): add selection-only clipping mode
refactor: simplify change rail review projection
chore: clean up pdf tab view zoom handling
Keep messages descriptive and action-focused. Write in lowercase unless starting with a proper noun.
- Strict mode is enabled (
strict: true,noUnusedLocals,noUnusedParameters) - Use
import typefor type-only imports (verbatimModuleSyntaxis enforced) - Prefix unused parameters with
_ - ESLint with TypeScript strict rules — run
npm run lintbefore committing
- Functional components only
- Zustand for state management — stores live in
app/store/ - Feature code goes in
features/<feature-name>/ - Shared components go in
components/
- Default
rustfmtformatting - Edition 2021
- Use
cargo clippyfor additional lint checks
- Simplicity first — the simplest solution that works
- Fix root causes — don't patch around broken abstractions
- Bounded refactors — if a fix requires restructuring, keep it scoped to the affected module
- No speculative cleanup — don't refactor code that your change doesn't touch
# Desktop
cd apps/desktop
npm test # Run once
npm run test:watch # Watch mode
# Web clipper
cd apps/web-clipper
pnpm test # Watch mode
pnpm test:run # Run once- Test files live next to the code they test:
MyComponent.test.tsx - Use
describe(),it(),expect()from Vitest - Use Testing Library for component tests (
@testing-library/react) - Mock desktop runtime APIs through
@neverwrite/runtimehelpers andvi.mocked()
cargo test # All workspace tests
cargo test -p neverwrite-vault # Single crate- React 19 + TypeScript + Vite
- Tailwind CSS 4 — utility-first styling with CSS variables for theming
- CodeMirror 6 — markdown editor with custom extensions (live preview, change tracking)
- Zustand — lightweight state management with per-feature stores
- Electron — desktop shell and IPC bridge
- Tokio — async runtime
- notify — filesystem watching
- Node HTTP server — local desktop API for web clipper communication (
127.0.0.1:32145)
- Feature modules: each feature in
features/owns its components, hooks, and store slices - ActionLog: patch-based change tracking with author attribution (user vs AI)
- Live preview: CodeMirror ViewPlugin that hides markdown syntax when the cursor is elsewhere
- Wikilinks: parsed from markdown, resolved against the vault index for navigation and backlinks
For development, these optional variables can override default runtime paths:
| Variable | Purpose |
|---|---|
NEVERWRITE_CODEX_ACP_BIN |
Override Codex ACP runtime binary |
NEVERWRITE_CLAUDE_ACP_BIN |
Override Claude ACP runtime binary |
NEVERWRITE_GEMINI_ACP_BIN |
Override Gemini ACP runtime binary |
NEVERWRITE_KILO_ACP_BIN |
Override Kilo ACP runtime binary |
NEVERWRITE_WEB_CLIPPER_DEV_ORIGINS |
Allow unpacked extension origins |
We follow Semantic Versioning. During the 0.x phase, minor bumps may include breaking changes.
Versions are kept in sync across:
apps/desktop/package.jsonapps/desktop/package-lock.jsonapps/desktop/native-backend/Cargo.tomlCHANGELOG.md
Use scripts/bump-version.sh to update the package and native backend
version files at once, then add the matching CHANGELOG.md release entry.
Before creating a release tag, run:
node scripts/validate-release-metadata.mjs --tag vX.Y.ZDesktop releases are maintainer-driven and run through the Electron release workflow in GitHub Actions.
Before triggering .github/workflows/release-desktop.yml:
- Bump the desktop version sources with
scripts/bump-version.sh X.Y.Z - Add or update the matching
CHANGELOG.mdentry - Run
node scripts/validate-release-metadata.mjs --tag vX.Y.Z - Create and push the release tag, for example
v0.2.0 - Ensure the required signing secrets are configured in the GitHub repository settings
- Review the Electron release topology and signing requirements documented in
release/appcast/README.md
- Search the existing issue tracker before opening a new one
- Include steps to reproduce, expected behavior, and actual behavior
- For crashes, include the OS version and any relevant logs
By contributing, you agree that your contributions will be licensed under the Apache-2.0 License.