Files
agcanvas/AGENTS.md

4.9 KiB

AGENTS.md - agcanvas

Guidelines for AI agents working in this Rust codebase.

Project Overview

agcanvas is a desktop application for agent-human collaboration via SVG visualization.

  • GUI framework: egui/eframe
  • SVG parsing: usvg, rendering: resvg/tiny-skia
  • Async runtime: tokio
  • WebSocket: tokio-tungstenite
  • Serialization: serde/serde_json
  • Error handling: anyhow/thiserror
  • Logging: tracing

Build/Lint/Test Commands

cargo build                    # Debug build
cargo build --release          # Release build (optimized)
cargo run --release            # Run app

cargo clippy                   # Run clippy lints
cargo fmt                      # Format all code

cargo test                     # Run all tests
cargo test <test_name>         # Run single test by name
cargo test -- --nocapture      # Show println! output

cargo check                    # Type check only (fast)
cargo doc --open               # Generate and open docs

Project Structure

src/
├── main.rs              # Entry point, window setup
├── app.rs               # Main app state, UI (eframe::App impl)
├── element_tree.rs      # ElementTree, Element, ElementKind types
├── clipboard.rs         # System clipboard integration
├── canvas/
│   ├── state.rs         # Pan/zoom transformation state
│   └── interaction.rs   # Mouse/keyboard input handling
├── svg/
│   ├── parser.rs        # SVG -> ElementTree conversion
│   └── renderer.rs      # SVG -> pixels (resvg/tiny-skia)
└── agent/
    ├── protocol.rs      # JSON message types
    └── server.rs        # WebSocket server (ws://127.0.0.1:9876)

Code Style

Imports (ordered with blank lines between groups)

use std::collections::HashMap;   // 1. std
use anyhow::Result;              // 2. External crates
use crate::element_tree::ElementTree;  // 3. crate::
use super::protocol::AgentRequest;     // 4. super/self

Naming

Item Convention Example
Types/Structs PascalCase ElementTree, CanvasState
Functions/Methods snake_case parse_svg, find_by_id
Constants SCREAMING_SNAKE AGENT_PORT
Enum variants PascalCase ElementKind::Rectangle

Error Handling

// Use anyhow::Result for fallible functions
pub fn parse_svg(svg_data: &str) -> Result<(ElementTree, Tree)> {
    let tree = Tree::from_str(svg_data, &options)?;  // ? operator
    Ok((element_tree, tree))
}

// Return Option<T> for recoverable errors
pub fn find_by_id(&self, id: &str) -> Option<&Element> { ... }

// Use anyhow::anyhow!() for error messages
.ok_or_else(|| anyhow::anyhow!("Failed to create pixmap"))?

Struct/Enum Definitions

#[derive(Debug, Clone, Serialize, Deserialize)]  // Always derive common traits
pub struct ElementTree { pub root: Element, pub metadata: TreeMetadata }

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]  // Use tag for enum serialization
pub enum ElementKind {
    Group { name: Option<String> },
    Rectangle { rx: Option<f32>, ry: Option<f32> },
}

impl Default for CanvasState {  // Implement Default for configurable structs
    fn default() -> Self { Self { offset: Vec2::ZERO, zoom: 1.0, ... } }
}

Module Organization

Each module folder has a mod.rs that re-exports public items:

// src/svg/mod.rs
mod parser;
mod renderer;
pub use parser::parse_svg;
pub use renderer::SvgRenderer;

Async & Logging

// Spawn async tasks from runtime
runtime.spawn(async move {
    if let Err(e) = server.run().await {
        tracing::error!("Agent server error: {}", e);
    }
});

// Use tracing macros
tracing::info!("Agent server listening on ws://{}", addr);
tracing::debug!("Processing request: {:?}", request);

Common Patterns

// Arc + RwLock for shared state
tree_handle: Arc<RwLock<Option<ElementTree>>>
let mut guard = tree_handle.write().await;
*guard = Some(tree_clone);

// Option chaining
let svg_data = self.clipboard.as_mut().and_then(|c| c.get_svg());
name.as_ref().map(|n| format!(" '{}'", n)).unwrap_or_default()

// Turbofish for type inference
serde_json::from_str::<AgentRequest>(&text)

Formatting

Run cargo fmt before committing:

  • 4-space indentation
  • Max line width: 100 characters
  • Trailing commas in multi-line constructs

What NOT to Do

  • No unwrap() in production paths (use ? or explicit handling)
  • No clippy suppression without justification
  • No blocking the main thread (use async/spawn)
  • No unsafe without clear documentation
  • No unnecessary dependencies (consider binary size)

Agent Protocol

WebSocket server on ws://127.0.0.1:9876:

// Request examples
{"type": "GetTree"}
{"type": "GetElementById", "id": "button-1"}
{"type": "Describe"}
{"type": "GenerateCode", "target": "react", "element_id": null}

// Response
{"type": "Tree", "tree": {...}}
{"type": "Error", "message": "No SVG loaded"}