Skip to content

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 CommandContext with a frame-level snapshot, which is what makes undo/redo uniform across the app.
  • overlayVersion counter — 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.ts modules 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:

bun add file:../sleap-io.js

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.