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.
-
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-signatureordocs/readme-linux-prereq-and-deployment. -
Commit with conventional commit messages:
<type>(<scope>): <description>, where the scope is the area you touched (video,save,labels,connect,training, …). For examplefix(save): keep the draft after a download-only save. -
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 persrc/module (src/stores/appStore.ts→tests/unit/appStore.test.ts). Import test helpers from the repo's wrapper, not frombun:testdirectly: -
End-to-end tests go in
tests/e2e/and run withbun 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 (
.slpfiles) live intests/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 withbunx 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
CommandContextwith a frame-level snapshot, which is what makes undo/redo work the same everywhere. overlayVersioncounter. 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.tsmodules 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 ofnavstill builds it intosite/,sitemap.xmlandsearch.json. Keep the dot.deploy.ymlpublishes the site toapp.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.