Development¶
Contributing to the app itself. For using it, start with the guides.
Stack¶
| Layer | Technology |
|---|---|
| UI | React 19, TypeScript 5.7, Vite 6, Tailwind CSS v4 |
| Components | shadcn/ui (Radix primitives) |
| State | Zustand + Immer |
| Rendering | Canvas 2D — video frame layer + skeleton overlay layer |
| Data model | @talmolab/sleap-io.js — SLP/HDF5 via h5wasm |
| Video | WebCodecs, demuxed by mp4box.js (MP4), MediaBunny (WebM, MKV, MOV, Ogg, TS) and web-demuxer (AVI, WMV, MPEG) |
| Desktop | Tauri v2 |
| Shortcuts | tinykeys |
| Testing | bun test (unit), Playwright (E2E) |
bun is the package manager and runtime — there is no npm/Node step.
Commands¶
bun install
bun run dev # browser dev server, port 5173
bun run build # type check + production build
bun run lint # ESLint
bun run test # unit tests (bun's runner, --isolate)
bun run test:e2e # Playwright E2E
bun run fetch:ffmpeg # once: vendor the ffmpeg/ffprobe sidecars the desktop build needs
bun run tauri:dev # desktop dev mode
bun run tauri:build # desktop installer
The desktop build needs a Rust 1.95+ toolchain (the dev-only tauri-pilot
plugin uses edition 2024). Run bun run fetch:ffmpeg before the first
tauri:dev or tauri:build: the ffmpeg/ffprobe sidecars are not committed,
and without them tauri build fails with
resource path binaries/ffmpeg-<triple> doesn't exist.
Always use bun run test
The suite runs with --isolate. A bare bun test without it currently
panics bun 1.3.14.
Linux system dependencies for Tauri¶
sudo apt-get install libwebkit2gtk-4.1-dev libgtk-3-dev \
libjavascriptcoregtk-4.1-dev librsvg2-dev patchelf \
libglib2.0-dev libayatana-appindicator3-dev libdbus-1-dev \
gstreamer1.0-plugins-base gstreamer1.0-plugins-good \
gstreamer1.0-plugins-bad gstreamer1.0-gl \
gstreamer1.0-alsa gstreamer1.0-pulseaudio
The GStreamer plugins are what the Linux WebView needs to open videos; CI installs the same set.
Layout¶
src/
├── main.tsx # entry, exposes window.sleap debug API
├── stores/ # Zustand stores
├── commands/ # command pattern with undo/redo
├── canvas/SkeletonRenderer.ts # Canvas 2D overlay + hit testing
├── components/
│ ├── layout/ # AppShell, MenuBar, StatusBar, WelcomeScreen
│ ├── video/ # VideoPlayer, Seekbar, ContextMenu
│ ├── panels/ # Videos, Skeleton, Instances, Training, ...
│ ├── dialogs/ # GoToFrame, Training, Inference, Analyze, ...
│ ├── monitors/ # loss plots, log terminal, visualizations
│ └── ui/ # shadcn/ui (generated -- do not hand-edit)
├── hooks/
├── lib/
│ ├── analyze/ # instance size + label QC
│ ├── transcode/ # legacy-codec conversion (desktop)
│ ├── metrics/ # model metrics
│ └── ...
├── platform/ # Tauri vs browser I/O abstraction
└── types/
src-tauri/ # Tauri v2 desktop shell (Rust)
tests/
├── unit/ # bun tests
├── e2e/ # Playwright
└── fixtures/ # SLP files
@/ is aliased to ./src/.
Key patterns¶
- Two-canvas rendering — the video frame and the skeleton overlay are separate canvases, updated independently.
- Command pattern — every edit goes through
CommandContextwith a frame-level snapshot, which is what makes undo/redo uniform across the app. overlayVersioncounter — bumped to force overlay re-renders when mutable data changes without a React state change.- Platform abstraction —
src/platform/isolates file I/O so the same code runs in Tauri and the browser. - Pure cores — analysis and statistics logic lives in
*Core.tsmodules that touch no sleap-io objects, so it is unit-testable and worker-safe.
sleap-io.js¶
The data model comes from @talmolab/sleap-io.js. For developing against
unpublished changes, clone it alongside this repo and link it:
Vite auto-detects whether node_modules/@talmolab/sleap-io.js is a local link or
a normal install. CI always uses the published package.
Driving the desktop GUI¶
Playwright cannot drive the Tauri build — it runs in the OS WebView, not
Chromium. tauri-pilot can:
cargo install tauri-pilot-cli # once per machine
bun run tauri:dev
tauri-pilot ping
tauri-pilot snapshot -i # interactive elements with @refs
tauri-pilot click '@e3'
tauri-pilot screenshot shot.png
tauri-pilot logs --level error
The plugin is a no-op in release builds, so it never ships.
Contributing¶
main is protected — work on a branch, open a PR, and squash merge. Run
bun run lint, bun run test, and bun run build before you push.
Docs¶
This site is built with Zensical from docs/:
uvx --from zensical zensical serve # live preview
uvx --from zensical zensical build # static output in site/
docs/.internal/ holds engineering notes and is excluded from the published
site. The leading dot is what excludes it -- Zensical ignores mkdocs'
exclude_docs, and simply leaving a page out of nav still builds it into
site/, sitemap.xml and search.json. Keep the dot.
The site is published to app.sleap.ai/docs/<version>/ by deploy.yml, which
pins the Zensical version it installs. The unpinned uvx commands above are
fine for local preview, but if a build behaves differently in CI than it does
locally, compare against that pin first.