| name | elixir |
| description | Use this skill for Elixir/Phoenix development in this repo: implementing features, refactors, debugging, tests, Ecto changes, and production-safe fixes. |
Elixir Skill
When to use
Use for any task touching Elixir, Phoenix, Ecto, Mix, OTP, or ExUnit.
Workflow
- Read context first: routes, schema, context modules, and related tests.
- Keep business logic in contexts, not in controllers/channels/live views.
- Prefer small pure functions and explicit pattern matching.
- Prefer bracket access (
map[:key]) for map reads in app code. Do not use Map.get/2. Use Map.get/3 only when an explicit default value is required.
- Add
@spec for every function (def/defp).
- If a
@spec becomes hard to read, introduce named custom types (@type, @opaque) and reuse them in specs.
- Add or update tests with each behavior change.
- Run targeted tests first, then broader suite if needed.
Repo commands
- Format:
mix format
- Compile with warnings:
mix compile --warnings-as-errors
- Run tests:
mix test
- Run specific test file:
mix test path/to/test_file.exs
- Run one test line:
mix test path/to/test_file.exs:123
Ecto and DB
- Keep schema constraints mirrored in changesets (
validate_required, unique_constraint, foreign_key_constraint).
- Prefer explicit preload strategy; avoid hidden N+1 queries.
- For migrations: make them reversible and safe for existing data.
OTP and Concurrency
- Keep GenServer state minimal and explicit.
- Avoid blocking calls in server callbacks.
- Use
Task.Supervisor for isolated async work.
- Add timeouts and backpressure where needed.
Time durations (milliseconds)
Many APIs in this repo take or store durations in milliseconds (Process.send_after/3, :inet.setopts/2 send_timeout, Cachex TTL, Application.get_env/3 defaults, test assert_receive timeouts, etc.).
Prefer :timer over raw integer literals so the unit is obvious:
# Good
@default_interval_ms :timer.seconds(30)
@recv_timeout :timer.seconds(10)
Process.send_after(self(), :tick, :timer.seconds(5))
config :app, ttl_ms: Env.get_integer("TTL_MS", :timer.minutes(5))
# Avoid (unless the value is not a duration, e.g. bytes or a port)
@default_interval_ms 30_000
send_timeout: 10_000
Use :timer.seconds/1, :timer.minutes/1, or :timer.hours/1 as appropriate. :timer returns milliseconds. Existing examples: HydraSrt.Auth (:timer.minutes(5)), HydraSrt.Stats.Cleaner (:timer.hours(1)), RTMP config in config/runtime.exs.
Do not use :timer for non-duration numbers (byte counts, port numbers, numeric limits).
Phoenix
- Controllers should orchestrate, not own domain logic.
- Keep params validation close to boundaries.
- Return consistent error payloads and status codes.
Testing standards
- Cover happy path, validation failures, and edge cases.
- Use factories/builders instead of large inline fixtures.
- Assert behavior and observable side effects, not implementation details.
PR checklist
- Code formatted and compiles without warnings.
- New/changed behavior has tests.
- No obvious N+1 or unbounded process growth.
- Errors are actionable and structured.