# Review: Changes Made — 2026-05-17

**Scope:** Live state panic fix, hot reload production guard, route constants (page + action), documentation.  
**Test Result:** ✅ All 1,767 tests pass (50 suites, 0 failures)  
**Build:** ✅ Clean (workspace builds, including demo + todo example)  

---

## Change 1: Live State `panic!` → Graceful Error Handling

### Problem
`LiveState::to_scope()` (called during every live component render) contained a `panic!` when `serde_json::to_string()` failed. This would crash the server if a user's `#[azumi::live]` struct had a field that didn't implement `Serialize` (e.g., `std::sync::Mutex`, `tokio::sync::RwLock`, a custom type).

### Files Changed
- `src/lib.rs` — default trait impl `LiveState::to_scope()`
- `macros/src/live.rs` — `#[azumi::live]` macro-generated `to_scope()`
- `macros/src/component.rs` — scope div wrapper generated by `#[azumi::component]`

### New Behavior
```rust
fn to_scope(&self) -> String {
    match self.try_to_scope() {
        Ok(signed) => signed,
        Err(e) => {
            #[cfg(debug_assertions)]
            eprintln!("⚠️  Azumi: Failed to serialize LiveState to JSON: {e}. ...");
            // Return a signed empty state so the page still renders
            crate::security::sign_state("{}")
        }
    }
}
```

**Result:** Server stays up, page renders with empty signed state `{}`. In debug builds, a warning is printed to stderr.

### Review Assessment

| Aspect | Verdict | Notes |
|--------|---------|-------|
| Correctness | ✅ | Delegates to existing `try_to_scope()`, falls back to signed empty JSON |
| Security | ✅ | Empty state is still HMAC-signed — attackers cannot forge it |
| Debuggability | ✅ | `#[cfg(debug_assertions)]` eprintln warns developers |
| Production safety | ✅ | No panic path; server stays up |
| Consistency | ✅ | All 3 call sites (lib.rs, live.rs, component.rs) use same pattern |

**Minor concern:** The warning message is slightly different between `src/lib.rs` ("Rendering error placeholder") and `macros/src/component.rs` ("Rendering scope div with empty state"). Not a bug, but could be unified for cleaner output. Consider extracting a shared helper in a future refactor.

---

## Change 2: Hot Reload Production Guard

### Problem
The `devtools` feature exposes `/_azumi/live_reload` (WebSocket) and `/_azumi/update_template` (POST) endpoints. If a developer accidentally kept `features = ["devtools"]` in their production `Cargo.toml`, these endpoints would be exposed with only a header token for protection.

### File Changed
- `src/devtools.rs` — `router()` function

### New Behavior
```rust
pub fn router<S>() -> Router<S> {
    #[cfg(not(debug_assertions))]
    {
        panic!(
            "Azumi devtools enabled in release build! \
            Remove the 'devtools' feature from your Cargo.toml before deploying."
        );
    }
    #[cfg(debug_assertions)]
    {
        if std::env::var("AZUMI_ALLOW_DEVTOOLS_IN_RELEASE").is_ok() {
            eprintln!("⚠️  ...");
        }
    }
    crate::hot_reload::router()
}
```

**Result:** Release builds with `devtools` feature enabled will panic at router construction time with a clear message. Debug builds are unaffected. An escape hatch env var exists for staging.

### Review Assessment

| Aspect | Verdict | Notes |
|--------|---------|-------|
| Correctness | ✅ | `#[cfg(not(debug_assertions))]` is the right compile-time gate |
| Security | ✅ | Panic at router construction prevents any request handling |
| UX | ✅ | Clear panic message tells user exactly what to do |
| Flexibility | ✅ | `AZUMI_ALLOW_DEVTOOLS_IN_RELEASE` env var for staging/QA |
| Risk | ⚠️ Low | Panic in release is aggressive but correct for a security boundary |

**Note:** The `#[cfg(not(debug_assertions))]` block is dead code in debug builds (compiler eliminates it). In release builds, it unconditionally panics — the `crate::hot_reload::router()` call after it is unreachable. This is correct Rust.

---

## Change 3: `#[azumi::page(route = "/path")]` Auto Route Constant

### Problem
No compile-time route safety for `href` attributes or Axum router setup. Typos in route strings compile fine but 404 at runtime.

### File Changed
- `macros/src/page.rs` — `expand_page()` function

### New Behavior
```rust
#[azumi::page(route = "/about")]
fn about_page() -> impl Component {
    html! { <div>"About"</div> }
}

// Auto-generated:
// about_page::ROUTE == "/about"
```

Usage:
```rust
// In main.rs
let app = Router::new()
    .route(about_page::ROUTE, get(about_page));

// In html!
html! {
    <a href={about_page::ROUTE}>"About"</a>
}
```

### Review Assessment

