

Also from Kynth Studios


Also from Kynth Studios


Also from Kynth Studios
1# Spacedrive Core v2 Development Guide23## Quick Start45### Development Workflow671. Start daemon: `cargo run --bin sd-daemon`82. Make code changes93. Run tests: `cargo test`104. Rebuild and restart: `cargo run --bin sd-cli -- restart`115. Test via CLI: `cargo run --bin sd-cli -- <command>`1213### Common Commands1415```bash16cargo build # Build the project17cargo test # Run all tests18cargo test <test_name> # Run specific test19cargo clippy # Lint code20cargo fmt # Format code21cargo run --bin sd-cli -- <command> # Run CLI (binary is sd-cli, not spacedrive)22```2324### Common Mistakes2526- Running `spacedrive` instead of `sd-cli` (the binary name is `sd-cli`)27- Forgetting to restart daemon after rebuilding28- Using `println!` instead of `tracing` macros (`info!`, `debug!`, etc)29- Implementing `Wire` manually instead of using `register_*` macros30- Blocking the async runtime with synchronous I/O operations3132### Quick tips3334- On frontend apps, such as the interface in React, you must ALWAYS ensure type-safety based on the auto generated TypeScript types from `ts-client`. Never cast to as any or redefine backend types. our hooks are typesafe with correct input/output types, but sometimes you might need to access types directly from the `ts-client`.35- If you have changed types on the backend that are public to the frontend (have `Type` derive), then you must regenerate the types using `cargo run --bin generate_typescript_types`36- Read the `.mdx` files in /docs for context on any part of the app, they are kept up to date.37-3839## Architecture Overview4041Spacedrive uses daemon-client architecture. A single daemon process manages core functionality. Multiple clients (CLI, GraphQL server, desktop app) connect via Unix domain sockets.4243### CQRS and DDD Pattern4445- **Domain** (`src/domain/`): Core data structures and business logic (nouns)46- **Operations** (`src/ops/`): Actions and queries (verbs)47- **Actions**: State-changing operations (writes)48- **Queries**: Data retrieval without state changes (reads)4950### Feature Module Structure5152Each feature lives in its own module under `src/ops/`. Example: `src/ops/files/share`5354```55src/ops/files/share/56├── action.rs # State-changing logic57├── input.rs # Action input structures58├── output.rs # Action output structures59└── job.rs # Long-running job implementation (if needed)60```6162Complete feature example:6364```rust65// src/ops/files/share/input.rs66#[derive(Debug, Serialize, Deserialize)]67pub struct ShareFileInput {68 pub file_id: i32,69 pub recipient: String,70}7172// src/ops/files/share/output.rs73#[derive(Debug, Serialize, Deserialize)]74pub struct ShareFileOutput {75 pub share_id: String,76 pub url: String,77}7879// src/ops/files/share/action.rs80use super::{ShareFileInput, ShareFileOutput};8182pub struct ShareFileAction;8384crate::register_library_action!(ShareFileAction, "files.share");8586impl Action for ShareFileAction {87 type Input = ShareFileInput;88 type Output = ShareFileOutput;8990 async fn run(input: Self::Input, ctx: &ActionContext) -> Result<Self::Output> {91 // Implementation92 }93}94```9596## Communication Architecture9798Spacedrive supports multiple communication patterns for different platforms and use cases.99100### Daemon-Client Communication (Tauri Desktop, CLI, Web)101102The Tauri desktop app, CLI, and web interface connect to a daemon process via Unix domain sockets (or WebSockets for web). Communication uses JSON-RPC 2.0 with Wire method strings.103104**Registration Macros:**105106Never implement `Wire` manually. Use registration macros:107108```rust109// Queries110crate::register_query!(NetworkStatusQuery, "network.status");111// Generates: "query:network.status"112113// Library Actions114crate::register_library_action!(FileCopyAction, "files.copy");115// Generates: "action:files.copy.input"116117// Core Actions118crate::register_core_action!(LibraryCreateAction, "libraries.create");119// Generates: "action:libraries.create.input"120```121122**Registry System:**123124The `inventory` crate collects operations at compile time. When you use `register_query!` or `register_library_action!`, the operation automatically appears in global `QUERIES` and `ACTIONS` hashmaps at startup. You never manually register operations.125126Location: `core/src/ops/registry.rs`127128### Tauri Desktop Development129130The Tauri app (`apps/tauri/`) is the primary desktop application for Spacedrive. It connects to the daemon via the TypeScript client.131132**Development Workflow:**133134```bash135# Install dependencies136bun install137138# Run Tauri app in dev mode (auto-starts daemon)139cd apps/tauri140bun run tauri:dev141142# Build for production143bun run tauri:build144```145146**TypeScript Client:**147148The TypeScript client (`packages/ts-client/`) is auto-generated from Rust types using Specta:149150```bash151# Generate TypeScript types152cargo run --bin generate_typescript_types153```154155**Output:** `packages/ts-client/src/generated.ts`156157**Architecture:**158159```160Tauri App (React)161 ↓162@sd/ts-client (TypeScript)163 ↓164Daemon (Unix Socket / IPC)165 ↓166RpcServer (Rust)167 ↓168Operation Registry169```170171### Native Prototypes (iOS, macOS)172173**Note:** iOS and macOS apps are experimental prototypes, not production apps.174175Native prototypes embed the core directly as a library via FFI rather than connecting to a daemon. These are located in `apps/ios/` and `apps/macos/` but are private and not documented for public use.176177**Swift Client Generation:**178179For the prototypes, Swift types can be generated:180181```bash182cargo run --bin generate_swift_types183```184185Output: `packages/swift-client/Sources/SpacedriveClient/`186187### Extension System (WASM)188189Extensions run as sandboxed WASM modules that interact with Spacedrive core via host functions. Extensions are distributed as compiled `.wasm` files.190191**Architecture:**192193```194Extension.wasm (compiled Rust)195 ↓196spacedrive-sdk (Rust crate)197 ↓198Host Functions (FFI boundary)199 ↓200Core (VDFS, Jobs, AI, etc.)201```202203**Key Components:**204205**SDK Location:** `crates/sdk/`206207- High-level Rust API abstracting FFI details208- Procedural macros for extension definition209- Type-safe job, model, and action builders210211**Extension Development:**212213Extensions use procedural macros to minimize boilerplate:214215```rust216use spacedrive_sdk::prelude::*;217218#[extension(219 id = "test-extension",220 name = "Test Extension",221 version = "0.1.0",222 jobs = [test_counter],223)]224struct TestExtension;225226#[derive(Serialize, Deserialize, Default)]227pub struct CounterState {228 pub current: u32,229 pub target: u32,230 pub processed: Vec<String>,231}232233#[job(name = "counter")]234fn test_counter(ctx: &JobContext, state: &mut CounterState) -> Result<()> {235 ctx.log(&format!("Starting counter (current: {}, target: {})",236 state.current, state.target));237238 while state.current < state.target {239 if ctx.check_interrupt() {240 ctx.checkpoint(state)?;241 return Err(Error::OperationFailed("Interrupted".into()));242 }243244 state.current += 1;245 ctx.report_progress(246 state.current as f32 / state.target as f32,247 &format!("Counted {}/{}", state.current, state.target),248 );249250 if state.current % 10 == 0 {251 ctx.checkpoint(state)?;252 }253 }254255 Ok(())256}257```258259**Host Functions:**260261Extensions import minimal FFI functions:262263```rust264#[link(wasm_import_module = "spacedrive")]265extern "C" {266 fn spacedrive_log(level: u32, msg_ptr: *const u8, msg_len: usize);267 fn register_job(268 job_name_ptr: *const u8,269 job_name_len: u32,270 export_fn_ptr: *const u8,271 export_fn_len: u32,272 resumable: u32,273 ) -> i32;274}275```276277**Building Extensions:**278279```bash280# From extension directory281cargo build --target wasm32-unknown-unknown --release282283# Output: target/wasm32-unknown-unknown/release/extension_name.wasm284```285286**Extension Capabilities:**287288Extensions can define:289290- Models: Data structures stored in `models` table (content-scoped, standalone, or entry-scoped)291- Jobs: Long-running resumable operations292- Actions: User-invoked operations with preview-commit workflow293- Agents: Autonomous logic with memory and event handling294- UI: Custom views via `ui_manifest.json`295296**Example Use Cases:**297298- Photos extension: Face detection, scene tagging, album organization299- Finance extension: Receipt extraction, expense tracking300- Research extension: Citation extraction, knowledge graphs301302**Key Benefits:**303304- Single `.wasm` file works on all platforms305- True sandboxing (WASM isolation)306- Resumable jobs with checkpointing307- Type-safe API with procedural macros308- No core modifications needed for new features309310**Documentation:**311312- `/docs/sdk/sdk.md` - Complete SDK specification and API reference313- `extensions/test-extension/` - Working example extension314- `crates/sdk/` - SDK implementation315- `crates/sdk-macros/` - SDK procedural macros316317**Status:** SDK implementation in progress. Test extension compiles to WASM successfully. Core integration for loading and executing WASM modules is next phase.318319## Code Standards320321### Import Organization322323Group imports with blank lines between groups:324325```rust326// Standard library327use std::path::PathBuf;328use std::sync::Arc;329330// External crates331use serde::{Deserialize, Serialize};332use tokio::sync::RwLock;333334// Local modules335use crate::domain::library::Library;336use crate::ops::Action;337```338339### Naming Conventions340341- Functions/variables: `snake_case`342- Types: `PascalCase`343- Constants: `SCREAMING_SNAKE_CASE`344345### Error Handling346347Use `Result<T, E>` for all fallible operations. Use `thiserror` for custom errors, `anyhow` for application errors.348349```rust350use thiserror::Error;351352#[derive(Error, Debug)]353pub enum ShareError {354 #[error("File not found: {0}")]355 FileNotFound(i32),356357 #[error("Permission denied")]358 PermissionDenied,359360 #[error("Database error: {0}")]361 Database(#[from] sea_orm::DbErr),362}363364pub async fn share_file(id: i32) -> Result<String, ShareError> {365 let file = find_file(id).await.ok_or(ShareError::FileNotFound(id))?;366 // Implementation367 Ok(share_url)368}369```370371### Async Code372373- Use `async/await` syntax374- Prefer `tokio` primitives (`tokio::sync::RwLock`, `tokio::spawn`)375- Avoid blocking operations (use `tokio::fs` not `std::fs`)376- Use `tokio::task::spawn_blocking` for CPU-intensive work377378### Resumable Jobs379380Store job state within the job struct. Use `#[serde(skip)]` for non-persistent fields.381382```rust383#[derive(Serialize, Deserialize)]384pub struct FileCopyJob {385 pub source: PathBuf,386 pub destination: PathBuf,387 pub copied_files: Vec<PathBuf>, // Persisted for resumability388389 #[serde(skip)]390 pub progress_tx: Option<tokio::sync::mpsc::Sender<Progress>>, // Not persisted391}392393impl Job for FileCopyJob {394 async fn run(&mut self, ctx: &JobContext) -> Result<()> {395 ctx.log().info("Starting file copy job");396397 for file in &self.files_to_copy {398 if self.copied_files.contains(file) {399 continue; // Skip already copied files on resume400 }401402 copy_file(file).await?;403 self.copied_files.push(file.clone());404 }405406 Ok(())407 }408}409```410411### Documentation412413**Core principle:** Explain WHY, not WHAT. Keep comments as short as possible. One sentence explaining rationale beats a paragraph restating code.414415**Module docs (`//!`):**416- Add a title with `#` for the module name417- Explain what the module does in plain language (not bullet points)418- Include design rationale naturally in prose419- Add runnable code examples showing usage420421```rust422//! # File Sharing System423//!424//! `core::ops::files::share` provides temporary file sharing via signed URLs.425//! Share links expire after 7 days by default to prevent indefinite access to426//! private files. UUID v5 deterministic IDs ensure the same file generates427//! consistent share URLs across devices without coordination.428//!429//! ## Example430//! ```rust,no_run431//! use spacedrive_core::ops::files::share::{ShareFileAction, ShareFileInput};432//!433//! let input = ShareFileInput { file_id: 123, recipient: "user@example.com" };434//! let output = ShareFileAction::run(input, &ctx).await?;435//! ```436```437438**Function docs (`///`):**439- First line: brief one-liner440- Second paragraph: explain design rationale and why this exists441- Document error handling philosophy when relevant442- Explain non-obvious behavior and platform differences443444```rust445/// Creates a share link with automatic expiration.446///447/// Share links use signed JWTs so the daemon can validate them without448/// database lookups on every request. Expiration is enforced server-side449/// to prevent timezone manipulation. Recipients without library access450/// get read-only access to the specific file only.451///452/// Returns `ShareError::PermissionDenied` if the file is private and453/// the recipient isn't a library member. The share is still created454/// but marked inactive for audit logging.455pub async fn share_file(input: ShareFileInput) -> Result<ShareFileOutput>456```457458**Inline comments:**459- Delete comments that restate obvious code460- Explain WHY for decisions, not WHAT the code does461- Use one sentence when possible462- Only expand for truly non-obvious consequences463464```rust465// Good: explains WHY466// Lowercase for case-insensitive search matching.467let ext = path.extension().map(|e| e.to_lowercase());468469// Bad: restates code470// Extract file extension and convert to lowercase471let ext = path.extension().map(|e| e.to_lowercase());472473// Good: explains consequence474// Preserve ephemeral UUIDs so tags attached during browsing survive promotion to managed location.475let uuid = ephemeral_cache.get(path).unwrap_or_else(|| Uuid::new_v4());476477// Bad: verbose explanation of obvious behavior478// UUID assignment strategy:479// 1. First check if there's an ephemeral UUID480// 2. If not, generate a new one481let uuid = ephemeral_cache.get(path).unwrap_or_else(|| Uuid::new_v4());482```483484**Error handling comments:**485Explain strategy and recovery, not just "log and continue".486487```rust488// Good: explains recovery489// Best-effort: continue with remaining moves, stale paths cleaned up on next reindex.490Err(e) => ctx.log(format!("Failed to move: {}", e)),491492// Bad: states the obvious493// Log error but continue494Err(e) => ctx.log(format!("Failed to move: {}", e)),495```496497**Platform-specific comments:**498Explain consequences, not implementation blockers.499500```rust501// Good: explains why and fallback502#[cfg(windows)]503pub fn get_inode(_metadata: &std::fs::Metadata) -> Option<u64> {504 // Windows file indices are unstable across reboots; fall back to path-only matching.505 None506}507508// Bad: over-explains implementation details509#[cfg(windows)]510pub fn get_inode(_metadata: &std::fs::Metadata) -> Option<u64> {511 // Windows doesn't have inodes.512 // The method `file_index()` is unstable (issue #63010).513 // Returning None is safe as the field is Optional.514 None515}516```517518**Never use:**519- Placeholder comments ("for now", "TODO: extract this later")520- Markdown formatting (`**bold**`, `_italic_`) in code comments521- ASCII diagrams (put those in `/docs/` if needed)522- Section divider comments (`// ========== Section ==========`)523- Comments explaining removed code during refactors524525Track future work in GitHub issues, not code comments.526527### Formatting528529Run `cargo fmt` before committing. Tabs for indentation. No emojis.530531## Logging532533### Setup534535Use `tracing_subscriber` in main or examples:536537```rust538use tracing_subscriber::EnvFilter;539540fn main() {541 tracing_subscriber::fmt()542 .with_env_filter(543 EnvFilter::try_from_default_env()544 .unwrap_or_else(|_| EnvFilter::new("sd_core=info"))545 )546 .init();547}548```549550## Writing Style551552This applies to all documentation, code comments, and design documents.553554Use clear, simple language. Write short, impactful sentences. Use active voice. Focus on practical, actionable information.555556Address the reader directly with "you" and "your". Support claims with data and examples when possible.557558Avoid these constructions:559560- Em dashes (use commas or periods)561- "Not only this, but also this"562- Metaphors and cliches563- Generalizations564- Setup language like "in conclusion"565- Unnecessary adjectives and adverbs566- Emojis, hashtags, markdown formatting in prose567568Avoid these words:569comprehensive, delve, utilize, harness, realm, tapestry, unlock, revolutionary, groundbreaking, remarkable, pivotal570571### Macros572573Use `tracing` macros, never `println!`:574575```rust576use tracing::{info, warn, error, debug};577578info!("Server started on port {}", port);579debug!(file_id = %id, "Processing file");580warn!(error = %e, "Retrying operation");581error!("Failed to connect to database");582```583584### Job Logging585586Use `ctx.log()` in job implementations for automatic `job_id` tagging:587588```rust589impl Job for MyJob {590 async fn run(&mut self, ctx: &JobContext) -> Result<()> {591 ctx.log().info("Job started");592 ctx.log().debug!(progress = %self.progress, "Processing");593 Ok(())594 }595}596```597598### Log Levels599600- `debug`: Detailed flow for troubleshooting601- `info`: User-relevant events (server start, job completion)602- `warn`: Recoverable issues (retry, fallback)603- `error`: Failures requiring attention604605### Environment Control606607Use `RUST_LOG` environment variable:608609```bash610RUST_LOG=debug cargo run --bin sd-cli611RUST_LOG=sd_core=trace cargo run612RUST_LOG=sd_core::ops=debug cargo run613```614615## Testing616617### Test Organization618619- Unit tests: Colocated in `#[cfg(test)]` modules620- Integration tests: `tests/` directory at crate root621622```rust623// src/ops/files/share/action.rs624625#[cfg(test)]626mod tests {627 use super::*;628629 #[tokio::test]630 async fn test_share_file() {631 let input = ShareFileInput {632 file_id: 1,633 recipient: "test@example.com".to_string(),634 };635636 let output = share_file(input).await.unwrap();637 assert!(!output.share_id.is_empty());638 }639}640```641642### Running Tests643644```bash645cargo test # All tests646cargo test test_share_file # Specific test647cargo test --lib # Library tests only648cargo test -- --nocapture # Show output649```650651## Task Tracking652653Spacedrive uses a file-based task system in `/.tasks/` to track features, epics, and development work. All task files are version-controlled alongside the code.654655### When to Create Tasks656657Create tasks for work that:658659- Introduces a new feature or capability660- Refactors a significant system or module661- Fixes a bug requiring architectural changes662- Implements a whitepaper specification663664Do not create tasks for:665666- Routine code formatting or style fixes667- Trivial bug fixes (single line changes)668- Documentation updates to existing features669- Dependency version bumps670671### Task Structure672673Each task is a Markdown file: `CATEGORY-###-title-slug.md`674675```yaml676---677id: CORE-042678title: "Implement file sharing API"679status: "In Progress"680assignee: "james"681priority: "High"682tags: ["core", "networking"]683whitepaper: "Section 4.2" # And/or design_doc: DESIGN_DOC_NAME.md684---685686## Description687Brief overview of what needs to be done and why.688689## Implementation Steps690- [ ] Create share action in src/ops/files/share691- [ ] Add database schema for shares table692- [ ] Implement expiration logic693694## Acceptance Criteria695- Share links work across all platforms696- Expired shares return 404697- Tests cover edge cases698```699700### Managing Tasks701702```bash703# List your active tasks704cargo run -p task-validator -- list --assignee "yourname" --status "In Progress"705706# List high priority tasks707cargo run -p task-validator -- list --priority "High" --sort-by id708709# Validate before committing (automatic via git hook)710cargo run -p task-validator -- validate711```712713### Task Lifecycle7147151. Create task file in `/.tasks/` with `status: "To Do"`7162. Update status to `"In Progress"` when you start work7173. Complete implementation and tests7184. Update status to `"Done"` and commit719720Full documentation: `/docs/core/task-tracking.md`721722## Debugging723724### Log Files725726Job logs live in the `job_logs` directory in the data folder root.727728### Daemon Restart729730After rebuilding, restart the daemon to use the latest code:731732```bash733cargo build734cargo run --bin sd-cli -- restart735```736737### Verbose Logging738739```bash740RUST_LOG=debug cargo run --bin sd-daemon741RUST_LOG=sd_core::jobs=trace cargo run742```743744## Documentation Locations745746- Core architecture: `/docs/core/`747- Design docs and RFCs: `/docs/core/design/`748- Application docs: `/docs/`749- Daemon details: `/docs/core/daemon.md`750- Task tracking: `/docs/core/task-tracking.md`751
One repository carrying more than one format is the comparison this product exists for: does anyone actually write different content in each file, or is one a copy of the other?
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| spacedriveapp/spacedrivecore/AGENTS.md · 39k | AGENTS.md | buildtestlint-formatstyle+2 | 89/100 | 14 days ago | |
| spacedriveapp/spacedrivepackages/interface/CLAUDE.md · 39k | CLAUDE.md | stylearchtypesui+3 | 65/100 | 14 days ago |
Same format, overlapping stack, ranked by quality.
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| code-yeongyu/oh-my-openagentpackages/web/AGENTS.md · 68k | AGENTS.md | setupbuildtestlint-format+6 | 100/100 | 13 days ago | |
| mui/material-uiAGENTS.md · 99k | AGENTS.md | setupbuildtestlint-format+9 | 100/100 | 14 days ago | |
| aaif-goose/gooseAGENTS.md · 53k | AGENTS.md | setupbuildtestlint-format+7 | 100/100 | 8 days ago | |
| duckduckgo/content-scope-scriptsspecial-pages/AGENTS.md · 70 | AGENTS.md | buildteststylearch+3 | 100/100 | 14 days ago | |
| deepseek-ai/deepseek-harnessnative/landlock-run/AGENTS.md · 104k | AGENTS.md | setupteststylearch+3 | 100/100 | today | |
| TryGhost/Ghoste2e/AGENTS.md · 55k | AGENTS.md | setupteststylearch+2 | 100/100 | today | |
| n8n-io/n8npackages/@n8n/agents/AGENTS.md · 201k | AGENTS.md | buildteststylearch+3 | 100/100 | 14 days ago | |
| elastic/elasticsearchx-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/AGENTS.md · 78k | AGENTS.md | buildtestlint-formatstyle+2 | 100/100 | 14 days ago |
A badge carrying the measured quality of the strongest agent config file in this repository, out of 100. It reads from this index every time somebody loads your page, so it changes when the measurement changes and there is nothing to keep up to date. Free, no account, and the value is not something you or we can set by hand.
[](https://rulestack.kynth.studio/configs/spacedriveapp-spacedrive-agents)Would rather not hotlink us? Every badge is also served in shields.io’s endpoint schema, so shields renders the image and your readers never talk to our domain:
Published by Toolproof, the masthead over this index and eight others. The method behind the number is at toolproof.kynth.studio/methodology, and the whole thing is readable as JSON with no key at /api.