Some checks failed
Monorepo build / build-flake (push) Has been cancelled
3.7 KiB
3.7 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project Overview
This is a Rust monorepo. The primary active project is Visions, a tabletop RPG game management application with WebSocket-based real-time communication. It uses:
- Server: Axum web framework with SQLite (rusqlite)
- UI: Yew (WebAssembly) with yew-router
- Shared types: visions-types crate shared between server and UI
- Component library: glimmer-yew provides reusable Yew components
Build Commands
# Build server
cargo build -p visions-server
# Build UI (requires trunk)
cd visions/ui && trunk build
# Run server (requires env vars)
DATABASE_PATH=... RELYING_PARTY=... RELYING_PARTY_ORIGIN=... SPA_BASE_URL=... cargo run -p visions-server
# Run all tests
cargo test
# Run specific test
cargo test -p visions-core test_name
# Run integration tests for server
cargo test -p visions-server --test integrations
Architecture
Visions Crates
- visions/core: Business logic, database layer, App struct. Uses nested
Result<Result<T, Error>, Fatal>pattern for error handling. - visions/server: Axum HTTP/WebSocket handlers and routing. Handlers use
http_adapterto convert nested results to HTTP responses. - visions/types: Shared types between server and UI. Uses
identifier!macro to generate ID types (UserId, GameId, etc.). - visions/ui: Yew WebAssembly frontend. Uses
StateProviderfor app state,StyleProviderfor theming.
glimmer-yew
Reusable Yew component library with styling via stylist. Components use Stylesheet context for theming. Key exports in prelude module.
Rust Style Guidelines
Error Handling
Use nested results to separate recoverable errors from fatal errors:
fn fallible() -> Result<Result<(), Error>, Fatal>
fn search() -> Result<Option<()>, Fatal>
Fatal errors should never be caught except at the top level. Recoverable errors should never be promoted to fatal.
Let/Else Bindings
Prefer let/else over match for unwrapping:
// Preferred
let Some(game) = db.game(&image.game)? else {
return Ok(Err(Error::NotFound(image.game.as_str().to_string())));
};
// Avoid
let game = match db.game(&image.game)? {
Some(game) => game,
None => return Ok(Err(Error::NotFound(image.game.as_str().to_string()))),
};
Test Style
Tests use SCENARIO/GIVEN/WHEN/THEN comments and cool_asserts::assert_matches!:
/// SCENARIO: Description
/// GIVEN: Preconditions
/// WHEN: Action
/// THEN: Expected outcome
#[tokio::test]
async fn test_name() {
// GIVEN: ...
let support = TestSupport::new().await;
// WHEN: ...
let result = support.app.some_action().await;
// THEN: ...
assert_matches!(result, Ok(Ok(value)) => {
assert_eq!(value.field, expected);
});
}
Yew Component Guidelines (glimmer-yew)
Naming
- Name components naturally (e.g.,
Button, notButtonComponent) - Properties struct:
<ComponentName>Props
Properties
- Use
AttrValuefor string properties - Use
Prop<T>wrapper for sizable non-string properties - Always accept
#[prop_or_default] pub class: Classes - Callbacks:
on_<action>with#[prop_or(Callback::from(|_| {}))] - Optional properties:
#[prop_or_default]
Cloning in Callbacks
Use the clone! macro from glimmer-yew:
use glimmer_yew::clone;
let on_click = Callback::from(clone!(state, move |_| {
state.set(new_value);
}));
// Multiple values
clone!((state, props), move |_| { ... })
Styling
- Components should have no margin
- Avoid CSS nesting except for pseudo-selectors
- Use
stylist::css!macro with stylesheet tokens from context