Introduction

Welcome to the Auric SPA blog!

Recent Posts

Releases

ReleaseDateSummary
0.2.32026-10-17Supports conditionals in templates, and event handlers and tracked properties in controllers.
0.2.22026-10-10Supports binding views to models, and generates derived signals for HTMLbars expression terms.
0.2.12026-10-03New HTMLbars parser and AST-based Leptos view serializer.
0.2.02026-09-26This release completes support for route params.
0.1.82026-09-19This release adds quoting of raw text, to fix its display within a Leptos view! macro.
0.1.72026-09-12This release adds two new features I'm incredibly excited about: Bucket enums, and component params.
0.1.62026-09-05This release provides routes with access to the store.
0.1.52026-08-15This release introduces support for Ember components, which are translated at build time into Leptos components. A LinkTo built-in component is also implemented. When generating an application adapter, it also provides an option to extend the REST adapter via delegation.
0.1.42026-08-08This release introduces a configuration mechanism, and replaces Handlebars-based client-side rendering of Handlebars templates with generated Leptos components. This release also replaces actix-router with leptos_router.
0.1.32026-08-01This release kicks off a Data Framework!, including model management, adapter management, and a REST adapter.
0.1.22026-07-25This release focuses on internal organization and bug fixes.
0.1.12026-07-18This release is primarily a maintenance release fixing lots of things like cyclical page reload during auric serve. It also introduces a new Route trait, and template management.
0.1.02026-07-04Welcome to the first release of the Auric SPA project! We're attempting to recreate our favorite Ember.js design patterns in type-safe async Rust that compiles to WASM.

Release 0.2.3 - 2026-10-17

This is the biggest Auric release yet. It introduces controllers with tracked properties and event handlers, conditional content in templates, dynamic class attributes, and the built-in array and concat helpers. On the data side, it adds a JSON:API adapter, Store::save(), and dirty tracking for models.

Read the full change log here.

Controllers

A controller holds the business logic for a route, such as event handlers and tracked values. Generate one with the CLI:

$ auric generate controller customers/new
created src/controllers/customers/new_controller.rs
updated src/controllers/customers/mod.rs
updated src/controllers/mod.rs

Add a tracked value by decorating a field with #[tracked], and add an event handler to the #[controller] impl block:

#[derive(AuricController, Default)]
pub struct CustomersNewController {
    #[tracked]
    counter: u16,
}

#[controller]
impl CustomersNewController {
    pub fn increment(&mut self) {
        self.counter += 1;
    }
}

Then reference both from the route's template:

{{!-- src/templates/customers/new.hbs --}}
<h2>Counter Example</h2>

Counter: {{this.counter}}

<button class="btn btn-default" {{on "click" this.increment}}>
    Increment
</button>

When you build, the tracked field becomes a Leptos signal, and the {{on "click" ...}} modifier becomes an on:click handler that moves the value into the controller, calls your function, and moves the result back out through generated accessor functions:

let (counter, set_counter) = signal::<u16>(Default::default()); // tracked from controller

view! {
    <h2>"Counter Example"</h2>
    "Counter: "
    {move || counter.get()}
    <button
        class="btn btn-default"
        on:click=move |_| {
            controller.set_counter(&counter);
            controller.increment();
            set_counter.set(*controller.get_counter());
        }
    >
        "Increment"
    </button>
}

Because {{this.counter}} and this.increment are bound to real Rust fields and functions, a reference to a field or function that doesn't exist is a compile error. The Rust compiler checks your templates.

The Auric Guide has a new Routing/Controllers page with more detail.

Conditional content

Templates now support if, both inline and as a block.

The inline form takes a condition, a value if true, and an optional value if false:

The car is {{if this.model.isVeryNice 'very' 'not very'}} nice.
let signal0 = move || model.get().map(|model| model.get("isVeryNice")).unwrap_or_default();
let signal1 = move || {if (signal0()).as_bool().unwrap_or_default() {"very"} else {"not very"}};

view! {
    "The car is " {move || signal1()} "."
}

The block form handles nested content, and is generated as a Leptos <Show> component:

