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
live "/path" in router).mount/3 contract: which params and session keys are used, what assigns are set.handle_event, handle_info, handle_params) this LiveView must handle.HARD GATE — Contract Defined:
live_isolated for component-level testslive/2 (LiveViewTest) for full-page testsmix test test/my_app_web/live/my_live_test.exs — failure must be confirmed before moving to implementation.Sample test skeleton:
defmodule MyAppWeb.ItemLiveTest do
use MyAppWeb.ConnCase, async: true
import Phoenix.LiveViewTest
test "mounts and renders item list", %{conn: conn} do
{:ok, lv, html} = live(conn, ~p"/items")
assert html =~ "Items"
assert has_element?(lv, "[data-role=item-list]")
end
test "adding an item updates the stream", %{conn: conn} do
{:ok, lv, _html} = live(conn, ~p"/items")
lv |> form("#item-form", item: %{name: "Widget"}) |> render_submit()
assert has_element?(lv, "[data-role=item-list]", "Widget")
end
endHARD GATE — Test Fails:
mix test confirms tests fail (module not yet implemented)mount/3: assign all documented keys, use stream/3 for collections >10 items.handle_event/3 for each listed event; update socket with assign/2 or stream_insert/3.render/1 (or .html.heex template): use bracket access (@items[id]) in templates, not dot access.Minimal LiveView module:
defmodule MyAppWeb.ItemLive do
use MyAppWeb, :live_view
alias MyApp.Catalog
@impl true
def mount(_params, _session, socket) do
{:ok,
socket
|> assign(:page_title, "Items")
|> assign(:form, to_form(Catalog.change_item(%Catalog.Item{})))
|> stream(:items, Catalog.list_items())}
end
@impl true
def handle_event("save", %{"item" => item_params}, socket) do
case Catalog.create_item(item_params) do
{:ok, item} ->
{:noreply, stream_insert(socket, :items, item)}
{:error, changeset} ->
{:noreply, assign(socket, :form, to_form(changeset))}
end
end
@impl true
def render(assigns) do
~H"""
<h1><%= @page_title %></h1>
<.form for={@form} id="item-form" phx-submit="save">
<.input field={@form[:name]} label="Name" />
<.button>Save</.button>
</.form>
<ul id="items" phx-update="stream" data-role="item-list">
<li :for={{dom_id, item} <- @streams.items} id={dom_id}>
<%= item.name %>
</li>
</ul>
"""
end
endHARD GATE — Implementation Complete:
mount/3 sets all documented assigns and streamsphx-update="stream" on stream containersmix test passesRun these checks before considering the LiveView done.
Assigns bloat check — bad vs. good:
# ❌ Assigns bloat — storing a full list
assign(socket, :items, Catalog.list_items()) # grows unbounded
# ✅ Correct — use a stream for collections
stream(socket, :items, Catalog.list_items()) # server memory stays flatBracket access in templates — bad vs. good:
<%# ❌ Dot access fails when key may be absent %>
<%= @user.name %>
<%# ✅ Bracket access is safe %>
<%= @user[:name] %>Stream usage threshold: any collection that may exceed 10 items must use stream/3 + phx-update="stream".
Quality gate checklist:
assigns[:key]) for optional or stream-derived keysrender/1 — all data preparation done in mount/3 or event handlershandle_event clauses return {:noreply, socket} (or {:reply, map, socket}) — no bare socket returnsmix test one final time; all tests greenHARD GATE — Quality Gate Passes: