Skip to content

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.json engines; repository configuration enforces engine checks so pnpm install fails 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.

  1. Create and activate a local virtual environment (recommended on macOS/Homebrew Python because of PEP 668 externally managed environments):
  2. macOS/Linux:
    • python3 -m venv .venv
    • source .venv/bin/activate
  3. Windows PowerShell:
    • py -3 -m venv .venv
    • .\.venv\Scripts\Activate.ps1
  4. Install the docs dependencies:
  5. pnpm run docs:install
  6. or (manual): python3 -m pip install -r docs/requirements.txt
  7. Build locally:
  8. pnpm run docs:build
  9. Preview locally:
  10. 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; electron separate, 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):

  • vitest and renderer/main test dependencies
  • eslint
  • typescript
  • prettier
  • prettier-plugin-sh
  • markdownlint-cli2

Not installed by pnpm (install separately when needed):

  • actionlint (recommended for workflow linting; run pnpm run setup:actionlint or install system-wide)
  • yamllint (required for YAML linting; install via pip install yamllint or brew install yamllint on macOS)
  • docker and act (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.

  • Docker (required to run act locally)
  • 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.

  1. Install actionlint (used by the git pre-commit hook):
  2. pnpm run setup:actionlint
  3. This installs into .githooks/bin so the hook can find it.
  4. Install yamllint (required by the git pre-commit hook):
  5. Install manually via pip: pip install yamllint
  6. macOS alternative: brew install yamllint
  7. Linux alternative: sudo apt install yamllint (Debian/Ubuntu) or sudo dnf install yamllint (Fedora)
  8. Install native build dependencies:
  9. pnpm run setup:build-deps
  10. Linux/macOS: attempts to install what native builds need (requires sudo where applicable).
  11. Windows: prints a message to install Visual Studio Build Tools manually.
  12. (Linux only) Fix serial port permissions:
  13. pnpm run setup:dialout
  14. Adds your user to the dialout group (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-translate to machine-translate the new key into other supported languages.
  • Run pnpm run check:i18n to 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

  1. Install Git (Xcode CLT includes it): bash xcode-select --install
  2. 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

  1. Install Git and Node.js (winget primary path): powershell winget install git.git winget install OpenJS.NodeJS
  2. Allow npm script execution in current user scope: powershell Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
  3. Install Visual Studio Build Tools with Desktop development with C++ workload.
  4. Install Python 3 and ensure it is on PATH: powershell winget install Python.Python.3.12 If 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.