The car is
{{#if this.model.isVeryNice}}
    <strong>very nice</strong>
{{else}}
    nice
{{/if}}.
view! {
    "The car is "
    <Show when=move || { (&signal0()).as_bool().unwrap_or_default() } fallback=|| view! { "nice" }>
        <strong>"very nice"</strong>
    </Show>
    "."
}

See Templates/Conditional Content in the guide.

Dynamic class attributes

The 0.2.2 release noted that mustaches inside a class attribute were not supported yet. They are now. A class attribute that mixes static text and mustaches is compiled into a derived signal:

<button class="btn {{if this.model.isVeryNice 'color-highlight' 'color-normal'}}">Click</button>

array and concat helpers

The built-in array and concat helpers join the string, math, and truth helpers that shipped in 0.2.2:

<p>{{sum (array 3 5 7)}}</p>
<p>{{concat this.model.firstName " " this.model.lastName}}</p>

concat joins its parameters into a single string, and array builds a JSON array from its parameters, which you can pass to other helpers.

Data framework

JSON:API adapter

A new JsonApiAdapter lets your application talk to a JSON:API server. Target it when you generate your application adapter:

$ auric generate adapter application --target json-api

Serialization logic now sits behind a new Serializer trait, and the REST-specific logic has moved into a RestSerializer.

Saving records and dirty tracking

Store::save() creates or updates a record, depending on whether it was loaded from an adapter:

let mut customer: Customer = store.find_record(Bucket::Customers, "123", None).await?;
customer.name = "New Name".to_string();
let customer = store.save(&customer).await?;

Generated models now keep a snapshot of the resource they were loaded from, which makes Ember's isNew and hasDirtyAttributes available:

let mut customer: Customer = store.find_record(Bucket::Customers, "123", None).await?;
assert!(!customer.is_new());
assert!(!customer.has_dirty_attributes());

customer.credit_limit = 1000;
assert!(customer.has_dirty_attributes());

Fixes

Some notable fixes in this release:

  • A failing route model() fn no longer panics and crashes the app.
  • Templates can now bind to fields of data framework models, not only to serde_json::Value models.
  • Content after {{outlet}} in the application template now renders below the outlet instead of above it.
  • The REST adapter now passes query params to the server, and keeps numeric ids numeric.
  • LinkTo creates the correct href for @route="index", and supports a new @model param.
  • All HTMLbars grammar rules now have human-readable parse error messages.

What's Next?

Take a peek at the 0.2.4 Roadmap.

Release 0.2.2 - 2026-10-10

This release adds major new functionality supporting binding views to models. The Auric build now generates a Leptos derived signal for every HTMLbars expression term.

It also includes built-in helper support for ember-cli-string-helpers, ember-math-helpers, and ember-truth-helpers helper collections, which are popular within the Ember ecosystem.

Helper expression terms become Leptos derived signals

Helpers are pre-packaged Rust functions that you can call within your HTMLbars expressions. They streamline your development experience by helping to keep your templates clean, highly readable, and maintainable.

Consider this HTMLbars template:

{{!-- src/templates/customer.hbs --}}
<h2>Customer</h2>

<li>Name: {{titleize (trim this.model.name)}}</li>
<li>Address: {{trim this.model.address}}</li>

When you build your Auric project, let's look at the Leptos view that is generated for you, and some of its complexities that you won't have to face directly:

$ make
$ cat src/generated/views/customer.rs
// This file is @generated by auric-build.

use auric_runtime::{*, components::*, data::*, routing::*};
use leptos::prelude::*;
use leptos_router::{path, components::*};

#[component]
pub fn CustomerView() -> impl IntoView {
    let model = LocalResource::new(async move || {
        let router = use_context::<Arc<Router>>().expect("Router not found");
        let params = move || use_params_map().get();
        router.set_params_for("customer", params().into());
        router.model_for("customer").await.expect("Failed loading model")
    });
    let signal0 = move || model.get().map(|model| model.get("name")).unwrap_or_default();
    let signal1 = move || auric_runtime::helpers::string::trim(&signal0());
    let signal2 = move || auric_runtime::helpers::string::titleize(&signal1());
    let signal3 = move || auric_runtime::helpers::to_leptos_string(&signal2());
    let signal4 = move || model.get().map(|model| model.get("address")).unwrap_or_default();
    let signal5 = move || auric_runtime::helpers::string::trim(&signal4());
    let signal6 = move || auric_runtime::helpers::to_leptos_string(&signal5());

    view! {
        <h2>"Customer"</h2>
        <li>"Name: " {move || signal3()} </li>
        <li>"Address: " {move || signal6()} </li>
    }
}

The Auric Guide has a new Templates/Helpers page with this content, and also documents the built-in helpers.

HTMLbars Expression Grammar

To support this major new functionality, the Auric project's htmlbars crate had to be extended to support full HTMLbars expressions within the XHTML grammar and its AST.

In HTMLbars, mustaches can appear in two different places: (1) in an HTML snippet body such as <div>{{ this.model.name }}</div>, or (2) in a class attribute such as <div class="text-muted {{color}}"/>. So far, only the first case is supported. Future work still has to figure out how to translate HTMLbars expressions into the way Leptos handles dynamic class attributes.

What's Next?

Take a peek at the 0.2.3 Roadmap. The goal is to support more built-in helpers such as array and concat, and maybe take a stab at supporting HTMLbars conditional logic and iterations, or even handling events with controllers.

Release 0.2.1 - 2026-10-03

This release introduces a proper HTMLbars parser to replace regex for template parsing. It also reworks Leptos View generation by making it AST-based. This fixes a multitude of parsing and view generation issues.

The new htmlbars crate is now available at crates.io, in case others find some use for it.

Read the full change log here.

Index Routes

Index routes are now generated for every level of route nesting. This is now documented in the guide at Routing / Defining Routes / Index Routes.

Generated views are now standardized by leptosfmt

After views or components are generated, leptosfmt standardized formatting is applied, for more consistent readability.

New AuricModel derive macro

Generated models now implement this new derive macro, to make future code generation possible. Code generation will eventually be required to implement model attributes such as isNew and isSaving.

Release 0.2.0 - 2026-09-26

This release completes support for route params, and makes a route's model available to its view.

Read the full change log here.

Route params and models

When a Leptos view is generated for an Auric route, it now captures the parameters from the Leptos router, and passes them along to the Auric router, which now tracks the latest route parameters for the current route, and all its parent routes, if any.

When the Auric router's model_for(route_name) fn is called, it now asks the router for the route params before invoking the route handler's model() fn.

$ cat src/generated/views/customers.rs
#[component]
pub fn CustomersView() -> impl IntoView {
    let model = LocalResource:: new(async move || {
        let params = move || use_params_map().get();
        router.set_params_for("customers", params().into());
        router.model_for("customers").await
    };
    view! {
        ...
    }
}

Release 0.1.8 - 2026-09-19

This release adds quoting of raw text, to fix its display within a Leptos view! macro.

Read the full change log here.

Quoted Raw Text

Template views, once generated, now wrap raw text in double-quotes, ensuring they are parsed correctly by the Leptos view! macro.

Prior to this change, template content such as num items: {{itemCount}} would visually display in a skewed manner such as num items :23.

This use of a component within a template shows the new generated quoted raw text:

$ cat src/generated/views/customers.rs
#[component]
pub fn CustomersView() -> impl IntoView {
    ...
    view! {
        <CustomerCardComponent name="Acme Widgets".to_string() invoices=23.into() active=true>
            <div>"some nested content"</div>
        </CustomerCardComponent>
        ...
    }
}

Release 0.1.7 - 2026-09-12

This release adds two new features I'm incredibly excited about: Bucket enums, and component params.

Read the full change log here.

Bucket Enums!

This release now brings compiler verification for every reference you make to a resource type when using the Data Framework.

A Bucket enum is generated at src/generated/bucket.rs, and contains a variant for every model in your application.

use strum_macros::AsRefStr;

#[derive(AsRefStr, Clone, Debug)]
pub enum Bucket {
    #[strum(serialize = "customers")]
    Customers,

    #[strum(serialize = "invoiceLineItems")]
    InvoiceLineItems,
}

This enum can now be used in Store fns, wherever a resource type is required:

use crate::generated::Bucket;

let customer: Customer = store.find_record(Bucket::Customers, "123", None).await?;

Static Literal Component Params

Up until now, component support has been limited to wrapping HTML content with child content that replaces {{yield}}.

Now first steps have been taken to support passing parameters into components. In this release, you can include boolean, numeric, or string static literals in component references.

The generated component will have the necessary parameters added, after they were collected from each and every component reference.

Given this component:

<div class="card text-center">
    <div class="card-body">
        <div class="card-title">{{@name}}</div>
        {{yield}}
    </div>
    <div class="card-footer text-muted">
        Invoices: {{@invoices}}, Active: {{@active}}
    </div>
</div>

And this component reference:

<CustomerCard @name="Acme Widgets" @invoices=23 @active=true>
    <div>some nested content</div>
</CustomerCard>

The following Leptos component is generated:

// src/generated/components/customer_card.rs
#[component]
pub fn CustomerCardComponent(
    children: Children,
    name: String,
    invoices: f64,
    active: bool,
) -> impl IntoView {
    view! {
        <div class="card text-center">
            <div class="card-body">
                <div class="card-title">{name}</div>
                {children()}
            </div>
            <div class="card-footer text-muted">
                Invoices: {invoices}, Active: {active}
            </div>
        </div>
    }
}

Release 0.1.6 - 2026-09-05

After a few weeks in the heart of Bavaria, I am restored, and back at it. This release provides routes with access to the store.

Read the full change log here.

Using the Store from a Route

Let's generate a new route to display a list of customers:

$ auric generate route customers
created src/routes/customers.rs
updated src/routes/mod.rs
created src/templates/customers.hbs
==> make
   Compiling my-app v0.1.0 (/Users/davidr/workspaces/drr)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.50s

Let's look at the default route that was generated, before we make any changes:

// src/routes/customers.rs
use anyhow::Result;
use auric_runtime::async_trait::async_trait;
use auric_runtime::routing::{Route, RouteModel};
use auric_runtime::{AuricRoute, Context};
use serde_json::{Value, json};

#[derive(AuricRoute, Default)]
#[route("/customers")]
pub struct CustomersRoute {}

#[async_trait]
impl Route for CustomersRoute {
    fn init(&mut self, _context: &dyn Context) -> Result<()> {
        Ok(())
    }

    async fn model(&self, _params: &[Value]) -> Result<RouteModel> {
        Ok(RouteModel::Json(json!({})))
    }
}

#[cfg(test)]
mod tests {
    use super::CustomersRoute;

    #[test]
    fn can_construct() {
        let _ = CustomersRoute::default();
    }
}

Now let's modify the init() fn to keep our own reference to the store:

pub struct CustomersRoute {
    store: Arc<Store>,
}

#[async_trait]
impl Route for CustomersRoute {
    fn init(&mut self, context: &dyn Context) -> Result<()> {
        self.store = context.store();
        Ok(())
    }
    ...
}

Now we can use the store when preparing the route's model:

use crate::models::customer::Customer;

#[async_trait]
impl Route for CustomersRoute {
    ...
    async fn model(&self, _params: &[Value]) -> Result<RouteModel> {
        let customers: Vec<Customer> = self.store.find_all("customer", None).await?;
        ...
    }
}

Release 0.1.5 - 2026-08-15

This release introduces support for Ember components, which are translated at build time into Leptos components. A LinkTo built-in component is also implemented. When generating an application adapter, it also provides an option to extend the REST adapter via delegation.

Read the full change log here.

Components!

Let's generate a new component:

$ auric generate component customer-card
created src/components/customer_card.hbs
==> make
   Compiling my-app v0.1.0 (/Users/davidr/workspaces/drr)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.13s

The default contents are pretty basic:

$ cat src/components/customer_card.hbs
{{yield}}

Let's customize it:

<div class="card text-center">
    <div class="card-body">
        {{yield}}
    </div>
</div>

And now let's use it from a template:

{{!-- src/templates/customers.hbs --}}
<CustomerCard>
    Acme Widgets
</CustomerCard>

<CustomerCard>
    Standard Paper Supplies, Inc.
</CustomerCard>

Now let's build our application, and examine the generated customer view:

$ make
$ cat src/generated/views/customers.rs
use crate::generated::components::{
    customer_card::CustomerCardComponent,
};

#[component]
pub fn CustomersView() -> impl IntoView {
    view! {
        <CustomerCardComponent>
            Acme Widgets
        </CustomerCardComponent>
        
        <CustomerCardComponent>
            Standard Paper Supplies, Inc.
        </CustomerCardComponent>
    }
}

LinkTo

A built-in LinkTo component is provided, with initial support for a static literal @route parameter. Use it like this from a template:

<LinkTo @route="customers">Customers</LinkTo>

And it will be translated by the build process into a Leptos view like this:

pub fn MyView() -> impl IntoView {
    view! {
        ...
        <LinkTo path="customers".to_string()>Customers</LinkTo>
    }
}

Application Adapter → REST Adapter

Previously, performing auric generate adapter application generated an empty struct full of todo() markers.

A new --target rest flag is now supported, which will leave you with a fully-functioning application adapter. Here's how it looks:

$ auric generate adapter application --target rest
created src/adapters/application.rs
updated src/adapters/mod.rs
==> make
   Compiling auric-build v0.1.5 (/Users/davidr/workspaces/auric-spa/auric/crates/auric-build)
   Compiling my-app v0.1.0 (/Users/davidr/workspaces/drr)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 3.32s

$ cat src/adapters/application.rs
#[derive(Default)]
pub struct ApplicationAdapter {
    target: RestAdapter,
}

#[async_trait(?Send)]
impl Adapter for ApplicationAdapter {
    fn init(&mut self, config: &Config) -> Result<()> {
        self.target.init(config)
    }

    async fn create_record(&self, resource: Resource) -> Result<Resource> {
        self.target.create_record(resource).await
    }

    async fn delete_record(&self, resource: &Resource) -> Result<()> {
        self.target.delete_record(resource).await
    }

    async fn find_record(&self, resource_type: &str, id: &str) -> Result<Resource> {
        self.target.find_record(resource_type, id).await
    }

    async fn query(&self, resource_type: &str, query: serde_json::Value) -> Result<Vec<Resource>> {
        self.target.query(resource_type, query).await
    }

    async fn query_record(
        &self,
        resource_type: &str,
        query: serde_json::Value,
        options: QueryRecordOptions,
    ) -> Result<Option<Resource>> {
        self.target
            .query_record(resource_type, query, options)
            .await
    }

    async fn update_record(&self, resource: Resource) -> Result<Resource> {
        self.target.update_record(resource).await
    }
}

Release 0.1.4 - 2026-08-08

This release introduces a configuration mechanism, and replaces Handlebars-based client-side rendering of Handlebars templates with generated Leptos components. This release also replaces actix-router with leptos_router.

Read the full change log here.

Configuration

A src/config.rs file now exists in every application, which defines a function that is responsible for returning a JSON object:

// src/config.rs
pub fn new() -> serde_json::Value {
    serde_json::json!({
        "api_url": option_env!("API_URL").unwrap_or("http://localhost:3000"),
    })
}

This configuration is compiled into your application at build time, ensuring your WASM module contains a fully sealed configuration for strict release versioning.

The app configuration is also passed into adapters through their init() fn, which is how they are able to read settings such as the API server URL.

Views Become Leptos Components

Handlebars is no longer a runtime dependency, and is now only used during build time. At build time, Handlebars templates are translated into Leptos components.

Given this src/templates/application.hbs template:

<h1>Welcome to Auric SPA!</h1>
{{outlet}}

The following Leptos component is generated:

$ make
$ cat src/generated/views/application.rs
#[component]
pub fn ApplicationView() -> impl IntoView {
    view! {
        <Router>
            <Routes fallback=|| "Not found.">
                <Route path=path!("/") view=IndexView />
                <Route path=path!("/*any") view=|| view! { <h1>Not found.</h1> } />
            </Routes>

            <h1>Welcome to Auric SPA!</h1>
            <div />
        </Router>
    }
}

Release 0.1.3 - 2026-08-01

This release kicks off a Data Framework!, including model management, adapter management, and a REST adapter.

The data framework re-imagines Ember Data in type-safe async Rust, which brings one of Ember's killer features -- client-side model state management -- to Rust and WASM.

Read the full change log here.

Manage Models

The Auric CLI now supports a new generate model subcommand:

$ auric generate model customer
created src/models/customer.rs
updated src/models/mod.rs
==> make
   Compiling my-app v0.1.0 (/Users/davidr/workspaces/drr)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 2.47s

Which generates the following file:

// src/models/customer.rs
use jsonapi_core::JsonApi;

#[derive(Clone, Debug, Default, JsonApi, PartialEq)]
#[jsonapi(type = "customers")]
pub struct Customer {
    #[jsonapi(id)]
    pub id: String,
}

#[cfg(test)]
mod tests {
    use super::Customer;

    #[test]
    fn can_construct() {
        let _ = Customer::default();
    }
}

The jsonapi_core crate is used for annotations and serde.

This example demonstrates a few additional fields, including a relation:

// src/models/customer.rs
use jsonapi_core::{JsonApi, Relationship};
use super::account::Account;

#[derive(Clone, Debug, Default, JsonApi, PartialEq)]
#[jsonapi(type = "customers")]
pub struct Customer {
    #[jsonapi(id)]
    pub id: String,
    
    pub name: String,
    
    #[jsonapi(relationship, type = "accounts")]
    pub owner: Relationship<Account>,
}

Manage Adapters

The CLI now supports generating an adapter:

$ auric generate adapter application
created src/adapters/application.rs
updated src/adapters/mod.rs
==> make
   Compiling my-app v0.1.0 (/Users/davidr/workspaces/drr)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.83s

The generated file looks like this:

use anyhow::Result;
use auric_runtime::{
    async_trait::async_trait,
    data::{Adapter, QueryRecordOptions},
};
use jsonapi_core::Resource;

#[derive(Debug, Default)]
pub struct ApplicationAdapter {}

#[async_trait(?Send)]
impl Adapter for ApplicationAdapter {
    async fn create_record(&self, _resource: Resource) -> Result<Resource> {
        todo!("NIY")
    }

    async fn delete_record(&self, _resource: &Resource) -> Result<()> {
        todo!("NIY")
    }

    async fn find_record(&self, _resource_type: &str, _id: &str) -> Result<Resource> {
        todo!("NIY")
    }

    async fn query(
        &self,
        _resource_type: &str,
        _query: serde_json::Value,
    ) -> Result<Vec<Resource>> {
        todo!("NIY")
    }

    async fn query_record(
        &self,
        _resource_type: &str,
        _query: serde_json::Value,
        _options: QueryRecordOptions,
    ) -> Result<Resource> {
        todo!("NIY")
    }

    async fn update_record(&self, _resource: Resource) -> Result<Resource> {
        todo!("NIY")
    }
}

You can also define additional model-specific adapters like this:

$ auric generate adapter customer

All of the adapters are registered with the Store, which uses an adapter_for() function to locate a model-specific adapter, or to fallback on the application adapter.

REST Adapter

A REST adapter is now available, and provides a concrete working example of the Adapter trait.

This unit tests shows how it is integrated with the Store:

#[tokio::test]
async fn can_find_record() {
    // Setup mock server and expections for a find Customer operation
    let server = Server::run();
    server.expect(
        Expectation::matching(request::method_path("GET", "/v1/customers/123"))
            .respond_with(json_encoded(json!({"customer": {"id": "123", "name": "Acme Widgets"}}))),
    );
    let server_url = server.url_str("/v1");

    #[derive(Debug, JsonApi)]
    #[jsonapi(type = "customers")]
    struct Customer {
        #[jsonapi(id)]
        id: String,
        name: String,
    }

    let adapter: Arc<dyn Adapter + Send + Sync> = Arc::new(RestAdapter::new(server_url));
    const MODEL: &str = "customer";
    let adapters = HashMap::from([(MODEL.to_string(), adapter)]);
    let store = Store::new(adapters);
    let _ = store.adapter_for(MODEL);
    let customer: Customer = store.find_record(MODEL, "123", None).await.unwrap();
    assert_eq!(customer.id, "123".to_string());
    assert_eq!(customer.name, "Acme Widgets".to_string());
}

Release 0.1.2 - 2026-07-25

This release focuses on internal organization and bug fixes.

  • Routing is now organized under a routing module
  • Route modules are now organized hierarchically
  • Added test coverage

And there are a few additional development features, including:

  • A Makefile is now generated; popular targets are make, make check, and make clean
  • Installation from crates.io is now documented
  • A .gitattributes file is now generated, to denote generated source files

Read the full change log here.

Release 0.1.1 - 2026-07-18

This release is primarily a maintenance release fixing lots of things like cyclical page reload during auric serve. It also introduces a new Route trait, and template management.

Read the full change log here.

New Route trait

When you generate a route, it now implements the new Route trait:

#[async_trait]
pub trait Route<M> {
    async fn model(&self, params: serde_json::Value) -> anyhow::Result<M>;
    fn name(&self) -> &'static str;
    fn router(&self) -> Arc<Router>;
}

This is just a scaffold at this point, and needs to be built out.

Manage Templates

The CLI now supports managing templates. In the previous 0.1.0 release, you could generate a new route, and its associated template would be generated as well, just like what the Ember CLI would do. However, sometimes you also want to manage templates directly, which is where this Ember CLI compatible pair of subcommands comes in.

$ auric generate template abc
created src/templates/abc.hbs
==> make
   Compiling auric-build v0.1.1 (/Users/davidr/workspaces/auric-spa/auric/crates/auric-build)
   Compiling auric-runtime v0.1.1 (/Users/davidr/workspaces/auric-spa/auric/crates/auric-runtime)
   Compiling my-app v0.1.0 (/Users/davidr/workspaces/my-app)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 3.05s
$ auric delete template abc
deleted src/templates/abc.hbs
==> make
   Compiling my-app v0.1.0 (/Users/davidr/workspaces/my-app)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.19s

Release 0.1.0 - 2026-07-04

Welcome to the first release of the Auric SPA project! We're attempting to recreate our favorite Ember.js design patterns in type-safe async Rust that compiles to WASM.

Read the full change log here.

New Auric CLI

An Auric CLI takes the place of the Ember CLI, and installs simply with cargo install auric.

new and init subcommands

To kick things off, a new subcommand will generate your new project into a new subdirectory:

$ auric new my-app
==> make
2026-08-13T14:20:39.039679Z  INFO 🚀 Starting trunk 0.21.14
2026-08-13T14:20:40.474657Z  INFO 📦 starting build
   ...
   Compiling auric-build v0.1.0
   Compiling auric-runtime v0.1.0
   Compiling handlebars v6.4.4
   Compiling js-sys v0.3.103
   Compiling console_error_panic_hook v0.1.7
   Compiling web-sys v0.3.104
   Compiling leptos v0.8.4
   Compiling my-app v0.1.0 (/Users/davidr/workspaces/my-app)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 14.25s

$ cd my-app
$ auric serve --open
2026-07-04T14:57:49.311838Z  INFO 🚀 Starting trunk 0.21.14
2026-07-04T14:57:49.385672Z  INFO 📦 starting build
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.03s
2026-07-04T14:57:49.734082Z  INFO applying new distribution
2026-07-04T14:57:49.734841Z  INFO ✅ success
2026-07-04T14:57:49.734883Z  INFO 📡 serving static assets at -> /
2026-07-04T14:57:49.734931Z  INFO 📡 server listening at:
2026-07-04T14:57:49.734935Z  INFO     🏠 http://127.0.0.1:4200/
2026-07-04T14:57:49.734937Z  INFO     🏠 http://[::1]:4200/
2026-07-04T14:57:49.735070Z  INFO     🏠 http://localhost.:4200/

And just like with the Ember CLI, the init subcommand can be used instead if your subdirectory already exists:

$ mkdir my-app
$ cd my-app
auric init 

Manage Routes

Routes are managed via the generate route subcommand:

$ auric generate route invoices
created src/routes/invoices.rs
updated src/routes/mod.rs
created src/templates/invoices.hbs
==> make
   Compiling my-app v0.1.0 (/Users/davidr/workspaces/my-app)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.73s
$ auric delete route invoices
deleted src/routes/invoices.rs
updated src/routes/mod.rs
deleted src/templates/invoices.hbs
==> make
   Compiling my-app v0.1.0 (/Users/davidr/workspaces/my-app)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.73s

Crates

The project's crates can be found at crates.io:

  • auric (the CLI)
  • auric-runtime (libraries required at runtime by your webapp)
  • auric-build (our builder, that transforms your models/views/controllers/components/adapters into a code-generated Leptos webapp)
  • auric-derive (our derive macros crate, required by the runtime to perform some last-minute code generation based on annotations)

That's it for now! Stay tuned for frequent releases.


©2026 Megalithic LLC | Website | Guide | GitLab | Contact