# Day 10: Out-of-Band Updates, Toasts, Notifications, Jobs, and Live Panels

**Onboarding track:** Alpine.js + htmx, co-equal architecture  
**Verified baseline:** Alpine.js 3.15.12 and htmx 2.0.10 (stable)  
**Verification date:** 2026-07-19  
**Compatibility boundary:** htmx 4.0.0-beta5 is a prerelease line and is not the syntax baseline for these lessons.

## Purpose

Coordinate several authoritative server projections and immediate local presentation without building a global client data store.

## How to use this document

Budget a working day. Read the lessons in order, type the examples instead of copying them blindly, perform every mini-task, and finish the exit exercise without looking at the answer notes. The examples use a fictional server-rendered operations application, but the ownership rules are framework-neutral.

A successful day ends with three artifacts:

1. working markup or a small runnable page;
2. written ownership notes for each state value and DOM region;
3. answers to the comprehension check.

## Learning outcomes

You will be able to:

- use OOB swaps for directly related server-authoritative projections;
- use Alpine for transient toasts and local panel behavior;
- model notification records separately from notification presentation;
- poll or stream authoritative job HTML;
- avoid OOB sprawl, store sprawl, and high-frequency HTML misuse;
- define stop conditions, cleanup, focus, and failure behavior.

## 1. Two kinds of consequences

A server operation may cause:

### Authoritative representation changes

Use normal returned HTML or OOB swaps:

- cart contents;
- cart count;
- order total;
- notification list;
- unread count;
- saved row;
- summary totals;
- job state.

### Local presentation reactions

Use Alpine and semantic events:

- open a drawer;
- close a dialog;
- show a toast;
- expand details;
- focus a result;
- animate a local shell.

This division is one of the strongest combined patterns.

## 2. OOB cart response

Request:

```html
<button
    hx-post="/cart/items/42"
    hx-target="#cart-contents"
    hx-swap="outerHTML"
>
    Add to cart
</button>
```

Response:

```html
<section id="cart-contents">
    <!-- complete updated cart -->
</section>

<span id="cart-count" hx-swap-oob="outerHTML">3</span>

<section id="order-summary" hx-swap-oob="outerHTML">
    <!-- authoritative summary -->
</section>
```

Response header:

```http
HX-Trigger-After-Swap: {"cart-updated":{"itemId":42}}
```

Alpine shell:

```html
<aside
    x-data="{ open: false }"
    @cart-updated.window="open = true"
>
    <!-- stable drawer -->
</aside>
```

The response updates three server projections and separately asks the local shell to react.

## 3. OOB suitability test

Use OOB when all are true:

1. the regions are direct consequences of the same server transaction;
2. each region is an authoritative server projection;
3. each has stable identity;
4. the response remains understandable;
5. the number of regions is bounded;
6. testing the response is straightforward.

Do not use OOB to:

- open drawers;
- switch local tabs;
- toggle animations;
- mutate unrelated page regions;
- create page-wide choreography.

If one action updates ten unrelated zones, reconsider the page and endpoint design.

## 4. Toast center

A toast is transient presentation, not durable truth.

```html
<script>
document.addEventListener('alpine:init', () => {
    Alpine.store('toastCenter', {
        items: [],

        push({ level = 'info', message, timeout = 5000 }) {
            const id = crypto.randomUUID()
            this.items.push({ id, level, message })

            window.setTimeout(() => this.remove(id), timeout)
        },

        remove(id) {
            this.items = this.items.filter(item => item.id !== id)
        }
    })
})
</script>

<section
    x-data
    aria-live="polite"
    aria-label="Notifications"
    @profile-saved.window="$store.toastCenter.push({
        level: 'success',
        message: 'Profile saved.'
    })"
>
    <template x-for="toast in $store.toastCenter.items" :key="toast.id">
        <article>
            <p x-text="toast.message"></p>
            <button type="button" @click="$store.toastCenter.remove(toast.id)">
                Dismiss
            </button>
        </article>
    </template>
</section>
```

Production notes:

- do not put essential error recovery only in a disappearing toast;
- pause or extend timeout on focus/hover if required;
- allow dismissal;
- avoid flooding;
- use an appropriate live-region politeness level;
- keep durable notification records on the server.

## 5. Durable notification center

Ownership:

- server: records, read/unread state, permissions;
- htmx: load, paginate, mark read, poll/stream HTML;
- OOB: unread count;
- Alpine: panel open state, local filters, toast presentation;
- URL: a navigable notification detail if product-relevant.

```html
<aside x-data="{ open: false }">
    <button
        type="button"
        @click="open = !open"
        :aria-expanded="open"
    >
        Notifications
        <span id="unread-count">4</span>
    </button>

    <section x-cloak x-show="open">
        <div
            id="notification-list"
            hx-get="/notifications"
            hx-trigger="notification-refresh-requested from:window"
            hx-target="this"
        >
            <!-- authoritative list -->
        </div>
    </section>
</aside>
```

Mark-read response:

```html
<li id="notification-42">
    <!-- updated read representation -->
</li>

<span id="unread-count" hx-swap-oob="outerHTML">3</span>
```

## 6. Polling job component

