Development Environment Setup
This guide covers local development setup for Mesh Client, including cloning, prerequisites, and test harness tooling. For runtime errors, connection issues, and packaged-app problems, see troubleshooting.md.
Shared Requirements and Tooling
These requirements apply to all platforms.
1) Required software
- Git
- Node.js 22.13.0+ and pnpm 10+ (
package.jsonengines; repository configuration enforces engine checks sopnpm installfails on version mismatch) - CI uses Node 22
- Python 3 +
pip(needed for MkDocs documentation build and yamllint)
Verify:
git --version
node --version
pnpm --version
MkDocs (documentation) tooling
Docs are built with MkDocs Material.
- Create and activate a local virtual environment (recommended on macOS/Homebrew Python because of PEP 668 externally managed environments):
- macOS/Linux:
python3 -m venv .venvsource .venv/bin/activate
- Windows PowerShell:
py -3 -m venv .venv.\.venv\Scripts\Activate.ps1
- Install the docs dependencies:
pnpm run docs:install- or (manual):
python3 -m pip install -r docs/requirements.txt - Build locally:
pnpm run docs:build- Preview locally:
pnpm run docs:serve
If pnpm run docs:install fails with externally-managed-environment, activate .venv and rerun.
2) Clone and install
git clone https://github.com/Colorado-Mesh/mesh-client
cd mesh-client
pnpm install
If you are updating from an older clone, use a clean install when troubleshooting native module issues:
rm -rf node_modules package-lock.json
pnpm install
3) Run the app
- Dev mode (hot reload):
pnpm run dev - Production-like local start:
pnpm start
Common pnpm commands
Use these from the repository root:
# App run/build
pnpm run dev
pnpm start
pnpm run build
# Platform packaging (binary artifacts in release/)
pnpm run dist:mac
pnpm run dist:linux
pnpm run dist:win
# Quality checks
pnpm run test:run
pnpm run lint
pnpm run typecheck
pnpm run format:check
# Docs
pnpm run docs:install
pnpm run docs:build
pnpm run docs:serve
All Scripts Reference
Complete reference of all pnpm scripts in package.json, organized by category.
Build
| Script | Description |
|---|---|
build |
Full production build: main (minified) + preload + renderer |
build:main |
Build main process (no minify) → dist-electron/main/index.js |
build:main:prod |
Build main process (minified) → dist-electron/main/index.js |
build:main:meta |
Build main with metadata JSON (no minify) → dist-electron/main/metafile.json |
build:main:minify-meta |
Build main with metadata JSON (minified) → dist-electron/main/meta.json |
build:main:size |
Print main bundle size |
build:preload |
Build preload script → dist-electron/preload/index.js |
build:renderer |
Build renderer (React app) via Vite → dist/ |
Run
| Script | Description |
|---|---|
dev |
Hot-reload dev mode: builds main/preload in watch mode + Vite dev server + Electron |
start |
Production-like local start: runs build then launches Electron |
electron:open |
Launch Electron (requires prior build) |
trace-deprecation |
Run with Node deprecation traces enabled |
Package (distributables)
| Script | Description |
|---|---|
dist |
Build for current platform |
dist:mac |
Build macOS .dmg + .zip → release/ |
dist:mac:publish |
Build macOS and upload to release server |
dist:linux |
Build Linux .AppImage + .deb + .rpm → release/ |
dist:linux:publish |
Build Linux and upload to release server |
dist:win |
Build Windows .exe installer → release/ |
dist:win:publish |
Build Windows and upload to release server |
Building a Flatpak (Linux)
Flatpak builds use flatpak-builder directly (not a pnpm script) and require a one-time local setup. The GitHub Actions workflow (flatpak.yaml) handles this in CI automatically; the steps below are for local iteration.
1. Install system tools (Debian/Ubuntu)
sudo apt install flatpak flatpak-builder elfutils
1. Install system tools (Fedora)
sudo dnf install flatpak flatpak-builder elfutils
2. Add Flathub and install runtimes (one-time, ~500 MB)
flatpak remote-add --user --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
flatpak install --user -y flathub org.freedesktop.Platform//24.08
flatpak install --user -y flathub org.freedesktop.Sdk//24.08
flatpak install --user -y flathub org.freedesktop.Sdk.Extension.node22//24.08
flatpak install --user -y flathub org.electronjs.Electron2.BaseApp//24.08
3. Generate offline pnpm sources (re-run whenever pnpm-lock.yaml changes)
pip install flatpak-node-generator
flatpak-node-generator pnpm pnpm-lock.yaml -o flatpak/generated-sources.json
flatpak/generated-sources.json is generated automatically in the flatpak.yaml CI workflow and does not need to be committed. For local builds you generate it manually as shown above; the file is only required locally and in a Flathub submission repo.
4. Build and install locally
flatpak-builder --user --install --force-clean build-dir org.coloradomesh.MeshClient.yml
This installs the app into your user Flatpak store.
5. Run
flatpak run org.coloradomesh.MeshClient
6. Produce a .flatpak bundle (for sharing without a repo)
flatpak build-bundle ~/.local/share/flatpak/repo \
org.coloradomesh.MeshClient.flatpak \
org.coloradomesh.MeshClient stable
Installing a .flatpak file creates a one-off remote named like meshclient-origin (not flathub); that is expected. The ref branch is stable (release CI sets this; older artifacts used master). Version is shown in MetaInfo / flatpak info, not in the remote name.
Reinstall after downloading a new bundle
flatpak uninstall --user org.coloradomesh.MeshClient
flatpak install --user ./org.coloradomesh.MeshClient-aarch64.flatpak
flatpak run org.coloradomesh.MeshClient
Runtime issues (GPU, VMware guests): see Flatpak: vmwgfx: driver missing (VMware on macOS).
Lint the manifest before submitting to Flathub:
flatpak run --command=flatpak-builder-lint org.freedesktop.Sdk \
manifest org.coloradomesh.MeshClient.yml
Test
| Script | Description |
|---|---|
test |
Run tests in watch mode |
test:run |
Run tests once (CI mode) |
test:verbose |
Run tests with verbose output |
Lint / Format
| Script | Description |
|---|---|
lint |
Run ESLint (type-aware) |
lint:fix |
Run ESLint with auto-fix |
lint:md |
Run markdownlint-cli2 on all .md files |
format |
Format all code via Prettier |
format:check |
Check formatting without fixing |
Typecheck
| Script | Description |
|---|---|
typecheck |
TypeScript check: renderer + main process |
Quality Checks
| Script | Description |
|---|---|
check:log-injection |
Detect unsanitized user data in log calls |
check:db-migrations |
Verify SQLite migrations are valid |
check:i18n |
Verify all UI strings have English keys and locale coverage |
check:ipc-contract |
Verify IPC channel contracts between main/preload/renderer |
Documentation
| Script | Description |
|---|---|
docs:install |
Install MkDocs Python dependencies |
docs:build |
Build static docs to site/ |
docs:serve |
Serve docs locally with live reload |
Setup / Helpers
| Script | Description |
|---|---|
setup:actionlint |
Install actionlint for GitHub workflow linting |
setup:build-deps |
Install native build dependencies |
setup:dialout |
Add user to dialout group for serial port access (Linux) |
i18n:auto-translate |
Machine-translate missing keys via MyMemory |
rebuild |
Rebuild native Node modules for Electron |
Lifecycle (automatic)
| Script | Description |
|---|---|
preinstall |
Enforce pnpm as package manager |
postinstall |
Rebuild native modules + apply patches |
prepare |
Enable git hooks |
predist |
Dedupe packages before packaging |
Dependabot dependency updates
Automated dependency updates are configured in .github/dependabot.yml:
- Schedule: Weekly on Saturdays
- pnpm dependencies: Grouped PRs;
electronseparate, all other deps together - GitHub Actions: Grouped into one PR
Testing Dependabot PRs locally:
Always use pnpm to test dependabot PRs:
git checkout <dependabot-branch>
pnpm install --frozen-lockfile
pnpm run build
pnpm run test:run
Do not use npm install; it creates a package-lock.json and may not respect pnpm's lockfile format.
4) Test harness setup and local quality checks
This section is the project test harness setup.
Installed via pnpm install (from package.json):
vitestand renderer/main test dependencieseslinttypescriptprettierprettier-plugin-shmarkdownlint-cli2
Not installed by pnpm (install separately when needed):
actionlint(recommended for workflow linting; runpnpm run setup:actionlintor install system-wide)yamllint(required for YAML linting; install viapip install yamllintorbrew install yamllinton macOS)dockerandact(only if you run GitHub Actions locally)- Python 3 +
venv+ MkDocs Python deps (for docs checks/builds)
Vitest projects and worker allocation
vitest.config.ts defines three projects:
| Project | Environment | Role |
|---|---|---|
renderer-ui |
jsdom | Component/hook tests with setup stubs |
renderer-logic |
node | Pure renderer unit tests (no setup) |
main |
node | Main, shared, preload, and script tests |
Worker counts are derived in vitest.harness.ts via computeVitestMaxWorkers(cpuCount, ratio): jsdom workers use RENDERER_UI_CPU_RATIO because they are memory-heavy; node workers use NODE_WORKER_CPU_RATIO. Both pools floor at MIN_VITEST_WORKERS (currently 2) and cap effective CPU count at MAX_VITEST_CPU_COUNT (32). When tuning worker allocation, change those constants in the harness (not this doc). Shared Vite dependency inline lists (VITEST_CORE_DEPS, VITEST_SERVER_INLINE_DEPS) also live there — add new deps to the harness when tests need them inlined.
Monolithic protocol runtimes (useMeshtasticRuntime, useMeshcoreRuntime) also use source contract tests (read .ts files and assert wiring strings) where full renderHook integration would require heavy BLE/MQTT mocking; see *.reconnect*.test.ts beside those runtimes.
Browser dev without Electron
When you open the Vite dev URL in a plain browser tab (not the Electron window), installDevElectronApiStubIfNeeded() in src/renderer/main.tsx installs a no-op window.electronAPI stub (devElectronApiStub.ts). UI shell and most panels render, but RF connect, SQLite, MQTT IPC, and file dialogs require the Electron window. The console logs [dev] Installed browser electronAPI stub… when the stub is active.
Run these quality checks before opening a PR:
pnpm run test:run
pnpm run lint
pnpm run lint:md
pnpm run typecheck
pnpm run format:check
pnpm run check:i18n
Other useful test commands:
pnpm test(watch mode)pnpm run test:verbose(verbose failures)pnpm run i18n:auto-translate(fill missing keys)
5) Building a distributable
Use the platform-specific packaging command:
pnpm run dist:mac # macOS -> .dmg + .zip in release/
pnpm run dist:linux # Linux -> .AppImage + .deb in release/
pnpm run dist:win # Windows -> .exe installer in release/
Output goes to the release/ directory.
Build analysis
To analyze the main process bundle size and composition:
pnpm run build:main:minify-meta
This generates dist-electron/main/meta.json. Upload this file to esbuild's online analyzer to visualize:
- Bundle size by dependency
- Code that could be externalized
- Minification effectiveness
6) Git hooks and pre-commit behavior
After pnpm install, repo hooks are enabled via core.hooksPath and pre-commit runs checks (format, lint, typecheck, audit, actionlint, tests).
Emergency bypass is available:
git commit --no-verify
Use this only as a temporary escape hatch, then run the skipped checks manually as soon as possible.
7) CI workflow tooling (optional but recommended)
- Docker (required to run
actlocally) - act: run GitHub Actions locally with Linux amd64 parity:
act --container-architecture linux/amd64 -P ubuntu-latest=ghcr.io/catthehacker/ubuntu:full-latest
- actionlint: required for local pre-commit if workflow files are touched.
8) Helper scripts (auto-install where possible)
These scripts try to install optional tooling automatically. If they fail (for example, missing sudo/admin rights), follow the manual steps in this doc instead.
- Install
actionlint(used by the git pre-commit hook): pnpm run setup:actionlint- This installs into
.githooks/binso the hook can find it. - Install
yamllint(required by the git pre-commit hook): - Install manually via pip:
pip install yamllint - macOS alternative:
brew install yamllint - Linux alternative:
sudo apt install yamllint(Debian/Ubuntu) orsudo dnf install yamllint(Fedora) - Install native build dependencies:
pnpm run setup:build-deps- Linux/macOS: attempts to install what native builds need (requires sudo where applicable).
- Windows: prints a message to install Visual Studio Build Tools manually.
- (Linux only) Fix serial port permissions:
pnpm run setup:dialout- Adds your user to the
dialoutgroup (requires sudo + re-login).
9) Internationalization (i18n)
The app uses i18next for localization. English is the source of truth.
- Locale files:
src/renderer/locales/{en,es,...}/translation.json - Adding strings:
- Add the new key and English value to
src/renderer/locales/en/translation.json. - Use the
t('key.name')hook in React components. - Run
pnpm run i18n:auto-translateto machine-translate the new key into other supported languages. - Run
pnpm run check:i18nto verify all keys are valid and accounted for.
Auto-translation uses MyMemory by default. Incremental translations (new keys only) run automatically during the git pre-commit hook. Use pnpm run i18n:auto-translate --all to force a full re-scan of all missing keys.
10) Optional editor/tooling
- VS Code (or Cursor) with TypeScript + ESLint support
- Prettier editor extension (optional convenience; repository already defines formatting rules)
- React DevTools for renderer debugging
macOS
Install prerequisites
- Install Git (Xcode CLT includes it):
bash xcode-select --install - Install Node 22 (22.13.0+ recommended via nvm) and npm:
```bash curl -fsSL https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.0/install.sh -o install_nvm.sh
less install_nvm.sh bash install_nvm.sh rm install_nvm.sh export NVM_DIR="$HOME/.nvm" [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" nvm install 22 nvm use 22 ```
Build/run flow
git clone https://github.com/Colorado-Mesh/mesh-client
cd mesh-client
pnpm install
pnpm run dev
Bluetooth permissions
On first BLE connection, macOS prompts for Bluetooth access. If denied accidentally:
- Go to System Settings > Privacy & Security > Bluetooth
- Enable access for Mesh-Client
macOS release-download note (not required for source development)
If a downloaded app reports "Mesh-client is damaged and can't be opened", see macOS: File is damaged and cannot be opened.
Windows
Install prerequisites
- Install Git and Node.js (winget primary path):
powershell winget install git.git winget install OpenJS.NodeJS - Allow npm script execution in current user scope:
powershell Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser - Install Visual Studio Build Tools with Desktop development with C++ workload.
- Install Python 3 and ensure it is on PATH:
powershell winget install Python.Python.3.12If needed, set npm Python path explicitly:powershell npm config set python "C:\\Path\\To\\python.exe"
Build/run flow
git clone https://github.com/Colorado-Mesh/mesh-client
cd mesh-client
pnpm install
pnpm run dev
Windows packaging note
The Windows build (dist:win) uses pnpm's node-linker=hoisted mode to work around asar packaging issues on Windows. The build command automatically reinstalls with hoisted mode, packages, then restores the default structure.
Serial device driver reminder
If serial ports do not appear, install the right USB UART driver (for example CH340/CH341, CP210x, or FTDI).
Troubleshooting
See troubleshooting.md (Visual Studio), Python, and dist:win path / EPERM.
Linux
Install prerequisites
Install Node 22 (22.13.0+ recommended), make, and C++ build tools (g++/gcc-c++) with native build dependencies.
Debian/Ubuntu:
curl -fsSL https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.0/install.sh | bash
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
nvm install 22
nvm use 22
sudo apt install build-essential
sudo apt install python3 libnspr4 libnss3
Fedora/RedHat:
curl -fsSL https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.0/install.sh | bash
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
nvm install 22
nvm use 22
sudo dnf install @development-tools
sudo dnf install python3 nspr nss
Build/run flow
git clone https://github.com/Colorado-Mesh/mesh-client
cd mesh-client
pnpm install
pnpm run dev
Serial permissions
Add your user to dialout:
sudo usermod -a -G dialout $USER
Log out/in after changing groups.
Linux Bluetooth (BLE)
Linux uses Web Bluetooth (Chromium's built-in BLE API) instead of @stoprocent/noble. This approach:
- Requires no setcap/setuid workaround scripts
- Requires the user to select a device from the in-app Bluetooth picker (backed by Chromium's chooser event)
- Requires a user gesture (button click) to trigger device selection
The app automatically enables --enable-experimental-web-platform-features on Linux at startup.
There is no portable Web Bluetooth API for the negotiated ATT MTU (WebBluetoothCG#383). When Chromium exposes maximumWriteValueLength on the TX characteristic, the client chunks writeValue accordingly; otherwise it sends each payload in one call.
Pairing failures and BlueZ steps: BLE known issues.
Linux launch notes
The supported dev and local run flows are:
pnpm run dev
pnpm start
ARM (for example Raspberry Pi) may also require:
sudo apt install zlib1g-dev libfuse2
sudo sysctl -w kernel.unprivileged_userns_clone=1
Troubleshooting
See Linux development: SIGILL / SIGSEGV and Linux: serial port access denied.