Curated library of 38 atomic skills, 7 personas, and 1 orchestrator for Elixir and Phoenix development. Organized by category: fundamentals, phoenix, database, testing, auth, infrastructure, quality, security, integrations, tooling, frameworks, personas, and orchestration. Covers core Elixir patterns, Phoenix LiveView, Ecto, OTP, Oban, testing, security, deployment, real-time, and modern tooling (Req, Swoosh, Cachex, Broadway, Ash).
73
91%
Does it follow best practices?
Impact
—
No eval scenarios have been run
Advisory
Suggest reviewing before use
stream_insert/3 and stream_delete/3 for incremental updates — never replace the entire stream assignphx-update="stream" in templates — required for stream DOM patching; missing this causes full re-rendersreset: true for filtering and sorting — clears existing items before inserting the new result setFollow this sequence when implementing LiveView streams:
id attribute format (e.g., post-#{post.id})stream_configure/3 in mount/3 to set custom DOM ID generatorstream(socket, :name, collection) in mount/3phx-update="stream" container and id={dom_id} on itemsstream_insert/stream_delete for all mutationsdefmodule MyAppWeb.PostLive.Index do
use MyAppWeb, :live_view
@impl true
def mount(_params, _session, socket) do
{:ok,
socket
|> stream_configure(:posts, dom_id: &"post-#{&1.id}")
|> stream(:posts, Blog.list_posts())}
end
@impl true
def handle_event("create", %{"post" => params}, socket) do
{:ok, post} = Blog.create_post(params)
{:noreply, stream_insert(socket, :posts, post, at: 0)}
end
@impl true
def handle_event("delete", %{"id" => id}, socket) do
post = Blog.get_post!(id)
{:ok, _} = Blog.delete_post(post)
{:noreply, stream_delete(socket, :posts, post)}
end
@impl true
def handle_info({:post_created, post}, socket) do
{:noreply, stream_insert(socket, :posts, post, at: 0)}
end
end<div id="posts" phx-update="stream">
<div :for={{dom_id, post} <- @streams.posts} id={dom_id} class="post">
<h3><%= post.title %></h3>
<p><%= post.body %></p>
<button phx-click="delete" phx-value-id={post.id}>
Delete
</button>
</div>
</div>Verify after adding the template: Open browser DevTools → Network → WS frames. Confirm incoming frames contain targeted patch operations for individual items, not full list replacements. If you see full re-renders, check:
phx-update="stream" is present on the container <div>id={dom_id} is present on each item element@per_page 20
@impl true
def mount(_params, _session, socket) do
{:ok,
socket
|> assign(:page, 1)
|> assign(:loading, false)
|> stream_configure(:posts, dom_id: &"post-#{&1.id}")
|> stream(:posts, Blog.list_posts(page: 1, per_page: @per_page))}
end
@impl true
def handle_event("load_more", _params, socket) do
page = socket.assigns.page + 1
new_posts = Blog.list_posts(page: page, per_page: @per_page)
{:noreply,
socket
|> assign(:page, page)
|> then(fn s -> Enum.reduce(new_posts, s, &stream_insert(&2, :posts, &1)) end)}
end<div id="posts" phx-update="stream">
<div :for={{dom_id, post} <- @streams.posts} id={dom_id}>
<%= post.title %>
</div>
</div>
<div phx-hook="InfiniteScroll" data-page={@page}>
<button phx-click="load_more" disabled={@loading}>Load More</button>
</div>Verify after implementing load_more: Trigger the event and confirm in DevTools WS frames that only the new batch of items is appended, not the entire list. If DOM IDs collide across pages, items will overwrite each other — ensure your ID scheme is globally unique.
@impl true
def mount(_params, _session, socket) do
if connected?(socket) do
Phoenix.PubSub.subscribe(MyApp.PubSub, "posts")
end
{:ok,
socket
|> stream_configure(:posts, dom_id: &"post-#{&1.id}")
|> stream(:posts, Blog.list_posts())}
end
@impl true
def handle_info({:post_created, post}, socket) do
{:noreply, stream_insert(socket, :posts, post, at: 0)}
endBroadcast from your context:
def create_post(attrs) do
{:ok, post} = Repo.insert(Post.changeset(%Post{}, attrs))
Phoenix.PubSub.broadcast(MyApp.PubSub, "posts", {:post_created, post})
{:ok, post}
endUse reset: true to clear existing items before inserting new ones — required for filtering and sorting:
# Filtering
def handle_event("filter", %{"status" => status}, socket) do
{:noreply, stream(socket, :posts, Blog.list_posts_by_status(status), reset: true)}
end
# Sorting
def handle_event("sort", %{"column" => col}, socket) do
direction = if socket.assigns.sort_direction == :asc, do: :desc, else: :asc
sorted = Enum.sort_by(socket.assigns.posts, &Map.get(&1, String.to_existing_atom(col)), direction)
{:noreply,
socket
|> assign(:sort_direction, direction)
|> stream(:posts, sorted, reset: true)}
endTrack editing state with a regular assign; use stream_insert to push the updated item back into the stream:
def handle_event("save_edit", %{"id" => id, "post" => params}, socket) do
post = Blog.get_post!(id)
{:ok, updated_post} = Blog.update_post(post, params)
{:noreply,
socket
|> assign(:editing_id, nil)
|> stream_insert(:posts, updated_post)}
end<div id="posts" phx-update="stream">
<div :for={{dom_id, post} <- @streams.posts} id={dom_id}>
<%= if @editing_id == post.id do %>
<.form phx-submit="save_edit">
<input name="post[title]" value={post.title} />
<button type="submit">Save</button>
<button type="button" phx-click="cancel_edit">Cancel</button>
</.form>
<% else %>
<h3><%= post.title %></h3>
<button phx-click="start_edit" phx-value-id={post.id}>Edit</button>
<% end %>
</div>
</div>If stream patching is not working as expected, check for:
phx-update="stream" on the container <div>id={dom_id} on each item elementstream_insert/stream_deletestream_insert without proper dom_id configurationstream_configure before stream when using custom IDs