Continuations

Continuations enable real-time updates from server to client using Server-Sent Events (SSE). They're perfect for progress indicators, live status updates, and any scenario where the server needs to push updates to the client over time.

What are Continuations?

A continuation is a long-running server process that can send multiple updates to the client over a single HTTP connection. Unlike regular requests that return once, continuations can push updates as they happen.

💡 Use Cases: Progress bars, build status, live logs, real-time notifications, file upload progress, long-running task monitoring.

The spry-continuation Attribute

Add spry-continuation to an element to enable SSE for its children. This attribute is shorthand for:

  • hx-ext="sse" - Enable HTMX SSE extension
  • sse-connect="(auto-endpoint)" - Connect to SSE endpoint
  • sse-close="_spry-close" - Close event name
HTML
<div spry-continuation>
    <!-- Children can receive SSE updates -->
    <div spry-dynamic="progress-bar">
        <div class="progress-bar" style-width-expr='format("%i%%", this.percent)'>
        </div>
    </div>
    <div spry-dynamic="status">
        <strong>Status:</strong> <span spry-unique content-expr="this.status">Ready</span>
    </div>
</div>

The spry-dynamic Attribute

Mark child elements with spry-dynamic="name" to make them updatable. When you call send_dynamic("name"), that element's HTML is sent to the client and swapped in place.

HTML
<!-- spry-dynamic marks elements for SSE swapping -->
<div class="progress-container" spry-dynamic="progress-bar">
    <div class="progress-bar">...</div>
</div>

<div class="status" spry-dynamic="status">
    <strong>Status:</strong> <span>Processing...</span>
</div>

<!-- Automatically gets: sse-swap="_spry-dynamic-{name}" hx-swap="outerHTML" -->

The continuation() Method

Override continuation(ContinuationContext context) in your component to implement long-running processes that send updates.

Vala
public async override void continuation(ContinuationContext ctx) throws Error {
    for (int i = 0; i <= 100; i += 10) {
        percent = i;
        status = @"Processing... $(i)%";

        // Send dynamic section updates to the client
        yield ctx.send_dynamic("progress-bar");
        yield ctx.send_dynamic("status");

        // Simulate async work
        Timeout.add(500, () => {
            continuation.callback();
            return false;
        });
        yield;
    }

    status = "Complete!";
    yield ctx.send_dynamic("status");
}

ContinuationContext API

The ContinuationContext parameter provides methods for sending updates:

Method Description
send_dynamic(name) Send a dynamic section (by spry-dynamic name) as an SSE event
send_json(event_type, node) Send JSON data as an SSE event
send_string(event_type, data) Send raw string data as an SSE event
send_full_update(event_type) Send the entire component document
send_event(event) Send a custom SseEvent

Required Scripts

Include the HTMX SSE extension in your page for continuations to work:

HTML
<!-- In your page template or component -->
<script spry-res="htmx.js"></script>
<script spry-res="htmx-sse.js"></script>

The spry-unique Attribute

Use spry-unique on elements that need stable IDs for targeting. Spry generates unique IDs automatically.

HTML
<!-- spry-unique generates a stable unique ID -->
<div class="progress-bar" spry-unique>
    <!-- Gets ID like: _spry-unique-0-abc123 -->
</div>

<!-- Use with content-expr for dynamic content -->
<span spry-unique content-expr="this.status">Loading...</span>

⚠️ Restrictions: Cannot use spry-unique with an explicit id attribute, or inside spry-per-* loops.

Cancellation Handling

Override continuation_canceled() to clean up resources when the client disconnects:

Vala
public async override void continuation_canceled() throws Error {
    // Client disconnected - clean up resources
    cancel_background_task();
    release_file_handles();
    log_message("Task canceled by client");
}

Live Demo: Progress Bar

This demo shows a progress bar that updates in real-time using SSE. Click "Start Task" to see continuations in action!

ProgressDemo
0% Ready

Next Steps