```html
<section x-data="{ detailsOpen: false }">
    <button type="button" @click="detailsOpen = !detailsOpen">
        Job details
    </button>

    <div
        id="job-42"
        hx-get="/jobs/42/status"
        hx-trigger="every 2s"
        hx-target="this"
        hx-swap="outerHTML"
    >
        <p>Processing 48%…</p>
    </div>

    <div x-cloak x-show="detailsOpen">
        Local explanatory content
    </div>
</section>
```

Completed response:

```html
<div id="job-42">
    <p role="status">Export complete.</p>
    <a href="/exports/42/download">Download</a>
</div>
```

The completed root omits polling attributes, so polling stops.

The Alpine shell survives because only `#job-42` is targeted.

## 7. “Pause” must have one clear meaning

A local Alpine flag called `paused` is ambiguous.

Possible meanings:

- stop network polling;
- keep polling but freeze chart animation;
- stop announcements;
- hide updates;
- postpone replacing a view while the user reads.

Name and implement the exact behavior.

If the requirement is to stop requests, redesign the trigger explicitly—for example, replace the polling component with a non-polling representation or trigger polling through a controlled custom event. Do not merely hide the component and assume transport stopped.

## 8. SSE and WebSockets

In stable htmx 2.x, SSE and WebSocket support are extensions. Use them when the server delivers authoritative HTML events.

Ownership still applies:

- server: event and durable state;
- htmx extension: transport and HTML installation;
- Alpine: local expansion, selection, mute, or presentation;
- plain JavaScript: high-frequency numerical visualization.

Pin extension versions and document lifecycle, reconnection, authentication, and teardown.

htmx 4 significantly changes extension mechanics; follow the migration guide.

## 9. High-frequency updates

Do not force constant HTML replacement for:

- 60 fps charts;
- canvas animation;
- audio visualization;
- rapidly changing geometry;
- large streaming numeric datasets.

Use a specialized renderer as a stable JavaScript island. htmx can update surrounding labels, controls, and authoritative snapshots at a lower frequency.

## 10. Multi-region testing

Test:

- every OOB target exists or failure is handled;
- IDs are unique;
- primary and OOB roots match expected swaps;
- a missing optional region does not corrupt the page;
- fact events fire after intended swaps;
- local shell state survives;
- focus is not stolen;
- back/forward remains coherent;
- polling stops;
- repeated notifications do not create duplicate toasts;
- server authorization applies to every projection.

## 11. Server response review

A reviewer should be able to read one response and answer:

```text
Primary target:
Primary representation:
OOB regions:
Why each OOB region is directly related:
Fact events:
Event timing:
Status:
History effect:
Focus effect:
Retry/idempotency:
```

## 12. Day lab: operations notification and export center

Build:

1. stable notification drawer;
2. server list and unread count;
3. mark-read OOB updates;
4. transient toast on new notification;
5. export-start form;
6. polling job card;
7. completed download representation;
8. local job-details disclosure;
9. explicit network-error and session-expiry behavior.

Do not introduce a client entity store.

## Mini-tasks

### Task A: OOB or event?

Choose for:

- updated cart count;
- open cart drawer;
- updated order total;
- show saved toast;
- replace saved row;
- expand help text.

### Task B: stop polling

Change a completed job response so it cannot continue polling.

### Task C: separate durable and transient notifications

Move unread truth from an Alpine store to server HTML while keeping toast presentation local.

### Task D: prevent OOB sprawl

Refactor a response that updates twelve unrelated page regions.

## Comprehension check

1. What should OOB swaps represent?
2. What should semantic events represent?
3. Why is opening a drawer not an OOB concern?
4. Who owns unread notification truth?
5. Who owns a toast timeout?
6. How does a polling component stop?
7. Why is a generic `paused` flag dangerous?
8. When should a specialized renderer replace HTML swaps?
9. What extension concern is version-sensitive?
10. What must multi-region tests prove?

## Cheat sheet

```text
PRIMARY AUTHORITATIVE REGION      -> normal swap
RELATED AUTHORITATIVE PROJECTION  -> OOB
LOCAL PRESENTATION REACTION       -> Alpine event listener
DURABLE NOTIFICATION              -> server
TOAST QUEUE                       -> Alpine
JOB TRUTH                         -> server HTML
POLLING                           -> htmx
HIGH-FREQUENCY GRAPHICS           -> JS island
```

## Exit exercise

Design a live incident-response panel. Server events update incident status, active responder count, and timeline. Alpine owns expanded details, local sound preference, and toast presentation. A chart is an isolated renderer. Document which updates use primary swap, OOB, events, polling/SSE, and plain JavaScript.

## Exit criteria

You pass when one server transaction can coherently update several projections without a client store, and local presentation remains independent.

## Official reference shelf

Use these as the authority when a project version or behavior is uncertain:

- Alpine.js documentation: https://alpinejs.dev/
- Alpine.js directives: https://alpinejs.dev/directives/
- Alpine.js `Alpine.data()` lifecycle and `destroy()`: https://alpinejs.dev/globals/alpine-data
- htmx 2.x documentation: https://htmx.org/docs/
- htmx 2.x reference: https://htmx.org/reference/
- htmx 2.x events: https://htmx.org/events/
- htmx 4 migration guide: https://four.htmx.org/migration-guide-htmx-4/

> Version note: These exercises use htmx 2.x event names such as `htmx:afterSwap` and `htmx:afterSettle`. htmx 4 changes event naming and several defaults. Do not mechanically port the examples without the migration guide.
