Active search
Typing changes Datastar signals, the server filters local data, and only the result panel is patched back.
Demo
12 matches
query: empty · segment: all
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
Signal Replay Console
Tool · Platform
Northstar Metrics
Dashboard · SRE
Patch Mode Workbench
Lab · UI Systems
Component Forge
Renderer · Design Eng
Edge Intake Queue
Dataset · Support
Beacon Release Notes
Document · Product
Atlas Region Map
Dataset · Field
Pulse Incident Feed
Stream · Reliability
Handoff Checklist
Workflow · Success
Control Plane Audit
Report · Security
Launch Readiness
Checklist · Product
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.
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
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 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