How a LiveTemplate Update Flows

A walkthrough of the moving parts behind a single user interaction — from browser click to DOM patch — rendered as a sequence diagram you can step through.

Tip. Hit the presentation-mode button (top-right) to walk through the H2 sections one at a time. Useful for demos, screen-share-friendly.

The full flow at a glance

sequenceDiagram
    autonumber
    participant Browser
    participant Client as @livetemplate/client
    participant Server as Go server
    participant State as Controller state
    participant Diff as Diff engine

    Browser->>Client: User clicks #lt;button name="Save"#gt;
    Client->>Server: WebSocket frame: {action: "Save", form: {...}}
    Server->>State: Resolve session, route to Save() handler
    State->>State: Mutate state.Items
    State->>Diff: Render template against new state
    Diff->>Server: Patch operations (path, op, value)
    Server-->>Client: WebSocket frame: {patches: [...]}
    Client->>Browser: Apply patches to DOM (no full re-render)

1. The user interacts

Standard HTML emits a click. There's no onclick= handler — just a name="Save" attribute on a <button> inside a <form>.

<form name="save-form">
    <input name="title" required>
    <button type="submit" name="Save">Save</button>
</form>

The @livetemplate/client script — loaded once per page — intercepts the submit, packages the form data, and ships it over an existing WebSocket connection.

2. The server routes the action

The server has a controller registered against the route. The Save button's name maps to a Save(ctx) method on the controller. There is no router-config table — the method name is the route.

func (c *NotesController) Save(ctx *livetemplate.Context) {
    var req struct{ Title string }
    ctx.BindAndValidate(&req)
    c.state.Items = append(c.state.Items, Item{Title: req.Title})
}

3. The state mutates

The controller holds a struct of plain Go fields. Save appends an item — that's it. No diffing logic in user code, no manual DOM bookkeeping, no virtual-DOM tree to construct.

4. The diff engine produces patches

After every action, LiveTemplate re-renders the page's Go template against the new state, then diffs against the previously-rendered HTML. The output is a small list of patch operations — replace this attribute, insert this child at index N, remove this node.

The patches travel back to the browser as a single WebSocket frame, typically under 1 KB even for visible UI changes.

5. The client applies patches

@livetemplate/client walks the patch list and mutates the live DOM in place. There is no React-style reconciliation step, no full innerHTML reset. The browser keeps focus, keeps scroll position, keeps in-flight CSS animations.

See the flow in action

The widget below is a real LiveTemplate counter — the same one from Your First App, embedded inline. Click +1. Every step of the diagram above happens, every time:

Counter: 0

Open browser DevTools → Network → WS to watch the WebSocket frames flow. The action frame goes up; the patch frame comes back. No reload, no full re-render — just the changed text node.

What you can change to see this in action

Open the todos example in two browser tabs. Add an item in tab 1 — it appears in tab 2 within ~30ms because each tab opted into peer fan-out via ctx.Subscribe(ctx.SelfTopic()) in Mount, and the controller calls ctx.Publish(ctx.SelfTopic(), "RefreshTodos", nil) after each shared mutation.

How this page works

This page is itself two interactive tinkerdown features:

This means a single markdown file doubles as both reference documentation (when read top-to-bottom) and a walkthrough talk (when presented). The diagram source lives next to the prose explaining it — they cannot drift.

source: livetemplate/docs · path: content/recipes/architecture-flow.md