Sync, Broadcast & Multi-User Sessions

The single-action flow recipe covers what happens when one user clicks one button. This page covers what happens when there are many users, many tabs, many sessions — and how the framework keeps them coherent without you writing diffing or messaging code.

The two propagation mechanisms

LiveTemplate exposes two ways for one user's action to update other connected viewers:

Both reuse the same diff-and-patch pipeline as a single-user action; the difference is just whose connections receive the patch frame.

Sync — same user, multiple tabs

Session registryGo serverTab B (browser)Tab A (browser)Session registryGo serverTab B (browser)Tab A (browser)Same user, two tabs, both connected to /todosBoth tabs render the same new state. Tab B never made an HTTP request.WS: action "Save", form data1Mutate state.Items2ctx.Sync() — broadcast to my own sessions3Find every session for this user + controller4[Tab A connection, Tab B connection]5WS: patches [...]6WS: patches [...]7

Code shape:

func (c *TodosController) Save(state *State, ctx *livetemplate.Context) error {
    state.Items = append(state.Items, Item{Title: ctx.GetString("title")})
    return ctx.Sync()
}

BroadcastAction — different users, shared channel

"chat-room-1" channelGo serverBobAlice"chat-room-1" channelGo serverBobAliceTwo users, both subscribed to chat-room-1Bob's tab updates without Bob doing anything.His controller's "Refresh" action ran, server-side, on his behalf.WS: action "Send", message="hi"1Append to room.Messages2ctx.BroadcastAction("chat-room-1", "Refresh")3Subscribers: [Alice's session, Bob's session]4WS: patches [..]5WS: patches [..]6

Code shape:

func (c *ChatController) OnConnect(state *State, ctx *livetemplate.Context) error {
    return ctx.Subscribe("chat-room-" + state.RoomID)
}

func (c *ChatController) Send(state *State, ctx *livetemplate.Context) error {
    state.Messages = append(state.Messages, Message{Body: ctx.GetString("body")})
    return ctx.BroadcastAction("chat-room-"+state.RoomID, "Refresh")
}

func (c *ChatController) Refresh(state *State, ctx *livetemplate.Context) error {
    // Re-fetch from canonical store if needed, then return.
    // The patch pipeline runs automatically.
    return nil
}

When to pick which

Need Use
Same logged-in user, multi-tab coherence ctx.Sync() — implicit, no channel name needed
Different users seeing the same shared state ctx.BroadcastAction(channel, action) — channel is your shard key
One-shot push from a background goroutine (no user action triggered it) controller.Push(channel, action) from anywhere

You almost always want Sync() for personal app interactions and BroadcastAction for collaborative / public state.

How this page works

Two mermaid sequence-diagram blocks render client-side via tinkerdown's bundled mermaid runtime. The diagrams live next to the code shapes they describe, so changing the code is a same-file edit — no out-of-tree diagram tool, no PNG that goes stale.

For runnable examples, see the chat example and the patterns under Real-Time (Multi-User Sync, Broadcasting, Presence Tracking).