Files
agcanvas/AGENTS.md

171 lines
4.9 KiB
Markdown

# 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
```bash
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)
```rust
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
```rust
// 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
```rust
#[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:
```rust
// src/svg/mod.rs
mod parser;
mod renderer;
pub use parser::parse_svg;
pub use renderer::SvgRenderer;
```
### Async & Logging
```rust
// 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
```rust
// 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`:
```json
// 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"}
```