# Agent Guidelines for Jello This document provides guidelines for AI coding agents working on the Jello codebase. ## Project Overview Jello is a WIP video client for Jellyfin written in Rust, focusing on HDR video playback using: - **iced** - Primary GUI toolkit - **gstreamer** - Video + audio decoding library - **wgpu** - Rendering video from GStreamer in iced ## Build, Test, and Lint Commands ### Building ```bash # Build in release mode cargo build --release cargo build -r # Build specific workspace member cargo build -p api cargo build -p gst cargo build -p ui-iced # Run the application cargo run --release -- -vv just jello # Uses justfile ``` ### Testing ```bash # Run all tests in workspace cargo test --workspace # Run tests for a specific package cargo test -p gst cargo test -p api cargo test -p iced-video # Run a single test by name cargo test test_appsink cargo test -p gst test_appsink # Run a specific test in a specific file cargo test -p gst --test # Run tests with output cargo test -- --nocapture cargo test -- --show-output ``` ### Linting and Formatting ```bash # Check code without building cargo check cargo check --workspace # Run clippy (linter) cargo clippy cargo clippy --workspace cargo clippy --workspace -- -D warnings # Format code cargo fmt cargo fmt --all # Check formatting without modifying files cargo fmt --all -- --check ``` ### Other Tools ```bash # Check for security vulnerabilities and license compliance cargo deny check # Generate Jellyfin type definitions just typegen ``` ## Code Style Guidelines ### Rust Edition - Use **Rust 2024 edition** (as specified in Cargo.toml files) ### Imports - Use `use` statements at the top of files - Group imports: std library, external crates, then local modules - Use `crate::` for absolute paths within the crate - Common pattern: create a `priv_prelude` module for internal imports - Use `pub use` to re-export commonly used items - Use wildcard imports (`use crate::priv_prelude::*;`) within internal modules when a prelude exists Example: ```rust use std::sync::Arc; use reqwest::{Method, header::InvalidHeaderValue}; use serde::{Deserialize, Serialize}; use crate::errors::*; ``` ### Naming Conventions - **Types/Structs/Enums**: PascalCase (e.g., `JellyfinClient`, `Error`, `AppSink`) - **Functions/Methods**: snake_case (e.g., `request_builder`, `stream_url`) - **Variables**: snake_case (e.g., `access_token`, `device_id`) - **Constants**: SCREAMING_SNAKE_CASE (e.g., `NEXT_ID`, `GST`) - **Modules**: snake_case (e.g., `priv_prelude`, `error_stack`) ### Error Handling - Use **`error-stack`** for error handling with context propagation - Use **`thiserror`** for defining error types - Standard error type pattern: ```rust pub use error_stack::{Report, ResultExt}; #[derive(Debug, thiserror::Error)] #[error("An error occurred")] pub struct Error; pub type Result> = core::result::Result; ``` - Attach context to errors using `.change_context(Error)` and `.attach("description")` - Use `#[track_caller]` on functions that may panic or error for better error messages - Error handling example: ```rust self.inner .set_state(gstreamer::State::Playing) .change_context(Error) .attach("Failed to set pipeline to Playing state")?; ``` ### Types - Prefer explicit types over type inference when it improves clarity - Use `impl Trait` for function parameters when appropriate (e.g., `impl AsRef`) - Use `Option` and `Result` idiomatically - Use `Arc` for shared ownership - Use newtype patterns for semantic clarity (e.g., `ApiKey` wrapping `secrecy::SecretBox`) ### Formatting - Use 4 spaces for indentation - Line length: aim for 100 characters, but not strictly enforced - Use trailing commas in multi-line collections - Follow standard Rust formatting conventions (enforced by `cargo fmt`) ### Documentation - Add doc comments (`///`) for public APIs - Use inline comments (`//`) sparingly, prefer self-documenting code - Include examples in doc comments when helpful ### Async/Await - Use `tokio` as the async runtime - Mark async functions with `async` keyword - Use `.await` for async operations - Common pattern: `tokio::fs` for file operations ### Module Structure - Use `mod.rs` or inline modules as appropriate - Keep related functionality together - Use `pub(crate)` for internal APIs - Re-export commonly used items at crate root ### Macros - Custom macros used: `wrap_gst!`, `parent_child!` - Use macros for reducing boilerplate, only in the `gst` crate ### Testing - Place tests in the same file with `#[test]` or `#[cfg(test)]` - Use descriptive test function names (e.g., `test_appsink`, `unique_generates_different_ids`) - Initialize tracing in tests when needed for debugging ### Dependencies - Prefer well-maintained crates from crates.io - Use `workspace.dependencies` for shared dependencies across workspace members - Pin versions when stability is important ### Workspace Structure The project uses a Cargo workspace with multiple members: - `.` - Main jello binary - `api` - Jellyfin API client - `gst` - GStreamer wrapper - `ui-iced` - Iced UI implementation - `ui-gpui` - GPUI UI implementation (optional) - `store` - Secret/data/storage management - `jello-types` - Shared type definitions - `typegen` - Jellyfin type generator - `crates/iced-video` - Custom iced video widget - `examples/hdr-gstreamer-wgpu` - HDR example ### Project-Specific Patterns - Use `LazyLock` for global initialization (e.g., GStreamer init) - Use the builder pattern with method chaining (e.g., `request_builder()`) - Use `tap` crate's `.pipe()` for functional transformations - Prefer `BTreeMap`/`BTreeSet` over `HashMap`/`HashSet` when order matters - Prefer a functional programming style instead of an imperative one. - When building UIs keep the handler and view code in the same module (eg. settings view and settings handle in the same file) ## License All code in this project is MIT licensed.