yggdrasil/AGENTS.md

7.2 KiB
Raw Blame History

AGENTS.md

Development Commands

make dev           # tailwindcss watch + dx serve (needs PostgreSQL)
make build         # build-editor → highlight-css → tailwindcss → dx build --release
make build-linux   # same as build but targets x86_64-unknown-linux-musl
make css           # one-shot CSS
make css-watch     # watch mode
make test          # cargo test
make clean         # cargo clean + rm public/style.css

Build order matters: make build runs build-editorhighlight-css (cargo run --bin generate_highlight_css) → tailwindcss --minifydx build --release. Do not run dx build --release alone.

Prerequisites

  • Rust 1.95+ with wasm32-unknown-unknown target
  • dx CLI (cargo install dioxus-cli)
  • tailwindcss CLI v4 (standalone binary)
  • PostgreSQL running locally

Environment

Create .env (not committed):

DATABASE_URL=postgres://postgres:postgres@localhost:5432/yggdrasil
RUST_LOG=info

Optional tuning via env vars (all have sane defaults):

WEBP_QUALITY=85.0           # 0.0100.0, clamped
WEBP_METHOD=2               # 06, clamped
RATE_LIMIT_STRICT_PER_SEC=1
RATE_LIMIT_STRICT_BURST=5
RATE_LIMIT_UPLOAD_PER_SEC=2
RATE_LIMIT_UPLOAD_BURST=15
RATE_LIMIT_IMAGE_PER_SEC=10
RATE_LIMIT_IMAGE_BURST=50

Run migrations before first dev server start:

./migrate.sh       # auto-creates DB, runs migrations/ in order

Architecture: Conditional Compilation

Dioxus 0.7 fullstack project with two independent gates — the most common source of compilation errors.

Gate Applies to Used for
#[cfg(feature = "server")] Server binary only DB, env loading, background tasks, server function bodies, highlight, WebP, caching
#[cfg(target_arch = "wasm32")] WASM frontend only localStorage, DOM APIs, web_sys calls, theme detection

Critical: Both default features (web + server) are enabled in Cargo.toml. The dx CLI handles feature selection during builds.

Stub pattern: src/db/mod.rs provides a DummyPool when server feature is disabled — do not remove.

Dead code allowances: src/auth/password.rs and src/auth/session.rs carry #[allow(dead_code)] because the compiler sees them as unused in WASM builds where server function bodies are stripped.

Dual API Architecture

The server exposes two distinct API patterns:

  1. Dioxus server functions (#[server(Name, "/api")] in src/api/) — auto-routed, callable from both client and server Rust. Spread across src/api/auth.rs and src/api/posts/.

  2. Axum routes (registered in src/main.rs) — manual axum::Router for endpoints that don't fit the server-function model:

    • POST /api/upload — image upload (multipart, auth-required, rate-limited)
    • GET /uploads/{*path} — image serving with on-the-fly resize/rotate/convert (query params: w, h, thumb, rotate, format, quality)

Server Module Structure

src/api/          — server functions + Axum handlers
  auth.rs         — login, register, session validation
  markdown.rs     — Markdown→HTML rendering (pulldown-cmark + ammonia sanitization)
  image.rs        — image serving with processing pipeline + disk+memory cache
  upload.rs       — image upload, auto-converts to WebP
  rate_limit.rs   — governor-based rate limiting (3 tiers: strict/upload/image)
  slug.rs         — URL slug generation
  posts/          — CRUD server functions for blog posts
src/auth/         — password hashing (Argon2) + session token management
src/bin/          — generate_highlight_css (build-time CSS generation)
src/cache.rs      — moka future-based caches for posts, tags, stats
src/components/   — Dioxus UI components
src/db/           — PostgreSQL pool (deadpool-postgres, LazyLock global)
src/hooks/        — shared Dioxus hooks
src/models/       — Post, User, Tag data models
src/pages/        — route page components (frontend + admin)
src/tasks/        — background tokio tasks (session cleanup)
src/theme.rs      — light/dark theme with SSR cookie + WASM localStorage
src/webp.rs       — zenwebp encode/decode (image crate has no WebP)

Tiptap Editor Subproject

Rich-text editor in libs/tiptap-editor/, built as an IIFE library exposing window.TiptapEditor.

  • Output: public/tiptap/
  • make build runs npm install && npx vite build inside libs/tiptap-editor
  • src/pages/admin/write.rs initializes via js_sys::eval, polls window.__tiptap_ready

Do not edit public/tiptap/ — they are build artifacts.

Syntax Highlighting Pipeline

  • themes/ contains Catppuccin Latte (light) and Mocha (dark) .tmTheme files
  • syntaxes/ has custom Sublime syntax definitions (Kotlin, Swift)
  • src/bin/generate_highlight_css.rs generates public/highlight.css with class-based rules scoped under .md-content pre code, with .dark prefix for dark mode
  • src/highlight.rs uses syntect at runtime for code block highlighting
  • All gated behind #[cfg(feature = "server")]

Auth & Session

  • Registration: first user becomes admin; subsequent registrations rejected with "Registration is closed"
  • Login: sets an HttpOnly cookie via FullstackContext::add_response_header
  • Session validation: get_current_user reads session cookie, queries sessions + users tables
  • Background cleanup: tasks::session_cleanup::run_cleanup() deletes expired sessions every hour

Caching

  • Post/tag caches (src/cache.rs): moka future-based, TTL varies by data type (60s600s). Invalidated on writes.
  • Image processing cache (src/api/image.rs): two-tier — in-memory moka cache + disk cache in uploads/.cache/. Keyed by path + query params.

Testing

cargo test        # standard Rust test suite
dx check          # Dioxus type-check (catches component/Router issues)
cargo clippy      # lint

Most tests use #[cfg(all(test, feature = "server"))] — they only run when the server feature is active (which is the default). No integration tests requiring a database connection.

Image Processing Constraints

  • The image crate is configured without WebP support (default-features = false, features = ["jpeg", "png", "gif"]). Do not add WebP to the image crate features.
  • All WebP encode/decode goes through zenwebp via src/webp.rs.
  • Upload pipeline auto-converts non-GIF/non-WebP images to WebP, keeping original format if WebP is larger.
  • Image serving supports on-the-fly resize (w, h), thumbnail (thumb=WxH), rotation (90/180/270), and format conversion.

Build Artifacts (gitignored)

  • public/style.css — Tailwind output
  • public/highlight.css — generated by generate_highlight_css binary
  • public/tiptap/ — Vite build output
  • /dist, /.dioxus, /target
  • node_modules (inside libs/tiptap-editor/)
  • uploads/.cache/ — image processing disk cache

Notes

  • rand + getrandom with js feature are required for Argon2 salt generation in WASM builds.
  • #[allow(unused_mut, unused_variables)] on Write component is intentional — mut signals are used in #[cfg(target_arch = "wasm32")] blocks stripped in server builds.
  • Server uses incremental rendering with 300s cache (IncrementalRendererConfig in src/main.rs).