Development¶
In this note, we outline current practices, tools, and workflows for viser
development. We assume that the repository is cloned to ~/viser.
Python install¶
We recommend using uv for Python development.
# Install uv (if not already installed).
curl -LsSf https://astral.sh/uv/install.sh | sh
# Run any example directly (uv handles dependencies automatically).
cd ~/viser
uv run --extra examples python examples/00_getting_started/00_hello_world.py
Linting, formatting, type-checking¶
For code quality, we use pyright for type checking and ruff for linting
and formatting.
# Check static types.
uv run --extra dev pyright
# Lint and auto-fix issues.
uvx ruff check --fix
# Format code.
uvx ruff format
Running tests¶
uv run --extra dev pytest
Client-Server Synchronization¶
The viser frontend and backend communicate via a shared set of message definitions and enforce
version compatibility to prevent security issues and crashes from mismatched versions.
Message Definitions¶
On the server, messages are defined as Python dataclasses in
~/viser/src/viser/_messages.py.On the client, these are defined as TypeScript interfaces in
~/viser/src/viser/client/src/WebsocketMessages.ts.
There is a 1:1 correspondence between the Python dataclasses and the TypeScript interfaces.
Version Compatibility¶
Viser implements strict version compatibility checking between client and server:
The client includes its version in the WebSocket subprotocol name (e.g.,
viser-v0.2.23)The server extracts the client version from the subprotocol and compares it with its own version
If versions don’t match, the connection is rejected with code 1002 (protocol error) and an informative message
This ensures that client and server components always operate with compatible functionality
Synchronization Script¶
To synchronize message definitions and version information between the Python backend and TypeScript frontend,
use the sync_client_server.py script:
cd ~/viser
uv run python sync_client_server.py --sync-messages --sync-version
This script:
Generates TypeScript interfaces from Python dataclasses
Creates the VersionInfo.ts file with the current server version
Formats the generated files using prettier
Always run this script after:
Changing message definitions in
_messages.pyUpdating the version in
__init__.py
Wire Format¶
Messages are sent over WebSocket using a hybrid format that separates lightweight metadata from binary array data.
Encoding (Python server):
Binary arrays (numpy) are extracted from messages and replaced with tagged placeholder dicts:
{"__binary_index": i, "dtype": "<f4"}.The remaining metadata is encoded with msgpack, then compressed with zstd.
The raw binary arrays are appended uncompressed after the compressed metadata, with 8-byte alignment padding between buffers.
The wire layout is:
[8 bytes] decompressed size of msgpack (little-endian uint64)
[8 bytes] compressed size of msgpack (little-endian uint64)
[N bytes] zstd-compressed msgpack payload
[P bytes] padding to 8-byte alignment
[B bytes] concatenated binary buffers (each 8-byte aligned)
Decoding (TypeScript client):
The zstd-compressed msgpack metadata is decompressed and decoded.
Placeholder dicts are replaced with typed array views (
Float32Array,Uint32Array, etc.) pointing directly into the WebSocket’s receive buffer.
Because the binary data is uncompressed and 8-byte aligned, the client can create typed array views without copying. This is important for high-frequency streaming (e.g., point clouds at 30-60fps), where eliminating copies avoids GC pressure that would otherwise cause frame drops.
Binary data is left uncompressed because float/int arrays compress poorly, and at high frame rates the zstd round-trip cost outweighs the modest bandwidth savings.
Recordings (the .viser file format) use the same placeholder format, but
the entire payload (msgpack + binary) is compressed together with zstd since
recordings aren’t latency-sensitive. An 8-byte msgpack length header precedes the
msgpack data inside the decompressed payload so the decoder knows where the binary
section begins.
Client development¶
For client development, we can start by launching a relevant Python script. The examples are a good place to start:
cd ~/viser/examples
uv run python 05_camera_commands.py
When a viser script is launched, two URLs will be printed:
An HTTP URL, like
http://localhost:8080, which can be used to open a pre-built version of the React frontend.A websocket URL, like
ws://localhost:8080, which client applications can connect to.
If changes to the client source files are detected on startup, viser will
re-build the client automatically. This is okay for quick changes, but for
faster iteration we can also launch a development version of the frontend, which
will reflect changes we make to the client source files
(~/viser/src/viser/client/src) without a full build. This requires a few more
steps.
Installing dependencies.
Install dependencies.
cd ~/viser/src/viser/client npm install
Launching client.
To launch the client, we can run:
cd ~/viser/src/viser/client
npm run dev
from the viser/src/viser/client directory. After opening the client in a web
browser, the websocket server address typically needs to be updated in the
“Server” tab.
Formatting.
We use prettier. This can be run via one of:
prettier -w .npx prettier -w .
from ~/viser/src/viser/client.