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.96.0 | Pinned by rust-toolchain.toml; 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_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.tomlapps/web-clipper/package.jsonCHANGELOG.md
Use scripts/bump-version.sh to update the package, native backend, and Web
Clipper 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. The same workflow validates and attaches the Web Clipper Chrome and Firefox MV3 zip artifacts to the GitHub Release.
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.1 - 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.