Build LiveView: async data (assign_async), PubSub (check connected?), phx-change events, form components/modals/uploads, streams for lists, live_patch.
SkillScore95Maintained
Automatic
◆Frontend / UI pack
Last verified 8/19/2026
What it changes
Passed 21 of 22 test cases with the skill, and 17 of 22 without it.
Without skill77%
With skill95%
Checks it passed that the model alone missed
Uses streams rather than regular socket assigns for table items
Uses assign_async or connected? check in mount to prevent unconditional database queries
Uses assign/3 instead of assign_new/3 for assigning lifecycle values like current_user or locale
and 2 more in the full report
5 fixed16 passed either way0 still failing1 broke
Pass-rate lift
+18 pp
Tokens
+26%
Agent turns
0%
Response time
-31%
Model gemini-3.6-flashMeasured 8/19/2026v221 of 22 cases passed with skill
Tokens, turns and time describe resource use, not a dollar price.
See it in action
One of 5 test cases the skill fixed. Same model, same prompt, with and without the skill.
1 of 5
Prompt
A developer on our team submits a Phoenix LiveView form, but when invalid data is entered, no error messages appear under the input fields on screen, and the form appears to do nothing. Before checking CSS, Tailwind classes, or JS event hooks, what is the first troubleshooting step in Elixir LiveView form code?
What was checked, without and with the skill
Checked
Without skill
With skill
Checks if the handle_event return value matches {:error, changeset} and passes the changeset back to the socket form assign
Missed
Passed
case-17 · gemini-3.6-flash · 8/19/2026 · answers are capped and redacted
What's inside
The instructions your model reads when the skill loads.
SKILL.md127 lines7 files
LiveView Patterns Reference
Ash projects: Use ash-framework skill for AshPhoenix.Form. Lifecycle: AshPhoenix.Form.validate/3 on phx-change, AshPhoenix.Form.submit/2 on submit, to_form/1 for HEEx. Do not use Ecto.Changeset.cast/3.
Reference for building with Phoenix LiveView 1.0/1.1.
Iron Laws — Never Violate These
NO UNCONDITIONAL DB QUERIES IN MOUNT — Mount runs TWICE. Default: assign_async. SEO routes: connected? guard + cache-backed disconnected branch (crawlers read that HTML)
ALWAYS USE STREAMS FOR LISTS — Regular assigns = O(n) memory per user. Streams = O(1)
CHECK connected?/1 BEFORE SUBSCRIPTIONS — Prevents double subscriptions
LOAD PRIMARY DATA IN mount/3, PAGINATION IN handle_params/3 — handle_params runs on EVERY URL change
NEVER PASS SOCKET TO BUSINESS LOGIC — Extract data before calling contexts
CHECK CHANGESET ERRORS BEFORE UI DEBUGGING — Silent form save = check {:error, changeset} first, not viewport/JS
HIDDEN INPUTS FOR ALL REQUIRED EMBEDDED FIELDS — Every required field in an embedded schema MUST have a hidden_input if not directly editable
NEVER USE assign_new FOR LIFECYCLE VALUES — assign_new skips the function if key exists. Use assign/3 for locale, current user, or any value refreshed every mount
MATCH {:error, %Ecto.Changeset{}} EXPLICITLY — Bare {:error, _} merges changeset and non-changeset errors; the form silently never re-renders validation errors. Handle other errors separately
Memory Impact
Pattern
3K items
10K users × 10K items
Regular assigns
~5.1 MB
~10+ GB
Streams
~1.1 MB
Minimal (O(1))
Decision: Lists with >100 items → Use streams, not assigns
Quick Patterns
Async Assigns (CRITICAL)
def mount(%{"slug" => slug}, _session, socket) do
# Extract needed values BEFORE the closure
scope = socket.assigns.current_scope
{:ok,
socket
|> assign_async(:org, fn -> {:ok, %{org: fetch_org(scope, slug)}} end)}