Skip to content
scalive
Menu
ConnectingLiveReconnectingOffline

Lifecycle, state ownership, and reconnects

Treat Each Mount As Independent

The project anatomy introduced the disconnected HTTP mount and the separate connected socket mount. Both must build a valid model from their inputs; the connected mount cannot recover the model created for the HTTP response.

Match ctx.connection when initial state or work depends on the lifecycle. The lifecycle example only records which kind of mount created the model:

scala
def mount(ctx: MountContext): Task[Model] =
  val connectedMount = ctx.connection match
    case Connection.Disconnected => false
    case Connection.Connected(_) => true

  ZIO.succeed(Model(connectedMount, currentTitle = DefaultTitle))

Start connection-owned work only from a matched Connection.Connected(capabilities) branch. This example starts no clock or other background task.

Follow The Handoff From HTTP To Live

The browser keeps one document while server ownership crosses two independent lifecycles. Read the following sequences as one handoff: the HTTP request creates Model A and useful DOM, then LiveSocket uses the retained bootstrap data to join and create Model B.

Disconnected HTTP render. The initial request owns Model A only long enough to produce the complete response:

Lifecycle trace

Disconnected HTTP render

One HTTP request produces useful HTML; its temporary model is not retained after the response.

  • Browser
  • Scalive runtime
  • Your LiveView
  1. Phase 1Request

    1. Browser

      01Navigate

      Starts navigation to the Live route.

    2. BrowserScalive runtime

      02HTTP GET

      Requests the typed Live route.

    3. Scalive runtime

      03Prepare lifecycle

      Decodes the route and prepares the request-scoped lifecycle.

  2. Phase 2Disconnected lifecycle

    1. Scalive runtimeYour LiveView

      04Disconnected mount

      Invokes mount with connected = false to create a temporary model for this HTTP request.

    2. Your LiveViewScalive runtime

      05Model A

      Returns immutable state used only for the disconnected render.

    3. Scalive runtimeYour LiveView

      06Render Model A

      Projects the disconnected model into typed HTML.

    4. Your LiveViewScalive runtime

      07Typed HTML

      Returns page content before layouts are applied.

    5. Scalive runtime

      08Assemble document

      Applies layouts, creates the live root, and embeds the signed session and CSRF metadata.

  3. Phase 3Response and teardown

    1. Scalive runtimeBrowser

      09HTML response

      Returns a useful HTTP response; the document can display before LiveSocket joins.

    2. Lifecycle boundary

      10End request lifecycle

      Model A ends with the request and is not carried into the socket lifecycle. A future LiveView join creates a fresh model.

When that request ends, the browser retains the rendered DOM, signed session, and CSRF metadata. Model A and its request-owned resources are gone.

Connected LiveSocket mount. The browser presents that bootstrap data while joining the live endpoint. A successful join starts a fresh lifecycle:

Lifecycle trace

Connected LiveSocket mount

A LiveSocket join validates the document's bootstrap data, creates Model B, and reconciles the existing DOM.

  • Browser
  • Scalive runtime
  • Your LiveView
  1. Phase 1Connect and join

    1. Browser

      01Discover live root

      Reads the root id, signed session, and CSRF metadata from the disconnected document.

    2. BrowserScalive runtime

      02Open LiveSocket

      Presents the browser-bound CSRF token and opens the WebSocket transport.

    3. BrowserScalive runtime

      03phx_join

      Sends the current URL, signed session, static tracking, and untrusted connect parameters.

    4. Scalive runtime

      04Validate join

      Verifies CSRF authorization, the signed topic-bound session, route, live session, mount claims, and root layout. Browser connect parameters remain untrusted.

  2. Phase 2Connected lifecycle

    1. Scalive runtimeYour LiveView

      05Connected mount

      Starts a fresh lifecycle, runs connected mount aspects, then decodes route parameters and invokes mount with connected = true. Model A is unavailable.

    2. Your LiveViewScalive runtime

      06Model B

      Returns fresh immutable state rebuilt from route, session, and durable inputs.

    3. Scalive runtimeYour LiveView

      07Render Model B

      Projects the connected model into typed HTML.

    4. Your LiveViewScalive runtime

      08Typed HTML

      Returns content that the runtime uses to build the initial rendered tree.

    5. Scalive runtime

      09Commit Model B

      Model B and its rendered snapshot become current only after the initial render and its after-render hooks succeed; the runtime then computes the initial diff.

  3. Phase 3Join response

    1. Scalive runtimeBrowser

      10Initial rendered diff

      Replies with response.rendered for the connected tree, not a second HTML document.

    2. Browser

      11Reconcile DOM

      Patches the existing disconnected DOM and marks the LiveView connected.

