Skip to content

Contributing

Bug reports, documentation fixes, and code are all welcome. This page covers how changes get into SLEAP App. To get the code running first, follow Development setup.

Reporting bugs and asking for features

Open an issue. For a bug, include:

  • what you did, what you expected, and what happened instead
  • whether it was the browser or the desktop app, and which channel (Help ▸ About SLEAP Label shows both)
  • the file from Help ▸ Collect Diagnostics…, which has the versions, GPU, environment state, and recent log

A small .slp or video that reproduces the problem makes a fix much faster.

Workflow

main is protected. Every change goes through a branch and a pull request, and is squash merged.

  1. Branch from an up-to-date main, named <type>/<short-description>:

    Type Use for
    feat/ New functionality
    fix/ Bug fixes
    refactor/ Restructuring with no behavior change
    docs/ Documentation only
    test/ Tests only
    chore/ Dependency bumps, tooling, maintenance

    For example fix/opfs-embedded-video-signature or docs/readme-linux-prereq-and-deployment.

  2. Commit with conventional commit messages: <type>(<scope>): <description>, where the scope is the area you touched (video, save, labels, connect, training, …). For example fix(save): keep the draft after a download-only save.

  3. Run the checks below, then open a pull request against main.

Before you open a pull request

Run the same checks CI runs, plus lint:

bun run lint             # ESLint (not in CI yet, but expected to be clean)
bun run build            # type check src/ + production build
bun run typecheck:tests  # type check tests/
bun run test             # unit tests

CI (test.yml) runs build, typecheck:tests, and test on every pull request.

Always use bun run test

The suite needs --isolate, which the script passes. A bare bun test without it currently panics bun 1.3.14.

Tests

  • Unit tests go in tests/unit/, usually one file per src/ module (src/stores/appStore.ts → tests/unit/appStore.test.ts). Import test helpers from the repo's wrapper, not from bun:test directly:

    import { describe, it, expect } from "../bun-test";
    
  • End-to-end tests go in tests/e2e/ and run with bun run test:e2e (Playwright). Add one when a browser flow can't be covered by unit tests. They don't run in CI, so run them yourself.

  • Fixtures (.slp files) live in tests/fixtures/.

Check desktop changes in the desktop app

Many code paths, especially file I/O, differ between the browser and the desktop app. If your change touches desktop behavior, try it in a real desktop window. Playwright can't drive the desktop app, because it runs in the operating system's WebView rather than 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 does nothing in release builds, so it never ships.

Pull request description

Start with a Summary of what changed and why. For a bug fix, lead with the root cause. Then add whatever fits: Changes, Testing (tests added, what you checked by hand in the browser or desktop app, and the results of the checks above), and Related issues (Closes #123).

If the PR depends on something unreleased, such as a sleap-io.js change, open it as a draft and say at the top what has to happen before it can merge.

Code conventions

  • Import from src/ with the @/ alias (@/stores/appStore).
  • Don't hand-edit src/components/ui/. It's generated by shadcn/ui. Add components with bunx shadcn@latest add <component>.
  • Read and write data through sleap-io.js rather than reimplementing data-model logic here.
  • Keep browser/desktop differences in src/platform/, so features work in both wherever they can.
  • Follow the patterns already in the surrounding code, and only comment where the why isn't obvious.

How the code is organized

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: a video frame layer and a 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)

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

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 work the same everywhere.
  • overlayVersion counter. Bumped to force an overlay re-render 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 don't touch sleap-io objects, so it can be unit tested and run in a worker.

Documentation

If your change affects what users see, update the docs in the same pull request. This site is built with Zensical from docs/, and the navigation is in mkdocs.yml:

uvx --from zensical zensical serve   # live preview
uvx --from zensical zensical build   # static output in site/
  • docs/.internal/ holds engineering notes and is kept off the published site. The leading dot is what keeps it off: Zensical ignores mkdocs' exclude_docs, and leaving a page out of nav still builds it into site/, sitemap.xml and search.json. Keep the dot.
  • deploy.yml publishes the site to app.sleap.ai/docs/<version>/ and pins the Zensical version it installs. If a build behaves differently in CI than locally, compare against that pin first.