2026-03-30 22:31:06 +02:00
# Contributing
How to set up the development environment and contribute to OwnCord.
## Development Setup
### Prerequisites
2026-04-03 14:37:46 +02:00
| Platform | Server | Client |
|----------|--------|--------|
| Windows 10+ x64 | ✅ | ✅ |
| Linux x64 | ✅ | ✅ |
| Linux ARM64 | ✅ | ✅ (CI only) |
2026-07-23 17:23:31 +02:00
- **Go 1.26+** (server)
2026-03-30 22:31:06 +02:00
- **Node.js 20+** (client)
2026-04-03 14:37:46 +02:00
- **Rust / Cargo** (Tauri client — not needed for server-only work)
- **Docker + Compose v2** (optional — alternative to building the server locally)
2026-03-30 22:31:06 +02:00
### Available Commands
#### Server (Go)
| Command | Description |
|---------|-------------|
2026-04-03 12:53:48 +02:00
| `go build -o chatserver.exe -ldflags "-s -w" .` | Build server binary (Windows) |
| `CGO_ENABLED=0 go build -o chatserver -ldflags "-s -w" .` | Build server binary (Linux) |
2026-04-06 22:51:53 +02:00
| `go build -tags otel .` | Build with OpenTelemetry SDK (requires `go get` first — see Phase B) |
| `go build -tags wazero .` | Build with Wazero plugin runtime (requires `go get` first — see Phase C) |
2026-03-30 22:31:06 +02:00
| `go test ./...` | Run all server tests |
| `go test ./... -cover` | Run server tests with coverage |
| `go test -race ./...` | Run server tests with race detection |
2026-04-06 22:51:53 +02:00
**Make targets** (run from `Server/` ):
| Command | Description |
|---------|-------------|
2026-08-07 21:20:48 +02:00
| `make test` | Run the test suite the way CI does (`-race` , 20 min timeout) |
| `make test-deadlock` | Run the deadlock-detection pass CI also runs (`-tags deadlock` ) |
| `make cover` | Per-package coverage (what CI uploads) + a function summary |
| `make cover-all` | Cross-package coverage — the honest number (also lists 0.0% functions) |
2026-04-06 22:51:53 +02:00
| `make sqlc-install` | Install the pinned sqlc version into `$GOBIN` |
2026-08-07 21:20:48 +02:00
| `make sqlc-generate` | Regenerate the type-safe Go query layer (`db/dbgen/` , SQLite engine) |
| `make sqlc-verify` | Fail if the committed `dbgen` output is stale (used by CI) |
| `make protocol-generate` | Regenerate the WS message-type constants (Go + TS) from `docs/protocol-schema.json` |
| `make protocol-verify` | Fail if the committed protocol constants are stale (used by CI) |
2026-04-06 22:51:53 +02:00
| `make otel-up` | Start Jaeger (traces) + Prometheus (metrics) via Docker for local OTel development |
| `make otel-down` | Stop and remove the OTel dev containers |
2026-03-30 22:31:06 +02:00
#### Client (Tauri v2)
2026-04-03 14:37:46 +02:00
**Build & dev**
2026-03-30 22:31:06 +02:00
| Command | Description |
|---------|-------------|
| `npm run dev` | Start Vite dev server with hot reload |
| `npm run build` | TypeScript check + Vite production build |
| `npm run tauri dev` | Launch Tauri app in dev mode |
2026-04-03 14:37:46 +02:00
| `npm run tauri build` | Build release installer (NSIS on Windows, AppImage+deb on Linux) |
**Tests**
| Command | Description |
|---------|-------------|
2026-03-30 22:31:06 +02:00
| `npm test` | Run all tests (vitest) |
| `npm run test:unit` | Unit tests only |
| `npm run test:integration` | Integration tests only |
| `npm run test:e2e` | Playwright E2E (mocked Tauri) |
| `npm run test:e2e:native` | Playwright E2E (real Tauri exe + CDP) |
| `npm run test:e2e:prod` | Playwright E2E (prod build) |
| `npm run test:e2e:ui` | Playwright UI mode |
| `npm run test:watch` | Vitest watch mode |
| `npm run test:coverage` | Coverage report |
2026-04-03 14:37:46 +02:00
| `npm run test:mutate` | Stryker mutation testing |
| `npm run test:mutate:dry` | Stryker dry-run (no mutations applied) |
| `npm run test:browser` | Vitest browser-mode tests |
**Type checking, linting & formatting**
| Command | Description |
|---------|-------------|
2026-03-30 22:31:06 +02:00
| `npm run typecheck` | Full typecheck (all sources) |
2026-03-31 18:07:23 +02:00
| `npm run typecheck:build` | Typecheck build config only |
2026-04-03 14:37:46 +02:00
| `npm run lint` | oxlint + ESLint check (src/) |
2026-03-30 22:31:06 +02:00
| `npm run lint:fix` | ESLint auto-fix |
2026-04-03 14:37:46 +02:00
| `npm run lint:ox` | oxlint only (fast correctness checks) |
| `npm run format` | Prettier format (src/ + tests/) |
| `npm run format:check` | Prettier check only (no writes) |
| `npm run knip` | Dead code and unused export detection |
2026-03-30 22:31:06 +02:00
2026-07-19 16:53:54 +00:00
### Git hooks (recommended)
Committed hooks in `.githooks/` catch the most common CI failures locally. Enable once per clone (from the repo root):
```bash
npm run hooks:install # = git config core.hooksPath .githooks
```
| Hook | What it runs |
|------|--------------|
| `pre-commit` | gofmt + `go vet` (when Go files staged), oxlint + prettier + `tsc --noEmit` (when client TS staged), `sqlc-verify` / `protocol-verify` (when their inputs staged) |
| `pre-push` | Server build in all build-tag variants, client typecheck + type-aware ESLint. Set `OWNCORD_PREPUSH_TESTS=1` to also run `go test -race ./...` |
Bypass with `--no-verify` or `OWNCORD_SKIP_HOOKS=1` when needed — CI still enforces everything.
2026-04-07 10:13:47 +02:00
## Plugin Development
Plugins are WASM modules loaded at runtime when the server is built with `-tags wazero` .
See `Server/plugin/examples/hello/README.md` for the full plugin ABI and build instructions.
**Toolchain requirements for building `.wasm` plugins with TinyGo:**
| Tool | Version | Notes |
|------|---------|-------|
| TinyGo | 0.40.1 | Supports Go 1.19– 1.25 only |
| Go SDK | 1.25.x | Install alongside the system Go via `go install golang.org/dl/go1.25.3@latest && go1.25.3 download` |
| wasm-opt | Binaryen 129 | Required by TinyGo for the `wasi` target; download from Binaryen GitHub releases |
Any WASM toolchain (Rust/`wasm32-wasi` , AssemblyScript, etc.) that exports the five ABI
functions is equally valid — TinyGo is just the example toolchain used by `examples/hello/` .
---
2026-03-30 22:31:06 +02:00
## Active Branches
- `main` -- stable releases
- `dev` -- active development
## Branch Naming
- `feature/<name>` -- new features
- `fix/<name>` -- bug fixes
- `docs/<name>` -- documentation changes
## Commit Format
Use conventional commits:
```text
feat: add thread support to channels
fix: prevent duplicate WebSocket connections
refactor: extract permission checks into middleware
docs: update quick-start guide
test: add integration tests for invite flow
chore: bump Go dependencies
perf: cache role permissions in memory
ci: add lint step to GitHub Actions
```
## Pull Request Process
2026-08-07 21:20:48 +02:00
1. Branch from `dev` (the active development branch)
2. PRs target `dev` ; `dev` is merged to `main` for releases, which are cut from tagged commits on `main`
2026-03-30 22:31:06 +02:00
3. CI must pass (build + test + lint)
4. Request code review
5. Squash merge preferred
## Testing
2026-08-07 21:20:48 +02:00
The client suite enforces **70% coverage thresholds** in `vitest.config.ts` ;
the Go suite has deliberately no floor (T-2026-07-25-19) — use `make cover-all`
to see the honest cross-package number. Follow a test-driven workflow and never
lower a threshold to make a change fit.
2026-03-30 22:31:06 +02:00
## Code Style
2026-08-07 21:20:48 +02:00
- **TypeScript**: See [Client Architecture ](architecture/client.md )
2026-03-30 22:31:06 +02:00
- **Go**: `gofmt` + `golangci-lint` , standard library preferred
- **Rust**: `cargo fmt` + `cargo clippy` , minimal code (native APIs only)
2026-08-07 21:20:48 +02:00
## Dependency Policy
The policy behind what the lockfiles already enforce (decided 2026-08-05,
closing audit findings 2026-04-07 #8 / DC-11):
- **Lockfiles are authoritative.** `package-lock.json` , `go.sum` and
`Cargo.lock` pin every transitive dependency; CI installs only from them
(`npm ci` , module/registry verification — never a bare `npm install` in CI
or hooks). `package.json` keeps ordinary caret ranges: exact-pinning it
would duplicate what the lockfile does while making every security patch a
manual edit.
- **Upgrades arrive as reviewed PRs, not ambient drift.** Dependabot runs
weekly per ecosystem (`.github/dependabot.yml` ) with semver-major updates
ignored across the board — majors are adopted deliberately, by a human,
reading the changelog. Peer-coupled groups (`vitest` /`@vitest/*` ,
`@stryker-mutator/*` ) update as one PR so exact peer pins cannot wedge.
- **Security gates run on every PR:** `npm audit --omit=dev
--audit-level=high` (shipped deps only — dev-tooling advisories are
triaged in the workflow comment instead of blocking on unfixable pins),
` govulncheck` for Go, ` cargo audit` for Rust, and ` knip` refuses unused
client dependencies outright.
- **Version skew is pinned at the toolchain level** too: ` .nvmrc` + CI both
say Node 20, ` Server/sqlc.version` pins sqlc, Go pins via ` go.mod`
(` GOTOOLCHAIN=auto`), and GitHub Actions are SHA-pinned with Dependabot
bumping the pins.