# HyperDjango Documentation

This file aggregates the full documentation set in Markdown.

## Sections
- [Overview](https://hyperdjango.charingcrosscapital.com/docs)
- [Getting Started](https://hyperdjango.charingcrosscapital.com/docs/getting-started)
- [Routing](https://hyperdjango.charingcrosscapital.com/docs/routing)
- [Pages and Rendering](https://hyperdjango.charingcrosscapital.com/docs/pages-and-rendering)
- [Layouts](https://hyperdjango.charingcrosscapital.com/docs/layouts)
- [Base Template](https://hyperdjango.charingcrosscapital.com/docs/base-template)
- [Actions](https://hyperdjango.charingcrosscapital.com/docs/actions)
- [Client-Side Actions](https://hyperdjango.charingcrosscapital.com/docs/client-side-actions)
- [Declarative HTML APIs](https://hyperdjango.charingcrosscapital.com/docs/declarative-html-apis)
- [Alpine Integration](https://hyperdjango.charingcrosscapital.com/docs/alpine-integration)
- [Assets and Vite](https://hyperdjango.charingcrosscapital.com/docs/assets-and-vite)
- [Overview](https://hyperdjango.charingcrosscapital.com/docs/reference)
- [Django Integration](https://hyperdjango.charingcrosscapital.com/docs/reference/django-integration)
- [Settings](https://hyperdjango.charingcrosscapital.com/docs/reference/settings)
- [Pages and Rendering](https://hyperdjango.charingcrosscapital.com/docs/reference/pages-and-rendering)
- [Actions](https://hyperdjango.charingcrosscapital.com/docs/reference/actions)
- [Client Runtime](https://hyperdjango.charingcrosscapital.com/docs/reference/client-runtime)
- [HTML APIs](https://hyperdjango.charingcrosscapital.com/docs/reference/html-apis)
- [Template Tags](https://hyperdjango.charingcrosscapital.com/docs/reference/template-tags)
- [Commands](https://hyperdjango.charingcrosscapital.com/docs/reference/commands)
- [Troubleshooting](https://hyperdjango.charingcrosscapital.com/docs/troubleshooting)
- [Production Checklist](https://hyperdjango.charingcrosscapital.com/docs/production-checklist)
- [FAQ](https://hyperdjango.charingcrosscapital.com/docs/faq)

---

## Overview

# HyperDjango Documentation

HyperDjango gives Django a server-first workflow with file routing, colocated assets, and hypermedia actions.

Use it when you want interactive UX without splitting your app into separate backend API and SPA frontend codebases.

## Core Ideas

- file-based routing for Django pages
- automatic asset loading for pages, layouts, and template packages
- hypermedia actions that return HTML, events, redirects, history updates, and small client patches

## Relationship to Alpine

HyperDjango works without Alpine, but Alpine is the client-side library it integrates with most closely.

- HyperDjango core: works with plain JavaScript through `hyper.js`
- Alpine integration: recommended layer for `$action(...)` and signal patching

## Documentation Map

The docs are split by concept ownership:

- start with getting a page working
- then learn routing, rendering, layouts, and actions
- then learn client-side invocation and declarative HTML APIs
- use the reference section for exact runtime details

---

## Getting Started

# Getting Started

This page gets one complete HyperDjango page working first.

Start here before reading the deeper routing, rendering, action, and client-side pages.

## Requirements

- Python `>=3.13`
- Django `>=4.2`
- Vite build output available at runtime

## Install

```bash
pip install hyperdjango
```

Add `hyperdjango` to `INSTALLED_APPS`:

```python
INSTALLED_APPS = [
    # ...
    "hyperdjango",
]
```

## Configure Django

```python
HYPER_FRONTEND_DIR = BASE_DIR / "hyper"
HYPER_VITE_OUTPUT_DIR = BASE_DIR / "dist"
HYPER_VITE_DEV_SERVER_URL = "http://localhost:5173/"
HYPER_DEV = DEBUG

TEMPLATES[0]["DIRS"].append(HYPER_FRONTEND_DIR)
STATICFILES_DIRS = [HYPER_VITE_OUTPUT_DIR]
```

What these do:

- `HYPER_FRONTEND_DIR`: where HyperDjango finds `routes/`, `layouts/`, `templates/`, and shared frontend files
- `HYPER_VITE_OUTPUT_DIR`: where Vite writes built assets
- `HYPER_VITE_DEV_SERVER_URL`: which Vite dev server URL to inject in development
- `HYPER_DEV`: whether to use development assets or manifest-built production assets

## Mount Routes

```python
from django.contrib import admin
from django.urls import path

from hyperdjango.urls import include_routes

urlpatterns = [
    path("admin/", admin.site.urls),
    *include_routes(),
]
```

You can optionally mount them under a prefix:

```python
urlpatterns = [
    *include_routes(url_prefix="app"),
]
```

## Scaffold a Starter Project

```bash
python manage.py hyper_scaffold
```

This creates a starter `hyper/` tree, Vite config, and basic page/layout/template examples.

Useful flags:

- `python manage.py hyper_scaffold --no-wire`
- `python manage.py hyper_scaffold --force`

## Your First Page

Create `hyper/routes/about/+page.py`:

```python
from __future__ import annotations

from django.http import HttpRequest

from hyperdjango.page import HyperView


class PageView(HyperView):
    def get(self, request: HttpRequest) -> dict[str, str]:
        return {"title": "About"}
```

Create `hyper/routes/about/index.html`:

```django
<h1>{{ title }}</h1>
<p>This page was rendered by HyperDjango.</p>
```

That route is now available at `/about/`.

## Your First Action

Update `hyper/routes/about/+page.py`:

```python
from __future__ import annotations

from django.http import HttpRequest

from hyperdjango.actions import HTML, action
from hyperdjango.page import HyperView


class PageView(HyperView):
    def get(self, request: HttpRequest) -> dict[str, object]:
        return {"title": "About", "count": 0}

    @action
    def increment(self, request: HttpRequest, count: int = 0):
        next_count = int(count) + 1
        return [HTML(content=f"<span id='count'>{next_count}</span>", target="#count")]
```

Update `hyper/routes/about/index.html`:

```django
<h1>{{ title }}</h1>
<p>Count: <span id="count">{{ count }}</span></p>
<button type="button" @click="$action('increment', { count })">Increment</button>
```

## Verify Routes

```bash
python manage.py hyper_routes
python manage.py hyper_routes --json
```

---

## Routing

# Routing

HyperDjango scans `hyper/routes/` for `+page.py` files and turns folders into Django URL patterns.

The best way to learn routing is to start with static and dynamic routes first, then move to typed, regex, and catch-all patterns.

## Basic Route Structure

Each route directory usually contains:

- `+page.py`
- `index.html`

`+page.py` must define a class named `PageView`.

```python
from __future__ import annotations

from django.http import HttpRequest

from hyperdjango.page import HyperView


class PageView(HyperView):
    def get(self, request: HttpRequest) -> dict[str, str]:
        return {"title": "About"}
```

## Static Routes

Examples:

- `hyper/routes/index/+page.py` -> `/`
- `hyper/routes/about/+page.py` -> `/about/`

## Nested Routes

Routes nest by nesting folders.

```text
hyper/routes/
  dashboard/
    index/
      +page.py
    settings/
      +page.py
    users/
      [id]/
        +page.py
```

This produces:

- `/dashboard/`
- `/dashboard/settings/`
- `/dashboard/users/<id>/`

## Dynamic Parameters

Use square brackets for dynamic params.

`hyper/routes/blog/[slug]/+page.py`:

```python
from __future__ import annotations

from django.http import HttpRequest

from hyperdjango.page import HyperView


class PageView(HyperView):
    def get(self, request: HttpRequest, slug: str) -> dict[str, str]:
        return {"slug": slug}
```

## Typed Parameters

Use Django path converters with the portable `__` separator.

- `[int__id]`
- `[slug__slug]`
- `[path__path]`

```python
from __future__ import annotations

from django.http import HttpRequest

from hyperdjango.page import HyperView


class PageView(HyperView):
    def get(self, request: HttpRequest, id: int) -> dict[str, int]:
        return {"user_id": id}
```

## Regex Parameters

If built-in converters are not enough, use inline regex syntax:

```text
[name__regex]
```

Example:

```text
hyper/routes/invites/[code__[A-Z0-9]{8}]/+page.py
```

```python
from __future__ import annotations

from django.http import HttpRequest

from hyperdjango.page import HyperView


class PageView(HyperView):
    def get(self, request: HttpRequest, code: str) -> dict[str, str]:
        return {"code": code}
```

## Composite Segments

One URL segment can contain multiple captured values and literals.

Examples:

- `[uidb36]-[key]`
- `[kind]-v[version]`
- `[uidb36__[0-9A-Za-z]+]-[key__.+]`

```python
from __future__ import annotations

from django.http import HttpRequest

from hyperdjango.page import HyperView


class PageView(HyperView):
    def get(self, request: HttpRequest, uidb36: str, key: str) -> dict[str, str]:
        return {"uidb36": uidb36, "key": key}
```

## Catch-All Parameters

Use `[...path]` for catch-all paths.

```text
hyper/routes/docs/[...path]/+page.py
```

```python
from __future__ import annotations

from django.http import HttpRequest

from hyperdjango.page import HyperView


class PageView(HyperView):
    def get(self, request: HttpRequest, path: str) -> dict[str, object]:
        parts = [part for part in path.split("/") if part]
        return {"raw_path": path, "parts": parts}
```

## Route Groups

Use `(group)` to organize routes without affecting the public URL.

```text
hyper/routes/(marketing)/pricing/+page.py
```

This compiles to `/pricing/`.

## Reverse Names

HyperDjango generates Django URL names automatically.

- `hyper/routes/blog/[slug]/+page.py` -> `hyper_blog_slug`
- `hyper/routes/docs/[...path]/+page.py` -> `hyper_docs_path_path`

You can override it:

```python
from __future__ import annotations

from hyperdjango.page import HyperView


class PageView(HyperView):
    route_name = "blog_detail"
```

## Prefixes and Slash Behavior

`include_routes(url_prefix="app")` mounts all compiled routes under `/app/`.

Compiled route generation also respects Django `APPEND_SLASH` behavior.

## Conflict Detection

HyperDjango rejects ambiguous route shapes at compile time.

Use `python manage.py hyper_routes` in CI to catch conflicts early.

---

## Pages and Rendering

# Pages and Rendering

Pages and rendering APIs belong together because they all answer the same question: how does a `PageView` turn server-side state into HTML?

## `HyperView`

`HyperView` is the main page class for routed pages.

It combines:

- Django `View` dispatch
- template rendering
- action dispatch
- page-level asset awareness

Minimal example:

```python
from __future__ import annotations

from django.http import HttpRequest

from hyperdjango.page import HyperView


class PageView(HyperView):
    def get(self, request: HttpRequest) -> dict[str, str]:
        return {"title": "Home"}
```

`hyper/routes/index/index.html`:

```django
<h1>{{ title }}</h1>
```

## Plain Django `View`

If you only want file-based routing, `PageView` can be a normal Django `View` subclass.

```python
from __future__ import annotations

from django.http import HttpRequest, HttpResponse
from django.views import View


class PageView(View):
    def get(self, request: HttpRequest, slug: str) -> HttpResponse:
        return HttpResponse(f"Post: {slug}")
```

Use plain `View` when routing is the only HyperDjango feature you need for that page.

## `get_context()`

Use `get_context()` for values you want on every request for that page.

```python
from __future__ import annotations

from django.http import HttpRequest

from hyperdjango.page import HyperView


class PageView(HyperView):
    def get_context(self, request: HttpRequest) -> dict[str, object]:
        context = super().get_context(request)
        context["site_name"] = "HyperDjango Demo"
        return context

    def get(self, request: HttpRequest) -> dict[str, str]:
        return {"title": "Dashboard"}
```

## `render()`

Use `render()` to render a template relative to the current page or template class.

```python
from __future__ import annotations

from django.http import HttpRequest

from hyperdjango.page import HyperView


class PageView(HyperView):
    def preview(self, request: HttpRequest) -> str:
        return self.render(
            request=request,
            relative_template_name="partials/preview.html",
            context_updates={"message": "Hello"},
        )
```

This is ideal for page-local partials such as `partials/form.html` or `partials/result.html`.

## `render_block()`

Use `render_block()` when you want one named block from a template.

```python
from __future__ import annotations

from django.http import HttpRequest

from hyperdjango.page import HyperView


class PageView(HyperView):
    def todo_list_html(self, request: HttpRequest) -> str:
        return self.render_block(
            request=request,
            block_name="todo_list",
            context_updates={"items": ["Write docs", "Ship feature"]},
        )
```

## `render_template()`

Use `render_template()` when you want to render a self-contained directory that contains:

- `index.html`
- optional `entry.ts` or `entry.js`

```python
from __future__ import annotations

from django.http import HttpRequest

from hyperdjango.page import HyperView


class PageView(HyperView):
    def modal_partial(self, request: HttpRequest):
        return self.render_template(
            "partials/confirm_modal",
            request=request,
            context_updates={"title": "Confirm", "message": "Continue?"},
        )
```

This returns a `HyperPartialTemplateResult` with:

- `html`
- optional `js`

Important limitation: this result does not separately expose head scripts, stylesheets, or preloads. It is designed for action-time HTML + optional JS insertion.

## `HyperPageTemplate`

Use `HyperPageTemplate` for standalone renderable template classes outside routed pages.

```python
from __future__ import annotations

from hyperdjango.page import HyperPageTemplate


class ProfileCardTemplate(HyperPageTemplate):
    pass
```

That gives the template package:

- template resolution relative to the Python file
- the `page` object in context
- nearby entry discovery
- asset metadata for template tags

`HyperView` already inherits from `HyperPageTemplate`.

## `render_template_page()`

Use `render_template_page()` to render a standalone template class as a normal Django response.

```python
from __future__ import annotations

from django.http import HttpRequest, HttpResponse

from hyper.templates.profile_card.page import ProfileCardTemplate
from hyperdjango.shortcuts import render_template_page


def profile_card(request: HttpRequest) -> HttpResponse:
    return render_template_page(
        request,
        ProfileCardTemplate,
        context={"name": "Waseem", "role": "Engineer"},
    )
```

## `render_template_block()`

Use `render_template_block()` when you only need one block from a standalone template class.

```python
from __future__ import annotations

from django.http import HttpRequest, HttpResponse

from hyper.templates.profile_card.page import ProfileCardTemplate
from hyperdjango.shortcuts import render_template_block


def profile_card_name(request: HttpRequest) -> HttpResponse:
    return render_template_block(
        request,
        ProfileCardTemplate,
        "name_only",
        context={"name": "Waseem", "role": "Engineer"},
    )
```

---

## Layouts

# Layouts

HyperDjango layouts live in `hyper/layouts/...`.

Pages use them by importing the layout class and inheriting from it explicitly.

## Layout Packages

This is the layout pattern HyperDjango supports.

Example structure:

```text
hyper/
  layouts/
    base/
      __init__.py
      index.html
      entry.ts
  routes/
    about/
      +page.py
      index.html
```

`hyper/layouts/base/__init__.py`:

```python
from __future__ import annotations

from typing import Any

from django.http import HttpRequest

from hyperdjango.page import HyperView


class BaseLayout(HyperView):
    def get_context(self, request: HttpRequest) -> dict[str, Any]:
        context = super().get_context(request)
        context["site_name"] = "HyperDjango Example"
        return context
```

`hyper/layouts/base/index.html`:

```django
{% extends "hyperdjango/base.html" %}

{% block body %}
  <nav>{{ site_name }}</nav>
  <main>{% block page %}{% endblock page %}</main>
{% endblock body %}
```

`hyper/layouts/base/entry.ts`:

```ts
import Alpine from "alpinejs";
import "./base.css";

Alpine.start();
```

`hyper/routes/about/+page.py`:

```python
from __future__ import annotations

from django.http import HttpRequest

from hyper.layouts.base import BaseLayout


class PageView(BaseLayout):
    def get(self, request: HttpRequest) -> dict[str, str]:
        return {"title": "About", "content": "This page inherits BaseLayout."}
```

`hyper/routes/about/index.html`:

```django
{% extends "layouts/base/index.html" %}

{% block page %}
  <h1>{{ title }}</h1>
  <p>{{ content }}</p>
{% endblock page %}
```

## What Layout Packages Are For

Use them for:

- shared template shells
- shared Python helpers
- shared `get_context()` data
- shared `entry.ts` / `entry.head.ts`
- shared CSS imported by those entries

---

## Base Template

# Base Template

HyperDjango ships a base template at `hyperdjango/base.html`.

Use it when you want the fastest path to a working app with the runtime scripts, asset tags, and CSRF hooks already in place.

## What It Provides

The shipped template includes:

- `{% hyper_preloads %}`
- `{% hyper_stylesheets %}`
- `{% hyper_head_scripts %}`
- `{% hyper_body_scripts %}`
- the core runtime script: `hyper.js`
- the Alpine bridge script: `hyper-alpine.js`
- a hidden CSRF token mount
- a `head` block and a `body` block you can extend normally

Current template:

```django
{% load static hyper_tags %}
<!DOCTYPE html>
<html lang="en">
    <head>
        {% block head %}
            <meta charset="utf-8" />
            <meta name="viewport" content="width=device-width, initial-scale=1" />
            <title>{{ title|default:"HyperDjango" }}</title>
        {% endblock head %}
        {% hyper_preloads %}
        {% hyper_stylesheets %}
        {% hyper_head_scripts %}
    </head>
    <body>
        <div id="hyper-csrf-token" hidden>{% csrf_token %}</div>
        {% block body %}
        {% endblock body %}
        <script src="{% static 'hyperdjango/hyper.js' %}"></script>
        <script src="{% static 'hyperdjango/hyper-alpine.js' %}"></script>
        {% hyper_body_scripts %}
    </body>
</html>
```

## Extending It

The normal pattern is to extend `hyperdjango/base.html` from your layout template.

```django
{% extends "hyperdjango/base.html" %}

{% block body %}
  <nav>...</nav>
  <main>{% block page %}{% endblock page %}</main>
{% endblock body %}
```

This works well for `hyper/layouts/...` packages.

## When to Use It

Use the shipped base template when you want:

- automatic page asset tags
- the runtime scripts already included
- CSRF wiring already present
- the default HyperDjango browser behavior without extra setup

For most projects, this should be the default starting point.

## Replacing It With Your Own Base Template

You can use your own base template instead.

If you do, keep these pieces:

### Load the template tags

```django
{% load static hyper_tags %}
```

### Render asset tags

In the head:

```django
{% hyper_preloads %}
{% hyper_stylesheets %}
{% hyper_head_scripts %}
```

Before the end of the body:

```django
{% hyper_body_scripts %}
```

### Include the runtime scripts

```django
<script src="{% static 'hyperdjango/hyper.js' %}"></script>
<script src="{% static 'hyperdjango/hyper-alpine.js' %}"></script>
```

If you do not want Alpine integration, you can omit `hyper-alpine.js`.

### Provide a CSRF source

The default template uses:

```django
<div id="hyper-csrf-token" hidden>{% csrf_token %}</div>
```

Keep that or provide an equivalent CSRF source the runtime can read.

## Minimal Custom Base

```django
{% load static hyper_tags %}
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>{{ title|default:"My App" }}</title>
    {% hyper_preloads %}
    {% hyper_stylesheets %}
    {% hyper_head_scripts %}
  </head>
  <body>
    <div id="hyper-csrf-token" hidden>{% csrf_token %}</div>
    {% block body %}{% endblock body %}
    <script src="{% static 'hyperdjango/hyper.js' %}"></script>
    <script src="{% static 'hyperdjango/hyper-alpine.js' %}"></script>
    {% hyper_body_scripts %}
  </body>
</html>
```

## Related Pages

- Assets and Vite: where the asset tags get their data
- Declarative HTML APIs: loading states, form-disable behavior, and view-transition naming
- Layouts: how your app-level templates usually extend the base template

---

## Actions

# Actions

Actions are the server-side interaction API of HyperDjango.

Use page handlers like `get()` and `post()` for full-page rendering.

Use `@action` for interaction-level updates.

## What an Action Is

An action is a method marked with `@action`.

```python
from __future__ import annotations

from hyperdjango.actions import HTML, action
from hyperdjango.page import HyperView


class PageView(HyperView):
    @action
    def save(self, request):
        return [HTML(content="<div id='flash'>Saved</div>", target="#flash")]
```

Actions let the server tell the browser to:

- patch HTML
- remove elements
- dispatch browser events
- show toasts
- redirect
- update history
- load JavaScript
- patch Alpine signals when Alpine integration is in use

## Recommended Return Shapes

The clearest action return styles are:

`list of action items`

Use a list when the whole response is known immediately.

```python
from __future__ import annotations

from hyperdjango.actions import HTML, Toast, action
from hyperdjango.page import HyperView


class PageView(HyperView):
    @action
    def create(self, request):
        return [
            HTML(content="<li>New item</li>", target="#todo-list", swap="append"),
            Toast(payload={"type": "success", "message": "Created"}),
        ]
```

`generator of action items`

Use a generator when items should be streamed over time.

```python
from __future__ import annotations

from time import sleep

from hyperdjango.actions import HTML, Redirect, Toast, action
from hyperdjango.page import HyperView


class PageView(HyperView):
    @action
    def run_job(self, request):
        yield HTML(content="<p id='job-status'>Starting...</p>", target="#job-status")
        sleep(1)
        yield HTML(content="<p id='job-status'>Working...</p>", target="#job-status")
        sleep(1)
        yield Toast(payload={"type": "success", "message": "Done"})
        yield Redirect(url="/done/")
```

Treat `Redirect(...)` as the last action item. Once the runtime sends a redirect, later items are not delivered.

## Other Supported Return Shapes

The current runtime also accepts a few other return forms.

`single action item`

```python
from __future__ import annotations

from hyperdjango.actions import HTML, action
from hyperdjango.page import HyperView


class PageView(HyperView):
    @action
    def save(self, request):
        return HTML(content="<div id='flash'>Saved</div>", target="#flash")
```

`Actions(...)`

```python
from __future__ import annotations

from hyperdjango.actions import Actions, HTML, Toast, action
from hyperdjango.page import HyperView


class PageView(HyperView):
    @action
    def create(self, request):
        return Actions(
            HTML(content="<li>New item</li>", target="#todo-list", swap="append"),
            Toast(payload={"type": "success", "message": "Created"}),
        )
```

`str`

```python
from __future__ import annotations

from hyperdjango.actions import action
from hyperdjango.page import HyperView


class PageView(HyperView):
    @action
    def simple(self, request):
        return "<div id='flash'>Saved</div>"
```

`dict`

A `dict` is treated as context for block rendering from the current page template.

```python
from __future__ import annotations

from hyperdjango.actions import action
from hyperdjango.page import HyperView


class PageView(HyperView):
    @action
    def stats(self, request):
        return {"count": 12, "completed": 4}
```

```django
{% block stats %}
  <div><strong>{{ count }}</strong> total, <strong>{{ completed }}</strong> completed</div>
{% endblock stats %}
```

`HttpResponse`

```python
from __future__ import annotations

from django.http import HttpResponse

from hyperdjango.actions import action
from hyperdjango.page import HyperView


class PageView(HyperView):
    @action
    def download(self, request):
        return HttpResponse("ok", content_type="text/plain")
```

## Typed Action Items

### `HTML`

Use `HTML(...)` to patch HTML into the page.

```python
from __future__ import annotations

from hyperdjango.actions import HTML, action
from hyperdjango.page import HyperView


class PageView(HyperView):
    @action
    def save(self, request):
        return [
            HTML(
                content="<div id='flash'>Saved</div>",
                target="#flash",
                swap="outer",
                transition=True,
                focus="preserve",
            )
        ]
```

### `Delete`

Use `Delete(...)` to remove a target element.

```python
from __future__ import annotations

from hyperdjango.actions import Delete, action
from hyperdjango.page import HyperView


class PageView(HyperView):
    @action
    def remove_row(self, request, id: int):
        return [Delete(target=f"#row-{id}")]
```

### `Event`

Use `Event(...)` to dispatch a browser `CustomEvent`.

```python
from __future__ import annotations

from hyperdjango.actions import Event, action
from hyperdjango.page import HyperView


class PageView(HyperView):
    @action
    def save_profile(self, request):
        return [Event(name="profile:saved", payload={"message": "Saved"}, target="#panel")]
```

### `Toast`

Use `Toast(...)` to emit a toast payload.

```python
from __future__ import annotations

from hyperdjango.actions import Toast, action
from hyperdjango.page import HyperView


class PageView(HyperView):
    @action
    def save(self, request):
        return [Toast(payload={"type": "success", "message": "Saved"})]
```

### `Redirect`

Use `Redirect(...)` when the interaction should leave the current page.

```python
from __future__ import annotations

from hyperdjango.actions import Redirect, action
from hyperdjango.page import HyperView


class PageView(HyperView):
    @action
    def finish(self, request):
        return [Redirect(url="/dashboard/")]
```

### `History`

Use `History(...)` when the URL should change without leaving the current page.

```python
from __future__ import annotations

from hyperdjango.actions import History, HTML, action
from hyperdjango.page import HyperView


class PageView(HyperView):
    @action
    def filter(self, request, q: str = ""):
        return [
            History(replace_url=f"/search/?q={q}"),
            HTML(content=f"<div id='results'>Results for {q}</div>", target="#results"),
        ]
```

### `LoadJS`

Use `LoadJS(...)` when an action-loaded fragment needs its own JS module.

```python
from __future__ import annotations

from hyperdjango.actions import HTML, LoadJS, action
from hyperdjango.page import HyperView


class PageView(HyperView):
    @action
    def open_modal(self, request):
        partial = self.render_template(
            "partials/confirm_modal",
            request=request,
            context_updates={"title": "Confirm", "message": "Continue?"},
        )
        items = [HTML(content=partial.html, target="#modal-root", swap="inner")]
        if partial.js:
            items.append(LoadJS(src=partial.js))
        return items
```

### `Signal` and `Signals`

Signals are Alpine-oriented state patches.

```python
from __future__ import annotations

from hyperdjango.actions import action
from hyperdjango.integrations.alpine.actions import Signal, Signals
from hyperdjango.page import HyperView


class PageView(HyperView):
    @action
    def counter(self, request, count: int = 0):
        return [Signal(name="count", value=int(count) + 1)]

    @action
    def increment_both(self, request, current: int = 0):
        local_count = int(current) + 1
        global_count = 42
        return [Signals(values={"count": local_count, "$count": global_count})]
```

- `count` patches the nearest Alpine `x-data`
- `$count` patches `Alpine.store("hyper")`

---

## Client-Side Actions

# Client-Side Actions

Client-side action invocation belongs in one place because this is where request coordination concepts like `sync` and `key` actually matter.

## Triggering Actions

HyperDjango exposes:

- Alpine `$action(...)`
- plain JavaScript `window.action(...)`

Alpine example:

```html
<div x-data="{ q: '' }">
  <input x-model="q" />
  <button @click="$action('search', { q }, { key: 'search' })">Search</button>
</div>
```

Vanilla JavaScript example:

```html
<input id="search-input" />
<button id="search-button" type="button">Search</button>

<script>
  document.querySelector("#search-button").addEventListener("click", () => {
    const q = document.querySelector("#search-input").value;
    window.action("search", { q }, { key: "search" });
  });
</script>
```

## `$action(name, data, options)`

```js
$action(name, data, options)
```

### `data`

The `data` object is sent to the server and merged into action kwargs.

```html
<button @click="$action('search', { q, page: 2 })">Search</button>
```

```python
from __future__ import annotations

from hyperdjango.actions import action
from hyperdjango.page import HyperView


class PageView(HyperView):
    @action
    def search(self, request, q: str = "", page: int = 1):
        ...
```

### `form`

Use `form` when the action should submit an existing form.

```html
<form id="profile-form" method="post" action="/profile">
  {% csrf_token %}
  <input name="name" />
  <button type="button" @click="$action('save_profile', {}, { form: '#profile-form' })">
    Save
  </button>
</form>
```

When `form` is provided, HyperDjango reads the form method and action URL automatically.

### `method`

Use `method` to override the HTTP method.

```html
<button @click="$action('search', { q }, { method: 'GET' })">Search</button>
```

### `url`

Use `url` to send the action to a URL other than the current page.

```html
<button @click="$action('search', { q }, { url: '/search/' })">Search</button>
```

### `sync`

Use `sync` to control how repeated requests coordinate.

- `replace`: replace the in-flight request in the same lane
- `block`: ignore new requests while one is active
- `none`: allow concurrent requests

```html
<button @click="$action('search', { q }, { sync: 'replace' })">Live search</button>
```

```html
<button @click="$action('save_profile', {}, { form: '#profile-form', sync: 'block' })">
  Save
</button>
```

```html
<button @click="$action('append_log', {}, { sync: 'none' })">Send many</button>
```

Default behavior:

- form-backed calls default to `block`
- non-form calls default to `replace`

### `key`

Use `key` to place requests into a named coordination lane.

This affects:

- `sync` behavior
- loading indicators scoped by key
- disable states scoped by key
- upload progress correlation
- request bookkeeping in general

If two requests share a key, then `sync: 'replace'` or `sync: 'block'` applies across both of them.

```html
<input x-model="q" />
<button @click="$action('search', { q }, { key: 'search', sync: 'replace' })">
  Search
</button>
<p hyper-loading-key="search">Searching...</p>
```

Different keys isolate concurrent work:

```html
<button @click="$action('save_profile', {}, { key: 'profile-save', sync: 'block' })">
  Save profile
</button>

<button @click="$action('upload_avatar', {}, { key: 'avatar-upload', sync: 'block' })">
  Upload avatar
</button>
```

### `onBeforeSubmit`

Use `onBeforeSubmit` to run client-side code immediately before the request is sent.

```html
<button
  @click="$action('save_profile', {}, {
    form: '#profile-form',
    onBeforeSubmit() {
      console.log('Submitting profile form');
    }
  })"
>
  Save
</button>
```

### `onUploadProgress`

Use `onUploadProgress` for file uploads or any request body where you want progress information.

```html
<button
  @click="$action('upload_avatar', {}, {
    form: '#avatar-form',
    method: 'POST',
    key: 'avatar-upload',
    onUploadProgress(detail) {
      console.log(detail.loaded, detail.total, detail.progress);
    }
  })"
>
  Upload avatar
</button>
```

## Outcomes

The Promise returned by `$action(...)` can resolve into different states.

`success`

```html
<button @click="$action('save_profile', {}, { form: '#profile-form' }).then(() => {
  console.log('Saved');
})">
  Save
</button>
```

`blocked`

```html
<button @click="$action('save_profile', {}, { form: '#profile-form', sync: 'block', key: 'profile-save' }).then((result) => {
  if (result && result.blocked) {
    console.log('Blocked');
  }
})">
  Save
</button>
```

`aborted`

```html
<button @click="$action('search', { q }, { sync: 'replace', key: 'search' }).then((result) => {
  if (result && result.aborted) {
    console.log('Replaced');
  }
})">
  Search
</button>
```

`rejected`

```html
<button @click="$action('upload_avatar', {}, { form: '#avatar-form', method: 'POST' }).catch((error) => {
  console.error(error);
})">
  Upload
</button>
```

## Runtime Events

HyperDjango emits browser events during the request lifecycle.

Common ones:

- `hyper:beforeRequest`
- `hyper:afterRequest`
- `hyper:requestBlocked`
- `hyper:requestReplaced`
- `hyper:requestAborted`
- `hyper:requestSuccess`
- `hyper:requestError`
- `hyper:requestException`
- `hyper:uploadProgress`
- `hyper:streamEvent`
- `hyper:toast`

```js
window.addEventListener("hyper:afterRequest", (event) => {
  console.log(event.detail.key, event.detail.ok, event.detail.aborted);
});
```

```js
window.addEventListener("hyper:uploadProgress", (event) => {
  if (event.detail.key !== "avatar-upload") {
    return;
  }
  console.log(event.detail.loaded, event.detail.total, event.detail.progress);
});
```

```js
window.addEventListener("hyper:streamEvent", (event) => {
  console.log(event.detail.event, event.detail.data);
});
```

## Server-Driven UI Options

In HyperDjango, UI behavior such as:

- target selector
- swap mode
- transition
- focus handling
- history push/replace

is expected to come from server-returned action items like `HTML(...)`, `Delete(...)`, `Redirect(...)`, and `History(...)`.

```python
from __future__ import annotations

from hyperdjango.actions import HTML, History


def build_result(results_html: str, q: str):
    return [
        HTML(
            content=results_html,
            target="#results",
            swap="inner",
            transition=True,
            focus="preserve",
        ),
        History(replace_url=f"/search/?q={q}"),
    ]
```

---

## Declarative HTML APIs

# Declarative HTML APIs

Most client-side behavior should go through `$action(...)` or `window.action(...)`.

These HTML APIs are still especially useful when you want less inline JavaScript.

## `hyper-form-disable`

Use `hyper-form-disable` on a form to disable submit buttons and button-like controls while the request is active.

```html
<form id="profile-form" method="post" action="/profile" hyper-form-disable>
  {% csrf_token %}
  <input name="name" />
  <button type="button" @click="$action('save_profile', {}, { form: '#profile-form', sync: 'block' })">
    Save
  </button>
</form>
```

This is mainly submit-button protection, not a full all-input disable.

## `hyper-view-transition-name`

Use `hyper-view-transition-name` to label a DOM region for browser view transitions.

```html
<section id="profile-panel" hyper-view-transition-name="profile-panel"></section>
```

Pair it with a server response that enables transitions:

```python
from __future__ import annotations

from hyperdjango.actions import HTML, action
from hyperdjango.page import HyperView


class PageView(HyperView):
    @action
    def update_profile(self, request):
        return [
            HTML(
                content="<section id='profile-panel'>Updated</section>",
                target="#profile-panel",
                transition=True,
            )
        ]
```

## Loading APIs

Loading attributes work best when you understand `key` and request coordination from the client-side actions page.

## `hyper-loading`

Show an element while requests are active.

```html
<p hyper-loading>Loading...</p>
```

## `hyper-loading-delay`

Delay loading UI to avoid flicker.

```html
<p hyper-loading hyper-loading-delay="150">Loading...</p>
```

## `hyper-loading-action`

Scope a loading indicator to one action name.

```html
<p hyper-loading-action="search">Searching...</p>
```

## `hyper-loading-key`

Scope a loading indicator to one request key.

```html
<p hyper-loading-key="search">Searching...</p>
```

## `hyper-loading="key"`

You can also pass the key directly through `hyper-loading`.

```html
<p hyper-loading="search">Searching...</p>
```

## `hyper-loading-class`

Add classes while the matching request is active.

```html
<section hyper-loading hyper-loading-class="opacity-50">Content</section>
```

## `hyper-loading-remove-class`

Remove classes while the matching request is active.

```html
<section hyper-loading hyper-loading-remove-class="hidden" class="hidden">Loading...</section>
```

## `hyper-loading-disable`

Disable a control while requests are active.

```html
<button hyper-loading-disable>Save</button>
```

## `hyper-loading-disable-key`

Disable a control only for one request key.

```html
<button hyper-loading-disable-key="search">Search</button>
```

## `hyper-loading-disable="key"`

You can also pass the key directly through `hyper-loading-disable`.

```html
<button hyper-loading-disable="search">Search</button>
```

## `hyper-target-busy`

Mirror busy state for a specific target selector.

```html
<div hyper-target-busy="#results"></div>
```

## Loading Example

```html
<input id="search-input" />
<button @click="$action('search', { q: document.querySelector('#search-input').value }, { key: 'search' })">
  Search
</button>

<p hyper-loading-key="search" hyper-loading-delay="150">Searching...</p>
<button hyper-loading-disable-key="search">Stop double submit</button>
```

---

## Alpine Integration

# Alpine Integration

HyperDjango works without Alpine, but Alpine is the integration layer it supports most directly.

Use Alpine when you want:

- `$action(...)`
- concise local state
- signal patching into `x-data`
- signal patching into `$store.hyper`

## `$action(...)`

When Alpine is present, HyperDjango installs the `$action(...)` magic automatically.

```html
<div x-data="{ q: '' }">
  <input x-model="q" />
  <button @click="$action('search', { q }, { key: 'search' })">Search</button>
</div>
```

## Local Signals

An unprefixed signal name patches the nearest Alpine `x-data` scope.

```python
from __future__ import annotations

from hyperdjango.actions import action
from hyperdjango.integrations.alpine.actions import Signal
from hyperdjango.page import HyperView


class PageView(HyperView):
    @action
    def counter(self, request, count: int = 0):
        return [Signal(name="count", value=int(count) + 1)]
```

```html
<section x-data="{ count: 0 }">
  <button @click="$action('counter', { count })">Increment</button>
  <strong x-text="count"></strong>
</section>
```

## Global Signals

A signal name starting with `$` patches `Alpine.store("hyper")`.

```python
from __future__ import annotations

from hyperdjango.actions import action
from hyperdjango.integrations.alpine.actions import Signal
from hyperdjango.page import HyperView


class PageView(HyperView):
    @action
    def reset_global(self, request):
        return [Signal(name="$count", value=0)]
```

```html
<section x-data="{}" x-init="$store.hyper.count = 5">
  <button @click="$action('reset_global')">Reset global</button>
  <strong x-text="$store.hyper.count"></strong>
</section>
```

## Local and Global Together

```python
from __future__ import annotations

from hyperdjango.actions import action
from hyperdjango.integrations.alpine.actions import Signals
from hyperdjango.page import HyperView


class PageView(HyperView):
    @action
    def increment_both(self, request, current: int = 0):
        local_count = int(current) + 1
        global_count = 42
        return [Signals(values={"count": local_count, "$count": global_count})]
```

```html
<section x-data="{ count: 0 }" x-init="$store.hyper.count = 0">
  <button @click="$action('increment_both', { current: count })">Increment</button>
  <p>Local: <strong x-text="count"></strong></p>
  <p>Global: <strong x-text="$store.hyper.count"></strong></p>
</section>
```

---

## Assets and Vite

# Assets and Vite

HyperDjango is built around automatic asset discovery.

Pages, layouts, and template packages can load nearby Vite entries without hand-wiring script tags for every feature.

## Core Settings

```python
HYPER_FRONTEND_DIR = BASE_DIR / "hyper"
HYPER_VITE_OUTPUT_DIR = BASE_DIR / "dist"
HYPER_VITE_DEV_SERVER_URL = "http://localhost:5173/"
HYPER_DEV = DEBUG

TEMPLATES[0]["DIRS"].append(HYPER_FRONTEND_DIR)
STATICFILES_DIRS = [HYPER_VITE_OUTPUT_DIR]
```

### `HYPER_FRONTEND_DIR`

Where HyperDjango finds your `hyper/` tree.

### `HYPER_VITE_OUTPUT_DIR`

Where Vite writes built assets. In production, HyperDjango reads the manifest here.

### `HYPER_VITE_DEV_SERVER_URL`

Which Vite dev server URL should be injected during development.

### `HYPER_DEV`

Whether to use dev-server assets or manifest-based production assets.

## Automatic Entry Discovery

HyperDjango looks for nearby files such as:

- `entry.ts`
- `entry.js`
- `entry.head.ts`
- `entry.head.js`
- custom entries like `admin.entry.ts`

This works for:

- routed pages
- layout packages
- standalone template packages

## What Gets Loaded

- `entry.ts` / `entry.js`: body module scripts
- `entry.head.ts` / `entry.head.js`: head module scripts
- CSS imported by those entries
- Vite module preloads

## Development vs Production

In development:

- HyperDjango injects the Vite dev server URL
- Vite client is added automatically where needed

In production:

- HyperDjango resolves built assets from `dist/.vite/manifest.json`

## Template Tags

Load the tags:

```django
{% load hyper_tags %}
```

Available tags:

- `{% hyper_preloads %}`
- `{% hyper_stylesheets %}`
- `{% hyper_head_scripts %}`
- `{% hyper_body_scripts %}`
- `{% hyper_custom_entry "admin" %}`

These tags read asset information from the current `page` object in template context.

---

## Overview

# Reference

Use the guide pages to learn HyperDjango conceptually.

Use the reference pages when you already know what feature you need and want the exact arguments, return shapes, or API behavior.

---

## Django Integration

# Django Integration

## `include_routes(url_prefix="")`

Import:

```python
from hyperdjango.urls import include_routes
```

Usage:

```python
from django.contrib import admin
from django.urls import path

from hyperdjango.urls import include_routes

urlpatterns = [
    path("admin/", admin.site.urls),
    *include_routes(),
]
```

Arguments:

- `url_prefix: str = ""`
  Mount every compiled HyperDjango route under a prefix without changing the route files themselves.

Behavior:

- scans `HYPER_FRONTEND_DIR / "routes"` for `+page.py` files
- compiles route segments into Django `path(...)` or `re_path(...)` entries
- returns a list of URL patterns you can spread directly into `urlpatterns`

Notes:

- `url_prefix` is purely a mount-time prefix; it does not change route names or page classes
- if `APPEND_SLASH` is enabled, compiled routes include trailing slashes
- route conflicts are detected at compile time

---

## Settings

# Settings

## `HYPER_FRONTEND_DIR`

Type:

- `Path | str`

Purpose:

- tells HyperDjango where your `hyper/` directory lives

Expected contents usually include:

- `routes/`
- `layouts/`
- `templates/`
- shared frontend files

Example:

```python
HYPER_FRONTEND_DIR = BASE_DIR / "hyper"
```

## `HYPER_VITE_OUTPUT_DIR`

Type:

- `Path | str`

Purpose:

- tells HyperDjango where Vite writes built assets

Example:

```python
HYPER_VITE_OUTPUT_DIR = BASE_DIR / "dist"
```

## `HYPER_VITE_DEV_SERVER_URL`

Type:

- `str`

Purpose:

- tells HyperDjango which Vite dev server URL to inject during development

Example:

```python
HYPER_VITE_DEV_SERVER_URL = "http://localhost:5173/"
```

## `HYPER_DEV`

Type:

- `bool`

Purpose:

- switches asset loading between development mode and manifest-based production mode

Typical usage:

```python
HYPER_DEV = DEBUG
```

Behavior:

- `True`: use Vite dev server URLs and inject `@vite/client`
- `False`: resolve assets from the built Vite manifest in `HYPER_VITE_OUTPUT_DIR`

---

## Pages and Rendering

# Pages and Rendering Reference

## `PageView`

In routed pages, `+page.py` must define a class named `PageView`.

That class can inherit from:

- `hyperdjango.page.HyperView`
- `django.views.View`

Requirements:

- the file must be named `+page.py`
- the class must be named `PageView`

## `HyperView`

Import:

```python
from hyperdjango.page import HyperView
```

`HyperView` inherits from:

- `HyperPageTemplate`
- `HyperActionMixin`
- Django `View`

That means a `HyperView` has both:

- template rendering APIs
- action registration and dispatch APIs

### `get_context(request)`

Signature:

```python
def get_context(self, request: HttpRequest) -> dict[str, Any]:
    ...
```

Purpose:

- build the base template context for the page
- usually extended with `super().get_context(request)`

### `render(request, relative_template_name="", context_updates=None)`

Signature:

```python
def render(
    self,
    *,
    request: HttpRequest,
    relative_template_name: str = "",
    context_updates: dict[str, Any] | None = None,
) -> str:
    ...
```

Arguments:

- `request`
  Current Django request
- `relative_template_name`
  Template path relative to the current page or template class directory. If omitted, `index.html` is used.
- `context_updates`
  Extra context merged on top of `get_context(request)`.

Returns:

- rendered HTML string

### `render_block(block_name, request, relative_template_name="", context_updates=None)`

Signature:

```python
def render_block(
    self,
    *,
    block_name: str,
    request: HttpRequest,
    relative_template_name: str = "",
    context_updates: dict[str, Any] | None = None,
) -> str:
    ...
```

Arguments:

- `block_name`
  Django template block name to render
- `request`
  Current Django request
- `relative_template_name`
  Optional relative template path. If omitted, the current page template is used.
- `context_updates`
  Extra context merged on top of the page context for this render only

Returns:

- rendered block HTML string

### `render_template(template_dir, request, context_updates=None)`

Signature:

```python
def render_template(
    self,
    template_dir: str,
    *,
    request: HttpRequest,
    context_updates: dict[str, Any] | None = None,
) -> HyperPartialTemplateResult:
    ...
```

Arguments:

- `template_dir`
  Directory relative to the current file location. HyperDjango expects `index.html` inside it.
- `request`
  Current Django request
- `context_updates`
  Extra context merged into the render

Returns:

- `HyperPartialTemplateResult`

Fields:

- `html: str`
- `js: str | None`

Limitation:

- action-time partial rendering only exposes HTML and one body JS entry path

## `HyperPageTemplate`

Import:

```python
from hyperdjango.page import HyperPageTemplate
```

Use it for standalone renderable template classes outside routed pages.

What it provides:

- template resolution relative to the class file
- inherited body/head/style/preload asset collection
- `page` in template context

### `get_template_name()`

Signature:

```python
@classmethod
def get_template_name(cls) -> str:
    ...
```

Purpose:
- Returns the full template name for the page's default `index.html` template.

### `get_relative_template_name(name)`

Signature:

```python
@classmethod
def get_relative_template_name(cls, name: str) -> str:
    ...
```

Arguments:
- `name: str`
  The relative file name (e.g., `"index.html"`, `"partial.html"`).

Purpose:
- Converts a relative template name to a full Django template name, relative to the class location.

### `resolve_import(file_name)`

Signature:

```python
@classmethod
def resolve_import(cls, *, file_name: str) -> Generator[AssetTag, None, None]:
    ...
```

Arguments:
- `file_name: str`
  The entry file to resolve (e.g., `"entry.js"`, `"entry.head.ts"`).

Purpose:
- Resolves Vite imports for a given entry file, yielding `AssetTag` objects (`ModuleTag`, `StyleSheetTag`, `ModulePreloadTag`).

Raises:
- `FileNotFoundError`: If the file does not exist.
- `FileNotLoadedFromViteError`: If the file exists but was not included in the Vite manifest.

## Shortcuts

```python
from hyperdjango.shortcuts import render_template_block, render_template_page
```

### `render_template_page(request, template_cls, context=None, status=200, headers=None)`

Renders a standalone `HyperPageTemplate` class as a full Django `HttpResponse`.

**Arguments:**

- `request` (`HttpRequest`): The current Django request object.
- `template_cls` (`type[HyperPageTemplate]`): The template class to render.
- `context` (`dict[str, Any] | None`): Initial context data to pass to the template. Default: `None`.
- `status` (`int`): HTTP status code for the response. Default: `200`.
- `headers` (`dict[str, str] | None`): Additional HTTP headers to include in the response. Default: `None`.

**Returns:**

- `HttpResponse` containing the rendered HTML.

### `render_template_block(request, template_cls, block_name, context=None, relative_template_name="", status=200, headers=None)`

Renders a specific template block from a `HyperPageTemplate` class as a full Django `HttpResponse`.

**Arguments:**

- `request` (`HttpRequest`): The current Django request object.
- `template_cls` (`type[HyperPageTemplate]`): The template class to render.
- `block_name` (`str`): The name of the Django template block (e.g., `{% block content %}`) to render.
- `context` (`dict[str, Any] | None`): Initial context data to pass to the template. Default: `None`.
- `relative_template_name` (`str`): Optional relative path to a specific template file if not using the class default. Default: `""`.
- `status` (`int`): HTTP status code for the response. Default: `200`.
- `headers` (`dict[str, str] | None`): Additional HTTP headers to include in the response. Default: `None`.

**Returns:**

- `HttpResponse` containing the rendered block HTML.

---

## Actions

# Actions Reference

## `@action`

Import:

```python
from hyperdjango.actions import action
```

Usage forms:

```python
@action
def save(self, request):
    ...
```

```python
@action("save_profile")
def save(self, request):
    ...
```

Behavior:

- marks the method as an action
- stores the action name used by the runtime
- the action becomes discoverable through `get_action(name)`

## `Actions(*items)`

Import:

```python
from hyperdjango.actions import Actions
```

Purpose:

- wrapper around multiple typed action items

Arguments:

- `*items: ActionItem`

Notes:

- iterable at runtime
- functionally equivalent to returning a list of action items

## Return Shapes

Recommended:

- list of action items
- generator yielding action items

Supported by the current runtime:

- single action item
- `Actions(...)`
- `str`
- `dict`
- `HttpResponse`

Recommended guidance:

- use a list when the whole response is known immediately
- use a generator when the response should stream over time
- use typed action items for clarity

Dispatch compatibility details:

- `str` is converted into a patch action
- `dict` is treated as context for `render_block(...)`
- `HttpResponse` is passed through after Hyper headers are ensured

## `HTML(...)`

Arguments:

- `content: str | None = None`
- `target: str | None = None`
- `swap: str = "outer"`
- `transition: bool = False`
- `focus: str | None = None`
- `swap_delay: int | None = None`
- `settle_delay: int | None = None`
- `strict_targets: bool | None = None`

Argument details:

- `content`
  HTML string to patch into the page
- `target`
  CSS selector the client runtime should patch
- `swap`
  DOM insertion mode. Supported values: `inner`, `outer`, `before`, `after`, `prepend`, `append`, `delete`, `none`.
- `transition`
  Whether to request view-transition-aware patching
- `focus`
  Focus mode after patching. Common values are handled by the client runtime such as preserving focus or moving to the first invalid field.
- `swap_delay`
  Delay before the swap step starts
- `settle_delay`
  Delay before the settle step completes
- `strict_targets`
  Whether missing targets should fail loudly for this patch

Event emitted to the client runtime:

- `patch_html`

## `Delete(target)`

Arguments:

- `target: str`

Behavior:

- translated into an HTML patch with `swap="delete"`

Event emitted to the client runtime:

- `patch_html`

## `Event(name, payload=None, target=None)`

Arguments:

- `name: str`
- `payload: dict[str, Any] | None = None`
- `target: str | None = None`

Behavior:

- if `target` is provided, the event is dispatched on that element
- otherwise the event is dispatched on `window`

Event emitted to the client runtime:

- `dispatch_event`

## `Toast(payload)`

Arguments:

- `payload: Any`

Behavior:

- emitted to the client as `hyper:toast`
- your frontend chooses how to display it

Event emitted to the client runtime:

- `toast`

## `Redirect(url, replace=False)`

Arguments:

- `url: str`
- `replace: bool = False`

Behavior:

- redirects the browser immediately
- if returned from a list or generator, treat it as the last item because later items are not delivered

Event emitted to the client runtime:

- `redirect`

## `History(push_url=None, replace_url=None)`

Arguments:

- `push_url: str | None = None`
- `replace_url: str | None = None`

Behavior:

- `push_url` adds a history entry
- `replace_url` replaces the current history entry
- no full redirect occurs

Event emitted to the client runtime:

- `history`

## `LoadJS(src)`

Arguments:

- `src: str`

Behavior:

- loads a module script dynamically after the action response reaches the client

Event emitted to the client runtime:

- `load_js`

## `Signal(name, value)`

Arguments:

- `name: str`
- `value: Any`

Behavior:

- `count` patches the nearest Alpine `x-data`
- `$count` patches `Alpine.store("hyper")`

Event emitted to the client runtime:

- `patch_signals`

## `Signals(values)`

Arguments:

- `values: dict[str, Any]`

Behavior:

- patches multiple Alpine values at once

Event emitted to the client runtime:

- `patch_signals`

---

## Client Runtime

# Client Runtime Reference

## `$action(name, data, options)`

Available in Alpine environments through the HyperDjango Alpine bridge.

Arguments:

- `name: str`
  Action name to call on the server
- `data: dict[str, Any]`
  Data merged into the action kwargs
- `options: dict[str, Any]`
  Client-side request options

Request metadata sent by the runtime can include:

- `X-Hyper-Action`
- `X-Hyper-Target`
- `X-Hyper-Data`
- `X-Requested-With`

## `window.action(name, data, options)`

Plain JavaScript equivalent of `$action(...)`.

Arguments are the same as `$action(...)`.

## Action Loading Attributes

These options define how the client runtime orchestrates request lifecycle, state, and coordination.

### `form`
- **Type**: `CSS selector string | HTMLFormElement`
- **Purpose**: Associates the action with an existing form. 
- **Behavior**: 
  - Extracts method and URL from the form element.
  - Automatically serializes form fields into the action kwargs.
  - Form fields are overridden by explicit JSON action data if keys collide.

### `method`
- **Type**: `str` (e.g., `"GET"`, `"POST"`)
- **Purpose**: Explicitly overrides the request method.
- **Default**: Derived from the associated `form` if present, otherwise `"POST"` for actions.

### `url`
- **Type**: `str`
- **Purpose**: Defines the target URL for the action request.
- **Default**: The current browser URL.

### `sync`
- **Type**: `"replace" | "block" | "none"`
- **Purpose**: Defines how concurrent requests in the same coordination lane are handled.
- **Options**:
  - `replace`: Cancels the existing in-flight request and sends the new one.
  - `block`: Ignores the new request while an existing one is still in-flight.
  - `none`: Allows multiple concurrent requests to proceed.
- **Defaults**: 
  - `block` for form-backed requests.
  - `replace` for non-form requests.

### `key`
- **Type**: `str`
- **Purpose**: Identifies the specific coordination lane for `sync` behavior.
- **Behavior**: 
  - Requests with the same key share the same `sync` policy and loading state.
  - If omitted, the runtime automatically derives a key based on the action name and target.

### `onBeforeSubmit`
- **Type**: `(requestOptions) => void | boolean`
- **Purpose**: Client-side hook immediately before the request is dispatched.
- **Behavior**: If it returns `false`, the request is aborted. Useful for client-side validation.

### `onUploadProgress`
- **Type**: `(progressEvent) => void`
- **Purpose**: Enables tracking for multipart/form-data upload progress.
- **Behavior**: Provides access to `loaded` and `total` bytes for UI progress indicators.

## Outcomes

Common outcome flags:

- `blocked`
- `aborted`
- success with no special flag

Rejected cases:

- network failure
- thrown client/runtime exception

Meaning:

- `blocked`: the request never started because `sync="block"` rejected it
- `aborted`: the request started, but a later request replaced it
- success: the request completed and the response was processed normally

## Runtime Events

The HyperDjango client runtime dispatches events to `window` for lifecycle monitoring and integration.

| Event | Fired When | Payload Properties |
| :--- | :--- | :--- |
| `hyper:beforeRequest` | Immediately before sending an action request. | `key`, `url`, `method`, `action` |
| `hyper:afterRequest` | After a request completes or fails. | `key` |
| `hyper:requestBlocked` | When `sync="block"` prevents a new request. | `key` |
| `hyper:requestReplaced` | When `sync="replace"` aborts an in-flight request. | `key` |
| `hyper:requestAborted` | When a request is intentionally cancelled. | `key` |
| `hyper:requestSuccess` | When a request completes successfully. | `key`, `status` |
| `hyper:requestError` | When the server returns an error status. | `key`, `status`, `message` |
| `hyper:requestException` | When client-side code throws an exception. | `key`, `error` |
| `hyper:uploadProgress` | During file upload progress tracking. | `key`, `progress` (0-1) |
| `hyper:streamEvent` | When a new SSE event is received from the server. | `event` (type), `data` (payload) |
| `hyper:toast` | When a `Toast` action is received. | `value` |


## Server-Side Action Detection

At dispatch time, the server treats a request as an action request when an action name is present through one of these sources:

- `X-Hyper-Action`
- query string `_action`
- POST field `_action`

Action kwargs are assembled in this order:

1. JSON from `X-Hyper-Data`
2. query parameters not already present
3. POST fields not already present for non-GET requests

---

## HTML APIs

# HTML Loading and UI APIs

HyperDjango provides declarative HTML attributes to manage UI states and transitions during action requests.

## Loading Attributes

These attributes allow you to modify elements based on pending action requests.

### `hyper-loading`
- **Behavior**: Toggles element visibility (`hidden` attribute).
- **Scope**: Matches active requests. If a request is active, the element becomes visible (`hidden=false`).

### `hyper-loading-disable`
- **Behavior**: Toggles the `disabled` property on form elements (`button`, `input`).
- **Scope**: If a request is active, the element is disabled (`disabled=true`). Restores original state when the request completes.

### `hyper-loading-class`
- **Behavior**: Toggles a CSS class based on active requests.
- **Scope**: Adds the specified class(es) when active, removes them when inactive.

### `hyper-loading-remove-class`
- **Behavior**: The inverse of `hyper-loading-class`.
- **Scope**: Removes the specified class(es) when active, adds them when inactive.

### `hyper-loading-delay`
- **Behavior**: Adds a debounce delay (in milliseconds) before applying visibility changes for `hyper-loading` elements.
- **Purpose**: Prevents UI flicker for very fast requests.

### `hyper-target-busy`
- **Behavior**: Adds `aria-busy="true"` to a target element when the associated action is active.

### Scoping Attributes

These attributes refine which requests trigger the loading state on the element.

| Attribute | Purpose |
| :--- | :--- |
| `hyper-loading-key` | Links the element to requests with a specific coordination `key`. |
| `hyper-loading-action`| Links the element to requests for a specific action name. |
| `hyper-loading="key"` | Shorthand for applying loading state to a specific request key. |
| `hyper-loading-disable-key` | Links `hyper-loading-disable` to a specific key. |
| `hyper-loading-disable="key"` | Shorthand for disabling based on a specific key. |

If no scoping attribute is provided, the element tracks **global** request state.

## UI Management Attributes

### `hyper-form-disable`
- **Behavior**: Applied to a `<form>` element. Automatically adds `hyper-loading-disable` to all `<button>`, `input[type="submit"]`, and `input[type="button"]` controls within the form when a request is active.
- **Purpose**: Provides a declarative way to disable all form controls during submission without adding individual attributes to every button.

### `hyper-view-transition-name`
- **Behavior**: Sets the CSS `view-transition-name` property on the element to the provided value.
- **Purpose**: Enables the browser's View Transitions API for this element. When a `transition=True` flag is passed in an `HTML` action, the runtime uses these transition names to coordinate smooth animations between DOM updates.
- **Note**: Ensure the transition name is unique within the document.

## Guidance

- Use `hyper-loading-key` when the request lane is already named with `$action(..., { key })`.
- Use `hyper-loading-action` when you care about one action name regardless of key.
- Use `hyper-loading-disable*` when controls should stop duplicate input during active requests.

---

## Template Tags

# Template Tags

Load tags:

```django
{% load hyper_tags %}
```

Available tags:

- `{% hyper_preloads %}`
- `{% hyper_stylesheets %}`
- `{% hyper_head_scripts %}`
- `{% hyper_body_scripts %}`
- `{% hyper_custom_entry "admin" %}`

These read asset information from the current `page` object.

If `page` is missing from template context, HyperDjango raises `PageContextNotFoundError`.

## `hyper_preloads`

Renders module preload tags collected from the current page and its inherited templates/layouts.

Source:

- `page.preload_imports`

## `hyper_stylesheets`

Renders stylesheet tags collected from Vite entry imports.

Source:

- `page.stylesheets`

Nonce behavior:

- if `request._csp_nonce` exists, the nonce is attached to the rendered tags

## `hyper_head_scripts`

Renders module scripts discovered from `entry.head.js` or `entry.head.ts`.

Source:

- `page.head_imports`

## `hyper_body_scripts`

Renders module scripts discovered from `entry.js` or `entry.ts`.

Source:

- `page.body_imports`

## `hyper_custom_entry "name"`

Looks for:

- `name.entry.js`
- `name.entry.ts`

If neither exists, HyperDjango raises `FileNotFoundError`.

Resolution order:

1. `name.entry.js`
2. `name.entry.ts`

---

## Commands

# Commands

## `python manage.py hyper_scaffold`

Purpose:

- generate a starter HyperDjango project structure

Useful flags:

- `--no-wire`
- `--force`

Argument details:

- `--no-wire`
  Do not patch Django settings or urls automatically
- `--force`
  Overwrite existing scaffolded files

Behavior:

- creates starter `hyper/routes`, `hyper/layouts`, and `hyper/templates` files
- creates or updates `vite.config.js`
- creates or updates `package.json`
- optionally patches Django settings and urls unless `--no-wire` is used

Generated layout starter:

- `hyper/layouts/base/__init__.py`
- `hyper/layouts/base/index.html`
- `hyper/layouts/base/entry.ts`

## `python manage.py hyper_routes`

Purpose:

- print compiled routes for inspection

Useful flags:

- `--json`

Argument details:

- `--prefix`
  Compile routes as if they were mounted under a URL prefix
- `--dir`
  Override the routes directory path
- `--json`
  Print route metadata as JSON instead of human-readable lines

Behavior:

- compiles the current route tree
- prints route paths and names
- useful in CI to catch route conflicts early

---

## Troubleshooting

# Troubleshooting

This guide shortens the feedback loop when integrating progressive interactions. It focuses on failure modes teams hit most often in production rollouts.

## Action returns full page on back/forward

Symptoms:

- popstate shows partial/action payload instead of full page shell

Checks:

- ensure you are using Hyper action responses with no-cache headers
- verify reverse proxy/CDN is not caching action endpoints

## Target swap does nothing

Symptoms:

- action succeeds but DOM is unchanged

Checks:

- confirm target selector exists at swap time
- ensure returned HTML is non-empty for swap modes that require HTML

## CSRF failures on POST actions/forms

Checks:

- keep Django CSRF middleware enabled
- include `{% csrf_token %}` in forms
- ensure CSRF cookie is present for authenticated pages
- if using CSP/meta-only flow, expose `meta[name='csrf-token']`

## View transition not visible

Checks:

- browser must support `document.startViewTransition`
- server response must set `transition: true`
- `hyper-view-transition-name` only labels transition parts; it does not enable transitions alone

## Duplicate or stale form fragments after swaps

Checks:

- keep IDs unique in replaced fragments
- prefer stable container target (for example `#profile-panel`) for `outer`/`inner` swaps
- avoid returning nested duplicate roots for the same target

## Route conflict error at startup

Checks:

- inspect `python manage.py hyper_routes`
- remove equivalent route shapes (for example `[slug]` and `[id]` in same path level)
- remove group-colliding paths (`(group)/x` vs `x`)

## `runserver` does not reload after editing `hyper/*`

Checks:

- ensure `hyperdjango` is in `INSTALLED_APPS`
- ensure `HYPER_FRONTEND_DIR` points to the directory you edit
- restart `runserver` once after changing settings
- verify you are using Django `runserver` autoreload (not a custom process manager without reload)

---

## Production Checklist

# Production Checklist

HyperDjango spans template rendering, runtime JS, and caching layers. Small config mismatches across these layers are a common source of release regressions.

## Runtime and App Settings

- Set `HYPER_FRONTEND_DIR` to your deployed frontend source directory.
- Set `HYPER_VITE_OUTPUT_DIR` to built static asset output.
- Set `HYPER_DEV = False` in production.
- Ensure collectstatic includes Vite output and `hyperdjango/static`.

See the Assets and Vite page for the main asset build and manifest flow.

## Caching and Action Responses

Action responses include no-store/no-cache and `Vary` headers for Hyper request metadata.

- keep reverse proxies from overriding these headers on action endpoints
- avoid caching action JSON/partial responses in CDN edge caches

## Security

- keep Django CSRF middleware enabled
- send CSRF cookie or render `{% csrf_token %}` in base layout
- if using CSP, ensure nonce support for rendered asset tags

## Client Contracts

- use stable DOM IDs/selectors for server-targeted HTML and delete patches
- enable strict targets (`hyper-strict-targets`) in QA to catch selector drift
- define fallback behavior for missing JS (full-page paths should still work)

## Performance

- use `sync: "replace"` or explicit keys for rapid interactions (search, typeahead)
- use `hyper-loading-delay` to avoid flicker on fast requests
- prefer block rendering (`render_block`) for hot action paths

## Testing

- run routing checks in CI: `python manage.py hyper_routes`
- add tests for route conflict cases and action response contracts
- test back/forward navigation with enhanced links/forms
- verify 422 validation flows for form-driven `$action(..., {}, { form })` submits

## Deployment Validation

- start app with production settings and run key routes manually
- verify assets resolve from manifest (no Vite dev server URLs)
- verify toasts/signals/swaps on at least one action-heavy page
- verify target-not-found errors are absent in browser logs

---

## FAQ

# FAQ

## Is Alpine required?

No. HyperDjango works with plain JavaScript through `hyper.js`.

Alpine is recommended because it gives you `$action(...)` and direct signal integration.

## Should I start with `HyperView` or plain Django `View`?

Start with `HyperView` unless you only want file-based routing.

Use plain `View` when routing is the only HyperDjango feature you need for that page.

## Where should layouts live?

Put layouts in `hyper/layouts/...` and inherit them explicitly from `PageView` classes.

## When should I use `render()` vs `render_template()`?

Use `render()` for page-local templates such as `partials/form.html`.

Use `render_template()` for directory-based partial units with `index.html` and optional `entry.ts`.

## Should actions return lists or generators?

Use a list when the whole response is known immediately.

Use a generator when action items should be streamed over time.

## What does `key` do?

`key` creates a named request coordination lane.

It affects:

- `sync`
- loading indicators
- disable states
- upload progress correlation

## Where should target, swap, transition, and history behavior live?

Prefer to define those on the server through typed action items such as `HTML(...)`, `Delete(...)`, `Redirect(...)`, and `History(...)`.

---