The join reply contains an initial rendered diff for Model B. The browser reconciles it with the existing disconnected DOM rather than loading a second HTML document. This preserves a useful initial page while keeping the two server models and their resources independent.

Follow The Lifecycle Timeline

The complete lifecycle can now be summarized as:

StageConnectionModel and work
HTTP mountDisconnectedBuild a temporary model for useful initial HTML
HTTP renderDisconnectedRender the response inside layouts; this model ends with the request
Socket mountConnectedBuild a new model and start connection-owned work
Initial live renderConnectedRender and commit the initial connected tree
Message handlingConnectedProduce a proposed model from the last committed model
Render and diffConnectedRender the proposal, compare trees, then commit after success
Socket terminationEndingInterrupt and release connection-owned resources
RejoinConnectedStart a new lifecycle and mount again from durable inputs

Async completions and subscription values enter the same typed message flow as browser events. Lifecycle capabilities such as flash, navigation, async work, and subscriptions belong to the context for the phase in which they are valid.

Put State In The Right Lifetime

StateOwner and lifetimeExamples
Render-derived valueRecomputed from the modelLabels, totals, disabled state
Disconnected modelOne HTTP renderInitial page data and useful no-JavaScript HTML
Connected modelOne socket lifecycleSelection, validation, loaded view data
Lifecycle resourceCurrent connectionSubscriptions, async tasks, uploads
Injected serviceApplication-defined lifetimeRepositories, caches, shared domain state
Durable storageBeyond the process or connectionOrders, documents, audit history
Browser-local stateCurrent document or hookFocus, scroll, third-party widget state

A module-level mutable value is not visitor state. A LiveView model is isolated to one lifecycle. An injected service can deliberately outlive that lifecycle, but it must define its own concurrency, isolation, and durability semantics.

State that must survive reconnect belongs in a service or durable store. Reload it during mount and keep only the connection's rendering and interaction state in the model.

Treat Reconnect As A New Lifecycle

When the transport rejoins, the LiveView mounts again. Rebuild its model from durable inputs, restart required connection-scoped work, and expect the old socket's subscriptions, async tasks, uploads, and nested LiveViews to be released.

Use this checklist:

  • Make mount safe to run repeatedly.

  • Match ctx.connection and run socket-only work only in the Connection.Connected(capabilities) branch.

  • Use lifecycle-managed APIs for async work and subscriptions.

  • Make repeated external mount effects idempotent where necessary.

  • Test the reconnect behavior that matters to the application in a browser.

Understand Failure And Commit Boundaries

A handler returns a proposed model. Scalive renders that model and makes it the next committed model only after the transition and render path succeed. An unhandled handler or render failure therefore does not commit the proposal.

This is not a database transaction. Service calls, writes, or other external effects completed before a later render failure are not rolled back. Recover expected, user-actionable failures into explicit model state and use normal transaction boundaries for durable operations.

A failed connected lifecycle may terminate and later be replaced by a fresh join. Reconnect logic must not depend on recovering the previous model object. Exact protocol diagnostics and operational recovery belong in the troubleshooting guide.

Render Connection State Declaratively

connection.visibleWhenConnected and connection.visibleWhenDisconnected render browser bindings that react immediately to socket state. Use them for an offline label or to disable controls whose events cannot reach the server.

The server does not receive a normal application message merely because the transport drops. Design recovery around remounting rather than an assumed disconnect message.

Test At The Lifecycle Boundary

Use a disconnected test for initial HTTP state and a connected test for typed server interactions. Use a real browser when the behavior depends on transport loss, reconnects, JavaScript, or DOM patching. The testing guide explains these boundaries and their available support.