Guide / 16 — Hot Morphing

Hot Morphing

Hot Morphing is Stew's built-in live reloading system. It updates the browser instantly when code changes, without reloading the page and without losing UI state.

How It Works

  1. CLI Watcher: stew run dev monitors project files. When a file changes, it sends a reload signal via a shared channel.
  2. SSE Middleware: sdk/live injects a GET /_stew/live endpoint into the Go server. The browser maintains an open Server-Sent Events connection to this endpoint.
  3. Client Script: live.InjectScript() injects a <script> into the HTML that listens for SSE and triggers a morph-reload.
  4. Idiomorph Morphing: Instead of a full reload, idiomorph performs a smart diff between the current DOM and the new HTML, preserving scroll position, focus, and input states.

Configuring a Layout for Hot Morphing

In your root layout pages/@layout.stew:

<goscript>
    import "os"
    import "github.com/ZiplEix/stew/sdk/live"
</goscript>

<!DOCTYPE html>
<html lang="en">
    <head>
        <!-- idiomorph is required for morphing -->
        <script src="https://unpkg.com/idiomorph/dist/idiomorph-ext.min.js"></script>
        <script src="https://unpkg.com/htmx.org@1.9.10"></script>
    </head>
    <body hx-ext="morph">

        <slot />

        <!-- Live script injected only in dev mode -->
        {{ if os.Getenv("STEW_DEV") == "true" }}
            {{ raw(live.InjectScript()) }}
        {{ end }}

    </body>
</html>

⚠️ Critical: The Body Tag Limitation

By design, Hot Morphing only updates elements located inside the <body> tag.

While you must place the hx-ext="morph" attribute on the <body> itself, the <body> tag and its siblings (like <head>) are not morphed. This ensures that the Live Reload script connection remains active throughout the development session.

Configuring the Server

In main.go, wrap your router with the live middleware:

package main

import (
    "net/http"
    "os"
    "github.com/ZiplEix/stew/sdk/live"
)

func main() {
    mux := http.NewServeMux()
    RegisterStewRoutes(mux)

    var handler http.Handler = mux
    if os.Getenv("STEW_DEV") == "true" {
        handler = live.Middleware(mux)
    }

    http.ListenAndServe(":8080", handler)
}

The live.Middleware adds the /_stew/live SSE route and manages the reload signal broadcast.

Automatic Environment Variables

Variable Dev Value Role
STEW_DEVtrueEnables Hot Morphing and development logging