Active search

Typing changes Datastar signals, the server filters local data, and only the result panel is patched back.

Demo

Updating results

12 matches

query: empty · segment: all

server patch

Start typing to narrow the inventory.

The first render still shows the full local dataset so there is something real to scan.

Sierra Command Center

Workflow · Ops

commands validation

Signal Replay Console

Tool · Platform

signals inspector

Northstar Metrics

Dashboard · SRE

streaming metrics

Patch Mode Workbench

Lab · UI Systems

patches dom

Component Forge

Renderer · Design Eng

heex components

Edge Intake Queue

Dataset · Support

queue search

Beacon Release Notes

Document · Product

content components

Atlas Region Map

Dataset · Field

geo search

Pulse Incident Feed

Stream · Reliability

streaming activity

Handoff Checklist

Workflow · Success

commands lifecycle

Control Plane Audit

Report · Security

signals audit

Launch Readiness

Checklist · Product

validation ready

How it works

  • data-bind keeps the query and segment on the client.
  • Typing posts a page event with the latest signals.
  • Only #search-results is patched, not the input chrome.
  • Empty and no-results states are server-rendered.

data-bind:query keeps the query on the client, and data-on:input__debounce.240ms posts a page event with the current signals 240ms after you stop typing. The handler filters and patches #search-results alone — and deliberately does not echo the query back, because a patch landing while you are still typing would clobber the input.

lib/dstar_demo_web/pages/active_search_page.ex
def handle_event(conn, "search", signals) do
  query = String.trim(signals["query"] || "")
  segment = signals["segment"] || "all"
  results = search(query, segment)

  # The client owns query/segment — echoing them back would clobber
  # anything typed while this response was in flight.
  patch(conn, &result_panel/1,
    results: results,
    query: query,
    segment: segment,
    result_count: length(results)
  )
end

If this were LiveView

Both sides debounce at 240ms, filter the same local dataset, and render the same empty and no-results states.

Dstar

lib/dstar_demo_web/pages/active_search_page.ex
def handle_event(conn, "search", signals) do
  query = String.trim(signals["query"] || "")
  segment = signals["segment"] || "all"
  results = search(query, segment)

  # The client owns query/segment — echoing them back would clobber
  # anything typed while this response was in flight.
  patch(conn, &result_panel/1,
    results: results,
    query: query,
    segment: segment,
    result_count: length(results)
  )
end

LiveView

illustrative
# Illustrative LiveView equivalent — not compiled or run.
def mount(_params, _session, socket) do
  {:ok, assign(socket, query: "", segment: "all", results: search("", "all"))}
end

def handle_event("search", %{"query" => query, "segment" => segment}, socket) do
  query = String.trim(query)

  {:noreply,
   assign(socket,
     query: query,
     segment: segment,
     results: search(query, segment)
   )}
end

def render(assigns) do
  ~H"""
  <form phx-change="search" phx-submit="search">
    <input
      type="search"
      name="query"
      value={@query}
      phx-debounce="240"
      autocomplete="off"
      placeholder="Try stream, patch, signal, or owner names"
    />

    <select name="segment">
      <option :for={s <- ~w(all workflow dashboard tool)} value={s} selected={s == @segment}>
        {s}
      </option>
    </select>
  </form>

  <div id="search-results">
    <p>{length(@results)} matches</p>

    <article :for={item <- @results} id={"result-" <> item.name}>
      <h2>{item.name}</h2>
      <p>{item.type} · {item.owner}</p>
    </article>
  </div>
  """
end