Contribute

Contributing

Dev setup, quality gates, project structure, and release flow.

Development setup

Prerequisites

  • Vite+ (vp): the sole developer command interface for dependencies, tasks, checks, builds, and development servers.
  • Bun: runtime, package manager, test runner, and compiler used underneath Vite+. CI uses the version pinned in package.json.

Use vp for every command you run directly. vp run dispatches package scripts, which may use Bun for tests, scripts, and single-binary compilation.

Clone and install

git clone https://github.com/czbiohub-sf/nd-embedding-atlas.git
cd nd-embedding-atlas
vp install
cd docs && vp install && cd ..

The root install resolves the application and shared packages. The docs app keeps its own lockfile and install under docs/.

Dependency management

# Audit root tooling and every workspace
vp outdated
vp outdated -r
# Update workspace resolutions within declared ranges
vp update -r

# Add or remove a dependency in the selected workspace
vp add --filter @ndea/app <package>
vp remove --filter @ndea/app <package>

# Update the independent docs application
cd docs
vp outdated
vp update

Shared catalog constraints live in the root package.json; update each constraint there once, then run vp install. Update root-only tooling packages reported by vp outdated explicitly with vp update <package...>; a blanket root update would replace unrelated catalog: references with package-local ranges. Review overrides separately because they are deliberately pinned.

Development workflow

# Full dev stack: backend on :5055 + Vite frontend on :5173 with HMR
vp run dev path/to/data.zarr

The command starts apps/ndea/src/cli/index.ts and the app's Vite server. Production builds open a shipped --preset layout (annotate by default).

Frontend-only when the backend already runs separately:

vp dev apps/ndea

Quality gates

vp check vite.config.ts bunli.config.ts scripts
vp run -r check          # package checks in dependency order
vp run -r test           # package tests in dependency order
vp run build             # frontend + single-file binary

CI runs the same checks and tests, verifies generated CLI metadata, and builds the native binary matrix.

Optional code-health audit

Fallow checks for dead code, duplication, complexity, and dependency problems. Run it through the root Vite+ task when reviewing a branch:

vp run audit --changed-since main

This audit is optional and does not run in CI.

Code style

Enforced by vp check:

  • TypeScript 6 strict: no implicit any, import type for type-only imports
  • Oxlint + Oxfmt: config in vite.config.ts lint / fmt blocks
  • 2-space indent, double quotes, trailing commas, semicolons
  • @/ path alias → apps/ndea/src/frontend/
  • Kebab-case ordinary modules, PascalCase React component modules, and useX hook modules. Oxlint enforces these filename shapes; review enforces each file's semantic role.
  • Shared packages use canonical @ndea/* barrels.

Project structure

apps/ndea/         # CLI, server, frontend, and binary builder
packages/protocol/ # Shared Zod request and response schemas
packages/sdk/      # Host contracts and shared application types
packages/zarr/     # Bun-backed AnnData, MuData, and OME-Zarr I/O
docs/              # Independent Waku app and lockfile

See AGENTS.md for the canonical command catalogue, key abstractions, and gotchas.

Releases

ChannelHow it ships
stableManual: tag vX.Y.Z and push; release.yml builds + publishes
pre-releaseManual: tag vX.Y.Z-alpha.N / -beta.N / -rc.N and push

GitHub marks hyphenated semver tags as pre-releases. Stable/latest resolve through GitHub's latest release endpoint; pre-release resolves to the newest published, non-draft semver pre-release.

The release workflow uploads scripts/install.sh and scripts/install.ps1 as per-tag assets, so users can pin the installer URL: curl …/releases/download/v0.X.Y/install.sh | sh. The docs workflow copies both to https://czbiohub-sf.github.io/nd-embedding-atlas/install.sh and …/install.ps1, which always reflect the latest installers on main.

Each platform builds on its own runner. duckdb.node is a native N-API addon, so a cross-compiled binary would embed the host's addon rather than the target's:

Runnerbun --targetRelease asset
macos-14bun-darwin-arm64ndea-darwin-arm64
ubuntu-latestbun-linux-x64ndea-linux-x64
ubuntu-24.04-armbun-linux-arm64ndea-linux-arm64
windows-latestbun-windows-x64ndea-windows-x64.exe

Note that process.platform reports win32 while Bun's target triple spells the OS windows, and the DuckDB bindings directory is node-bindings-win32-x64. apps/ndea/scripts/build.ts owns the mapping between the three.

After editing apps/ndea/src/cli/commands/**, regenerate the completion metadata:

vp run gen

Updates .bunli/commands.gen.ts, which feeds shell-completion script generation. CI fails on drift (.github/scripts/check-bunli-gen.sh).

Verifying a build

vp run build
./dist/ndea doctor

ndea doctor prints binary path, symlink integrity, active version, and the installed-versions tree. Exit code 1 on hard anomalies (broken symlink, missing active binary). Add --check-network to probe GitHub Releases API reachability with a 3-second timeout. On Windows the symlink and installed-versions sections are omitted, because that layout has neither.

CI also runs .github/scripts/verify-isolated.sh, which copies the fresh binary into an otherwise empty directory before launching it. That guards the single-file invariant: the binary embeds libduckdb and extracts it to a cache directory, so a sibling library file must never be required. The check matters most on Windows, where the loader searches the executable's own directory early enough that a stray duckdb.dll would mask a broken embed.

Editing this docs site

This site is built with Fumapress (Fumadocs + Waku). Pages are MDX under docs/content/; the app lives in docs/ with its own package.json and node_modules, isolated from the main project's dependency graph.

# From the repository root
vp run docs:build
vp run docs:serve  # http://localhost:8080/nd-embedding-atlas/

# Local development with hot reload
vp run docs:dev

Because pages are MDX, they can embed React. vp run docs:build writes docs/dist/public; CI deploys that directory to GitHub Pages.