# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Project Overview VaultLink is a self-hosted Obsidian plugin for real-time collaborative file syncing. The project consists of a Rust-based sync server and a TypeScript frontend with four main components: an Obsidian plugin, a sync client library, a test client, and a standalone CLI client. ## Architecture ### Core Components - **sync-server/**: Rust-based WebSocket server with SQLite database for document versioning and real-time synchronization - **frontend/sync-client/**: TypeScript library providing core sync functionality, WebSocket management, and file operations - **frontend/obsidian-plugin/**: Obsidian plugin that integrates the sync client with Obsidian's API - **frontend/test-client/**: CLI testing tool for simulating multiple concurrent users - **frontend/local-client-cli/**: Standalone CLI for VaultLink sync client - **frontend/history-ui/**: Svelte 5 web UI for browsing vault history, viewing diffs, and restoring versions ### Key Technologies - **Backend**: Rust with Axum framework, SQLite with SQLx, WebSockets for real-time sync - **Frontend**: TypeScript, Webpack for bundling, Node.js native test runner - **History UI**: Svelte 5 with runes, Vite for bundling, embedded in server binary via `rust-embed` - **Sync Algorithm**: Uses reconcile-text library for operational transformation ### Architectural Patterns **Server Architecture:** - `AppState`: Central state container holding `Database`, `Cursors`, and `Broadcasts` - `Database`: SQLite-backed document versioning with SQLx for compile-time query verification - `Broadcasts`: WebSocket broadcast system for real-time updates to connected clients - `Cursors`: Tracks user cursor positions across documents with background cleanup task **Client Architecture:** - `SyncClient`: Main entry point, orchestrates all sync operations - `SyncService`: HTTP API client for CRUD operations on documents - `WebSocketManager`: Manages WebSocket connection and real-time updates - `Syncer`: Coordinates file synchronization between local filesystem and server - `CursorTracker`: Manages local and remote cursor positions - `Database`: Client-side document metadata cache - `FileOperations`: Abstraction layer for filesystem operations **Dual-Bundle Strategy:** The sync-client builds two separate bundles: - `sync-client.web.js`: Browser-compatible UMD bundle (excludes `ws` package) - `sync-client.node.js`: Node.js CommonJS bundle with WebSocket support **History UI Architecture:** The history UI (`frontend/history-ui/`) is a standalone Svelte 5 SPA that provides read-only vault history browsing. It communicates with the server via the same REST API used by sync clients, plus three additional endpoints: - `GET /vaults/:vault_id/documents/:document_id/versions` — all versions of a document (without content) - `GET /vaults/:vault_id/history?limit=&before_update_id=` — paginated vault-wide version history (cursor-based) - `POST /vaults/:vault_id/documents/:document_id/restore` — restore a document to a historical version (creates a new version with old content) Server-side implementation: - Database methods: `get_document_versions()` and `get_vault_history()` in `database.rs`, plus a `VaultHistoryRow` helper struct for `sqlx::query_as!` - Handlers: `fetch_document_versions.rs`, `fetch_vault_history.rs`, `restore_document_version.rs` - Response type: `VaultHistoryResponse { versions, hasMore }` in `responses.rs` - SPA serving: `rust-embed` embeds `frontend/history-ui/dist/` into the binary; `index.rs` serves the SPA at `/` and assets at `/assets/*` Client-side component hierarchy: - `App.svelte` — session restore, routing - `Login.svelte` — vault name + token auth via `/ping` - `Dashboard.svelte` — main layout: file tree sidebar, activity feed, time-travel slider - `DocumentDetail.svelte` — version timeline, content preview, diff view, restore - `DiffView.svelte` — unified diff with LCS algorithm - `FileTree.svelte` — recursive tree built from flat `relativePath` values - `ActivityFeed.svelte` — git-log-style feed with action pills (created/updated/renamed/deleted/restored) - `TimeSlider.svelte` — scrubs through `vaultUpdateId` range, reconstructs vault state at any point State is managed with Svelte 5 runes (`$state`, `$derived`, `$effect`) in `lib/stores.svelte.ts`. Auth is stored in `sessionStorage`. The API client (`lib/api.ts`) sets `Authorization: Bearer` and `device-id: history-ui` headers on all requests. ## Development Commands ### Initial Setup **Node.js (requires version 25):** ```bash curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash nvm install 25 nvm use 25 nvm alias default 25 # Optional: set as system default ``` **Rust:** ```bash curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh cargo install sqlx-cli cargo-machete cargo-edit cargo-insta ``` **Frontend:** ```bash cd frontend npm install ``` ### Server Development ```bash cd sync-server cargo run config-e2e.yml # Start development server cargo test --verbose # Run all Rust tests cargo test # Run specific test cargo clippy --all-targets --all-features # Lint Rust code cargo clippy --all-targets --all-features --fix --allow-dirty --allow-staged # Auto-fix clippy warnings cargo fmt --all -- --check # Check Rust formatting cargo fmt --all # Auto-format Rust code cargo machete --with-metadata # Detect unused dependencies ``` ### Frontend Development ```bash cd frontend npm run dev # Start development mode (watches sync-client and obsidian-plugin) npm run build # Build all workspaces npm run build -w sync-client # Build specific workspace npm run test # Run all tests across all workspaces npm run test -w sync-client # Run tests for specific workspace npm run lint # Lint and format TypeScript code with ESLint + Prettier ``` ### History UI Development ```bash cd frontend npm run dev -w history-ui # Start Vite dev server (localhost:5173, proxies API to localhost:3000) npm run build -w history-ui # Build for production (output: frontend/history-ui/dist/) ``` The history UI is a Svelte 5 SPA embedded in the server binary via `rust-embed`. The build flow is: 1. `npm run build -w history-ui` produces `frontend/history-ui/dist/` 2. The Rust server embeds these files at compile time (`sync-server/src/server/index.rs`) 3. The server serves `index.html` at `GET /` and static assets at `GET /assets/*` 4. If the dist directory doesn't exist at Rust compile time, `build.rs` creates a placeholder During development, run the Vite dev server separately and use its proxy to forward API calls to the running sync server. ### Database Operations ```bash cd sync-server # Create/reset database for development rm -rf db.sqlite* sqlx database create --database-url sqlite://db.sqlite3 sqlx migrate run --source src/app_state/database/migrations --database-url sqlite://db.sqlite3 cargo sqlx prepare --workspace # Add new migration sqlx migrate add --source src/app_state/database/migrations sqlx migrate run --source src/app_state/database/migrations --database-url sqlite://db.sqlite3 ``` ### Project Scripts - `scripts/check.sh`: Full CI check (builds, lints, tests both server and frontend). **Run before pushing.** - `scripts/check.sh --fix`: Same as above but auto-fixes linting and formatting issues - `scripts/e2e.sh`: End-to-end testing (e.g., `scripts/e2e.sh 8` for 8 concurrent clients) - `scripts/clean-up.sh`: Clean logs and database files - `scripts/bump-version.sh patch`: Publish new version (options: patch, minor, major) - `scripts/update-api-types.sh`: Update TypeScript bindings from Rust types (uses ts-rs) ## Code Structure ### Workspace Configuration The frontend uses npm workspaces with five packages: - `sync-client`: Core synchronization logic (builds dual bundles for web and Node.js) - `obsidian-plugin`: Obsidian-specific integration - `test-client`: Testing utilities for E2E tests - `local-client-cli`: Standalone CLI for VaultLink sync client - `history-ui`: Svelte 5 SPA for vault history browsing (built with Vite, embedded in server binary) ### Type Generation and API Updates Rust structs generate TypeScript types via ts-rs crate: 1. Rust structs annotated with `#[derive(TS)]` export to `sync-server/bindings/` 2. Run `scripts/update-api-types.sh` to copy bindings to `frontend/sync-client/src/services/types/` 3. Frontend imports these types for type-safe API communication ### Important Implementation Details **SQLx Compile-Time Verification:** - SQLx verifies SQL queries at compile time against the database schema - Run `cargo sqlx prepare --workspace` after schema changes to update `.sqlx/` directory - CI builds require prepared query metadata to avoid needing a live database ## Testing ### Running Tests **Server:** ```bash cargo test --verbose # All tests cargo test # Specific test ``` **Frontend:** ```bash npm run test # All workspaces npm run test -w sync-client # Specific workspace ``` **E2E:** ```bash scripts/e2e.sh 8 # 8 concurrent clients scripts/clean-up.sh # Clean up after tests ``` ### Test Structure - **Rust**: Unit tests alongside source files, uses `cargo-insta` for snapshot testing - **TypeScript**: `.test.ts` files using Node.js native test runner (not Jest) - **E2E**: Uses `test-client` to simulate multiple concurrent users with random operations ## Code Style and Formatting ### Rust - Extensive Clippy lints (see `Cargo.toml`) - Pedantic linting rules enabled - Forbids unsafe code - Uses `rustfmt.toml` for formatting configuration (4 spaces, Unix line endings) - Run `cargo fmt --all` to format ### TypeScript - **Prettier**: 4-space indentation, no trailing commas, LF line endings - **YAML/Markdown override**: 2-space indentation (via prettier config) - **ESLint**: Strict rules with unused imports detection - Configuration in `frontend/package.json` - Run `npm run lint` to format and fix issues ### Svelte (History UI) - Uses Svelte 5 runes syntax (`$state`, `$derived`, `$effect`, `$props`) - Vite as bundler with `@sveltejs/vite-plugin-svelte` - Excluded from the main ESLint config (Svelte files need different linting); `history-ui/**` is in the eslint ignores list - CSS is component-scoped via Svelte's `