Backend/: Rust + Tauri backend (src/), commands (src/tauri_commands/), plugin runtime (src/plugin_runtime/), and bundled plugin support (src/plugin_runtime,scripts/).Backend/built-in-plugins/: local copies of bundled plugins (do not edit their code unless explicitly requested; update submodule pointers instead).Frontend/: TypeScript + Vite UI code (src/scripts/,src/styles/,src/modals/), with Vitest tests colocated as*.test.tsfiles.docs/: UX docs, plugin architecture notes, and plugin/theme packaging guides referenced by contributors.packaging/flatpak/: Flatpak manifests and Flatpak-specific build notes.- Supporting files at the repo root include the workspace
Cargo.toml,Justfile,README.md,ARCHITECTURE.md,SECURITY.md, and installer scripts.
just build(orjust build client|plugins): builds the backend, frontend, and plugin bundles from the workspace Justfile.just tauri-build: production Tauri build wrapper (AppImage/Flatpak).git submodule update --init --recursiveis required before building bundled plugins.
All tests:
just test: runs workspace Rust tests plus frontend type-check + Vitest via the Justfile.
Frontend (Vitest):
npm --prefix Frontend test: run all testsnpm --prefix Frontend test run: run tests once (non-watch mode)npm --prefix Frontend test -- --run: explicit non-watch modenpm --prefix Frontend test -- src/scripts/lib/dom.test.ts: run single test filenpm --prefix Frontend test -- --run -t "qs and qsa": run single test by name patternnpm --prefix Frontend test -- --watch: watch mode for development
Backend (Rust):
cargo test --workspace: run all Rust testscargo test: run tests for current cratecargo test --package openvcs_lib: test specific cratecargo test --lib: run only library tests (not integration tests)cargo test --lib -- branch: run tests matching "branch" in namecargo test --lib -- --test-threads=1: run tests sequentially (for flaky tests)
just fix: formatter/lint quick fixes (runscargo fmt,cargo clippy --fix, and frontend type-check).cargo fmt --all: format Rust codecargo clippy --all-targets -- -D warnings: check Rust for issuescargo clippy --fix --all-targets --allow-dirty: auto-fix clippy issuesnpm --prefix Frontend exec tsc -- -p tsconfig.json --noEmit: TypeScript type-check
cargo tauri dev: run the desktop app in dev mode (Backend/directory).npm --prefix Frontend run dev: run the frontend-only Vite dev server.
- Plugin components live under
Backend/built-in-plugins/and follow the manifest format inopenvcs.plugin.json. Built-in bundles ship with the AppImage/Flatpak and are also built by the SDK (openvcs build+openvcs dist). - The backend loads plugin modules as Node.js runtime scripts (
*.mjs|*.js|*.cjs) viaBackend/src/plugin_runtime/node_instance.rs. - The canonical host/plugin contract is JSON-RPC over stdio with method names in
Backend/src/plugin_runtime/protocol.rs. - When changing host APIs or runtime behavior, update protocol constants and runtime logic in
Backend/src/plugin_runtime.
Formatting:
- Run
cargo fmt --allbefore committing - Use default Rust formatting (4-space indentation, etc.)
- Keep lines under ~100 characters when reasonable
Naming:
snake_casefor modules, functions, and variablesPascalCasefor structs, enums, and trait names- Prefix private fields with underscore (e.g.,
self._field) - Use descriptive names; avoid single letters except in tight loops
Imports:
- Group imports: std library first, then external crates, then local modules
- Use
usestatements for frequently used items - Prefer absolute paths from crate root (
crate::module::Item)
Error handling:
- Use
Result<T, E>for fallible operations; avoidpanic!in library code - Use
?operator for propagating errors - Include context in error messages:
some_func().context("failed to load config")? - Log warnings for recoverable errors with
log::warn - Use
anyhowfor application code (commands, main) when context is needed
Types & patterns:
- Prefer
Arc<T>for shared ownership across threads - Use
tokioasync runtime for I/O-bound async operations - Use
parking_lotmutexes (faster than std) - Derive
Clone,Debug,Serialize,Deserializeas needed
- ALL code must be documented, not just public APIs. This includes:
- Rust: Use doc comments (
///for items,//!for modules) for all functions, structs, enums, traits, and fields. - TypeScript: Use JSDoc comments (
/** ... */) for all functions, classes, interfaces, and types.
- Rust: Use doc comments (
- All functions must include documentation comments.
- All code files MUST be no more than 1000 lines; split files before they exceed this limit.
- When you change behavior, workflows, commands, paths, config, or plugin/runtime expectations, ALWAYS update the relevant documentation in the same change, even if the user does not explicitly ask.
- Include usage examples for complex functions.
- Keep README files in sync with code changes.
- Document configuration options and environment variables.
- All new files must include the following copyright header:
// Copyright © 2025-2026 OpenVCS Contributors
// SPDX-License-Identifier: GPL-3.0-or-later// Copyright © 2025-2026 OpenVCS Contributors
// SPDX-License-Identifier: GPL-3.0-or-laterFormatting:
- 2-space indentation (no tabs)
- Use ES modules (
import/export) - Keep lines under ~100 characters when reasonable
- Use semicolons at statement ends
Naming:
camelCasefor variables, functions, and methodsPascalCasefor classes, types, and interfaces- Prefix private class members with underscore (e.g.,
this._field) - Use descriptive names; avoid abbreviations except common ones (e.g.,
btn,cfg)
Imports:
- Use absolute imports from project root when possible
- Group imports: external libs, then relative
./paths, then../paths - Prefer named imports:
import { foo, bar } from './module' - Use type-only imports (
import type { Foo }) when only using types
Types:
- Use explicit types for function parameters and return values
- Prefer interfaces for object shapes, types for unions/intersections
- Use
nullfor "intentionally empty",undefinedfor "not yet set" - Avoid
any; useunknownwhen type is truly unknown
Error handling:
- Use try/catch for async operations; always handle or re-throw
- Use
Promise.catch(() => {})for fire-and-forget async calls - Show user-facing errors via
notify()fromlib/notify - Log internal errors with console for debugging
UI patterns:
- Use
qs<T>('#id')for single element queries fromlib/dom - Use
qsa('.class')for multiple elements - Use event delegation for list items
- Keep feature modules focused and small (<200 lines when possible)
- Colocate tests as
*.test.tsnext to the source file
- Run
just testbefore PRs - Frontend-only work: run
npm --prefix Frontend exec tsc -- -p tsconfig.json --noEmitandnpm --prefix Frontend test - Use Vitest's
describe/it/expectfor frontend tests - Use descriptive test names:
it('finds elements by selector') - Use
beforeEachto reset DOM state in DOM tests
- For multi-component features or refactors, create/update an ExecPlan (
Client/PLANS.md). Outline design, component impacts, and how the plugin runtime is exercised.
- Run
just testbefore PRs; frontend-only work should at least covernpm --prefix Frontend exec tsc -- -p tsconfig.json --noEmitandnpm --prefix Frontend test. - Use
cargo tauri devto verify runtime plugin interactions (especially when touchingBackend/src/plugin_runtime/), and make suredocs/plugin architecture.mdstays aligned with behavior.
- Use short, imperative commit subjects (optionally scoped, e.g.,
backend: refresh plugin runtime config). Keep changelist focused; avoid mixing UI and backend refactors unless necessary. - PRs should target the
Devbranch, include a summary, issue links, commands/tests run, and highlight architecture implications (host API/protocol changes and security decisions). - Do not modify plugin code inside submodules unless explicitly asked; treat submodule updates as pointer bumps after upstream changes.
- Keep this AGENTS (and other module-level copies you rely on) current whenever workflows, tooling, or responsibilities change so future contributors can find accurate guidance.
- Review
SECURITY.mdbefore making plugin, plugin-install, or network-related changes. - Keep secrets out of the repo; use
.env.tauri.localfor local overrides and do not check them in. If new config flags are introduced, document them indocs/and update relevant settings screens/logs.