Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
0296e79
feat(sdk): scaffold @meshtastic/sdk + @meshtastic/sdk-react
danditomaso Apr 24, 2026
82eb64a
refactor(web): import @meshtastic/sdk instead of @meshtastic/core
danditomaso Apr 24, 2026
0b1bddb
feat(sdk): add MeshRegistry + multi-client React providers
danditomaso Apr 24, 2026
4e33ffa
refactor(sdk-react): rename useDevice → useMeshDevice
danditomaso Apr 24, 2026
79b2865
feat(web): mount MeshRegistryProvider at app root
danditomaso Apr 24, 2026
a92db8e
feat(web,sdk): register per-connection MeshClient in registry
danditomaso Apr 24, 2026
84d82c2
feat(sdk): add MessageRepository port + InMemoryMessageRepository
danditomaso Apr 24, 2026
2cc649e
feat(sdk-storage-sqlocal): SQLite WASM persistence adapters
danditomaso Apr 25, 2026
7a0706e
feat(web): persist chat history via @meshtastic/sdk-storage-sqlocal
danditomaso Apr 25, 2026
c53edc2
test: testing strategy + 19 new SDK / storage / hook tests
danditomaso Apr 25, 2026
d4d902a
feat(web): MessagesPage reads chat history from SDK / SQLite
danditomaso Apr 25, 2026
446cc14
feat(web): MessagesPage outbound send through SDK ChatClient
danditomaso Apr 25, 2026
5e19392
feat(sdk,web): drafts persisted in SDK chat slice (SQLite-backed)
danditomaso Apr 25, 2026
eee8f6c
chore(web): drop saveMessage path from subscriptions, delete dead DTO
danditomaso Apr 25, 2026
2d184d0
feat(sdk,web): ChatClient.clearConversation + clearAll; wire DeleteMe…
danditomaso Apr 25, 2026
6d0a491
feat(sdk,sdk-storage-sqlocal,web): NodesRepository port + SQLite pers…
danditomaso Apr 26, 2026
e8628ac
feat(sdk,web): SDK Node carries hops/mqtt/key-verified; legacy adapte…
danditomaso Apr 26, 2026
bef8682
refactor(web): NodesPage reads node list from SDK signals
danditomaso Apr 26, 2026
c13aba8
refactor(web): migrate 9 nodeDB consumers onto SDK adapter
danditomaso Apr 26, 2026
ae9cc1e
refactor(web): rename node legacy adapters to *AsProto
danditomaso Apr 26, 2026
73cfcb1
refactor(web): useFavoriteNode + useIgnoreNode drive SDK NodesClient
danditomaso Apr 26, 2026
0778581
refactor(web): ResetNodeDb + RefreshKeys dialogs read SDK; safer adapter
danditomaso Apr 26, 2026
bfa62e4
feat(sdk,sdk-react,web): node PKI / routing-error tracking on the SDK
danditomaso Apr 26, 2026
ea5ad54
chore(web): remove dead PKI / nodeError paths from legacy nodeDB
danditomaso Apr 26, 2026
1347e74
feat(sdk,web): SDK NodesClient owns user/position/lastHeard/snr +
danditomaso Apr 26, 2026
eee0674
chore(web): drop leftover useNodeDB from Position settings page
danditomaso Apr 26, 2026
a6074e1
chore(web): delete legacy nodeDBStore — SDK NodesClient is the only s…
danditomaso Apr 26, 2026
8bdd295
chore(web): retire Zustand messageStore — keep only Message types/enums
danditomaso Apr 26, 2026
fc738b2
chore(web): rename useChatLegacy → useChatAsLegacyMessages
danditomaso Apr 26, 2026
679e8bf
feat(sdk-react,web): migrate read-only channel consumers to SDK Chann…
danditomaso Apr 26, 2026
1626ef3
feat(sdk,sdk-react): scaffold ConfigEditor for the radio/module/chann…
danditomaso Apr 26, 2026
91d92c6
feat(web): pilot Position / LoRa / MQTT pages on ConfigEditor; Androi…
danditomaso Apr 26, 2026
0e47cb5
feat(web): migrate Bluetooth/Device/Display/Power/Network/Security pa…
danditomaso Apr 26, 2026
2137ef7
feat(web): migrate 11 module-config pages to ConfigEditor; Android-al…
danditomaso Apr 26, 2026
cb7c6cb
feat(sdk,web): migrate User + Channel + ImportDialog off changeRegist…
danditomaso Apr 26, 2026
b4116ac
feat(sdk,web): delete changeRegistry; ConfigEditor owns all settings …
danditomaso Apr 26, 2026
bd291ff
feat(sdk,sdk-storage-sqlocal,web): TelemetryRepository port + Sqlocal…
danditomaso Apr 26, 2026
b17ee7f
feat(sdk,sdk-react,web): unread counts on the SDK ChatClient
danditomaso Apr 26, 2026
a8cd134
refactor(web): split useConnections into focused modules
danditomaso Apr 26, 2026
c49c0db
chore: move packages/web → apps/web
danditomaso Apr 26, 2026
26055e8
feat: PR #12 Phase C — delete packages/core, retarget transports to @…
danditomaso Apr 26, 2026
1b5d6a9
fix(web/connections): probe = "online" not "configured"; flip status …
danditomaso Apr 26, 2026
6293737
feat(web): Position "Use browser location" button + Telemetry firmwar…
danditomaso Apr 26, 2026
6659a6c
fix(sdk): build clean dts for npm consumers; transports drop Types/Ut…
danditomaso Apr 26, 2026
f4ff751
chore(build): tighten tsdown perf flags across SDK + transport packages
danditomaso Apr 27, 2026
fdad330
fix(web): clear pre-existing typecheck errors (36 → 0)
danditomaso Apr 28, 2026
8abc8d5
fix(web/connections): retry-once + clearer error on Bluetooth GATT co…
danditomaso Apr 28, 2026
c092f0f
refactor(transport-web-bluetooth): own GATT connect-retry semantics +…
danditomaso Apr 28, 2026
9a839da
feat(sdk,web): detect newly-flashed device + prompt region setup
danditomaso Apr 28, 2026
9ea9d77
refactor(transport-web-serial): own port-state hygiene + Result-typed…
danditomaso Apr 28, 2026
b87130a
chore(logging): tslog-gated debug logs across the serial connect path
danditomaso Apr 28, 2026
654bc0b
chore(web/storage): logs + 5s timeout around sqlocal DB open
danditomaso Apr 29, 2026
2ddcbb4
feat(sdk,web): live connect-progress overlay (terminal-style log)
danditomaso May 2, 2026
e3a2ce9
refactor(web): redesign ConnectingOverlay as hero card + fix early vi…
danditomaso May 3, 2026
6946fd6
fix(web/connecting-overlay): clear stale in-flight statuses + Cancel …
danditomaso May 3, 2026
8b26ac5
fix(web/connections): close configured-transition race when configCom…
danditomaso May 3, 2026
3ec2af0
style(connecting-overlay): drop hardcoded slate/sky for the app's the…
danditomaso May 4, 2026
0263240
fix(sdk/chat): optimistic-append outbound text so it shows up after send
danditomaso May 6, 2026
e9c5498
fix(sdk/chat): append outbound message before awaiting send (instant …
danditomaso May 6, 2026
12dfef6
adding a claude to repo
danditomaso May 27, 2026
c597b09
handle device reboots
danditomaso May 27, 2026
9da3fbe
chore(web): post-rebase fixups — lockfile, dedupe dep, formatting
thebentern Jun 15, 2026
a166964
test: retire main's nodeDBStore + messageStore tests superseded by SD…
thebentern Jun 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion .github/workflows/crowdin-download.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
uses: crowdin/github-action@v2
with:
base_url: "https://meshtastic.crowdin.com/api/v2"
config: "./packages/web/crowdin.yml"
config: "./apps/web/crowdin.yml"
upload_sources: false
upload_translations: false
download_translations: true
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/crowdin-upload-sources.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ on:
# Monitor all .json files within the /i18n/locales/en/ directory.
# This ensures the workflow triggers if any the English namespace files are modified on the main branch.
paths:
- "/packages/web/public/i18n/locales/en/*.json"
- "/apps/web/public/i18n/locales/en/*.json"
branches: [main]
workflow_dispatch: # Allow manual triggering

Expand All @@ -21,7 +21,7 @@ jobs:
uses: crowdin/github-action@v2
with:
base_url: "https://meshtastic.crowdin.com/api/v2"
config: "./packages/web/crowdin.yml"
config: "./apps/web/crowdin.yml"
upload_sources: true
upload_translations: false
download_translations: false
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/crowdin-upload-translations.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
uses: crowdin/github-action@v2
with:
base_url: "https://meshtastic.crowdin.com/api/v2"
config: "./packages/web/crowdin.yml"
config: "./apps/web/crowdin.yml"
upload_sources: false
upload_translations: true
download_translations: false
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release-packages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ jobs:
run: |
set -euo pipefail
if [ "${{ github.event.inputs.packages }}" = "all" ] || [ -z "${{ github.event.inputs.packages }}" ]; then
mapfile -t TARGETS < <(ls -d packages/* | grep -v 'packages/web')
mapfile -t TARGETS < <(ls -d packages/*)
else
IFS=',' read -ra TARGETS <<< "${{ github.event.inputs.packages }}"
TARGETS=("${TARGETS[@]/#/packages/}")
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,6 @@ packages/protobufs/packages/ts/dist
*.pem
*.crt
*.key

# Claude Code session locks
.claude/
2 changes: 1 addition & 1 deletion .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"recommendations": ["bradlc.vscode-tailwindcss", "biomejs.biome"]
"recommendations": ["bradlc.vscode-tailwindcss", "oxc.oxc-vscode"]
}
9 changes: 9 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Meshtastic Web Client - Claude Code Guide

@AGENTS.md

## Claude-Specific Instructions

- **Think First:** Always outline your step-by-step reasoning inside `<thinking>` tags before writing code or shell commands. Claude models perform significantly better on complex TypeScript tasks when they "think out loud" first.
- **Skills:** The `.skills/` directory contains task-specific instruction modules. Load them as needed — only the skill relevant to your current task.
- **Plan Mode:** Use plan mode for architectural changes spanning multiple modules. Write plans to `.agent_plans/` (git-ignored). The Copilot-CLI-specific `/plan`, `/delegate`, `/research`, and `/share` guidance in `AGENTS.md` does not apply to Claude Code — skip the `<copilot_cli_workflow>` section.
190 changes: 134 additions & 56 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,89 +9,163 @@
## Overview

This monorepo consolidates the official [Meshtastic](https://meshtastic.org) web
interface and its supporting JavaScript libraries. It aims to provide a unified
development experience for interacting with Meshtastic devices.
interface, the domain-driven JavaScript SDK that drives it, and a set of
runtime-specific transport packages. Everything you need to read state from or
send commands to a Meshtastic device lives here.

> [!NOTE]
> You can find the main Meshtastic documentation at https://meshtastic.org/docs/introduction/.

### Projects within this Monorepo (`packages/`)
## Packages

All projects live under `packages/`.

| Package | Purpose |
| --- | --- |
| `packages/sdk` | Framework-agnostic TypeScript SDK. Domain-driven feature slices (device, chat, nodes, channels, config, telemetry, position, traceroute, files) built around a `MeshClient` orchestrator with `@preact/signals-core` reactive state. |
| `packages/sdk-react` | React hooks + `MeshProvider` on top of `@meshtastic/sdk`. Wraps signals in `useSyncExternalStore` for concurrent-safe renders. |
| `apps/web` | Reference React web client. Hosted at [client.meshtastic.org](https://client.meshtastic.org). |
| `packages/ui` | Shared Radix + Tailwind component library. |
| `packages/protobufs` | Generated TypeScript stubs from [`meshtastic/protobufs`](https://github.com/meshtastic/protobufs), produced via `buf generate`. Source of truth for every wire-level type. |
| `packages/transport-http` | HTTP transport for devices exposing a network interface. |
| `packages/transport-web-bluetooth` | Web Bluetooth transport for BLE-capable devices (browsers). |
| `packages/transport-web-serial` | Web Serial transport for USB-serial devices (browsers). |
| `packages/transport-node` | TCP transport for Node.js. |
| `packages/transport-node-serial` | Serial transport for Node.js. |
| `packages/transport-deno` | TCP transport for Deno. |
| `packages/transport-mock` | In-memory transport for tests. |

All publishable packages ship to both [JSR](https://jsr.io/@meshtastic) and [NPM](https://www.npmjs.com/org/meshtastic).

## Architecture

`@meshtastic/sdk` organises its source by feature slice. Each slice follows the
same DDD layout:

```
features/<slice>/
domain/ # entities & value objects — pure TypeScript types
application/ # use-cases (SendTextUseCase, FavoriteNodeUseCase, …)
infrastructure/ # protobuf ↔ domain mappers, admin-message adapters
state/ # signals-backed reactive stores
<Slice>Client.ts # public facade exposing readable signals + command methods
index.ts
```

The shared kernel under `packages/sdk/src/core/` owns:

- **`client/`** — `MeshClient`, the thin orchestrator that owns the transport, queue, event bus, and one instance of every slice client.
- **`transport/`** — the `Transport` interface every `transport-*` package implements.
- **`event-bus/`** — typed pub/sub channels populated by the packet codec.
- **`packet-codec/`** — frame parser (0x94 0xC3), `FromRadio` decoder, portnum router.
- **`queue/`**, **`xmodem/`** — packet ack/timeout pipeline and file-transfer protocol.
- **`signals/`** — signal and keyed-collection helpers consumed by every slice.
- **`logging/`** — `tslog` factory used consistently by every class.
- **`identifiers/`**, **`errors/`** — small primitives shared across slices.

The protobuf boundary is strict: wire messages enter through the packet codec,
get mapped into domain entities inside `features/*/infrastructure/*Mapper.ts`,
and signals only ever expose the domain shape.

```
Transport ─▶ Packet codec ─▶ EventBus ─▶ Slice infrastructure
Signals (state) ─▶ sdk-react hooks ─▶ UI
Slice application (use-cases) ─▶ MeshClient.sendPacket ─▶ Queue ─▶ Transport
```

Expected domain errors are returned as `Result<T, E>` via [`better-result`](https://www.npmjs.com/package/better-result); exceptions are reserved for programmer errors and truly exceptional conditions.

All projects are located within the `packages/` directory:
## Getting Started

- **`packages/web` (Meshtastic Web Client):** The official web interface,
designed to be hosted or served directly from a Meshtastic node.
- **[Hosted version](https://client.meshtastic.org)**
- **`packages/core`:** Core functionality for Meshtastic JS.
- **`packages/transport-node`:** TCP Transport for the NodeJS runtime.
- **`packages/transport-node-serial`:** NodeJS Serial Transport for the NodeJS runtime.
- **`packages/transport-deno`:** TCP Transport for the Deno runtime.
- **`packages/transport-http`:** HTTP Transport.
- **`packages/transport-web-bluetooth`:** Web Bluetooth Transport.
- **`packages/transport-web-serial`:** Web Serial Transport.
- **`packages/protobufs`:** Git submodule containing Meshtastic’s shared protobuf definitions, used to generate and publish the JSR protobuf package.
### Prerequisites

All `Meshtastic JS` packages (core and transports) are published both to
[JSR](https://jsr.io/@meshtastic). [NPM](https://www.npmjs.com/org/meshtastic)
You need [pnpm](https://pnpm.io/) installed. If you plan to regenerate
protobufs, also install the [Buf CLI](https://buf.build/docs/cli/installation/).

---
### Setup

## Repository activity
```bash
git clone https://github.com/meshtastic/web.git
cd web
pnpm install
```

| Project | Repobeats |
| :------------- | :-------------------------------------------------------------------------------------------------------------------- |
| Meshtastic Web | ![Alt](https://repobeats.axiom.co/api/embed/e5b062db986cb005d83e81724c00cb2b9cce8e4c.svg "Repobeats analytics image") |
### Run the web client

---
```bash
pnpm --filter @meshtastic/web dev
```

## Tech Stack
### Build everything

This monorepo leverages the following technologies:
```bash
pnpm -r build
```

- **Runtime:** pnpm / Deno
- **Web Client:** React.js
- **Styling:** Tailwind CSS
- **Bundling:** Vite
- **Language:** TypeScript
- **Testing:** Vitest, React Testing Library
### Run tests

---
```bash
pnpm -r test
```

## Getting Started
### Lint + format

### Prerequisites
```bash
pnpm check
pnpm check:fix
```

## Developing

### Adding a new feature slice to `@meshtastic/sdk`

1. Create `packages/sdk/src/features/<slice>/` with `domain/`, `application/`, `infrastructure/`, `state/` subdirectories plus an `index.ts` barrel.
2. Implement a signals-backed store in `state/`.
3. Subscribe to the relevant `EventBus` channel(s) inside a `<Slice>Client.ts` class and write mapped domain entities to the store.
4. Wire the new client into `MeshClient` and re-export types from `packages/sdk/mod.ts`.
5. Add vitest coverage: domain invariants, use-cases against `createFakeTransport()`, and round-trip mapper fixtures.
6. If the slice has React callers, add a matching hook under `packages/sdk-react/src/hooks/`.

You'll need to have [pnpm](https://pnpm.io/) installed to work with this monorepo.
Follow the installation instructions on their home page.
### Adding a new transport

### Development Setup
Implement the `Transport` interface exported from `@meshtastic/sdk/transport`:

1. **Clone the repository:**
```bash
git clone https://github.com/meshtastic/meshtastic-web.git
cd meshtastic-web
```
2. **Install dependencies for all packages:**
```bash
pnpm install
```
This command installs all necessary dependencies for all packages within the
monorepo.
3. **Install the Buf CLI**
Required for building `packages/protobufs`
https://buf.build/docs/cli/installation/
```ts
interface Transport {
toDevice: WritableStream<Uint8Array>;
fromDevice: ReadableStream<DeviceOutput>;
disconnect(): Promise<void>;
}
```

### Running Projects
The SDK does the framing and decoding — transports only supply raw bytes.

#### Meshtastic Web Client
### Testing

Please refer to the [Meshtastic Web README](packages/web/README.md) for setup and usage.
Vitest is wired at the repo root and picks up `packages/*` projects. The SDK
ships `@meshtastic/sdk/testing` with `createFakeTransport()` for wiring tests
without real hardware.

### Feedback
## Publishing

Each publishable package has `build:npm` / `publish:npm` / `prepare:jsr` /
`publish:jsr` scripts. See each package's `package.json` for details.

## Repository activity

| Project | Repobeats |
| :------------- | :-------------------------------------------------------------------------------------------------------------------- |
| Meshtastic Web | ![Alt](https://repobeats.axiom.co/api/embed/e5b062db986cb005d83e81724c00cb2b9cce8e4c.svg "Repobeats analytics image") |

## Feedback

If you encounter any issues, please report them in our
[issues tracker](https://github.com/meshtastic/web/issues). Your feedback helps
improve the stability of future releases
improve the stability of future releases.

## Star history

Expand All @@ -108,3 +182,7 @@ improve the stability of future releases
<a href="https://github.com/meshtastic/web/graphs/contributors">
<img src="https://contrib.rocks/image?repo=meshtastic/web" width="100%"/>
</a>

## License

GPL-3.0-only. See [LICENSE](LICENSE).
83 changes: 83 additions & 0 deletions TESTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# Testing strategy

How this monorepo proves correctness, and where coverage currently sits.

## Levels

Tests live in five tiers. Each PR should add coverage at the **lowest level that catches the regression**, and only climb tiers when that's not possible.

| Tier | Scope | Tooling | Where |
| --- | --- | --- | --- |
| 1. **Unit** | Pure functions, value objects, mappers, single classes with mocked deps | `vitest run` (Node env) | `*.test.ts` colocated with source |
| 2. **Slice integration** | Use-case + store + mapper exercised against in-memory deps; assert outbound bytes and signal state | `vitest` + `createFakeTransport()` | `*.test.ts` in slice dirs |
| 3. **Client integration** | `MeshClient` end-to-end with a fake transport feeding canned `FromRadio` packets | `vitest` + `@meshtastic/sdk/testing` | `packages/sdk/tests/integration/` |
| 4. **Storage integration** | Real Drizzle queries against sql.js in-memory; or against `@vitest/browser` for OPFS | `vitest` (Node + browser) | `packages/sdk-storage-sqlocal/{src,tests}` |
| 5. **Hook / DOM** | React hooks render under `<MeshProvider>` / `<MeshRegistryProvider>`, react to signals | `vitest` + `@testing-library/react` + `jsdom` | `packages/sdk-react/tests/` |
| 6. **E2E / simulator** | Whole stack: SDK → transport → simulator/firmware. Catches protocol drift | `@vitest/browser` for OPFS; `meshtasticd` simulator over TCP for protocol | future `tests/e2e/` |

## Per-package coverage gates

| Package | Required floor | Notes |
| --- | --- | --- |
| `packages/sdk/core` | every primitive (signals, EventBus, Queue, packet-codec, identifiers) has a unit test; lifecycle covered by `MeshClient.test.ts` | adopt `c8` thresholds once stable |
| `packages/sdk/features/*` | each slice ships: 1 domain invariant test, 1 use-case test against fake transport, 1 mapper round-trip (where mappers exist) | Integration covered by `tests/integration/fake-transport.test.ts` |
| `packages/sdk-react` | each public hook has a `renderHook` test that asserts initial render + re-render on signal change | uses jsdom; provider wrapper required |
| `packages/sdk-storage-sqlocal` | every repository method tested against sql.js in-memory; **at least one OPFS-real test per repo** runs in browser mode | sql.js validates SQL correctness; OPFS validates VFS / Worker plumbing |
| `packages/transport-*` | minimum: framing round-trip, disconnect cleans up streams, error path emits status | low-level; ship as is |
| `packages/web` | component tests at `34` baseline; new SDK-driven UI components must add a hook-mock test | currently all green |

## Current state (audit)

| Package | Test files | Tests pass | Gaps |
| --- | --- | --- | --- |
| `packages/sdk` | 7 | 25 ✅ | nodes/channels/config/telemetry/position/traceroute/files slices have **no tests**; no `MeshClient` lifecycle test (only fake-transport integration); no schema migration test |
| `packages/sdk-react` | 1 | 2 ✅ | only `useMeshDevice` + a stubbed `useChat` test; missing `useNodes`, `useChannels`, `useConfig`, `useConnection`, `useTraceroute`, `useTelemetry`, `usePosition`, `useFileTransfer`, `useFavoriteNode`, `useIgnoreNode`, `useMeshRegistry`, `useClientById`, `useActiveClient`. No registry-aware re-render coverage. |
| `packages/sdk-storage-sqlocal` | 2 | 8 ✅ | sql.js only — **no real OPFS test**, no Worker boot test, no cross-tab BroadcastChannel test (mocked away), no migration v1→v2 test |
| `packages/web` | 34 | 294 ✅ | no `useConnections` test; new `meshRegistry` + `sdkStorage` modules untested; chat persistence end-to-end not exercised |
| `packages/transport-*` | 1 each (5 of 7) | varies | `transport-deno` + `transport-mock` have no tests; transports likely lack disconnect/error coverage |
| `packages/core` | 0 | n/a | legacy, slated for deletion in Phase C; tolerable |
| `packages/ui` | 0 | n/a | pure presentational; visual regression only |
| `packages/protobufs` | 0 | n/a | generated code; upstream's responsibility |

## Concrete additions queued (priority order)

1. **`packages/sdk` slice tests** — one Use-case + one Mapper test per slice (`nodes`, `channels`, `config`, `telemetry`, `position`, `traceroute`). Pattern: build a stub `MeshClient`, dispatch a synthetic event, assert signal value or outbound bytes.
2. **`packages/sdk-react` hook tests** — for every hook listed above, mount under `<MeshProvider>`, drive a signal change, assert `result.current`. One file, ~15 cases.
3. **`packages/sdk` `ChatClient` persistence test** — wire `InMemoryMessageRepository`, append messages, re-construct `ChatClient`, assert hydration. Validates the lazy-load contract.
4. **`packages/sdk-storage-sqlocal` migration test** — bootstrap empty DB, run `MIGRATIONS[]`, assert `_schema.version`. Then add a fake `version: 2` migration and prove it's applied idempotently.
5. **`packages/sdk-storage-sqlocal` browser mode** — add a second `vitest.browser.config.ts` using `@vitest/browser` (Playwright provider) so we exercise real OPFS + Worker. CI runs both modes.
6. **`packages/sdk-storage-sqlocal` BroadcastChannel test** — instantiate two `MultiTabCoordinator` instances in same process; one broadcasts, the other observes. (Node has no `BroadcastChannel` global; use `worker_threads`'s `BroadcastChannel` polyfill or jsdom env.)
7. **`packages/web` `meshRegistry` + `sdkStorage` lazy-init test** — assert `getStorageDb()` returns the same promise on repeated calls and only opens the DB once.
8. **`packages/sdk-react` registry test** — mount `<MeshRegistryProvider>` with two clients, switch active, confirm a hook re-renders against the new client.

## E2E / simulator (Tier 6) — scope only

Out of immediate scope; documenting for a follow-up PR.

- Run `meshtasticd` (firmware simulator) in CI Docker via `services:` block.
- Spin up `MeshClient` with `TransportHTTP` pointed at the simulator's HTTP endpoint.
- Drive scripted scenarios: configure → send text → expect ack; channel update; node info exchange; traceroute.
- Use `@vitest/browser` so we also exercise the real OPFS persistence path during E2E.
- Run on `main` only (cost). Smoke subset on PR.

Until that lands, `createFakeTransport()` covers the protocol layer at unit/integration speed.

## Conventions

- Test file colocated with source: `Foo.ts` → `Foo.test.ts`.
- Integration tests under `tests/integration/`.
- Browser-mode tests use the suffix `.browser.test.ts` so they can be filtered.
- Protobuf fixtures live in `__fixtures__/*.fixtures.ts` next to mappers; binary data committed as base64, not raw bytes.
- Each test imports concrete classes from the source path (`./Foo.ts`), not the package barrel — fast type-check, zero re-export drift.
- No mocked SDK from inside SDK tests. Use `createFakeTransport()` and real `MeshClient` instances.

## Running

```sh
pnpm -r test # all packages, Node env
pnpm --filter @meshtastic/sdk test
pnpm --filter @meshtastic/sdk-storage-sqlocal test
pnpm --filter meshtastic-web test
# future:
pnpm --filter @meshtastic/sdk-storage-sqlocal test:browser
```
1 change: 1 addition & 0 deletions apps/web/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@jsr:registry=https://npm.jsr.io
Loading