loomx 0.1.0-alpha.1

Typed headless rendering for HTMX
Documentation
# loomx

**loomx** is a strongly‑typed, server‑driven UI framework for Rust.
It renders HTML fragments and pages using typed components, designed for **HTMX‑first** applications with minimal JavaScript.

At its core, loomx lets you:

- Define components as **typed Rust structs**
- Render them using **Askama templates**
- Register components at compile time
- Render fragments dynamically by name
- Integrate cleanly with **Axum**
- Avoid JavaScript-heavy frontend frameworks

---

## Project Goals

- Strong typing end‑to‑end (props → templates → rendering)
- Server‑driven HTML with HTMX
- Explicit, debuggable architecture
- Framework‑agnostic core
- Thin transport integrations (Axum today, others possible later)

Non‑goals:
- Client‑side virtual DOM
- JS component systems
- Implicit magic routing

---

## Workspace Layout

```text
loomx/
├─ crates/
│  ├─ loomx-core        # Registry, rendering, props parsing
│  ├─ loomx-macros      # register_component! macro
│  ├─ loomx-axum        # Axum integration (routes, handlers)
│  └─ loomx-cli         # CLI (experimental)
├─ components/
│  └─ loomx-components-basic
│     └─ Example component pack
├─ examples/
│  └─ basic             # Example Axum app
```

---

## Core Concepts

### Components

A component is:

- A Rust struct (typed props)
- An Askama template
- A stable string name

```rust
#[derive(Template, Deserialize)]
#[template(path = "components/hello.html")]
pub struct Hello {
    pub name: String,
}

impl Component for Hello {
    const NAME: &'static str = "hello";
}

register_component!(Hello);
```

### Component Registration

Components are registered at **compile time** using `inventory`.

- No global mutable registries
- No runtime plugin loading
- Deterministic startup behavior

Duplicate component names are detected at startup.

---

## Rendering

```rust
use loomx::render;

let html = render("hello", json!({ "name": "Ada" }))?;
```

Rendering returns HTML or a typed error.

---

## Props Parsing (Core)

`loomx-core` provides a transport‑agnostic parser:

- `application/json`
- `application/x-www-form-urlencoded`

```rust
parse_props(content_type, body_bytes)
```

This logic is reused by all integrations.

---

## Axum Integration

`loomx-axum` provides:

### Generic Fragment Routes

- `GET  /fragments/:name` → query params
- `POST /fragments/:name` → form or JSON body

```rust
Router::new().merge(loomx_axum::fragment_router())
```

### Typed Fragment Routes (Ergonomic)

```rust
Router::new().merge(
    loomx_axum::typed_fragment_routes! {
        "/fragments/hello" => ("hello", HelloProps),
    }
)
```

This generates:

- `GET  /fragments/hello`
- `POST /fragments/hello` (form)
- `POST /fragments/hello/json` (JSON)

All fully typed.

---

## HTMX Support

loomx is HTMX‑first:

- Fragment responses are HTML
- HTMX headers are detected
- Designed for `hx-get`, `hx-post`, `hx-swap`

No JS framework required.

---

## Logging

Structured logging via `tracing`:

- Component render attempts
- HTMX detection
- Request metadata
- Startup validation

---

## Safety & Correctness

- Strong typing everywhere
- Duplicate component detection
- Explicit linking of component crates
- No hidden runtime state

---

## Status

loomx is **early but functional**.

Current focus:
- API stabilization
- Documentation
- CLI & tooling
- More integrations (Actix / Poem)

---

## License

MIT

---

## Update: Typed Rendering & Gallery

loomx now provides an ergonomic typed rendering helper:

- `render_with<T: Serialize>(name, &T)` — preferred for application code

This avoids manual construction of `serde_json::Value` while preserving the dynamic
component registry model.

The example application includes an **interactive component gallery** at `/gallery`
showing:
- inline rendering
- HTMX-powered preview slots
- typed fragment routes under `/typed/...`

See `docs/gallery.md` for details.