{
  "id": "elixir-phoenix-liveview",
  "name": "Elixir: Phoenix + LiveView + Ecto/PostgreSQL, with Oban when needed",
  "use_cases": [
    "Connected browser products and portals",
    "Real-time collaboration interfaces",
    "JSON APIs for native clients",
    "Durable application background work"
  ],
  "choose_when": [
    "Server-owned state and business rules dominate a connected product.",
    "Phoenix conventions and OTP process lifecycles match the service requirements.",
    "A relational store can also support durable background jobs."
  ],
  "avoid_when": [
    "The main deliverable is a platform-native mobile UI or offline-first client.",
    "The application depends on a library, accelerator or vendor SDK unsupported by the intended Elixir deployment.",
    "A small static site, simple script or CLI does not need a running web application."
  ],
  "tools": [
    {
      "job": "Web application",
      "default": "Phoenix + its generated HTTP/asset defaults",
      "alternatives": [
        "Phoenix API-only",
        "Plain supervised Mix application for non-web services"
      ],
      "reason": "Coherent routing, contexts and generated application structure.",
      "limits": "Phoenix is unnecessary for a CLI or a service without HTTP; preserve existing architecture.",
      "source_urls": [
        "https://hexdocs.pm/phoenix/Mix.Tasks.Phx.New.html",
        "https://hexdocs.pm/phoenix/json_and_apis.html"
      ]
    },
    {
      "job": "Dependency documentation and agent guidance",
      "default": "usage_rules for selected dependency guidance",
      "alternatives": [
        "Installed-version HexDocs and maintained repository guidance"
      ],
      "reason": "Discover dependency documentation and synchronize selected upstream usage rules for agents",
      "limits": "Development-only tool; inspect installed-version options and review generated content. Preserve Phoenix-generated guidance by default. An explicit README-only repository may configure file: \"docs/dependency-rules.md\" in mix.exs and link it from README; this destination is an optional project convention, not an upstream default.",
      "source_urls": [
        "https://hexdocs.pm/usage_rules/readme.html",
        "https://hexdocs.pm/usage_rules/Mix.Tasks.UsageRules.Sync.html"
      ]
    },
    {
      "job": "Browser UI",
      "default": "LiveView + HEEx components",
      "alternatives": [
        "Server-rendered Phoenix HTML",
        "JavaScript hooks for isolated client behavior",
        "Separate TypeScript browser application over Phoenix APIs"
      ],
      "reason": "Shared server-side business operations and interactive screens suit connected forms, portals and collaboration.",
      "limits": "Offline-first state, high-frequency local interactions and rich canvas editors can justify a dedicated client. LiveView does not remove HTML/CSS or all JavaScript.",
      "source_urls": [
        "https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html",
        "https://hexdocs.pm/phoenix_live_view/js-interop.html",
        "https://hexdocs.pm/phoenix/json_and_apis.html"
      ]
    },
    {
      "job": "Shared relational persistence",
      "default": "Ecto + PostgreSQL",
      "alternatives": [
        "Another Ecto adapter when an actual persistence requirement warrants it"
      ],
      "reason": "Explicit queries, changesets and database transactions fit shared application data.",
      "limits": "Changeset validation is not a replacement for database constraints or authorization; database operations and migrations need operational ownership. Current Ecto documentation deprecates Repo.transaction/2 in favor of Repo.transact/2. Follow the installed-version API and callback/result semantics; do not mechanically rename an existing transaction without tests.",
      "source_urls": [
        "https://hexdocs.pm/ecto/Ecto.html",
        "https://hexdocs.pm/ecto/Ecto.Repo.html#c:transact/2"
      ]
    },
    {
      "job": "Accounts and access control",
      "default": "phx.gen.auth + scoped context authorization",
      "alternatives": [
        "External identity integration for SSO requirements"
      ],
      "reason": "Generated first-party authentication keeps application-owned code; contexts authorize operations.",
      "limits": "Check permission both on mount/navigation and each operation. Test revoked access and reconnect behavior; magic-link delivery needs a configured mail provider.",
      "source_urls": [
        "https://hexdocs.pm/phoenix/mix_phx_gen_auth.html",
        "https://hexdocs.pm/phoenix_live_view/security-model.html"
      ]
    },
    {
      "job": "Durable background work",
      "default": "Oban with the existing Ecto/PostgreSQL database",
      "alternatives": [
        "Another supported Oban SQL engine after engine-specific qualification; retain the existing datastore when it meets requirements"
      ],
      "reason": "Persist jobs and enqueue alongside domain changes in one transaction.",
      "limits": "Task is only for explicitly disposable work and does not satisfy crash-durable delivery. Oban engine support and guarantees must be checked against the selected version; Redis is not a shipped Oban persistence engine. Oban Pro is an optional licensed extension, not a replacement for persistence.",
      "source_urls": [
        "https://hexdocs.pm/oban/Oban.html",
        "https://hexdocs.pm/oban/testing.html"
      ]
    },
    {
      "job": "Behavior and persistence tests",
      "default": "ExUnit + Ecto SQL Sandbox + LiveViewTest",
      "alternatives": [
        "Oban.Testing when jobs exist"
      ],
      "reason": "Test contexts, data invariants, denied access and server-side UI behavior.",
      "limits": "LiveViewTest does not establish browser JavaScript, accessibility or visual correctness. Inline/manual job tests are not a production recovery simulation.",
      "source_urls": [
        "https://hexdocs.pm/ex_unit/ExUnit.html",
        "https://hexdocs.pm/ecto_sql/Ecto.Adapters.SQL.Sandbox.html",
        "https://hexdocs.pm/phoenix_live_view/Phoenix.LiveViewTest.html",
        "https://hexdocs.pm/oban/testing.html"
      ]
    },
    {
      "job": "Real-browser checks",
      "default": "PhoenixTest.Playwright for critical browser flows",
      "alternatives": [
        "A separately maintained Playwright suite"
      ],
      "reason": "Exercise hooks, navigation, reconnects and actual browser behavior omitted by server-side tests.",
      "limits": "Adds Node/package-manager/browser binaries and sandbox configuration; verify supported wrapper features. It is an editorial extension, not a Phoenix generator default.",
      "source_urls": [
        "https://hexdocs.pm/phoenix_test_playwright/readme.html",
        "https://hexdocs.pm/phoenix_live_view/Phoenix.LiveViewTest.html"
      ]
    },
    {
      "job": "Code quality",
      "default": "Mix formatter and compiler warnings; Credo when selected",
      "alternatives": [
        "Dialyxir for additional success-typing analysis"
      ],
      "reason": "Fast local feedback plus consistency checks; select checks appropriate to project size.",
      "limits": "Static analysis does not prove domain correctness. Dialyxir adds analysis/PLT maintenance and is not a mandatory starter dependency.",
      "source_urls": [
        "https://hexdocs.pm/credo/overview.html",
        "https://hexdocs.pm/dialyxir/readme.html"
      ]
    },
    {
      "job": "Security checks",
      "default": "Hex audit + Sobelow, alongside permission tests",
      "alternatives": [],
      "reason": "Hex reports retired packages and known advisories; Sobelow finds common risky code patterns.",
      "limits": "Use the installed Hex version’s behavior. Scan findings need review; configure a non-zero Sobelow exit threshold and do not blanket-suppress findings. Neither proves the application secure.",
      "source_urls": [
        "https://hexdocs.pm/hex/Mix.Tasks.Hex.Audit.html",
        "https://hexdocs.pm/sobelow/readme.html",
        "https://hexdocs.pm/phoenix_live_view/security-model.html"
      ]
    },
    {
      "job": "Deployment",
      "default": "Mix release + runtime configuration",
      "alternatives": [
        "Containerized release when the host benefits"
      ],
      "reason": "Package application and runtime with explicit migration/server entry points.",
      "limits": "Build for the target OS/architecture and native libraries. Host choice, secrets, migrations, TLS, backups, restore and rollback remain separate responsibilities.",
      "source_urls": [
        "https://hexdocs.pm/phoenix/releases.html"
      ]
    },
    {
      "job": "Native mobile client boundary",
      "default": "Platform-native clients consuming Phoenix APIs",
      "alternatives": [
        "Evaluate LiveView Native separately for an explicitly suitable connected client"
      ],
      "reason": "Preserve native UI/platform ownership while sharing server-side domain operations.",
      "limits": "Do not advertise Elixir as a complete native mobile default: LiveView Native’s inspected README uses release-candidate dependencies and platform-specific clients. Validate target support, offline behavior, accessibility and signing independently.",
      "source_urls": [
        "https://hexdocs.pm/phoenix/json_and_apis.html",
        "https://github.com/liveview-native/live_view_native"
      ]
    },
    {
      "job": "Project instructions",
      "default": "README navigation + generated Phoenix agent guidance",
      "alternatives": [
        "Preserve an existing compatible project choice"
      ],
      "reason": "Keep generated stack conventions available to coding agents",
      "limits": "Preserve maintained repository rules; --no-agents-md is an optional project choice, not the Phoenix default.",
      "source_urls": [
        "https://hexdocs.pm/phoenix/Mix.Tasks.Phx.New.html"
      ]
    },
    {
      "job": "Transactional email",
      "default": "Swoosh + selected provider adapter",
      "alternatives": [
        "Preserve an existing compatible project choice"
      ],
      "reason": "Mailer adapters and test delivery fit generated Phoenix account flows",
      "limits": "Configure adapter/client dependencies and sender credentials; keep test/local adapters in development only. Use Oban for required queued delivery; a Task has no durable retry guarantee.",
      "source_urls": [
        "https://hexdocs.pm/swoosh/Swoosh.html"
      ]
    },
    {
      "job": "Uploads",
      "default": "LiveView uploads",
      "alternatives": [
        "Preserve an existing compatible project choice"
      ],
      "reason": "Built-in upload configuration and consumption fit connected forms",
      "limits": "Configure allow_upload limits and authorize attachment association; persist consumed files before temp cleanup. External storage integration and content validation remain application work.",
      "source_urls": [
        "https://hexdocs.pm/phoenix_live_view/uploads.html"
      ]
    },
    {
      "job": "Realtime application events",
      "default": "Phoenix.PubSub",
      "alternatives": [
        "Preserve an existing compatible project choice"
      ],
      "reason": "Local/distributed publication updates connected views and channels",
      "limits": "Supervise PubSub and subscribe to scoped topics; authorize each subscription/action and test reconnect. PubSub is transient broadcast, not durable replay or a message queue.",
      "source_urls": [
        "https://hexdocs.pm/phoenix_pubsub/Phoenix.PubSub.html"
      ]
    }
  ],
  "setup_steps": [
    "Read README.md, mix.exs, mix.lock and runtime pins; preserve existing applications. This is a recipe, not executed setup evidence.",
    "For a new project, choose an empty source directory in a local development workspace. Check elixir --version, mix --version, mix phx.new --version and actual PostgreSQL connectivity; install only missing prerequisites using official instructions.",
    "Resolve and record a compatible Elixir/OTP pair and Phoenix generator. Generate with mix phx.new my_app --database postgres; honor generated guidance. Use --no-agents-md only when the repository explicitly adopts a README-only instruction policy.",
    "If dependency guidance synchronization is useful, add usage_rules as a development dependency, configure selected rules in mix.exs and review mix usage_rules.sync output. Honor generated Phoenix guidance; configure an alternate file only for an explicit repository policy.",
    "Enter my_app; configure development database credentials outside committed secrets; inspect and run mix setup. For API-only creation use the documented --no-html --no-assets options instead.",
    "If accounts are required, run mix phx.gen.auth Accounts User users --live, then mix deps.get and mix ecto.migrate; review generated instructions and email setup.",
    "If jobs are required, follow the installed Oban installation/migration instructions, supervise Oban and configure bounded queues; write idempotent workers and tests.",
    "Add selected development/test checks only. For PhoenixTest.Playwright install its JS dependency and matching browsers, configure test endpoint and SQL sandbox as documented.",
    "Run the checks below and actual browser flows. Fix failures in place, retaining logs; never regenerate over work to hide a failure.",
    "For deployment qualification, follow Phoenix releases: generate release helpers, compile production assets and build a target-compatible release. Rehearse migration, startup, reconnect, job retry and backup/restore on the selected host before claiming production readiness.",
    "Completion: record exact versions, lockfile, checks and observed flows in README-linked evidence; report untested deployment/mobile paths explicitly."
  ],
  "verification_commands": [
    "mix hex.audit",
    "mix format --check-formatted",
    "mix compile --warnings-as-errors",
    "mix test",
    "mix credo --strict  # only after installing/configuring Credo",
    "mix sobelow --strict --exit low  # only after installing/configuring Sobelow; review findings",
    "mix dialyzer  # optional, after Dialyxir/PLT configuration",
    "mix phx.server  # interactive smoke check, not an automated assertion",
    "MIX_ENV=prod mix assets.deploy  # release qualification, after production prerequisites",
    "MIX_ENV=prod mix release  # after release configuration; not deployment proof"
  ],
  "evidence_status": "documentation-reviewed",
  "sources": [
    {
      "url": "https://hexdocs.pm/phoenix/Mix.Tasks.Phx.New.html",
      "title": "Phoenix application generator",
      "checked_date": "2026-10-10"
    },
    {
      "url": "https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html",
      "title": "Phoenix LiveView",
      "checked_date": "2026-10-10"
    },
    {
      "url": "https://hexdocs.pm/phoenix_live_view/js-interop.html",
      "title": "LiveView JavaScript interoperability",
      "checked_date": "2026-10-10"
    },
    {
      "url": "https://hexdocs.pm/ecto/Ecto.html",
      "title": "Ecto",
      "checked_date": "2026-10-10"
    },
    {
      "url": "https://hexdocs.pm/phoenix/mix_phx_gen_auth.html",
      "title": "Phoenix authentication generator",
      "checked_date": "2026-10-10"
    },
    {
      "url": "https://hexdocs.pm/phoenix_live_view/security-model.html",
      "title": "LiveView security model",
      "checked_date": "2026-10-10"
    },
    {
      "url": "https://hexdocs.pm/oban/Oban.html",
      "title": "Oban",
      "checked_date": "2026-10-10"
    },
    {
      "url": "https://hexdocs.pm/oban/testing.html",
      "title": "Oban testing",
      "checked_date": "2026-10-10"
    },
    {
      "url": "https://hexdocs.pm/ex_unit/ExUnit.html",
      "title": "ExUnit",
      "checked_date": "2026-10-10"
    },
    {
      "url": "https://hexdocs.pm/phoenix_live_view/Phoenix.LiveViewTest.html",
      "title": "LiveViewTest",
      "checked_date": "2026-10-10"
    },
    {
      "url": "https://hexdocs.pm/ecto_sql/Ecto.Adapters.SQL.Sandbox.html",
      "title": "Ecto SQL Sandbox",
      "checked_date": "2026-10-10"
    },
    {
      "url": "https://hexdocs.pm/phoenix_test_playwright/readme.html",
      "title": "PhoenixTest.Playwright",
      "checked_date": "2026-10-10"
    },
    {
      "url": "https://hexdocs.pm/credo/overview.html",
      "title": "Credo",
      "checked_date": "2026-10-10"
    },
    {
      "url": "https://hexdocs.pm/dialyxir/readme.html",
      "title": "Dialyxir",
      "checked_date": "2026-10-10"
    },
    {
      "url": "https://hexdocs.pm/sobelow/readme.html",
      "title": "Sobelow",
      "checked_date": "2026-10-10"
    },
    {
      "url": "https://hexdocs.pm/hex/Mix.Tasks.Hex.Audit.html",
      "title": "Hex dependency audit",
      "checked_date": "2026-10-10"
    },
    {
      "url": "https://hexdocs.pm/phoenix/releases.html",
      "title": "Phoenix releases",
      "checked_date": "2026-10-10"
    },
    {
      "url": "https://hexdocs.pm/phoenix/json_and_apis.html",
      "title": "Phoenix JSON and APIs",
      "checked_date": "2026-10-10"
    },
    {
      "url": "https://github.com/liveview-native/live_view_native",
      "title": "LiveView Native source README",
      "checked_date": "2026-10-10"
    },
    {
      "url": "https://hexdocs.pm/swoosh/Swoosh.html",
      "title": "Swoosh",
      "checked_date": "2026-10-10"
    },
    {
      "url": "https://hexdocs.pm/phoenix_live_view/uploads.html",
      "title": "LiveView uploads",
      "checked_date": "2026-10-10"
    },
    {
      "url": "https://hexdocs.pm/phoenix_pubsub/Phoenix.PubSub.html",
      "title": "Phoenix PubSub",
      "checked_date": "2026-10-10"
    },
    {
      "url": "https://hexdocs.pm/usage_rules/readme.html",
      "title": "usage_rules README",
      "checked_date": "2026-10-10"
    },
    {
      "url": "https://hexdocs.pm/usage_rules/Mix.Tasks.UsageRules.Sync.html",
      "title": "usage_rules sync task",
      "checked_date": "2026-10-10"
    },
    {
      "url": "https://hexdocs.pm/ecto/Ecto.Repo.html#c:transact/2",
      "title": "Ecto current transaction API and semantics",
      "checked_date": "2026-10-13"
    }
  ],
  "open_questions": [
    "Does the product fit connected server-owned UI, or need a separate offline/native client?",
    "Which account/tenant permissions and email recovery requirements apply?",
    "Which work needs durable retries and duplicate-effect protection?",
    "Which host, object storage, observability and backup/restore requirements apply?"
  ],
  "title": "Phoenix + LiveView",
  "language": "Elixir",
  "group": "Web applications",
  "kind": [
    "web",
    "api"
  ],
  "ui": "server",
  "host": "server",
  "summary": "Connected products, live updates and background work in one application.",
  "shape": "Phoenix · LiveView · Ecto · PostgreSQL",
  "tradeoff": "A persistent server owns the UI state. Use a separate client for substantial offline work.",
  "research_file": "elixir.json",
  "reviewed": "2026-10-10",
  "best_practices": [
    "Keep business operations in contexts shared by controllers, LiveViews and workers. Contexts are not automatically processes.",
    "Enforce authorization on every protected operation, including direct context calls and after access is revoked.",
    "Use ordinary functions first. Add processes for state ownership or lifecycle; add durable storage for work that must survive a restart.",
    "Give retried external effects stable idempotency keys. Exercise reconnects, failed jobs and cross-account access."
  ],
  "research_freshness": {
    "core_guidance_checked_on": "2026-10-13",
    "sources": [
      "https://phoenix.hexdocs.pm/overview.html",
      "https://phoenix.hexdocs.pm/installation.html",
      "https://hexdocs.pm/ecto/Ecto.Repo.html#c:transact/2"
    ],
    "scope": "Core framework roles, current setup/agent documentation and cited-source availability rechecked; not a reimplementation or exhaustive semantic audit of every third-party dependency."
  },
  "guide_url": "/stacks/elixir-phoenix-liveview/",
  "markdown_url": "/stacks/elixir-phoenix-liveview/guide.md",
  "json_url": "/stacks/elixir-phoenix-liveview/record.json",
  "architecture_and_migration": {
    "id": "elixir-phoenix-liveview",
    "version_scope": "Fetched documentation: Phoenix 1.8.15, LiveView 1.2.12, PubSub 2.4.1, Plug 1.20.3, Ecto 3.14.2 / Ecto SQL 3.14.0, Oban 2.24.1, Exq 0.25.0 and OTP 29.1.1 ETS. Installed dependency compatibility is unqualified; pin the actual application before using version-specific APIs.",
    "operating_model": "Phoenix web modules call application contexts; Ecto supplies persistence boundaries. LiveView maintains connected server-side interaction, while Channels expose a client-facing realtime protocol. Recommendation: model domain operations through contexts and supervised responsibilities rather than translating every old controller, global singleton or database serialization bottleneck literally.",
    "architecture": [
      "Recommendation: contexts own business operations, Ecto Repo owns database access and explicit transactions join related writes. A single GenServer around all queries is an application choice, not a Phoenix requirement.",
      "Phoenix PubSub has a distributed-Elixir adapter and a Redis adapter. Redis can remain appropriate when deployment or interoperability requires it. Recommendation: identify the required delivery boundary before selecting the topology.",
      "Oban requires an Ecto-backed supported SQL engine. Its installation documents PostgreSQL, SQLite and MySQL; selecting PostgreSQL is this catalogue's target composition, not a universal Oban prerequisite. Recommendation: preserve durable external effects through idempotency and recovery checks."
    ],
    "developer_workflow": [
      "For a disposable target, mix phx.new PATH generates a Phoenix application with selectable database and HTML/LiveView options. In an existing project, inspect mix.lock, generated modules and maintained conventions before regenerating anything.",
      "When manually installing the selected Oban version, adapt mix ecto.gen.migration add_oban_jobs_table and mix ecto.migrate from its installation guide; configure the engine, Repo and supervised Oban child. Do not copy PostgreSQL settings into a SQLite or MySQL target.",
      "Use mix test for application tests and the generated Phoenix connection/channel fixtures. Recommendation: add mixed-version payload, authorization and restart scenarios; a passing formatter or compiler does not establish those contracts.",
      "For a release build, adapt MIX_ENV=prod mix assets.deploy and mix release from the Phoenix deployment workflow; runtime.exs owns runtime configuration. Recommendation: verify database migrations, job supervision and cluster/PubSub connectivity separately from HTTP startup."
    ],
    "avoid": [
      "Avoid replacing a durable queue with spawned tasks or an in-memory notification list while calling the change equivalent. Recommendation: explicitly accept the changed crash and retry behavior or retain a persistent queue.",
      "Avoid treating Exq's Resque/Sidekiq-compatible transport format as proof that Ruby Active Job wrappers, classes and serializers execute in Elixir. The exact consumer and payload contract still needs qualification.",
      "Avoid assuming Plug cookies decode Rails cookies: Plug exposes its own serializer, salts and cryptographic options. Avoid relying only on HTTP plugs for LiveView authorization; mount and events require checks.",
      "Avoid using ETS as a shared session or global lock store. Tables are local to their node and normally disappear with their owner unless an heir takes ownership. Recommendation: reserve ETS for explicitly reconstructible local state."
    ],
    "transitions": [
      {
        "capability": "data",
        "existing_contract": "Inventory schema, constraints, record identifiers and callbacks previously triggered by writes. Ecto schemas and changesets must preserve the intended domain semantics, not simply the old table shapes.",
        "coexistence": "Recommendation: keep the source application as the authoritative writer while Phoenix contexts read fixtures; move a tenant or domain operation with one named writer. Use additive schema changes and a single migration runner during shared-table operation.",
        "target": "Contexts calling Ecto Repo with explicit validation and transaction boundaries. Ecto.Multi composes related Repo operations in one database transaction; cross-store side effects require a separate delivery design.",
        "benefits": [
          {
            "benefit": "Recommendation: a context boundary can expose one reusable domain operation to LiveView, controllers and workers.",
            "prerequisites": "Resource authorization, constraints and side effects moved with that operation.",
            "tradeoffs": "Shared-schema coexistence limits schema changes; external effects do not become part of a database transaction."
          }
        ],
        "checks": [
          "Write a fixture through each allowed writer and read it from both runtimes; compare IDs, enum/null values, timestamps and constraint failures.",
          "Fail the second operation in an Ecto.Multi and verify the first database operation is absent; prove old readers still accept successful new writes."
        ],
        "rollback": "Recommendation: return the domain writer to the source while preserving additive schema and reconciling accepted new writes; delay destructive contractions until the rollback window closes.",
        "sources": [
          "https://hexdocs.pm/phoenix/contexts.html",
          "https://hexdocs.pm/ecto/Ecto.Multi.html",
          "https://hexdocs.pm/ecto_sql/Ecto.Migrator.html"
        ],
        "id": "data-1"
      },
      {
        "capability": "jobs",
        "existing_contract": "Rails workers currently own Ruby job execution, including Active Job/GlobalID serializers and backend retry/scheduling behavior. Record the actual Redis-backed backend and every backlog state rather than inferring it from the word Rails.",
        "coexistence": "Recommendation, retain/wrap path: keep Ruby workers and their broker; expose a narrow producer endpoint or durable outbox relay for Phoenix. Ruby remains the only consumer and scheduler for retained categories, and the interface names payload version, business idempotency key and enqueue acknowledgement.",
        "target": "Phoenix web/domain code with retained Ruby worker ownership is a valid permanent composition when queue interoperability or Ruby libraries remain necessary.",
        "benefits": [
          {
            "benefit": "Recommendation: the web layer can transfer before job execution without transforming the old backlog.",
            "prerequisites": "Defined producer interface, monitored delivery and retained worker deployment.",
            "tradeoffs": "Two runtimes and the broker remain operated; domain write plus broker enqueue is not a shared SQL transaction."
          }
        ],
        "checks": [
          "Submit a captured serializer/GlobalID case through the interface and confirm the intended Ruby class executes with its normal failure policy.",
          "Interrupt delivery after domain commit; recover it without duplicating a business effect. Verify only the Ruby scheduler owns the retained category."
        ],
        "rollback": "Recommendation: restore the old web producer while retaining the same workers; reconcile accepted interface/outbox entries before disabling the relay.",
        "sources": [
          "https://guides.rubyonrails.org/active_job_basics.html",
          "https://github.com/sidekiq/sidekiq/wiki/Job-Format",
          "https://github.com/sidekiq/sidekiq/wiki/Best-Practices"
        ],
        "id": "jobs-1",
        "option_title": "Retain Ruby workers behind an interface"
      },
      {
        "capability": "jobs",
        "existing_contract": "A retained Redis broker may carry Sidekiq/Resque-shaped jobs; framework wrappers, class names, serializer tags and version-sensitive timestamps are still part of the execution contract.",
        "coexistence": "Recommendation, broker-retaining replacement path: qualify Exq for selected job types on isolated queues. Keep unsupported Ruby types on Ruby consumers; map only deliberately supported payload versions, and give each category one consumer family and schedule owner.",
        "target": "Selected Elixir workers using Exq's documented Redis-backed Resque/Sidekiq-compatible format. This is a candidate for qualification, not a claim that arbitrary Active Job payloads are interoperable.",
        "benefits": [
          {
            "benefit": "Recommendation: selected job execution can move to Elixir while preserving the broker boundary.",
            "prerequisites": "Fixture-tested serialization, queue isolation, retry mapping and scheduler ownership.",
            "tradeoffs": "Redis remains; compatibility and unsupported Ruby argument types require explicit maintenance."
          }
        ],
        "checks": [
          "Replay ordinary arguments, an Active Job wrapper, serializer edge cases and scheduled/retry payloads; reject unsupported cases without consuming them silently.",
          "Crash after a business effect before acknowledgement and verify replay safety; confirm old and new consumers never race for an unqualified category."
        ],
        "rollback": "Recommendation: stop new consumption and return only backward-readable queued work to Ruby; preserve both consumers for payloads accepted during the transition.",
        "sources": [
          "https://exq.hexdocs.pm/readme.html",
          "https://github.com/sidekiq/sidekiq/wiki/Job-Format",
          "https://guides.rubyonrails.org/active_job_basics.html"
        ],
        "id": "jobs-2",
        "option_title": "Keep Redis and qualify Exq consumers"
      },
      {
        "capability": "jobs",
        "existing_contract": "Legacy Redis jobs and a proposed SQL queue have distinct payload/storage formats and retry/schedule state. A backend change does not copy the backlog or the recurring schedule.",
        "coexistence": "Recommendation, SQL replacement path: run Oban alongside the legacy broker and route selected categories exclusively to one system. Keep old ready, retry, delayed, failed and running work accounted for. Transfer each schedule once; use a recoverable relay/outbox while domain data and queue insertion span stores.",
        "target": "Oban workers on the chosen supported SQL engine. With the same Repo/transaction, Oban.insert in Ecto.Multi can join domain changes; a separate database cannot gain that atomicity merely from both databases being SQL.",
        "benefits": [
          {
            "benefit": "Atomic domain-write-and-job-insert is available within a shared transaction.",
            "prerequisites": "One supported database transaction/Repo for both writes and business idempotency for execution.",
            "tradeoffs": "Queue load shares the database; SQL atomic insertion does not guarantee exactly-once external effects."
          },
          {
            "benefit": "Recommendation: broker retirement can reduce the operated service inventory.",
            "prerequisites": "All Redis uses transferred, all backlog states reconciled and recovery/rollback checks passed.",
            "tradeoffs": "Redis plus SQL coexistence temporarily adds operational work; no service reduction occurs while Redis remains needed."
          }
        ],
        "checks": [
          "Rollback the combined transaction and confirm neither domain change nor executable Oban job remains.",
          "Pause a selected Oban queue, reconcile running work and inspect retained backlog; resume without duplicated effects.",
          "Restart during a job and validate the selected engine's configured recovery policy, including a failure after an external side effect."
        ],
        "rollback": "Recommendation: pause new producers/consumption, keep accepted Oban payloads executable, and reconcile effects before moving routing back. Do not blindly copy all rows into the old broker.",
        "sources": [
          "https://oban.hexdocs.pm/installation.html",
          "https://oban.hexdocs.pm/Oban.html",
          "https://hexdocs.pm/ecto/Ecto.Multi.html"
        ],
        "id": "jobs-3",
        "option_title": "Run Oban SQL workers alongside legacy queues"
      },
      {
        "capability": "identity",
        "existing_contract": "Record old cookie encoding, serializer, keys/salts, expiration and session revocation; shared account IDs alone do not establish cookie compatibility. Plug COOKIE defaults its serializer to external_term_format and exposes rotating cryptographic options.",
        "coexistence": "Recommendation: retain the existing identity authority through a qualified session exchange or compatibility decoder. Map authorization rules into Phoenix contexts and LiveView mount/events; keep account and revocation ownership explicit.",
        "target": "Phoenix identity with the selected session store and LiveView authorization in mount/on_mount and actions. Recommendation: choose planned reauthentication when cookie compatibility is not worth maintaining.",
        "benefits": [
          {
            "benefit": "Recommendation: users can preserve access while a connected feature moves.",
            "prerequisites": "Qualified decoder/exchange, shared revocation and event-level authorization.",
            "tradeoffs": "Long-lived sockets need explicit disconnect/recheck behavior when access changes; sharing secrets alone is insufficient."
          }
        ],
        "checks": [
          "Login through the old runtime, access the target feature, revoke the account/session and confirm HTTP plus existing/reconnected LiveView access follows the required policy.",
          "Send a forged delete event for another tenant despite a hidden UI control; confirm the server rejects it."
        ],
        "rollback": "Recommendation: retain the old decoder/exchange until routing can return; never reverse revocation merely to restore traffic.",
        "sources": [
          "https://hexdocs.pm/plug/Plug.Session.COOKIE.html",
          "https://hexdocs.pm/phoenix_live_view/security-model.html",
          "https://guides.rubyonrails.org/security.html"
        ],
        "id": "identity-1"
      },
      {
        "capability": "realtime",
        "existing_contract": "Action Cable, Phoenix Channels and LiveView are different protocols. Capture subscription identity, event envelope, authorization, ordering and missed-message recovery. Channels documents at-most-once client delivery without server persistence.",
        "coexistence": "Recommendation: retain old client endpoints and bridge versioned domain events into each renderer. A room/tenant cohort owns its subscriptions and reconnects. Choose Redis PubSub or a qualified distributed-Elixir cluster deliberately; sharing Redis does not translate the wire protocol.",
        "target": "LiveView for connected server-owned screens or Channels for explicit realtime clients, with durable domain state and an application catch-up mechanism when missed events matter.",
        "benefits": [
          {
            "benefit": "Recommendation: connected UI and domain operations can share Phoenix contexts.",
            "prerequisites": "Authorized subscriptions, working multi-node fanout and persisted state/cursors for recovery requirements.",
            "tradeoffs": "Live protocol/client changes and cluster operations remain; PubSub does not supply a durable user-message history."
          }
        ],
        "checks": [
          "Place old/new subscribers on separate nodes; commit through either permitted path and verify every authorized recipient converges once.",
          "Disconnect before an event, restart a node, reconnect with the chosen cursor/state mechanism and verify missed domain changes recover; test revocation while connected."
        ],
        "rollback": "Recommendation: preserve the old endpoint/event envelope and cohort routing through reconnects; rebuild UI from persisted state instead of duplicating broadcasts.",
        "sources": [
          "https://hexdocs.pm/phoenix/channels.html",
          "https://hexdocs.pm/phoenix_pubsub/Phoenix.PubSub.html",
          "https://hexdocs.pm/phoenix_live_view/security-model.html"
        ],
        "id": "realtime-1"
      },
      {
        "capability": "cache",
        "existing_contract": "Classify every Redis use as cache, session, limiter, lock, job or PubSub. ETS tables are node-local and their ownership lifetime matters; they do not provide Redis's cross-node boundary.",
        "coexistence": "Recommendation: move only reconstructible cache entries into supervised local ownership, with explicit TTL/invalidation and bounded memory. Keep shared identity, coordination, queues and broadcast infrastructure until their own transitions qualify.",
        "target": "ETS for deliberately local reconstructed values, or a retained shared cache where consistency across nodes is required. Recommendation: state stale-read tolerance and rebuilding behavior in the application contract.",
        "benefits": [
          {
            "benefit": "Recommendation: a local cache can remove a remote dependency from selected reads.",
            "prerequisites": "Acceptable node-local semantics, bounded memory and verified invalidation/rebuild behavior.",
            "tradeoffs": "Each node maintains separate contents; owner/node failure loses local state. Performance benefit requires measurement."
          }
        ],
        "checks": [
          "Warm two nodes, update the authoritative record and verify their documented staleness/invalidation policy.",
          "Terminate the table owner or restart the node; verify the application rebuilds and no durable job/session/lock state was stored there."
        ],
        "rollback": "Recommendation: switch those reads back to the qualified shared store or authoritative data; retain Redis until every remaining capability is reconciled.",
        "sources": [
          "https://www.erlang.org/doc/apps/stdlib/ets.html",
          "https://hexdocs.pm/phoenix_pubsub/Phoenix.PubSub.html"
        ],
        "id": "cache-1"
      },
      {
        "capability": "operations",
        "existing_contract": "Record endpoint, worker, scheduler, Repo, assets and cluster/PubSub ownership. Phoenix runtime configuration, release build and schema migrations have distinct deployment steps.",
        "coexistence": "Recommendation: maintain both releases with stable cohort routing and a single schedule/writer owner per responsibility. Monitor queue age, retry effects, node connectivity and authorization failures against application-specific promotion thresholds.",
        "target": "A Phoenix release with runtime.exs configuration, built assets, named migration runner and supervised workers. Recommendation: make cluster formation, backups and restore ordering explicit instead of assuming VM startup establishes them.",
        "benefits": [
          {
            "benefit": "Recommendation: explicit supervised roles and release inputs make restart behavior reviewable.",
            "prerequisites": "Configured children, deployment inventory and exercised recovery procedures.",
            "tradeoffs": "Durable recovery still depends on the chosen databases/queues; supervision alone cannot recreate lost external state."
          }
        ],
        "checks": [
          "Start the actual release with a disposable restored database and confirm assets, authenticated routes, a queued job and cross-node broadcast work.",
          "Stop a worker/node, observe detection and recovery, and verify no second schedule owner activates during rollback."
        ],
        "rollback": "Recommendation: retain the previous release and compatible database reads; return cohort routing and role ownership separately, reconciling new jobs and external effects.",
        "sources": [
          "https://hexdocs.pm/phoenix/deployment.html",
          "https://oban.hexdocs.pm/installation.html",
          "https://hexdocs.pm/phoenix_pubsub/Phoenix.PubSub.html"
        ],
        "id": "operations-1"
      }
    ],
    "sources": [
      {
        "url": "https://hexdocs.pm/phoenix/contexts.html",
        "title": "Phoenix Intro to Contexts",
        "claim": "Contexts separate web features from application operations."
      },
      {
        "url": "https://hexdocs.pm/phoenix/Mix.Tasks.Phx.New.html",
        "title": "mix phx.new",
        "claim": "Phoenix generator syntax and selectable target options."
      },
      {
        "url": "https://hexdocs.pm/ecto/Ecto.Multi.html",
        "title": "Ecto.Multi",
        "claim": "Composes related database operations within a transaction."
      },
      {
        "url": "https://hexdocs.pm/ecto_sql/Ecto.Migrator.html",
        "title": "Ecto.Migrator",
        "claim": "Migration execution, repository connection and release migration concerns."
      },
      {
        "url": "https://oban.hexdocs.pm/installation.html",
        "title": "Oban Installation",
        "claim": "PostgreSQL, SQLite and MySQL engine setup; migrations, configuration and supervision."
      },
      {
        "url": "https://oban.hexdocs.pm/Oban.html",
        "title": "Oban API",
        "claim": "Transactional Ecto.Multi insertion and queue pause/resume APIs; uniqueness is an enqueue feature."
      },
      {
        "url": "https://exq.hexdocs.pm/readme.html",
        "title": "Exq README",
        "claim": "Redis persistence, retrying and Resque/Sidekiq-format compatibility."
      },
      {
        "url": "https://github.com/sidekiq/sidekiq/wiki/Job-Format",
        "title": "Sidekiq Job Format",
        "claim": "Job envelopes and Active Job wrapper/version-sensitive payload details."
      },
      {
        "url": "https://github.com/sidekiq/sidekiq/wiki/Best-Practices",
        "title": "Sidekiq Best Practices",
        "claim": "Idempotent jobs and at-least-once execution."
      },
      {
        "url": "https://guides.rubyonrails.org/active_job_basics.html",
        "title": "Rails Active Job Basics",
        "claim": "Active Job serializers/GlobalID, adapter abstraction and scheduled/retry execution."
      },
      {
        "url": "https://guides.rubyonrails.org/security.html",
        "title": "Rails Security",
        "claim": "Rails cookie/session contract and rotation considerations."
      },
      {
        "url": "https://hexdocs.pm/plug/Plug.Session.COOKIE.html",
        "title": "Plug cookie session store",
        "claim": "Cookie serializer, signing/encryption configuration and rotating options."
      },
      {
        "url": "https://hexdocs.pm/phoenix_live_view/security-model.html",
        "title": "LiveView Security",
        "claim": "Authorization on mount/events and stateful connection revocation."
      },
      {
        "url": "https://hexdocs.pm/phoenix/channels.html",
        "title": "Phoenix Channels",
        "claim": "Channel protocol, joins and at-most-once non-persistent client delivery with application catch-up options."
      },
      {
        "url": "https://hexdocs.pm/phoenix_pubsub/Phoenix.PubSub.html",
        "title": "Phoenix PubSub",
        "claim": "Distributed-Elixir and Redis adapters, pool configuration and migration."
      },
      {
        "url": "https://www.erlang.org/doc/apps/stdlib/ets.html",
        "title": "OTP ETS",
        "claim": "Node-local tables, owner/heir lifetime and table options."
      },
      {
        "url": "https://hexdocs.pm/phoenix/testing.html",
        "title": "Phoenix Testing",
        "claim": "mix test and generated web test helpers."
      },
      {
        "url": "https://hexdocs.pm/phoenix/deployment.html",
        "title": "Phoenix Deployment",
        "claim": "Runtime configuration, assets.deploy, release and production build responsibilities."
      }
    ],
    "reviewed_at": "2026-10-11",
    "evidence": "documentation-reviewed"
  },
  "rewrite_url": "/rewrite/guide.md",
  "migration_contracts_url": "/rewrite/contracts.json",
  "extended_reference": {
    "stack": {
      "name": "elixir.menu — Web application stack",
      "edition": "01",
      "reviewed": "2026-10-06",
      "status": "Documentation-based recommendations; verify the selected dependencies together in your application.",
      "scope": "Database-backed web products built by individuals and small teams",
      "principle": "One default per job. Add a dependency when its job exists. Revisit a choice when a concrete requirement warrants it.",
      "groups": [
        {
          "id": "foundation",
          "title": "Application",
          "description": "A Phoenix application, a database, and familiar conventions.",
          "items": [
            {
              "id": "web",
              "job": "Web framework",
              "tool": "Phoenix",
              "stage": "Start here",
              "summary": "Routes, controllers, HTML and application conventions in one place.",
              "why": "Start with one Phoenix application and its generated structure. Keep the standard HTTP server and asset setup until a measured requirement calls for a change.",
              "boundary": "Phoenix does not choose every application policy for you. This guide supplies the next layer of defaults; it does not replace Phoenix.",
              "next": "Use the official getting-started guide and a supported Elixir/OTP pair. Commit the resolved dependency lockfile.",
              "sources": [
                [
                  "Phoenix getting started",
                  "https://hexdocs.pm/phoenix/up_and_running.html"
                ]
              ]
            },
            {
              "id": "ui",
              "job": "Interactive interfaces",
              "tool": "Phoenix LiveView",
              "stage": "Start here",
              "summary": "Build forms, dashboards and live updates alongside your Elixir code.",
              "why": "Use HEEx and function components for markup, with LiveView for interactive screens. Start ordinary content pages as server-rendered HTML when they need no live process.",
              "boundary": "Offline-first products, rich canvas editors and heavy local interaction can justify a dedicated client application. Add small JavaScript hooks for isolated browser behavior.",
              "next": "Keep business rules in contexts; use LiveView for interaction and presentation.",
              "sources": [
                [
                  "LiveView documentation",
                  "https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html"
                ]
              ]
            },
            {
              "id": "styling",
              "job": "Styling and components",
              "tool": "Tailwind CSS + HEEx components",
              "stage": "Start here",
              "summary": "Own a small set of reusable components and the visual language around them.",
              "why": "Use Tailwind for styling and HEEx function components to share markup and behavior. Keep theme tokens and application components in your project, where you can shape them around the product.",
              "boundary": "Phoenix 1.8 includes daisyUI, but its generators do not require it. Keep daisyUI when its component classes and themes save useful work; it is optional, not the design system every product must adopt.",
              "next": "Define the product’s type, spacing and color rules, then build the components repeated across its screens.",
              "sources": [
                [
                  "Phoenix 1.8 release",
                  "https://phoenixframework.org/blog/phoenix-1-8-released"
                ],
                [
                  "daisyUI customization",
                  "https://daisyui.com/docs/customize/"
                ]
              ]
            },
            {
              "id": "data",
              "job": "Database and queries",
              "tool": "PostgreSQL + Ecto",
              "stage": "Start here",
              "summary": "One durable store, explicit queries, changesets and migrations.",
              "why": "Use Ecto for data access and changesets, backed by PostgreSQL. Database constraints enforce invariants; transactions keep related writes together.",
              "boundary": "Ecto is not Active Record: queries and persistence remain explicit. SQLite is reasonable for a genuinely embedded or single-file application, outside this web-product default.",
              "next": "Use Ecto.Multi for multi-step transactions. Inspect slow queries and their plans before adding caches.",
              "sources": [
                [
                  "Ecto documentation",
                  "https://hexdocs.pm/ecto/Ecto.html"
                ],
                [
                  "Ecto.Multi",
                  "https://hexdocs.pm/ecto/Ecto.Multi.html"
                ]
              ]
            },
            {
              "id": "domain",
              "job": "Business logic",
              "tool": "Phoenix contexts",
              "stage": "Start here",
              "summary": "Plain Elixir modules around the language of your product.",
              "why": "Give related business operations a clear public API. Both LiveViews and controllers should call that API instead of implementing separate versions of the same rules.",
              "boundary": "A context is an organizational boundary, not a process. Do not turn ordinary CRUD or pure business logic into a GenServer.",
              "next": "Define public operations around the application’s domain. Call those operations from controllers, LiveViews and workers; keep database access behind the context boundary.",
              "sources": [
                [
                  "Phoenix contexts",
                  "https://hexdocs.pm/phoenix/contexts.html"
                ]
              ]
            }
          ]
        },
        {
          "id": "accounts",
          "title": "Accounts and permissions",
          "description": "Authentication establishes identity. Your application still owns permissions.",
          "items": [
            {
              "id": "auth",
              "job": "Accounts and sign-in",
              "tool": "phx.gen.auth",
              "stage": "When needed",
              "summary": "Start with Phoenix’s generated authentication.",
              "why": "Use the authentication generator for a first-party web application. Phoenix 1.8 includes magic-link authentication and sudo-mode conventions; the generated code lives in your application.",
              "boundary": "Authentication is not authorization. Enterprise SSO, external identity or complex organization requirements need their own deliberate integration.",
              "next": "Read the generated code and current security guidance. Configure real email delivery before relying on magic links.",
              "sources": [
                [
                  "Authentication generator",
                  "https://hexdocs.pm/phoenix/mix_phx_gen_auth.html"
                ]
              ]
            },
            {
              "id": "authorization",
              "job": "Authorization",
              "tool": "Scopes + context checks",
              "stage": "When needed",
              "summary": "Carry the actor into business operations and constrain data access.",
              "why": "Pass the authenticated scope through context functions. Apply ownership and permission rules to reads and writes; test denied access as deliberately as the successful path.",
              "boundary": "Scopes carry identity and context; they are not an automatic policy engine. Hidden buttons and client parameters are never sufficient access control.",
              "next": "Pass the current scope into context operations. Test that another user cannot read or modify the protected resource, including through direct context calls.",
              "sources": [
                [
                  "Phoenix scopes",
                  "https://hexdocs.pm/phoenix/scopes.html"
                ]
              ]
            }
          ]
        },
        {
          "id": "work",
          "title": "Jobs and integrations",
          "description": "Reliable background work and the integrations most products need.",
          "items": [
            {
              "id": "jobs",
              "job": "Durable background jobs",
              "tool": "Oban",
              "stage": "When needed",
              "summary": "Retries and persistent jobs using the database you already run.",
              "why": "Use Oban with PostgreSQL for work that must survive a process or application restart. Insert related jobs and domain changes within the same transaction.",
              "boundary": "Jobs may be retried. Make side effects idempotent; uniqueness is not an exactly-once guarantee. Some advanced orchestration features belong to paid Oban Pro.",
              "next": "Use separate queues for distinct workloads and monitor retries. An ordinary Task is suitable for disposable concurrent work, not a durable queue.",
              "sources": [
                [
                  "Oban documentation",
                  "https://hexdocs.pm/oban/Oban.html"
                ]
              ]
            },
            {
              "id": "schedule",
              "job": "Recurring tasks",
              "tool": "Oban Cron",
              "stage": "When needed",
              "summary": "Put scheduled work in the same queue as other durable jobs.",
              "why": "Use Oban.Cron for periodic job insertion. Keep execution, retries and operational visibility in the job system.",
              "boundary": "A schedule is not a catch-up policy for time when the app is down. Define missed-run behavior explicitly; dynamic scheduling has different requirements.",
              "next": "Set the intended timezone, check daylight-saving behavior where applicable, and keep workers idempotent.",
              "sources": [
                [
                  "Oban.Cron",
                  "https://hexdocs.pm/oban/Oban.Cron.html"
                ]
              ]
            },
            {
              "id": "email",
              "job": "Transactional email",
              "tool": "Swoosh",
              "stage": "When needed",
              "summary": "Compose mail in Elixir and send through a delivery adapter.",
              "why": "Use Swoosh, included in generated Phoenix applications, for account mail and product notifications. Use Oban when delivery needs durable retries.",
              "boundary": "Swoosh is a library, not an email delivery service. The provider, verified domain and deliverability setup are separate operational choices.",
              "next": "Keep development mail local. Test message content and recipient selection before enabling production delivery.",
              "sources": [
                [
                  "Swoosh documentation",
                  "https://hexdocs.pm/swoosh/Swoosh.html"
                ]
              ]
            },
            {
              "id": "http",
              "job": "Outgoing HTTP",
              "tool": "Req",
              "stage": "When needed",
              "summary": "A readable client for calling external APIs.",
              "why": "Use Req for request construction, response handling and client features rather than assembling a new HTTP abstraction for each integration.",
              "boundary": "Timeouts, retries and error handling are part of the integration design. A retried payment or other side effect needs an idempotency strategy.",
              "next": "Wrap each external service behind a small module. Use Req.Test for deterministic request tests.",
              "sources": [
                [
                  "Req documentation",
                  "https://hexdocs.pm/req/Req.html"
                ],
                [
                  "Req.Test",
                  "https://hexdocs.pm/req/Req.Test.html"
                ]
              ]
            },
            {
              "id": "realtime",
              "job": "Realtime notifications",
              "tool": "Phoenix PubSub",
              "stage": "When needed",
              "summary": "Broadcast changes to connected parts of your application.",
              "why": "Use Phoenix.PubSub to notify subscribed LiveViews or processes of a change. Persist important facts in PostgreSQL and load their current state when clients reconnect.",
              "boundary": "PubSub is not a persistent message log. Multiple nodes require a suitable cluster or adapter configuration.",
              "next": "Use Presence only when you actually need to track who is connected.",
              "sources": [
                [
                  "Phoenix.PubSub",
                  "https://hexdocs.pm/phoenix_pubsub/Phoenix.PubSub.html"
                ]
              ]
            },
            {
              "id": "uploads",
              "job": "File uploads",
              "tool": "LiveView uploads",
              "stage": "When needed",
              "summary": "Progress, validation and direct-to-cloud upload support.",
              "why": "Start with LiveView’s upload flow. For durable production attachments, pair it with object storage and store ownership and file metadata in PostgreSQL.",
              "boundary": "An upload widget does not supply permanent storage. Ephemeral application disks are not a durable attachment store; client metadata does not establish file safety.",
              "next": "Set size/type limits, authorize downloads, and implement the storage provider’s documented signing flow.",
              "sources": [
                [
                  "LiveView uploads",
                  "https://hexdocs.pm/phoenix_live_view/uploads.html"
                ]
              ]
            }
          ]
        },
        {
          "id": "grow",
          "title": "Queries and APIs",
          "description": "Start with the tools already in the application or runtime.",
          "items": [
            {
              "id": "search",
              "job": "Product text search",
              "tool": "PostgreSQL full-text search",
              "stage": "When needed",
              "summary": "Search your product’s data before operating another service.",
              "why": "For ordinary application search, start with PostgreSQL text-search features and an appropriate index. Keep the query and ranking logic close to your data model.",
              "boundary": "Advanced relevance, typo tolerance or specialist search scale can justify a dedicated engine. Evaluate against actual queries and user expectations.",
              "next": "Build representative search cases and inspect query plans; avoid claiming that an unindexed text scan will scale.",
              "sources": [
                [
                  "PostgreSQL text search",
                  "https://www.postgresql.org/docs/current/textsearch-intro.html"
                ]
              ]
            },
            {
              "id": "pagination",
              "job": "Filtering and pagination",
              "tool": "Flop",
              "stage": "When needed",
              "summary": "Validated filter, sort and page parameters for Ecto queries.",
              "why": "Use Flop when product lists need reusable sorting, filtering and pagination. Keep the allowed fields explicit and restrict the base query to authorized records first.",
              "boundary": "For one simple bounded list, an explicit Ecto query may be enough. Pagination does not replace tenant or ownership checks.",
              "next": "Use cursor pagination when the list and ordering warrant it; add Flop Phoenix only for the corresponding UI helpers.",
              "sources": [
                [
                  "Flop documentation",
                  "https://hexdocs.pm/flop/readme.html"
                ]
              ]
            },
            {
              "id": "cache",
              "job": "Local read caching",
              "tool": "ETS",
              "stage": "After measuring",
              "summary": "Use the runtime’s in-memory tables for disposable cached data.",
              "why": "First measure and improve database queries. When a local cache earns its keep, ETS provides concurrent in-memory storage without another service.",
              "boundary": "ETS is node-local, loses data when its owner dies, and does not supply an automatic TTL or invalidation policy. It is never the source of truth.",
              "next": "Define table ownership, expiry and invalidation. Shared durable state requires a different design.",
              "sources": [
                [
                  "ETS documentation",
                  "https://www.erlang.org/doc/apps/stdlib/ets.html"
                ]
              ]
            },
            {
              "id": "api",
              "job": "JSON APIs",
              "tool": "Phoenix controllers",
              "stage": "When needed",
              "summary": "Expose your existing contexts through explicit HTTP endpoints.",
              "why": "Use Phoenix controllers and JSON rendering when a mobile client, partner or external integration needs an API. Share the business API with your web interface.",
              "boundary": "Do not build an API simply to connect LiveView to its own server. Add GraphQL only when clients need that query model; Absinthe is the established Elixir option.",
              "next": "Define authentication, authorization, error shapes, pagination and versioning as part of the API contract.",
              "sources": [
                [
                  "Phoenix JSON and APIs",
                  "https://hexdocs.pm/phoenix/json_and_apis.html"
                ]
              ]
            }
          ]
        },
        {
          "id": "ship",
          "title": "Testing and deployment",
          "description": "Fast feedback, observable behavior and a repeatable deployment artifact.",
          "items": [
            {
              "id": "test",
              "job": "Application tests",
              "tool": "ExUnit + LiveViewTest",
              "stage": "Start here",
              "summary": "Test business behavior, persistence and server-driven interaction.",
              "why": "Use ExUnit and Phoenix’s generated test setup. Test contexts with the database sandbox and interactive screens with Phoenix.LiveViewTest.",
              "boundary": "Server-side LiveView tests do not execute browser JavaScript or prove the final visual layout. Avoid asserting incidental markup when behavior is the contract.",
              "next": "Prioritize permission boundaries, data invariants, job behavior and core user journeys.",
              "sources": [
                [
                  "ExUnit",
                  "https://hexdocs.pm/ex_unit/ExUnit.html"
                ],
                [
                  "LiveViewTest",
                  "https://hexdocs.pm/phoenix_live_view/Phoenix.LiveViewTest.html"
                ]
              ]
            },
            {
              "id": "browser",
              "job": "Browser integration tests",
              "tool": "PhoenixTest.Playwright",
              "stage": "When needed",
              "summary": "Exercise the small set of flows that need a real browser.",
              "why": "Use the Playwright-backed PhoenixTest driver for JavaScript hooks, browser navigation and integrations that server-side tests cannot cover.",
              "boundary": "This adds browser tooling and setup cost. Keep most application tests in ExUnit; this is an editorial extension, not a Phoenix-generated default.",
              "next": "Cover representative critical flows and keep browser tests focused on observable behavior.",
              "sources": [
                [
                  "PhoenixTest.Playwright",
                  "https://hexdocs.pm/phoenix_test_playwright/readme.html"
                ]
              ]
            },
            {
              "id": "observability",
              "job": "Instrumentation",
              "tool": "Telemetry + Logger",
              "stage": "Start here",
              "summary": "Expose timings, failures and structured operational events.",
              "why": "Use Telemetry events and Logger as the instrumentation foundation. Track request latency, database time, job failures and the product operations that matter.",
              "boundary": "Instrumentation is not a hosted monitoring backend. Choose collection, storage and alerting separately; restrict any production LiveDashboard access.",
              "next": "Make logs useful without exposing secrets. Establish alerts and retention before calling the production system operationally complete.",
              "sources": [
                [
                  "Telemetry",
                  "https://hexdocs.pm/telemetry/readme.html"
                ],
                [
                  "Logger",
                  "https://hexdocs.pm/logger/Logger.html"
                ]
              ]
            },
            {
              "id": "deployment",
              "job": "Deployment artifact",
              "tool": "Mix releases",
              "stage": "When shipping",
              "summary": "Package the application and runtime in a repeatable release.",
              "why": "Use Mix releases with runtime configuration. A container is a practical transport when the chosen host supports it; Phoenix documents the release and Dockerfile path.",
              "boundary": "A release is not a hosting platform. Database backups, migrations, TLS, monitoring and rollback remain production responsibilities.",
              "next": "Choose a host that fits the team. Rehearse migrations and restoration, and keep secrets out of the build artifact.",
              "sources": [
                [
                  "Phoenix releases",
                  "https://hexdocs.pm/phoenix/releases.html"
                ]
              ]
            }
          ]
        }
      ]
    },
    "optional": {
      "reviewed": "2026-10-11",
      "status": "Choose and configure each library against its installed-version documentation.",
      "instruction": "Select an optional tool only when the application's requirements match its use case. Explain the selection, check compatibility with the project's lockfile, and follow its installed-version documentation. Do not install the whole menu.",
      "groups": [
        {
          "id": "optional-accounts",
          "title": "Accounts and product controls",
          "items": [
            {
              "id": "social-login",
              "job": "Social login and OIDC",
              "tool": "Assent",
              "when": "Users need to sign in with Google, GitHub, Apple or an OpenID Connect provider.",
              "use": "Integrate provider authentication with the application's existing accounts and sessions.",
              "boundary": "Account linking, session management and permissions remain application concerns. SAML is a separate requirement.",
              "sources": [
                [
                  "Assent",
                  "https://github.com/pow-auth/assent"
                ]
              ]
            },
            {
              "id": "rate-limits",
              "job": "Rate limiting",
              "tool": "Hammer",
              "when": "Login attempts, API calls, uploads or expensive actions need limits.",
              "use": "Define limits by actor, operation and time window; choose a backend that matches the deployment.",
              "boundary": "A node-local counter does not automatically enforce a cluster-wide quota. Choose the algorithm's burst behavior deliberately.",
              "sources": [
                [
                  "Hammer",
                  "https://hexdocs.pm/hammer/readme.html"
                ]
              ]
            },
            {
              "id": "feature-flags",
              "job": "Feature flags",
              "tool": "FunWithFlags",
              "when": "Features need gradual rollout, per-customer access or an operational off switch.",
              "use": "Choose an Ecto or Redis persistence adapter and configure synchronization between nodes.",
              "boundary": "Keep permission checks separate from rollout flags. Remove expired flags and restrict the management interface.",
              "sources": [
                [
                  "FunWithFlags",
                  "https://hexdocs.pm/fun_with_flags/readme.html"
                ]
              ]
            }
          ]
        },
        {
          "id": "optional-files",
          "title": "Files and documents",
          "items": [
            {
              "id": "object-storage",
              "job": "Object storage",
              "tool": "ExAws.S3",
              "when": "Uploads need to live in an S3 or compatible object store.",
              "use": "Use ExAws with its S3 service module for object operations and signed URLs. In a LiveView app, connect it to the upload flow.",
              "boundary": "The storage service, credentials, access rules and lifecycle policy are separate. Check which operations a compatible service actually supports.",
              "sources": [
                [
                  "ExAws.S3",
                  "https://hexdocs.pm/ex_aws_s3/ExAws.S3.html"
                ]
              ]
            },
            {
              "id": "image-processing",
              "job": "Image processing",
              "tool": "Image",
              "when": "The product needs thumbnails, resized uploads, image conversion or watermarks.",
              "use": "Process images through the libvips-backed Image API; use Oban for work that needs retries.",
              "boundary": "Vix/libvips introduces native runtime requirements. Check the deployment target and required codecs, and limit input dimensions and processing work.",
              "sources": [
                [
                  "Image",
                  "https://hexdocs.pm/image/readme.html"
                ]
              ]
            },
            {
              "id": "markdown",
              "job": "Markdown parsing and rendering",
              "tool": "MDEx",
              "when": "The application needs CommonMark parsing, rendered Markdown content or transformations over a Markdown document tree.",
              "use": "Use MDEx to parse and transform a document or render HTML. Choose extensions and syntax highlighting deliberately; reserve HEEx components and expressions for trusted application-owned content.",
              "boundary": "Treat user-authored Markdown as untrusted input: configure and verify HTML sanitization, link handling and size limits. Never evaluate its HEEx expressions. MDEx uses native Rust code; verify precompiled artifact support or a Rust build toolchain for the deployment target.",
              "sources": [
                [
                  "MDEx",
                  "https://github.com/leandrocp/mdex"
                ]
              ]
            },
            {
              "id": "pdf-generation",
              "job": "PDF generation",
              "tool": "ChromicPDF",
              "when": "Invoices, reports or other HTML documents need downloadable PDFs.",
              "use": "Render application-owned HTML and CSS through a managed Chrome process.",
              "boundary": "Chrome is an additional runtime dependency; PDF/A conversion also uses Ghostscript. Isolate rendering and control access to external resources.",
              "sources": [
                [
                  "ChromicPDF",
                  "https://hexdocs.pm/chromic_pdf/ChromicPDF.html"
                ]
              ]
            },
            {
              "id": "csv",
              "job": "CSV import and export",
              "tool": "NimbleCSV",
              "when": "Users need bulk uploads, data exports or integrations through CSV files.",
              "use": "Parse and write streams; validate each imported row through the application's business rules.",
              "boundary": "CSV parsing does not validate field types or business rules. Define malformed-row handling and spreadsheet formula escaping for exports.",
              "sources": [
                [
                  "NimbleCSV",
                  "https://hexdocs.pm/nimble_csv/NimbleCSV.html"
                ]
              ]
            }
          ]
        },
        {
          "id": "optional-interfaces",
          "title": "APIs and collaboration",
          "items": [
            {
              "id": "openapi",
              "job": "Documented HTTP APIs",
              "tool": "OpenApiSpex",
              "when": "External consumers need an OpenAPI contract for the Phoenix API.",
              "use": "Describe operations and schemas, validate requests, and test responses against the specification.",
              "boundary": "A schema does not enforce ownership or authorization. Keep the published contract aligned with application behavior.",
              "sources": [
                [
                  "OpenApiSpex",
                  "https://hexdocs.pm/open_api_spex/readme.html"
                ]
              ]
            },
            {
              "id": "graphql",
              "job": "GraphQL APIs",
              "tool": "Absinthe",
              "when": "Clients specifically need GraphQL queries, mutations or subscriptions.",
              "use": "Expose the existing business layer through a schema; use Dataloader where batching is needed.",
              "boundary": "Resolvers still need authorization and query-cost limits. Ordinary JSON endpoints remain simpler when GraphQL adds no client benefit.",
              "sources": [
                [
                  "Absinthe",
                  "https://hexdocs.pm/absinthe/overview.html"
                ]
              ]
            },
            {
              "id": "presence",
              "job": "Who is online",
              "tool": "Phoenix Presence",
              "when": "Chat, collaborative screens or live rooms need connected-user state.",
              "use": "Track presence metadata and broadcast joins and leaves through Phoenix's existing realtime infrastructure.",
              "boundary": "Presence is ephemeral and eventually consistent. It is not a durable audit trail, access-control system or editing-conflict resolver.",
              "sources": [
                [
                  "Phoenix Presence",
                  "https://hexdocs.pm/phoenix/Phoenix.Presence.html"
                ]
              ]
            }
          ]
        },
        {
          "id": "optional-international",
          "title": "Languages, locales and money",
          "items": [
            {
              "id": "translations",
              "job": "Translated interface text",
              "tool": "Gettext",
              "when": "The application needs interface copy in multiple languages.",
              "use": "Use the Gettext integration already present in typical Phoenix projects; maintain translation catalogs and plural forms.",
              "boundary": "It does not translate user-authored content automatically. Dates, amounts and units need locale-aware formatting too.",
              "sources": [
                [
                  "Gettext",
                  "https://hexdocs.pm/gettext/Gettext.html"
                ]
              ]
            },
            {
              "id": "localization",
              "job": "Localized dates and numbers",
              "tool": "Localize",
              "when": "Dates, numbers, currencies and units must follow the user's locale.",
              "use": "Use CLDR-based formatting and parsing. For new projects, evaluate Localize as the documented successor to the ex_cldr family.",
              "boundary": "Existing ex_cldr applications need a planned migration. Check runtime requirements and package locale data for deployment.",
              "sources": [
                [
                  "Localize",
                  "https://hexdocs.pm/localize/readme.html"
                ],
                [
                  "ex_cldr migration notice",
                  "https://hexdocs.pm/ex_cldr/readme.html"
                ]
              ]
            },
            {
              "id": "money",
              "job": "Currency amounts",
              "tool": "Money (ex_money)",
              "when": "Prices, invoices or balances need explicit currencies and decimal arithmetic.",
              "use": "Represent amount and currency together and make rounding rules explicit.",
              "boundary": "This is not payment processing, tax calculation or an accounting ledger. The package is ex_money; check its major-version migration notes.",
              "sources": [
                [
                  "Money",
                  "https://hexdocs.pm/ex_money/readme.html"
                ]
              ]
            }
          ]
        },
        {
          "id": "optional-ai",
          "title": "AI features",
          "items": [
            {
              "id": "llm-client",
              "job": "Calling language models",
              "tool": "ReqLLM",
              "when": "The product needs text generation, streaming, structured outputs, embeddings or model tool calls through supported provider APIs.",
              "use": "Use a common Elixir interface to supported providers, including schema-constrained output and embedding generation. Keep provider-specific settings explicit; store embeddings separately when retrieval needs persistence.",
              "boundary": "Provider and model capabilities differ. Start here for direct model calls; add an agent framework only when orchestration needs it. Define application validation, permissions and representative evaluations separately.",
              "sources": [
                [
                  "ReqLLM",
                  "https://github.com/agentjido/req_llm"
                ]
              ]
            },
            {
              "id": "ai-agents",
              "job": "Tool-using AI agents",
              "tool": "Jido.AI 2.x",
              "when": "A language model must select tools, keep agent state or run a bounded multistep reasoning loop.",
              "use": "Add Jido.AI to a Jido agent and use ReqLLM for provider access. Define tools as actions, and keep model selection, timeouts and tool policy explicit.",
              "boundary": "Model output is untrusted and nondeterministic. Restrict tool permissions and side effects, set cost and step limits, require approval for high-risk operations, and test representative tasks.",
              "sources": [
                [
                  "Jido.AI documentation",
                  "https://hexdocs.pm/jido_ai/readme.html"
                ],
                [
                  "Jido ecosystem",
                  "https://jido.run/ecosystem"
                ]
              ]
            },
            {
              "id": "structured-extraction",
              "job": "Validated structured extraction",
              "tool": "InstructorLite",
              "when": "The main requirement is extracting typed fields from text into Ecto schemas, with casting, validation and retries.",
              "use": "Define an instruction with an Ecto schema and use changeset validation to check the returned fields. Choose this alternative when its Ecto integration fits better than ReqLLM structured output.",
              "boundary": "It is not an agent runtime and does not provide response streaming. Valid fields can still contain incorrect facts; handle failed validation, retry limits and domain checks explicitly.",
              "sources": [
                [
                  "InstructorLite",
                  "https://github.com/martosaur/instructor_lite"
                ]
              ]
            },
            {
              "id": "llm-chains",
              "job": "Conversational chains and tool loops",
              "tool": "LangChain for Elixir",
              "when": "The application is organized around message history, model responses and chained tool calls, or already uses the Elixir LangChain abstractions.",
              "use": "Compose messages, model adapters and tools through LLMChain. Use its callbacks for streaming and inspect tool-call trajectories when evaluating a conversation workflow.",
              "boundary": "This is an alternative orchestration approach, not an extra layer every ReqLLM or Jido application needs. Bound tool loops, authorize actions and implement persistence and retry behavior for the actual product.",
              "sources": [
                [
                  "LangChain for Elixir",
                  "https://github.com/brainlid/langchain"
                ]
              ]
            },
            {
              "id": "ash-ai",
              "job": "AI features for Ash applications",
              "tool": "Ash AI",
              "when": "An application already uses Ash resources and needs to expose selected actions as model tools or MCP tools, generate structured outputs or maintain embeddings.",
              "use": "Add Ash AI to the existing Ash domains and resources. Select the actions to expose and carry the authenticated actor and tenant into their execution; configure model access only for features that require it.",
              "boundary": "Choose Ash as an architecture first. Ash AI does not justify migrating an ordinary contexts application by itself. Test authorization for every exposed action and verify embedding updates, retrieval quality and model costs.",
              "sources": [
                [
                  "Ash AI",
                  "https://github.com/ash-project/ash_ai"
                ]
              ]
            },
            {
              "id": "vector-search",
              "job": "Vector similarity search",
              "tool": "pgvector",
              "when": "Embeddings are useful for semantic retrieval or recommendations alongside existing PostgreSQL data.",
              "use": "Enable the PostgreSQL vector extension and use the Elixir pgvector integration with Ecto or Postgrex.",
              "boundary": "The library does not generate embeddings. Verify database-extension support, dimensions, indexes and retrieval quality against representative queries.",
              "sources": [
                [
                  "pgvector for Elixir",
                  "https://hexdocs.pm/pgvector/readme.html"
                ]
              ]
            },
            {
              "id": "local-models",
              "job": "Running pretrained models",
              "tool": "Bumblebee + Nx",
              "when": "Inference should run on infrastructure you control, using a supported pretrained model.",
              "use": "Load supported model architectures with Bumblebee and serve predictions through Nx.Serving.",
              "boundary": "Plan model memory, a suitable numerical backend and hardware. Model compatibility and licensing need to be checked individually.",
              "sources": [
                [
                  "Bumblebee",
                  "https://hexdocs.pm/bumblebee/Bumblebee.html"
                ]
              ]
            }
          ]
        },
        {
          "id": "optional-data",
          "title": "Data processing and machine learning",
          "items": [
            {
              "id": "ingestion",
              "job": "Message ingestion pipelines",
              "tool": "Broadway",
              "when": "The application consumes a stream from Kafka, RabbitMQ, SQS or another supported source.",
              "use": "Build concurrent processing stages with batching, acknowledgements and back-pressure.",
              "boundary": "The source connector determines delivery behavior. Keep handlers safe to retry; ordinary application jobs still fit Oban.",
              "sources": [
                [
                  "Broadway",
                  "https://hexdocs.pm/broadway/Broadway.html"
                ]
              ]
            },
            {
              "id": "dataframes",
              "job": "Tabular data analysis",
              "tool": "Explorer",
              "when": "Imports, analytics or reports need dataframe transformations over CSV, Parquet or similar datasets.",
              "use": "Use typed series and dataframes for grouping, joining and transforming tabular data.",
              "boundary": "The default Polars backend uses native code. Check deployment support and memory use; routine application queries still belong in Ecto.",
              "sources": [
                [
                  "Explorer",
                  "https://hexdocs.pm/explorer/Explorer.html"
                ]
              ]
            },
            {
              "id": "neural-networks",
              "job": "Building and training neural networks",
              "tool": "Axon + Nx",
              "when": "The project needs to define or train a neural network in Elixir rather than only run a supported pretrained model.",
              "use": "Use Axon to build models and training loops on Nx, with a numerical compiler/backend suited to the target hardware. Use Bumblebee when loading a supported pretrained model meets the requirement.",
              "boundary": "Check native backend support, hardware, memory and training data. Validate on held-out data and measure deployment behavior; adding Axon does not establish model quality or a performance advantage.",
              "sources": [
                [
                  "Axon",
                  "https://github.com/elixir-nx/axon"
                ]
              ]
            }
          ]
        },
        {
          "id": "optional-performance",
          "title": "Performance and scaling",
          "items": [
            {
              "id": "genstage",
              "job": "Custom demand-driven pipelines",
              "tool": "GenStage",
              "when": "Producers and consumers need a custom event pipeline with demand-driven backpressure.",
              "use": "Model stages around runtime concurrency and data transfer; keep business steps in functions.",
              "boundary": "You own buffering, subscriptions and failure behavior. Use Task.async_stream for simpler bounded parallel work, or Broadway for its ingestion features.",
              "sources": [
                [
                  "GenStage",
                  "https://hexdocs.pm/gen_stage/GenStage.html"
                ]
              ]
            },
            {
              "id": "flow",
              "job": "Parallel dataset transformations",
              "tool": "Flow",
              "when": "A large enumerable needs parallel mapping, partitioned grouping, aggregation or windows.",
              "use": "Build a Flow pipeline and choose partition keys, stages and windows for the workload.",
              "boundary": "Coordination has overhead; small inputs can favor Enum or Task. Partitioning affects grouping and order; bound accumulating state.",
              "sources": [
                [
                  "Flow",
                  "https://hexdocs.pm/flow/Flow.html"
                ]
              ]
            },
            {
              "id": "benchmarking",
              "job": "Compare code performance",
              "tool": "Benchee",
              "when": "A measured hot path has alternative implementations to compare.",
              "use": "Benchmark representative inputs with warmup, runtime, memory and reduction measurements.",
              "boundary": "Microbenchmarks do not establish application throughput or production latency. Check correctness and end-to-end behavior separately.",
              "sources": [
                [
                  "Benchee",
                  "https://hexdocs.pm/benchee/readme.html"
                ]
              ]
            },
            {
              "id": "profiling",
              "job": "Locate runtime bottlenecks",
              "tool": "Mix + OTP profilers",
              "when": "The application is slow and you need to locate expensive calls or repeated work.",
              "use": "Use the supported mix profile task or OTP profiler for the question; combine findings with the existing Telemetry events.",
              "boundary": "Check installed runtime support for tprof, eprof and fprof. Tracing can change timing and add overhead; profile a representative bounded workload.",
              "sources": [
                [
                  "Mix + OTP profilers",
                  "https://hexdocs.pm/mix/Mix.Tasks.Profile.Tprof.html"
                ]
              ]
            },
            {
              "id": "cache-management",
              "job": "Managed in-memory caching",
              "tool": "Cachex",
              "when": "A node-local cache needs expiration, eviction or fallback loading beyond a small ETS implementation.",
              "use": "Configure cache limits, expiration and loading behavior around the data lifecycle.",
              "boundary": "Define invalidation and stale-data policy. A local cache is not a durable source of truth or a cluster-wide consistency guarantee.",
              "sources": [
                [
                  "Cachex",
                  "https://hexdocs.pm/cachex/Cachex.html"
                ]
              ]
            },
            {
              "id": "native-rust",
              "job": "Native computation and Rust integration",
              "tool": "Rustler",
              "when": "Profiling identifies CPU-heavy work suited to native code, or the product must call an existing Rust library.",
              "use": "Expose a narrow NIF interface and select normal versus dirty scheduling appropriate to the work.",
              "boundary": "Native faults can crash the VM. Validate target builds, scheduler impact and boundary-copying costs; consider a separate OS process when fault isolation is required.",
              "sources": [
                [
                  "Rustler",
                  "https://hexdocs.pm/rustler/readme.html"
                ]
              ]
            },
            {
              "id": "numerical-compiler",
              "job": "Compiled tensor computation",
              "tool": "EXLA + Nx",
              "when": "Nx numerical work needs a compiled backend on supported CPU or accelerator hardware.",
              "use": "Use EXLA as the Nx compiler/backend and select the supported target, precision and memory settings.",
              "boundary": "Compilation, data transfer and native dependencies have costs. Verify hardware/toolchain support and measure representative workloads; it is not a general accelerator for arbitrary Elixir functions.",
              "sources": [
                [
                  "EXLA + Nx",
                  "https://hexdocs.pm/exla/EXLA.html"
                ]
              ]
            },
            {
              "id": "elastic-compute",
              "job": "Elastic remote execution",
              "tool": "FLAME",
              "when": "Bursty expensive work should run on additional ephemeral nodes, such as media processing or model inference.",
              "use": "Configure a bounded runner pool and an appropriate backend. Start only required application children and size runner database pools deliberately.",
              "boundary": "Boot latency, network transfer and capacity costs matter. FLAME supplies compute execution, not a durable job queue; pair it with Oban when jobs must persist.",
              "sources": [
                [
                  "FLAME",
                  "https://hexdocs.pm/flame/FLAME.html"
                ]
              ]
            }
          ]
        },
        {
          "id": "optional-development",
          "title": "Development tools",
          "items": [
            {
              "id": "boundary-checks",
              "job": "Module dependency rules",
              "tool": "Boundary",
              "when": "Domains or layers need explicit, compiler-checked dependencies and public module exports. Useful for agent-maintained applications as well as human-maintained code.",
              "use": "Declare each boundary in its root module with use Boundary, explicit deps and a small exports list. Add the compile-time dependency and compiler below. A boundary always exports its root module; prefer explicit exports over exports: :all. Keep allowed calls aligned with the architecture.",
              "boundary": "Violations are compiler warnings unless compilation uses --warnings-as-errors. External OTP application calls are allowed by default; opt into selected app checks or strict mode when required. Dynamic calls such as apply/3 are not checked. Boundary enforces static module references; it does not isolate an agent’s files, processes or network access. Libraries can limit the compiler to dev/test as the compiler documentation describes.",
              "setup": "# In mix.exs deps/0:\n{:boundary, \"~> 0.11\", runtime: false}\n\n# In mix.exs project/0:\ncompilers: [:boundary] ++ Mix.compilers()\n\n# In each domain root module (adapt the names):\nuse Boundary, deps: [MyApp.Accounts], exports: []",
              "check": "Run mix deps.get, then mix compile --warnings-as-errors in the environments your CI checks. Add a temporary forbidden static call to confirm the check fails, remove it, and rerun successfully. The dependency alone does not enforce boundaries.",
              "sources": [
                [
                  "Boundary reference",
                  "https://boundary.hexdocs.pm/Boundary.html"
                ],
                [
                  "Boundary installation",
                  "https://boundary.hexdocs.pm/readme.html"
                ],
                [
                  "Compiler and CI enforcement",
                  "https://boundary.hexdocs.pm/Mix.Tasks.Compile.Boundary.html"
                ]
              ]
            },
            {
              "id": "ai-development",
              "job": "AI-assisted Phoenix development",
              "tool": "Tidewave",
              "when": "A coding agent needs runtime context from a Phoenix application during development.",
              "use": "Add the Tidewave Phoenix integration as a development-only dependency and plug. Connect your coding agent to inspect logs, evaluate code, query the development database and read installed-version documentation.",
              "boundary": "This gives the agent access to the running development application. Use a trusted local environment and keep the dependency and endpoint out of production; this is tooling for building the app, not an AI feature for its users.",
              "sources": [
                [
                  "Tidewave Phoenix",
                  "https://github.com/tidewave-ai/tidewave_phoenix"
                ]
              ]
            }
          ]
        },
        {
          "id": "optional-architecture",
          "title": "A different application architecture",
          "items": [
            {
              "id": "ash-architecture",
              "job": "Declarative business applications",
              "tool": "Ash",
              "when": "Resources, actions, policies and derived APIs form a substantial part of the application.",
              "use": "Evaluate Ash early as the domain layer beneath Phoenix, with only the extensions the product needs.",
              "boundary": "This changes how business logic is modeled. It is an architectural choice, not a small utility to install beside an unchanged contexts design.",
              "sources": [
                [
                  "What is Ash?",
                  "https://hexdocs.pm/ash/what-is-ash.html"
                ]
              ]
            },
            {
              "id": "agent-runtime",
              "job": "Stateful agent workflows",
              "tool": "Jido 2.x",
              "when": "The application needs cooperating agents that choose or coordinate steps, share reusable actions and react to routed signals.",
              "use": "Model the system with agents, actions, signals and directives, and run long-lived agents through Jido's supervised runtime. AI is optional; add Jido.AI only when decisions need a language model.",
              "boundary": "Prefer a plain GenServer, durable Oban jobs or an explicit function pipeline when behavior is fixed and simple. Supervision and in-memory state alone do not preserve progress across restarts; choose and verify persistence and resume behavior.",
              "sources": [
                [
                  "Jido documentation",
                  "https://hexdocs.pm/jido/readme.html"
                ],
                [
                  "Jido ecosystem",
                  "https://jido.run/ecosystem"
                ]
              ]
            }
          ]
        }
      ],
      "specialist_tracks": [
        {
          "tool": "Nerves",
          "job": "Embedded devices and firmware",
          "url": "https://hexdocs.pm/nerves/getting-started.html"
        },
        {
          "tool": "Membrane",
          "job": "Audio, video and streaming pipelines",
          "url": "https://membrane.stream/"
        }
      ]
    },
    "runtime": {
      "title": "OTP, concurrency and performance",
      "reviewed": "2026-10-06",
      "intro": "Start with functions. Add processes for state ownership, concurrency or lifecycle. Choose shared storage by its read/write pattern; measure before changing the architecture.",
      "status": "Built-in Elixir and Erlang tools need no extra package. Follow the installed Elixir/OTP documentation; node-local memory and supervision do not provide durable storage.",
      "groups": [
        {
          "id": "runtime-work",
          "title": "Computation and process state",
          "items": [
            {
              "id": "runtime-functions",
              "job": "Ordinary computation",
              "tool": "Functions + Enum",
              "when": "The work is business logic or a transformation that needs no independent lifecycle.",
              "boundary": "Keep it in functions. Adding a process adds messaging and coordination costs; it does not make a function faster.",
              "sources": [
                [
                  "Functions + Enum documentation",
                  "https://hexdocs.pm/elixir/Enum.html"
                ]
              ]
            },
            {
              "id": "runtime-stream",
              "job": "Lazy processing",
              "tool": "Stream + File.stream!",
              "when": "A file or collection should be processed incrementally, without building every intermediate result in memory.",
              "boundary": "Streams are lazy: enumerate them to run the work. They are not parallel; collecting all results can still exhaust memory.",
              "sources": [
                [
                  "Stream + File.stream! documentation",
                  "https://hexdocs.pm/elixir/Stream.html"
                ]
              ]
            },
            {
              "id": "runtime-task",
              "job": "One concurrent operation",
              "tool": "Task",
              "when": "Short-lived work can run alongside its caller, such as fetching independent inputs.",
              "boundary": "Task.async links failures to the caller. Await the result and define timeout/cleanup behavior. Tasks do not survive node restarts as durable jobs.",
              "sources": [
                [
                  "Task documentation",
                  "https://hexdocs.pm/elixir/Task.html"
                ]
              ]
            },
            {
              "id": "runtime-task-supervisor",
              "job": "Supervised concurrent work",
              "tool": "Task.Supervisor",
              "when": "Tasks need an application-owned supervisor or must fail independently of the requesting process.",
              "boundary": "async still links to the caller; choose async_nolink when isolation is required and handle results/exits. max_children rejects excess starts; it is not a waiting queue or a rate limiter.",
              "sources": [
                [
                  "Task.Supervisor documentation",
                  "https://hexdocs.pm/elixir/Task.Supervisor.html"
                ]
              ]
            },
            {
              "id": "runtime-async-stream",
              "job": "Bounded parallel batches",
              "tool": "Task.async_stream",
              "when": "Independent items can be processed concurrently with a bounded number of tasks.",
              "boundary": "Enumerate the stream; set concurrency and timeout policy. ordered: false avoids buffering for input order when order is unnecessary. Task.Supervisor.async_stream_nolink adds supervised, unlinked task execution.",
              "sources": [
                [
                  "Task.async_stream documentation",
                  "https://hexdocs.pm/elixir/Task.html#async_stream/3"
                ]
              ]
            },
            {
              "id": "runtime-agent",
              "job": "Simple shared process state",
              "tool": "Agent",
              "when": "One process needs to hold a small value accessed through get/update functions.",
              "boundary": "This is Elixir Agent, not an AI agent. Operations run serially inside its process. Choose GenServer for messages, timers or a richer lifecycle; ETS for concurrent table access.",
              "sources": [
                [
                  "Agent documentation",
                  "https://hexdocs.pm/elixir/Agent.html"
                ]
              ]
            },
            {
              "id": "runtime-genserver",
              "job": "State and coordination",
              "tool": "GenServer",
              "when": "A long-lived process owns state or coordinates a resource, for example a connection or session.",
              "boundary": "Callbacks execute serially. Keep slow work out of callbacks when responsiveness matters; call timeouts do not cancel server work. In-memory state needs a separate persistence strategy.",
              "sources": [
                [
                  "GenServer documentation",
                  "https://hexdocs.pm/elixir/GenServer.html"
                ]
              ]
            },
            {
              "id": "runtime-statem",
              "job": "Explicit runtime transitions",
              "tool": ":gen_statem",
              "when": "A protocol or connection has distinct states, state-specific events and transition/timeout rules.",
              "boundary": "Use functions or a data model for ordinary business statuses. The behaviour models a running process; supervision does not make its state durable.",
              "sources": [
                [
                  ":gen_statem documentation",
                  "https://www.erlang.org/doc/apps/stdlib/gen_statem.html"
                ]
              ]
            }
          ]
        },
        {
          "id": "runtime-lifecycle",
          "title": "Lifecycle, naming and coordination",
          "items": [
            {
              "id": "runtime-supervisor",
              "job": "Application lifecycle",
              "tool": "Application + Supervisor",
              "when": "Long-running children must start in order and recover according to their dependencies.",
              "boundary": "Choose restart and shutdown policies deliberately. Restarts do not restore lost state, and terminate callbacks are not a durability guarantee. Slow child initialization delays later children.",
              "sources": [
                [
                  "Application + Supervisor documentation",
                  "https://hexdocs.pm/elixir/Supervisor.html"
                ]
              ]
            },
            {
              "id": "runtime-dynamic-supervisor",
              "job": "Workers created at runtime",
              "tool": "DynamicSupervisor",
              "when": "The application starts and stops workers as demand changes, such as one worker per active room.",
              "boundary": "Use Registry for discovery. A restarted supervisor does not remember the former dynamic child set; reconcile it from an authoritative source when required.",
              "sources": [
                [
                  "DynamicSupervisor documentation",
                  "https://hexdocs.pm/elixir/DynamicSupervisor.html"
                ]
              ]
            },
            {
              "id": "runtime-registry",
              "job": "Find local processes",
              "tool": "Registry",
              "when": "Processes need names based on runtime keys, such as tenant/session IDs, or local duplicate-key dispatch.",
              "boundary": "Use :via tuples rather than creating atoms from user input. Registry is node-local; it is neither cluster discovery nor durable storage.",
              "sources": [
                [
                  "Registry documentation",
                  "https://hexdocs.pm/elixir/Registry.html"
                ]
              ]
            },
            {
              "id": "runtime-partition",
              "job": "Reduce supervisor contention",
              "tool": "PartitionSupervisor",
              "when": "Measurements show one process, such as a DynamicSupervisor or Task.Supervisor, bottlenecking concurrent starts.",
              "boundary": "Route work across partitioned children. This reduces contention in that layer; it does not parallelize a single worker or distribute work across nodes.",
              "sources": [
                [
                  "PartitionSupervisor documentation",
                  "https://hexdocs.pm/elixir/PartitionSupervisor.html"
                ]
              ]
            },
            {
              "id": "runtime-process",
              "job": "Observe failure and schedule messages",
              "tool": "Process monitors + timers",
              "when": "A process must observe another process exiting or receive a delayed message.",
              "boundary": "Monitors report DOWN without coupling failures; links couple lifecycles. Process.send_after schedules a message, not a durable job. Account for stale timer messages and mailbox growth.",
              "sources": [
                [
                  "Process monitors + timers documentation",
                  "https://hexdocs.pm/elixir/Process.html"
                ]
              ]
            },
            {
              "id": "runtime-inspection",
              "job": "Inspect a running process",
              "tool": ":sys + Process.info",
              "when": "A running server has unexpected state, memory growth or a growing mailbox.",
              "boundary": "Use :sys for OTP process state and bounded debugging; Process.info reports process memory and queue length. Inspecting full state can expose sensitive values and allocate large results. These tools diagnose behavior; they do not repair it.",
              "sources": [
                [
                  "OTP sys",
                  "https://www.erlang.org/doc/apps/stdlib/sys.html"
                ],
                [
                  "Process.info",
                  "https://hexdocs.pm/elixir/Process.html#info/2"
                ]
              ]
            }
          ]
        },
        {
          "id": "runtime-storage",
          "title": "Shared data and hot paths",
          "items": [
            {
              "id": "runtime-ets",
              "job": "Mutable shared tables",
              "tool": ":ets",
              "when": "Many processes need concurrent lookups or updates to node-local in-memory data.",
              "boundary": "Choose table type, ownership and access rights. Tune read/write concurrency against measurements. The table normally dies with its owner; TTL, invalidation and durability are separate.",
              "sources": [
                [
                  ":ets documentation",
                  "https://www.erlang.org/doc/apps/stdlib/ets.html"
                ]
              ]
            },
            {
              "id": "runtime-persistent-term",
              "job": "Read-mostly shared values",
              "tool": ":persistent_term",
              "when": "Many processes repeatedly read a value that changes rarely, such as a static lookup or a stable resource reference. A stable :counters reference can be stored once while the counter array itself is updated.",
              "boundary": "Reads avoid locks and copying the value onto the caller heap. Writes copy the key table; replacing/deleting complex terms triggers a node-wide GC scan. Avoid request-time updates and expiring caches. Persistent means VM lifetime, not disk durability.",
              "sources": [
                [
                  ":persistent_term documentation",
                  "https://www.erlang.org/doc/apps/erts/persistent_term.html"
                ]
              ]
            },
            {
              "id": "runtime-counters",
              "job": "Frequent shared increments",
              "tool": ":counters",
              "when": "Many processes update numeric metrics without routing each increment through one server.",
              "boundary": "write_concurrency favors add/sub throughput with weaker read consistency. Choose atomics mode when those semantics do not fit. Fixed-width integers wrap; this is node-local memory, not an accounting ledger.",
              "sources": [
                [
                  ":counters documentation",
                  "https://www.erlang.org/doc/apps/erts/counters.html"
                ]
              ]
            },
            {
              "id": "runtime-atomics",
              "job": "Atomic numeric coordination",
              "tool": ":atomics",
              "when": "An integer operation needs an atomic result or compare-and-exchange, such as claiming a numeric slot.",
              "boundary": "Individual operations are atomic and mutually ordered. A sequence of operations is not a transaction. Integers wrap on overflow, and these values are not cluster-wide.",
              "sources": [
                [
                  ":atomics documentation",
                  "https://www.erlang.org/doc/apps/erts/atomics.html"
                ]
              ]
            },
            {
              "id": "runtime-queue",
              "job": "FIFO data structure",
              "tool": ":queue",
              "when": "A process holds a FIFO buffer and repeated list appends would be costly.",
              "boundary": "A queue is a data structure, not a worker, backpressure mechanism or persistent job system. Bound its size and define overload behavior.",
              "sources": [
                [
                  ":queue documentation",
                  "https://www.erlang.org/doc/apps/stdlib/queue.html"
                ]
              ]
            },
            {
              "id": "runtime-iodata",
              "job": "Build output from fragments",
              "tool": "iodata",
              "when": "Output combines many byte fragments and its destination accepts iodata.",
              "boundary": "Keep binaries and byte lists nested until a binary is required. Avoid repeated copying; benchmark the actual construction because BEAM binary appends can also be optimized.",
              "sources": [
                [
                  "iodata documentation",
                  "https://hexdocs.pm/elixir/IO.html#module-io-data"
                ]
              ]
            },
            {
              "id": "runtime-binary",
              "job": "Control retained binary memory",
              "tool": ":binary",
              "when": "Small slices of large payloads live in process state or another long-lived store.",
              "boundary": "Inspect referenced_byte_size. Copy selected retained slices when they pin a much larger binary; copying every binary adds allocation and work.",
              "sources": [
                [
                  ":binary documentation",
                  "https://www.erlang.org/doc/apps/stdlib/binary.html"
                ]
              ]
            }
          ]
        }
      ],
      "pipeline_ids": [
        "genstage",
        "flow",
        "ingestion"
      ],
      "notes": [
        {
          "title": "Choose durability separately",
          "body": "Use Task for disposable work and Oban for application jobs that need persistence and retries. GenStage and Broadway regulate ingestion; source acknowledgements and retry-safe handlers determine delivery behavior.",
          "sources": [
            [
              "Oban",
              "https://hexdocs.pm/oban/Oban.html"
            ]
          ]
        },
        {
          "title": "Measure the limiting resource",
          "body": "Track latency, throughput, memory, mailbox growth, database queries and pool waits on representative traffic. Extra tasks can overload a database or an upstream API. Use Benchee for code comparisons and profilers to find where work accumulates.",
          "sources": [
            [
              "Benchee",
              "https://hexdocs.pm/benchee/readme.html"
            ],
            [
              "OTP profiling",
              "https://www.erlang.org/doc/apps/tools/tprof.html"
            ]
          ]
        },
        {
          "title": "Stream database exports deliberately",
          "body": "Use Ecto.Repo.stream for incremental row processing when the adapter supports it. SQL adapters require enumeration inside a transaction. Long streams occupy a database connection; batching and collecting results can change memory use.",
          "sources": [
            [
              "Ecto.Repo.stream",
              "https://hexdocs.pm/ecto/Ecto.Repo.html#c:stream/2"
            ]
          ]
        }
      ]
    },
    "app-types": {
      "reviewed": "2026-10-11",
      "title": "Choose an Elixir starting point",
      "status": "Choose tools by application requirements and preserve existing project decisions.",
      "types": [
        {
          "id": "command-line",
          "name": "Command-line tool",
          "short": "Developer tools, local automation and terminal applications.",
          "start": "Mix + OptionParser",
          "command": "mix new my_tool",
          "explanation": "Keep command parsing separate from the modules that perform the work. OptionParser handles flags; the application owns help text, subcommands, output and exit codes.",
          "boundary": "An escript needs Erlang/OTP on the target machine. Evaluate Burrito when distributing a bundled executable to users without that runtime, and test each supported OS and architecture.",
          "sources": [
            [
              "Mix projects",
              "https://hexdocs.pm/mix/Mix.Tasks.New.html"
            ],
            [
              "OptionParser",
              "https://hexdocs.pm/elixir/OptionParser.html"
            ],
            [
              "Escript packaging",
              "https://hexdocs.pm/mix/Mix.Tasks.Escript.Build.html"
            ],
            [
              "Burrito",
              "https://hexdocs.pm/burrito/readme.html"
            ]
          ]
        },
        {
          "id": "library-project",
          "name": "Library",
          "short": "Reusable modules used by other Elixir applications.",
          "start": "Mix",
          "command": "mix new my_library",
          "explanation": "Define a small public module API, examples and tests. Generate documentation with ExDoc; publish to Hex if the package is intended for reuse outside the project. Add a supervision tree only if the library owns long-lived processes.",
          "boundary": "Choose supported Elixir/OTP versions and dependency constraints for consumers. A library does not need an HTTP server or database unless that is its purpose.",
          "sources": [
            [
              "Mix projects",
              "https://hexdocs.pm/mix/Mix.Tasks.New.html"
            ],
            [
              "ExDoc",
              "https://hexdocs.pm/ex_doc/readme.html"
            ],
            [
              "Publishing to Hex",
              "https://hexdocs.pm/hex/Mix.Tasks.Hex.Publish.html"
            ]
          ]
        },
        {
          "id": "service-project",
          "name": "Background service",
          "short": "Workers, integrations and processes that keep running.",
          "start": "Mix + OTP supervision",
          "command": "mix new my_service --sup",
          "explanation": "Use supervision for process lifecycles and recovery. Keep calculations and business operations in ordinary modules; introduce stateful processes where they are needed.",
          "boundary": "Supervision does not persist work. Choose durable storage or a queue when recovery requires it. Add Plug + Bandit for a small HTTP interface if needed; use Mix releases for deployment.",
          "sources": [
            [
              "Supervision",
              "https://hexdocs.pm/elixir/Supervisor.html"
            ],
            [
              "Bandit",
              "https://hexdocs.pm/bandit/Bandit.html"
            ],
            [
              "Mix releases",
              "https://hexdocs.pm/mix/Mix.Tasks.Release.html"
            ]
          ]
        },
        {
          "id": "web-project",
          "name": "Web application or API",
          "short": "Browser products, customer portals and HTTP APIs.",
          "start": "Phoenix",
          "command": "mix phx.new my_web_app",
          "explanation": "Use Phoenix for routing, controllers and web application conventions. Add LiveView for server-driven interactive screens; an API-only product does not need a browser UI.",
          "boundary": "Choose persistence from the product's needs. PostgreSQL + Ecto is our recommendation for shared relational web data, not a prerequisite for every Elixir program. Review generator options for API-only or database-free applications.",
          "sources": [
            [
              "Phoenix generator options",
              "https://hexdocs.pm/phoenix/Mix.Tasks.Phx.New.html"
            ],
            [
              "Phoenix application guide",
              "https://elixir.menu/#stack"
            ],
            [
              "Phoenix setup recipe",
              "https://elixir.menu/setup.md"
            ]
          ],
          "start_note": "LiveView for interactive screens"
        },
        {
          "id": "data-project",
          "name": "Data analysis or machine learning",
          "short": "Notebooks, data transformations and numerical models.",
          "start": "Livebook; Mix for the application",
          "command": null,
          "explanation": "Use Livebook for interactive exploration, Explorer for tabular data, and Nx for numerical computation. Add Axon when defining or training neural networks is the requirement. Move reusable work into a Mix application when it needs a maintained API, tests or a service lifecycle.",
          "boundary": "Hosted model calls, local inference and training are different requirements. Check native backends, memory and hardware before selecting a deployment environment.",
          "sources": [
            [
              "Livebook",
              "https://hexdocs.pm/livebook/readme.html"
            ],
            [
              "Nx",
              "https://hexdocs.pm/nx/Nx.html"
            ],
            [
              "Explorer",
              "https://hexdocs.pm/explorer/Explorer.html"
            ],
            [
              "Axon",
              "https://github.com/elixir-nx/axon"
            ]
          ]
        },
        {
          "id": "device-project",
          "name": "Embedded device",
          "short": "Hardware products, sensors and deployed Linux devices.",
          "start": "Nerves",
          "command": null,
          "explanation": "Start from the Nerves installation and project guides for the target hardware. Build firmware with the system and libraries that the device requires.",
          "boundary": "Hardware support, firmware updates and access to the device determine the setup. A web interface can be added when useful; it does not define the device application.",
          "sources": [
            [
              "Nerves getting started",
              "https://hexdocs.pm/nerves/getting-started.html"
            ]
          ]
        },
        {
          "id": "media-project",
          "name": "Audio or video system",
          "short": "Streaming, transcoding and realtime media processing.",
          "start": "Membrane + an OTP application",
          "command": null,
          "explanation": "Build media pipelines with the source, processing and output elements needed for the product. Phoenix may provide a separate control interface if there is a web UI.",
          "boundary": "Protocols, codecs and native dependencies determine the pipeline and deployment. Ordinary file uploads do not require a media framework.",
          "sources": [
            [
              "Membrane",
              "https://membrane.stream/"
            ]
          ]
        }
      ],
      "decisions": [
        {
          "title": "Choose the interface",
          "body": "A CLI, reusable library, running service and web application have different starting points. Keep reusable business operations independent of the interface."
        },
        {
          "title": "Choose persistence when needed",
          "body": "Files can suit local configuration. SQLite can suit local relational data; PostgreSQL can suit a shared database. Ecto can be used independently of Phoenix. Disposable caches and process state are separate decisions."
        },
        {
          "title": "Choose the deployment target",
          "body": "A developer script, downloadable executable, deployed service and firmware image need different packaging. Decide which runtimes and native libraries can be present on the target."
        }
      ],
      "agent_steps": [
        {
          "title": "Read the existing project",
          "body": "First read https://elixir.menu/agents.md for direct Markdown and JSON retrieval. In the application, start with README.md, mix.exs, mix.lock and runtime pins. Preserve its architecture and follow its maintained workflow. Do not regenerate an existing project."
        },
        {
          "title": "State the application requirements",
          "body": "Identify the interface, whether work must keep running, persistence needs, and deployment target. Choose one of the starting points above; a product may combine more than one. Do not assume Phoenix or PostgreSQL."
        },
        {
          "title": "Check the environment",
          "body": "Check elixir --version and mix --version. Follow the official Elixir installation guide if prerequisites are missing; respect existing version management. Install a web generator, database or specialist toolchain only for the selected path."
        },
        {
          "title": "Generate in an empty destination",
          "body": "Use the selected starter and its current documentation. The commands above are scaffolding examples, not complete applications. For the Phoenix web path, follow setup.md; for Nerves and Membrane, follow their own project guides."
        },
        {
          "title": "Select libraries by requirement",
          "body": "Read runtime.md for built-in state, concurrency and storage choices. Read optional.md and add only tools whose use cases match the product. Check versions, native dependencies and deployment constraints. Record the reasons and setup in the project’s maintained documentation. Preserve its existing agent-guidance conventions."
        },
        {
          "title": "Verify the actual application",
          "body": "Run formatting, compilation and relevant tests. Exercise the real interface: CLI help, output and exit codes; service startup and recovery; HTTP behavior; or the target device. Verify the packaged artifact on the intended target and report what remains untested."
        }
      ],
      "shared_checks": {
        "title": "Checks for every Mix project",
        "body": "Use mix format for formatting, compile with warnings as errors, and test behavior with ExUnit. Add Credo for useful static feedback. Use Boundary when domains or layers need enforced module dependency rules; declare allowed dependencies and exports, enable its Mix compiler, and fail CI on violations. Keep the same checks in local work and CI.",
        "sources": [
          [
            "Mix formatter",
            "https://hexdocs.pm/mix/Mix.Tasks.Format.html"
          ],
          [
            "ExUnit",
            "https://hexdocs.pm/ex_unit/ExUnit.html"
          ],
          [
            "Credo",
            "https://hexdocs.pm/credo/overview.html"
          ],
          [
            "Boundary setup and limits",
            "https://elixir.menu/#boundary-checks"
          ]
        ]
      },
      "agent_guidance": {
        "title": "Dependency guidance for agents",
        "body": "Use usage_rules in any Mix project to collect dependency-authored instructions and find documentation. Follow the project’s existing conventions for where agents read guidance, keep Phoenix’s generated guidance when using its default setup, and refresh dependency rules when versions change.",
        "url": "https://hexdocs.pm/usage_rules/readme.html",
        "label": "usage_rules documentation"
      }
    }
  },
  "extended_reference_owner": "https://elixir.menu/",
  "agent_workflow": [
    "Read README, dependency manifests, lockfiles, runtime pins and the maintained project workflow before changing anything. Preserve an existing stack unless reconsidering it is part of the request.",
    "Use this guide to select tools by requirement. Resolve compatible versions from official and installed-version documentation; record the actual lockfile and runtime versions.",
    "Create a plan for schema changes, identity, authorization, external effects and deployment. A library choice does not complete its integration.",
    "Run relevant checks after configuration. Exercise the real interface, failure paths and packaged artifact on the intended target. Report commands, versions, results and untested behavior.",
    "On failure, retain the command and diagnostic, fix the failing prerequisite and resume there. Do not regenerate over existing work or suppress failures to make a check green."
  ],
  "selection": {
    "native_ui": false,
    "static_hosting": false,
    "offline_client_candidate": false
  },
  "coverage_note": "Tools are curated starting points for this application role. Exact versions, integrations and offline behavior require project-specific qualification."
}
