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).
91
91%
Does it follow best practices?
Impact
91%
1.37xAverage score across 56 eval scenarios
Advisory
Suggest reviewing before use
assigns[:current_scope] prevents crashes when unauthenticatedanonymous/0 for the unauthenticated case — return a Scope with user: nilscope to context functions, not a bare user — centralizes authorization and enables tenant/permission checksScope.can?/2 — enforce authorization server-side; never rely on hidden UI controlsdefmodule MyApp.Scope do
defstruct [:user, :role, :permissions, :tenant]
def for_user(%MyApp.Accounts.User{} = user) do
%__MODULE__{
user: user,
role: user.role,
permissions: permissions_for(user.role)
}
end
def anonymous, do: %__MODULE__{user: nil}
def authenticated?(%__MODULE__{user: nil}), do: false
def authenticated?(%__MODULE__{}), do: true
def can?(%__MODULE__{permissions: perms}, action) when is_list(perms), do: action in perms
def can?(%__MODULE__{}, _action), do: false
defp permissions_for(:admin), do: [:read, :write, :delete, :manage_users]
defp permissions_for(:editor), do: [:read, :write]
defp permissions_for(_), do: []
enddefmodule MyAppWeb.DashboardLive do
use MyAppWeb, :live_view
@impl true
def mount(_params, _session, socket) do
scope = socket.assigns.current_scope
if Scope.authenticated?(scope) do
{:ok, assign(socket, :posts, Blog.list_user_posts(scope))}
else
{:ok, push_navigate(socket, to: ~p"/login")}
end
end
@impl true
def handle_event("delete", %{"id" => id}, socket) do
scope = socket.assigns.current_scope
if Scope.can?(scope, :delete) do
# perform delete
{:noreply, socket}
else
{:noreply, put_flash(socket, :error, "Not authorized")}
end
end
end<%= if assigns[:current_scope] && Scope.authenticated?(@current_scope) do %>
<p>Welcome, <%= @current_scope.user.email %></p>
<.link href={~p"/settings"}>Settings</.link>
<% else %>
<.link href={~p"/login"}>Log in</.link>
<% end %>MyApp.Scope with for_user/1, anonymous/0, authenticated?/1, and can?/2 as shown above.on_mount hooks — replace user assignment with scope assignment (see before/after below). Run mix test test/my_app_web/live/ --trace and verify all on_mount tests pass before proceeding.@current_user — update all template references to @current_scope.user; use assigns[:current_scope] for optional access. Run mix test and fix any KeyError or FunctionClauseError failures before continuing.scope instead of user to functions that need auth context. Re-run mix test to confirm no regressions.def on_mount(:require_authenticated_user, _params, session, socket) do
case get_user_from_session(session) do
nil -> {:halt, redirect(socket, to: ~p"/login")}
user -> {:cont, assign(socket, :current_user, user)}
end
enddef on_mount(:require_authenticated_user, _params, session, socket) do
scope = get_scope_from_session(session)
if Scope.authenticated?(scope) do
{:cont, assign(socket, :current_scope, scope)}
else
{:halt, redirect(socket, to: ~p"/login")}
end
endCover both code paths. Context tests assert that scope-based filtering returns only the caller's data; LiveView tests assert scope-based access control.
Context test — scope-based filtering (MyApp.DataCase):
defmodule MyApp.BlogTest do
use MyApp.DataCase, async: true
alias MyApp.{Blog, Scope}
test "list_user_posts/1 returns only posts visible to the scope" do
owner = insert(:user)
other = insert(:user)
own_post = insert(:post, user: owner)
_hidden = insert(:post, user: other)
scope = Scope.for_user(owner)
assert Blog.list_user_posts(scope) == [own_post]
end
endLiveView test — scope-based access (Phoenix.LiveViewTest):
defmodule MyAppWeb.DashboardLiveTest do
use MyAppWeb.ConnCase, async: true
import Phoenix.LiveViewTest
test "shows dashboard content for an authenticated scope", %{conn: conn} do
conn = log_in_user(conn, insert(:user))
assert {:ok, _view, html} = live(conn, ~p"/dashboard")
assert html =~ "Welcome"
end
test "redirects to login for an unauthenticated scope", %{conn: conn} do
assert {:error, {:redirect, %{to: "/login"}}} = live(conn, ~p"/dashboard")
end
end| ❌ Don't | ✅ Do |
|---|---|
Read @current_scope directly in a template | Use assigns[:current_scope] so unauthenticated renders don't crash |
| Test only the authenticated path | Cover both authenticated and unauthenticated scopes |
| Trust the UI to hide privileged actions | Authorize every mutation server-side with Scope.can?/2 |
Pass a bare user into context functions | Pass the scope so filtering and permissions stay centralized |
Return nil or crash for anonymous callers | Define anonymous/0 returning a Scope with user: nil |
Assume permissions is always a list | Pattern-match with an is_list/1 guard, as can?/2 does |
| Render a partial page for an unauthenticated scope | Halt in on_mount and redirect to the login route |
| Predecessor | This Skill | Successor |
|---|---|---|
| phoenix-liveview-essentials | phoenix-scopes | phoenix-authorization-patterns |
| phoenix-liveview-auth | phoenix-scopes | testing-essentials |
Companion skills: phoenix-liveview-auth, phoenix-authorization-patterns, phoenix-auth-customization, testing-essentials.
.tessl-plugin
evals
scenario-1
scenario-2
scenario-3
scenario-4
scenario-5
scenario-6
scenario-7
scenario-8
scenario-9
scenario-10
scenario-11
scenario-12
scenario-13
scenario-14
scenario-15
scenario-16
scenario-17
scenario-18
scenario-19
scenario-20
scenario-21
scenario-22
scenario-23
scenario-24
scenario-25
scenario-26
scenario-27
scenario-28
scenario-29
scenario-30
scenario-31
scenario-32
scenario-33
scenario-34
scenario-35
scenario-36
scenario-37
scenario-38
scenario-39
scenario-40
scenario-41
scenario-42
scenario-43
scenario-44
scenario-45
scenario-46
scenario-47
scenario-48
scenario-49
scenario-50
scenario-51
scenario-52
scenario-53
scenario-54
scenario-55
scenario-56
skills
frameworks
ash-framework
infrastructure
orchestration
elixir-skill-router
personas
phoenix
quality
security
security-essentials
tooling
mix-tasks-generators