Files
jello/AGENTS.md
2026-01-28 02:00:58 +05:30

5.9 KiB

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

# 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

# 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 <test_file_name> <test_function_name>

# Run tests with output
cargo test -- --nocapture
cargo test -- --show-output

Linting and Formatting

# 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

# 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:

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:
pub use error_stack::{Report, ResultExt};

#[derive(Debug, thiserror::Error)]
#[error("An error occurred")]
pub struct Error;

pub type Result<T, E = error_stack::Report<Error>> = core::result::Result<T, E>;
  • 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:
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<str>)
  • Use Option<T> and Result<T, E> idiomatically
  • Use Arc<T> for shared ownership
  • Use newtype patterns for semantic clarity (e.g., ApiKey wrapping secrecy::SecretBox<String>)

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.