Signals lifecycle

Inspect the signals handed to handle_event/3, patch server-owned values, and remove client signals while watching the current flow.

Demo

Signal snapshot

mounted

tick 0
scope
release
filter
open
ephemeral
"client-note"
server_tick
0
{}

How it works

  • handle_event/3 receives the request signals already read.
  • patch_signals/3 updates server-owned signals.
  • remove_signals/3 clears selected client state.
  • The inspector itself is patched after each command.

Signals are client state. Datastar posts the current set with every event, so handle_event/3 receives them already read; patch_signals/2 writes server-owned values back and remove_signals/2 deletes client ones. The inspector prints the raw map it was handed, minus the CSRF and tab keys.

lib/dstar_demo_web/pages/signals_page.ex
def handle_event(conn, "patch", signals) do
  tick = next_tick(signals)
  state = client_state(signals, tick: tick, audit: "patch_signals tick #{tick}")

  conn
  |> patch_signals(state)
  |> patch(&inspector/1, state)
end

If this were LiveView

This is the one comparison where the two sides are not solving the same problem, because LiveView does not have the concept this page inspects.

Dstar

lib/dstar_demo_web/pages/signals_page.ex
def handle_event(conn, "patch", signals) do
  tick = next_tick(signals)
  state = client_state(signals, tick: tick, audit: "patch_signals tick #{tick}")

  conn
  |> patch_signals(state)
  |> patch(&inspector/1, state)
end

LiveView

illustrative
# Illustrative LiveView equivalent — not compiled or run.
#
# There is no client-owned state to inspect: assigns live on the server,
# and the client only ever sends what a binding names explicitly. So this
# page has no real LiveView counterpart — only the nearest thing.
def mount(_params, _session, socket) do
  {:ok,
   assign(socket,
     scope: "release",
     filter: "open",
     server_tick: 0,
     audit_trail: "mount"
   )}
end

def handle_event("patch", _params, socket) do
  tick = socket.assigns.server_tick + 1

  {:noreply, assign(socket, server_tick: tick, audit_trail: "assign tick")}
end

# To send client-held values at all, each one must be named:
#
#   <button phx-click="patch" phx-value-scope={@scope} phx-value-filter={@filter}>
#
# or a JS hook pushes them:
#
#   this.pushEvent("patch", {scope, filter})