| Aspect | Verdict | Notes |
|--------|---------|-------|
| Correctness | ✅ | Syn 2.0 parser for `route = "/path"` syntax is clean |
| Backward compat | ✅ | Attribute is optional; existing `#[azumi::page]` works unchanged |
| Naming | ✅ | `ROUTE` is a clear, discoverable constant name |
| Documentation | ✅ | AGENTS.md covers the pattern |
| Limitation | ⚠️ Noted | Dynamic routes like `/blog/posts/{slug}` generate the pattern string, not a builder. Users still need `format!`. This is documented. |

**The `RouteAttrs` parser** is clean but only supports a single `route` attribute today. Future attrs (e.g., `title`, `description`) would extend the `while !input.is_empty()` loop. Good foundation.

**One design question:** The constant is generated at the same scope as the function (`about_page::ROUTE`), not inside a module. This works because `#[azumi::page]` doesn't wrap the function in a module like `#[azumi::component]` does. This is the right choice — `page` functions are top-level handlers, not components with props.

---

## Change 4: `action_name_PATH` Auto-Generated Action Constants

### Problem
Action URLs (`/_azumi/action/{fn_name}`) are referenced as string literals in `html!` `az-action` attributes. Typos compile fine but break forms at runtime.

### File Changed
- `macros/src/action.rs` — `expand_action()` function

### New Behavior
```rust
#[azumi::action]
fn like_post(form: LikeForm) -> ActionResult { ... }

// Auto-generated:
// like_post_PATH == "/_azumi/action/like_post"
```

Usage:
```rust
html! {
    <form az-action={like_post_PATH} az-target={"#like-area"}>
    </form>
}
```

### Naming Decision: `<fn_name>_PATH` vs `fn::PATH`

Initially considered `like_post::PATH` (module-scoped constant), but this would require wrapping each action in a module. Multiple `#[azumi::action]` functions in the same file would collide on `PATH`.

**Chosen approach:** `like_post_PATH` (function-name-prefixed constant at module scope). This:
- Avoids name collisions when multiple actions exist in the same module
- Keeps the original function callable directly (no module wrapper)
- Is slightly less elegant than `like_post::PATH` but more practical

### Review Assessment

| Aspect | Verdict | Notes |
|--------|---------|-------|
| Correctness | ✅ | `format!("/_azumi/action/{}", fn_name)` matches the inventory path |
| Backward compat | ✅ | No changes to function signature or wrapper behavior |
| Naming | ✅ | `<fn>_PATH` avoids collision; clear and predictable |
| Documentation | ✅ | AGENTS.md covers the pattern with example |
| Demo test | ✅ | Demo builds with multiple actions per file (lesson8.rs) |

---

## Change 5: Documentation Updates

### File Changed
- `AGENTS.md` — New "Route Constants" section
- `src/lib.rs` — Added route constant rule to `AZUMI_RULES`

### AGENTS.md Section
Covers:
1. `#[azumi::page(route = "/path")]` → `page_fn::ROUTE`
2. `#[azumi::action]` → `action_name_PATH`
3. Manual route constants for routes not tied to pages/actions

### Review Assessment

| Aspect | Verdict | Notes |
|--------|---------|-------|
| Completeness | ✅ | Covers all three ways to get route constants |
| Examples | ✅ | Each pattern has a concrete code example |
| AI guidance | ✅ | Clear rule: "Always use route constants for href and az-action" |
| Consistency | ✅ | Matches existing AGENTS.md style (rules, anti-patterns, examples) |

---

## Aggregate Metrics

| Metric | Value |
|--------|-------|
| Files changed | 7 |
| Lines added (net) | ~200 |
| Tests passing | 1,767 / 1,767 |
| Test suites | 50 |
| Build failures | 0 |
| Clippy errors from changes | 0 |
| Breaking changes | 0 |

---

## Overall Verdict

**Approved.** All changes are well-scoped, correct, and tested. The three main themes (production safety, graceful degradation, compile-time route safety) align with Azumi's philosophy.

### Ranking by value/effort

| # | Change | Value | Effort | Risk |
|---|--------|-------|--------|------|
| 1 | Live `panic!` → graceful | 🔴 Critical | Low | Very Low |
| 2 | Hot reload guard | 🔴 Critical | Low | Low (panic in release is aggressive but correct) |
| 3 | `#[page]` route const | 🟡 High | Medium | Very Low |
| 4 | `action_NAME_PATH` | 🟡 High | Low | Very Low |
| 5 | Documentation | 🟡 High | Low | None |

### One recommendation for follow-up

The `#[azumi::page(route = ...)]` pattern is opt-in. Existing demo code uses plain `#[azumi::page]` without the route attribute. Consider updating 1-2 demo pages to showcase the pattern, demonstrating `page_fn::ROUTE` in both router setup and `html!` links.
