Repository: phoenixframework/phoenix
Pull request #5574 (2023-09-11 10:12:01)

Bump actions/checkout from 3 to 4
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/actions/checkout/releases">actions/checkout's releases</a>.</em></p>
<blockquote>
<h2>v4.0.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Update default runtime to node20 by <a href="https://github.com/takost"><code>@​takost</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/1436">actions/checkout#1436</a></li>
<li>Support fetching without the --progress option by <a href="https://github.com/simonbaird"><code>@​simonbaird</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/1067">actions/checkout#1067</a></li>
<li>Release 4.0.0 by <a href="https://github.com/takost"><code>@​takost</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/1447">actions/checkout#1447</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/takost"><code>@​takost</code></a> made their first contribution in <a href="https://redirect.github.com/actions/checkout/pull/1436">actions/checkout#1436</a></li>
<li><a href="https://github.com/simonbaird"><code>@​simonbaird</code></a> made their first contribution in <a href="https://redirect.github.com/actions/checkout/pull/1067">actions/checkout#1067</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a href="https://github.com/actions/checkout/compare/v3...v4.0.0">https://github.com/actions/checkout/compare/v3...v4.0.0</a></p>
<h2>v3.6.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Mark test scripts with Bash'isms to be run via Bash by <a href="https://github.com/dscho"><code>@​dscho</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/1377">actions/checkout#1377</a></li>
<li>Add option to fetch tags even if fetch-depth &gt; 0 by <a href="https://github.com/RobertWieczoreck"><code>@​RobertWieczoreck</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/579">actions/checkout#579</a></li>
<li>Release 3.6.0 by <a href="https://github.com/luketomlinson"><code>@​luketomlinson</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/1437">actions/checkout#1437</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/RobertWieczoreck"><code>@​RobertWieczoreck</code></a> made their first contribution in <a href="https://redirect.github.com/actions/checkout/pull/579">actions/checkout#579</a></li>
<li><a href="https://github.com/luketomlinson"><code>@​luketomlinson</code></a> made their first contribution in <a href="https://redirect.github.com/actions/checkout/pull/1437">actions/checkout#1437</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a href="https://github.com/actions/checkout/compare/v3.5.3...v3.6.0">https://github.com/actions/checkout/compare/v3.5.3...v3.6.0</a></p>
<h2>v3.5.3</h2>
<h2>What's Changed</h2>
<ul>
<li>Fix: Checkout Issue in self hosted runner due to faulty submodule check-ins by <a href="https://github.com/megamanics"><code>@​megamanics</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/1196">actions/checkout#1196</a></li>
<li>Fix typos found by codespell by <a href="https://github.com/DimitriPapadopoulos"><code>@​DimitriPapadopoulos</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/1287">actions/checkout#1287</a></li>
<li>Add support for sparse checkouts by <a href="https://github.com/dscho"><code>@​dscho</code></a> and <a href="https://github.com/dfdez"><code>@​dfdez</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/1369">actions/checkout#1369</a></li>
<li>Release v3.5.3 by <a href="https://github.com/TingluoHuang"><code>@​TingluoHuang</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/1376">actions/checkout#1376</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/megamanics"><code>@​megamanics</code></a> made their first contribution in <a href="https://redirect.github.com/actions/checkout/pull/1196">actions/checkout#1196</a></li>
<li><a href="https://github.com/DimitriPapadopoulos"><code>@​DimitriPapadopoulos</code></a> made their first contribution in <a href="https://redirect.github.com/actions/checkout/pull/1287">actions/checkout#1287</a></li>
<li><a href="https://github.com/dfdez"><code>@​dfdez</code></a> made their first contribution in <a href="https://redirect.github.com/actions/checkout/pull/1369">actions/checkout#1369</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a href="https://github.com/actions/checkout/compare/v3...v3.5.3">https://github.com/actions/checkout/compare/v3...v3.5.3</a></p>
<h2>v3.5.2</h2>
<h2>What's Changed</h2>
<ul>
<li>Fix: Use correct API url / endpoint in GHES by <a href="https://github.com/fhammerl"><code>@​fhammerl</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/1289">actions/checkout#1289</a> based on <a href="https://redirect.github.com/actions/checkout/issues/1286">#1286</a> by <a href="https://github.com/1newsr"><code>@​1newsr</code></a></li>
</ul>
<p><strong>Full Changelog</strong>: <a href="https://github.com/actions/checkout/compare/v3.5.1...v3.5.2">https://github.com/actions/checkout/compare/v3.5.1...v3.5.2</a></p>
<h2>v3.5.1</h2>
<h2>What's Changed</h2>
<ul>
<li>Improve checkout performance on Windows runners by upgrading <code>@​actions/github</code> dependency by <a href="https://github.com/BrettDong"><code>@​BrettDong</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/1246">actions/checkout#1246</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/BrettDong"><code>@​BrettDong</code></a> made their first contribution in <a href="https://redirect.github.com/actions/checkout/pull/1246">actions/checkout#1246</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a href="https://github.com/actions/checkout/blob/main/CHANGELOG.md">actions/checkout's changelog</a>.</em></p>
<blockquote>
<h1>Changelog</h1>
<h2>v4.0.0</h2>
<ul>
<li><a href="https://redirect.github.com/actions/checkout/pull/1067">Support fetching without the --progress option</a></li>
<li><a href="https://redirect.github.com/actions/checkout/pull/1436">Update to node20</a></li>
</ul>
<h2>v3.6.0</h2>
<ul>
<li><a href="https://redirect.github.com/actions/checkout/pull/1377">Fix: Mark test scripts with Bash'isms to be run via Bash</a></li>
<li><a href="https://redirect.github.com/actions/checkout/pull/579">Add option to fetch tags even if fetch-depth &gt; 0</a></li>
</ul>
<h2>v3.5.3</h2>
<ul>
<li><a href="https://redirect.github.com/actions/checkout/pull/1196">Fix: Checkout fail in self-hosted runners when faulty submodule are checked-in</a></li>
<li><a href="https://redirect.github.com/actions/checkout/pull/1287">Fix typos found by codespell</a></li>
<li><a href="https://redirect.github.com/actions/checkout/pull/1369">Add support for sparse checkouts</a></li>
</ul>
<h2>v3.5.2</h2>
<ul>
<li><a href="https://redirect.github.com/actions/checkout/pull/1289">Fix api endpoint for GHES</a></li>
</ul>
<h2>v3.5.1</h2>
<ul>
<li><a href="https://redirect.github.com/actions/checkout/pull/1246">Fix slow checkout on Windows</a></li>
</ul>
<h2>v3.5.0</h2>
<ul>
<li><a href="https://redirect.github.com/actions/checkout/pull/1237">Add new public key for known_hosts</a></li>
</ul>
<h2>v3.4.0</h2>
<ul>
<li><a href="https://redirect.github.com/actions/checkout/pull/1209">Upgrade codeql actions to v2</a></li>
<li><a href="https://redirect.github.com/actions/checkout/pull/1210">Upgrade dependencies</a></li>
<li><a href="https://redirect.github.com/actions/checkout/pull/1225">Upgrade <code>@​actions/io</code></a></li>
</ul>
<h2>v3.3.0</h2>
<ul>
<li><a href="https://redirect.github.com/actions/checkout/pull/1045">Implement branch list using callbacks from exec function</a></li>
<li><a href="https://redirect.github.com/actions/checkout/pull/1050">Add in explicit reference to private checkout options</a></li>
<li>[Fix comment typos (that got added in <a href="https://redirect.github.com/actions/checkout/issues/770">#770</a>)](<a href="https://redirect.github.com/actions/checkout/pull/1057">actions/checkout#1057</a>)</li>
</ul>
<h2>v3.2.0</h2>
<ul>
<li><a href="https://redirect.github.com/actions/checkout/pull/942">Add GitHub Action to perform release</a></li>
<li><a href="https://redirect.github.com/actions/checkout/pull/967">Fix status badge</a></li>
<li><a href="https://redirect.github.com/actions/checkout/pull/1002">Replace datadog/squid with ubuntu/squid Docker image</a></li>
<li><a href="https://redirect.github.com/actions/checkout/pull/964">Wrap pipeline commands for submoduleForeach in quotes</a></li>
<li><a href="https://redirect.github.com/actions/checkout/pull/1029">Update <code>@​actions/io</code> to 1.1.2</a></li>
<li><a href="https://redirect.github.com/actions/checkout/pull/1039">Upgrading version to 3.2.0</a></li>
</ul>
<h2>v3.1.0</h2>
<ul>
<li><a href="https://redirect.github.com/actions/checkout/pull/939">Use <code>@​actions/core</code> <code>saveState</code> and <code>getState</code></a></li>
<li><a href="https://redirect.github.com/actions/checkout/pull/922">Add <code>github-server-url</code> input</a></li>
</ul>
<h2>v3.0.2</h2>
<ul>
<li><a href="https://redirect.github.com/actions/checkout/pull/770">Add input <code>set-safe-directory</code></a></li>
</ul>
<h2>v3.0.1</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/actions/checkout/commit/3df4ab11eba7bda6032a0b82a6bb43b11571feac"><code>3df4ab1</code></a> Release 4.0.0 (<a href="https://redirect.github.com/actions/checkout/issues/1447">#1447</a>)</li>
<li><a href="https://github.com/actions/checkout/commit/8b5e8b768746b50394015010d25e690bfab9dfbc"><code>8b5e8b7</code></a> Support fetching without the --progress option (<a href="https://redirect.github.com/actions/checkout/issues/1067">#1067</a>)</li>
<li><a href="https://github.com/actions/checkout/commit/97a652b80035363df47baee5031ec8670b8878ac"><code>97a652b</code></a> Update default runtime to node20 (<a href="https://redirect.github.com/actions/checkout/issues/1436">#1436</a>)</li>
<li>See full diff in <a href="https://github.com/actions/checkout/compare/v3...v4">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/checkout&package-manager=github_actions&previous-version=3&new-version=4)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>


Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index fbf4c2ded..44b89e562 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -25,7 +25,7 @@ jobs:
 
     steps:
     - name: Checkout
-      uses: actions/checkout@v3
+      uses: actions/checkout@v4
 
     - name: Set up Elixir
       uses: erlef/setup-beam@v1
@@ -67,7 +67,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - name: Checkout
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
 
       - name: Restore deps and _build cache
         uses: actions/cache@v3
@@ -110,7 +110,7 @@ jobs:
           - elixir: 1.14.0
             otp: 24.3.4
     steps:
-      - uses: actions/checkout@v3
+      - uses: actions/checkout@v4
       - name: Download released earth
         run: "sudo /bin/sh -c 'wget https://github.com/earthly/earthly/releases/download/v0.5.16/earthly-linux-amd64 -O /usr/local/bin/earthly && chmod +x /usr/local/bin/earthly'"
       - name: Execute tests
diff --git a/guides/deployment/heroku.md b/guides/deployment/heroku.md
index 8d9cdf275..f34d434f2 100644
--- a/guides/deployment/heroku.md
+++ b/guides/deployment/heroku.md
@@ -152,19 +152,32 @@ Finally, note that since we are using multiple buildpacks, you might run into an
 
 Every new Phoenix project ships with a config file `config/runtime.exs` (formerly `config/prod.secret.exs`) which loads configuration and secrets from [environment variables](https://devcenter.heroku.com/articles/config-vars). This aligns well with Heroku best practices, so the only work left for us to do is to configure URLs and SSL.
 
-First let's tell Phoenix to use our Heroku URL and enforce we only use the SSL version of the website. Also, bind to the port requested by Heroku in the [`$PORT` environment variable](https://devcenter.heroku.com/articles/runtime-principles#web-servers). Find the url line in your `config/prod.exs`:
+First let's tell Phoenix enforce we only use the SSL version of the website. Find the endpoint config in your `config/runtime.exs`:
 
 ```elixir
-url: [host: "example.com", port: 80],
+config :scaffold, ScaffoldWeb.Endpoint,
+  url: [host: host, port: 443, scheme: "https"],
 ```
 
-... and replace it with this (don't forget to replace `mysterious-meadow-6277` with your application name):
+... and add `force_ssl`
 
 ```elixir
-url: [scheme: "https", host: "mysterious-meadow-6277.herokuapp.com", port: 443],
-force_ssl: [rewrite_on: [:x_forwarded_proto]],
+config :scaffold, ScaffoldWeb.Endpoint,
+  url: [host: host, port: 443, scheme: "https"],
+  force_ssl: [rewrite_on: [:x_forwarded_proto]],
 ```
 
+Then in your `config/runtime.exs` (formerly `config/prod.secret.exs`) and uncomment the `# ssl: true,` line in your repository configuration. It will look like this:
+
+```elixir
+config :hello, Hello.Repo,
+  ssl: true,
+  url: database_url,
+  pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10")
+```
+
+
+
 Then open up your `config/runtime.exs` (formerly `config/prod.secret.exs`) and uncomment the `# ssl: true,` line in your repository configuration. It will look like this:
 
 ```elixir
@@ -187,6 +200,12 @@ defmodule HelloWeb.Endpoint do
 end
 ```
 
+Also set the host in Heroku:
+
+```console
+$ heroku config:set PHX_HOST="mysterious-meadow-6277.herokuapp.com"
+```
+
 This ensures that any idle connections are closed by Phoenix before they reach Heroku's 55-second timeout window.
 
 ## Creating Environment Variables in Heroku
@@ -244,7 +263,7 @@ $ git commit -a -m "Use production config from Heroku ENV variables and decrease
 And deploy:
 
 ```console
-$ git push heroku master
+$ git push heroku main
 Counting objects: 55, done.
 Delta compression using up to 8 threads.
 Compressing objects: 100% (49/49), done.
diff --git a/installer/lib/mix/tasks/phx.new.ecto.ex b/installer/lib/mix/tasks/phx.new.ecto.ex
index 6b8673bb0..8b2addb47 100644
--- a/installer/lib/mix/tasks/phx.new.ecto.ex
+++ b/installer/lib/mix/tasks/phx.new.ecto.ex
@@ -48,7 +48,6 @@ defmodule Mix.Tasks.Phx.New.Ecto do
   @shortdoc "Creates a new Ecto project within an umbrella project"
 
   use Mix.Task
-  import Phx.New.Generator
 
   @impl true
   def run([]) do
@@ -56,8 +55,8 @@ defmodule Mix.Tasks.Phx.New.Ecto do
   end
 
   def run([path | _] = args) do
-    unless in_umbrella?(path) do
-      Mix.raise "The ecto task can only be run within an umbrella's apps directory"
+    unless Phx.New.Generator.in_umbrella?(path) do
+      Mix.raise("The ecto task can only be run within an umbrella's apps directory")
     end
 
     Mix.Tasks.Phx.New.run(args ++ ["--no-assets", "--ecto"], Phx.New.Ecto, :app_path)
diff --git a/installer/lib/mix/tasks/phx.new.ex b/installer/lib/mix/tasks/phx.new.ex
index b37d02a4f..e880431e1 100644
--- a/installer/lib/mix/tasks/phx.new.ex
+++ b/installer/lib/mix/tasks/phx.new.ex
@@ -156,8 +156,11 @@ defmodule Mix.Tasks.Phx.New do
         Mix.Tasks.Help.run(["phx.new"])
 
       {opts, [base_path | _]} ->
-        generator = if opts[:umbrella], do: Umbrella, else: Single
-        generate(base_path, generator, :project_path, opts)
+        if opts[:umbrella] do
+          generate(base_path, Umbrella, :project_path, opts)
+        else
+          generate(base_path, Single, :base_path, opts)
+        end
     end
   end
 
diff --git a/installer/lib/phx_new/generator.ex b/installer/lib/phx_new/generator.ex
index 5742fab10..71a9b9fa1 100644
--- a/installer/lib/phx_new/generator.ex
+++ b/installer/lib/phx_new/generator.ex
@@ -152,6 +152,7 @@ defmodule Phx.New.Generator do
 
   defp write_formatted!(file, contents) do
     formatted = contents |> IO.iodata_to_binary() |> Code.format_string!()
+    File.mkdir_p!(Path.dirname(file))
     File.write!(file, [formatted, ?\n])
   end
 
diff --git a/installer/lib/phx_new/single.ex b/installer/lib/phx_new/single.ex
index cc97514a1..9c6cffc67 100644
--- a/installer/lib/phx_new/single.ex
+++ b/installer/lib/phx_new/single.ex
@@ -4,12 +4,13 @@ defmodule Phx.New.Single do
   alias Phx.New.{Project}
 
   template(:new, [
-    {:eex, :project,
+    {:config, :project,
      "phx_single/config/config.exs": "config/config.exs",
      "phx_single/config/dev.exs": "config/dev.exs",
      "phx_single/config/prod.exs": "config/prod.exs",
      "phx_single/config/runtime.exs": "config/runtime.exs",
-     "phx_single/config/test.exs": "config/test.exs",
+     "phx_single/config/test.exs": "config/test.exs"},
+    {:eex, :web,
      "phx_single/lib/app_name/application.ex": "lib/:app/application.ex",
      "phx_single/lib/app_name.ex": "lib/:app.ex",
      "phx_web/controllers/error_json.ex": "lib/:lib_web_name/controllers/error_json.ex",
@@ -25,20 +26,20 @@ defmodule Phx.New.Single do
      "phx_single/test/test_helper.exs": "test/test_helper.exs",
      "phx_test/controllers/error_json_test.exs":
        "test/:lib_web_name/controllers/error_json_test.exs"},
-    {:keep, :project,
+    {:keep, :web,
      "phx_web/controllers": "lib/:lib_web_name/controllers",
      "phx_test/controllers": "test/:lib_web_name/controllers"}
   ])
 
   template(:gettext, [
-    {:eex, :project,
+    {:eex, :web,
      "phx_gettext/gettext.ex": "lib/:lib_web_name/gettext.ex",
      "phx_gettext/en/LC_MESSAGES/errors.po": "priv/gettext/en/LC_MESSAGES/errors.po",
      "phx_gettext/errors.pot": "priv/gettext/errors.pot"}
   ])
 
   template(:html, [
-    {:eex, :project,
+    {:eex, :web,
      "phx_web/controllers/error_html.ex": "lib/:lib_web_name/controllers/error_html.ex",
      "phx_test/controllers/error_html_test.exs":
        "test/:lib_web_name/controllers/error_html_test.exs",
@@ -101,15 +102,19 @@ defmodule Phx.New.Single do
     {:eex, :app, "phx_mailer/lib/app_name/mailer.ex": "lib/:app/mailer.ex"}
   ])
 
-  def prepare_project(%Project{app: app} = project) when not is_nil(app) do
-    %Project{project | project_path: project.base_path}
+  def prepare_project(%Project{app: app, base_path: base_path} = project) when not is_nil(app) do
+    if in_umbrella?(base_path) do
+      %Project{project | in_umbrella?: true, project_path: Path.dirname(Path.dirname(base_path))}
+    else
+      %Project{project | in_umbrella?: false, project_path: base_path}
+    end
     |> put_app()
     |> put_root_app()
     |> put_web_app()
   end
 
   defp put_app(%Project{base_path: base_path} = project) do
-    %Project{project | in_umbrella?: in_umbrella?(base_path), app_path: base_path}
+    %Project{project | app_path: base_path}
   end
 
   defp put_root_app(%Project{app: app, opts: opts} = project) do
@@ -126,7 +131,7 @@ defmodule Phx.New.Single do
       | web_app: app,
         lib_web_name: "#{app}_web",
         web_namespace: Module.concat(["#{project.root_mod}Web"]),
-        web_path: project.project_path
+        web_path: project.base_path
     }
   end
 
diff --git a/installer/templates/phx_assets/tailwind.config.js b/installer/templates/phx_assets/tailwind.config.js
index 856af57c6..38f3d1106 100644
--- a/installer/templates/phx_assets/tailwind.config.js
+++ b/installer/templates/phx_assets/tailwind.config.js
@@ -8,8 +8,8 @@ const path = require("path")
 module.exports = {
   content: [
     "./js/**/*.js",
-    "../lib/*_web.ex",
-    "../lib/*_web/**/*.*ex"
+    "../lib/<%= @lib_web_name || @app_name %>.ex",
+    "../lib/<%= @lib_web_name || @app_name %>/**/*.ex"
   ],
   theme: {
     extend: {
diff --git a/installer/templates/phx_single/config/config.exs b/installer/templates/phx_single/config/config.exs
index 990ec27a4..c80f690f2 100644
--- a/installer/templates/phx_single/config/config.exs
+++ b/installer/templates/phx_single/config/config.exs
@@ -38,7 +38,7 @@ config :esbuild,
   default: [
     args:
       ~w(js/app.js --bundle --target=es2017 --outdir=../priv/static/assets --external:/fonts/* --external:/images/*),
-    cd: Path.expand("../assets", __DIR__),
+    cd: Path.expand("..<%= if @in_umbrella, do: "/apps/#{@app_name}" %>/assets", __DIR__),
     env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)}
   ]<% end %><%= if @css do %>
 
@@ -51,7 +51,7 @@ config :tailwind,
       --input=css/app.css
       --output=../priv/static/assets/app.css
     ),
-    cd: Path.expand("../assets", __DIR__)
+    cd: Path.expand("..<%= if @in_umbrella, do: "/apps/#{@app_name}" %>/assets", __DIR__),
   ]<% end %>
 
 # Configures Elixir's Logger
diff --git a/installer/templates/phx_web/components/core_components.ex b/installer/templates/phx_web/components/core_components.ex
index f9f817bb2..7490d6189 100644
--- a/installer/templates/phx_web/components/core_components.ex
+++ b/installer/templates/phx_web/components/core_components.ex
@@ -115,7 +115,7 @@ defmodule <%= @web_namespace %>.CoreComponents do
       phx-click={JS.push("lv:clear-flash", value: %{key: @kind}) |> hide("##{@id}")}
       role="alert"
       class={[
-        "fixed top-2 right-2 w-80 sm:w-96 z-50 rounded-lg p-3 ring-1",
+        "fixed top-2 right-2 mr-2 w-80 sm:w-96 z-50 rounded-lg p-3 ring-1",
         @kind == :info && "bg-emerald-50 text-emerald-800 ring-emerald-500 fill-cyan-900",
         @kind == :error && "bg-rose-50 text-rose-900 shadow-md ring-rose-500 fill-rose-900"
       ]}
diff --git a/lib/mix/phoenix/schema.ex b/lib/mix/phoenix/schema.ex
index 95584a031..670322864 100644
--- a/lib/mix/phoenix/schema.ex
+++ b/lib/mix/phoenix/schema.ex
@@ -36,8 +36,8 @@ defmodule Mix.Phoenix.Schema do
             route_prefix: nil,
             api_route_prefix: nil,
             migration_module: nil,
-            fixture_unique_functions: %{},
-            fixture_params: %{},
+            fixture_unique_functions: [],
+            fixture_params: [],
             prefix: nil
 
   @valid_types [
@@ -69,15 +69,15 @@ defmodule Mix.Phoenix.Schema do
   end
 
   def new(schema_name, schema_plural, cli_attrs, opts) do
-    ctx_app   = opts[:context_app] || Mix.Phoenix.context_app()
-    otp_app   = Mix.Phoenix.otp_app()
-    opts      = Keyword.merge(Application.get_env(otp_app, :generators, []), opts)
-    base      = Mix.Phoenix.context_base(ctx_app)
-    basename  = Phoenix.Naming.underscore(schema_name)
-    module    = Module.concat([base, schema_name])
-    repo      = opts[:repo] || Module.concat([base, "Repo"])
-    file      = Mix.Phoenix.context_lib_path(ctx_app, basename <> ".ex")
-    table     = opts[:table] || schema_plural
+    ctx_app = opts[:context_app] || Mix.Phoenix.context_app()
+    otp_app = Mix.Phoenix.otp_app()
+    opts = Keyword.merge(Application.get_env(otp_app, :generators, []), opts)
+    base = Mix.Phoenix.context_base(ctx_app)
+    basename = Phoenix.Naming.underscore(schema_name)
+    module = Module.concat([base, schema_name])
+    repo = opts[:repo] || Module.concat([base, "Repo"])
+    file = Mix.Phoenix.context_lib_path(ctx_app, basename <> ".ex")
+    table = opts[:table] || schema_plural
     {cli_attrs, uniques, redacts} = extract_attr_flags(cli_attrs)
     {assocs, attrs} = partition_attrs_and_assocs(module, attrs(cli_attrs))
     types = types(attrs)
@@ -96,11 +96,13 @@ defmodule Mix.Phoenix.Schema do
     collection = if schema_plural == singular, do: singular <> "_collection", else: schema_plural
     string_attr = string_attr(types)
     create_params = params(attrs, :create)
+
     default_params_key =
       case Enum.at(create_params, 0) do
         {key, _} -> key
         nil -> :some_field
       end
+
     fixture_unique_functions = fixture_unique_functions(singular, uniques, attrs)
 
     %Schema{
@@ -141,7 +143,7 @@ defmodule Mix.Phoenix.Schema do
       context_app: ctx_app,
       generate?: generate?,
       migration_module: migration_module(),
-      fixture_unique_functions: fixture_unique_functions,
+      fixture_unique_functions: Enum.sort(fixture_unique_functions),
       fixture_params: fixture_params(attrs, fixture_unique_functions),
       prefix: opts[:prefix]
     }
@@ -158,11 +160,12 @@ defmodule Mix.Phoenix.Schema do
   end
 
   def extract_attr_flags(cli_attrs) do
-    {attrs, uniques, redacts} = Enum.reduce(cli_attrs, {[], [], []}, fn attr, {attrs, uniques, redacts} ->
-      [attr_name | rest] = String.split(attr, ":")
-      attr_name = String.to_atom(attr_name)
-      split_flags(Enum.reverse(rest), attr_name, attrs, uniques, redacts)
-    end)
+    {attrs, uniques, redacts} =
+      Enum.reduce(cli_attrs, {[], [], []}, fn attr, {attrs, uniques, redacts} ->
+        [attr_name | rest] = String.split(attr, ":")
+        attr_name = String.to_atom(attr_name)
+        split_flags(Enum.reverse(rest), attr_name, attrs, uniques, redacts)
+      end)
 
     {Enum.reverse(attrs), uniques, redacts}
   end
@@ -174,7 +177,7 @@ defmodule Mix.Phoenix.Schema do
     do: split_flags(rest, name, attrs, uniques, [name | redacts])
 
   defp split_flags(rest, name, attrs, uniques, redacts),
-    do: {[Enum.join([name | Enum.reverse(rest)], ":") | attrs ], uniques, redacts}
+    do: {[Enum.join([name | Enum.reverse(rest)], ":") | attrs], uniques, redacts}
 
   @doc """
   Parses the attrs as received by generators.
@@ -247,8 +250,10 @@ defmodule Mix.Phoenix.Schema do
     end)
   end
 
-  def type_and_opts_for_schema({:enum, opts}), do: ~s|Ecto.Enum, values: #{inspect Keyword.get(opts, :values)}|
-  def type_and_opts_for_schema(other), do: inspect other
+  def type_and_opts_for_schema({:enum, opts}),
+    do: ~s|Ecto.Enum, values: #{inspect(Keyword.get(opts, :values))}|
+
+  def type_and_opts_for_schema(other), do: inspect(other)
 
   def maybe_redact_field(true), do: ", redact: true"
   def maybe_redact_field(false), do: ""
@@ -261,11 +266,13 @@ defmodule Mix.Phoenix.Schema do
     |> Map.fetch!(field)
     |> inspect_value(value)
   end
+
   defp inspect_value(:decimal, value), do: "Decimal.new(\"#{value}\")"
   defp inspect_value(_type, value), do: inspect(value)
 
   defp list_to_attr([key]), do: {String.to_atom(key), :string}
   defp list_to_attr([key, value]), do: {String.to_atom(key), String.to_atom(value)}
+
   defp list_to_attr([key, comp, value]) do
     {String.to_atom(key), {String.to_atom(comp), String.to_atom(value)}}
   end
@@ -274,55 +281,103 @@ defmodule Mix.Phoenix.Schema do
 
   defp type_to_default(key, t, :create) do
     case t do
-        {:array, type} -> build_array_values(type, :create)
-        {:enum, values} -> build_enum_values(values, :create)
-        :integer        -> 42
-        :float          -> 120.5
-        :decimal        -> "120.5"
-        :boolean        -> true
-        :map            -> %{}
-        :text           -> "some #{key}"
-        :date           -> Date.add(Date.utc_today(), -1)
-        :time           -> ~T[14:00:00]
-        :time_usec      -> ~T[14:00:00.000000]
-        :uuid           -> "7488a646-e31f-11e4-aace-600308960662"
-        :utc_datetime   -> DateTime.add(build_utc_datetime(), -@one_day_in_seconds, :second, Calendar.UTCOnlyTimeZoneDatabase)
-        :utc_datetime_usec -> DateTime.add(build_utc_datetime_usec(), -@one_day_in_seconds, :second, Calendar.UTCOnlyTimeZoneDatabase)
-        :naive_datetime -> NaiveDateTime.add(build_utc_naive_datetime(), -@one_day_in_seconds)
-        :naive_datetime_usec -> NaiveDateTime.add(build_utc_naive_datetime_usec(), -@one_day_in_seconds)
-        _  -> "some #{key}"
+      {:array, type} ->
+        build_array_values(type, :create)
+
+      {:enum, values} ->
+        build_enum_values(values, :create)
+
+      :integer ->
+        42
+
+      :float ->
+        120.5
+
+      :decimal ->
+        "120.5"
+
+      :boolean ->
+        true
+
+      :map ->
+        %{}
+
+      :text ->
+        "some #{key}"
+
+      :date ->
+        Date.add(Date.utc_today(), -1)
+
+      :time ->
+        ~T[14:00:00]
+
+      :time_usec ->
+        ~T[14:00:00.000000]
+
+      :uuid ->
+        "7488a646-e31f-11e4-aace-600308960662"
+
+      :utc_datetime ->
+        DateTime.add(
+          build_utc_datetime(),
+          -@one_day_in_seconds,
+          :second,
+          Calendar.UTCOnlyTimeZoneDatabase
+        )
+
+      :utc_datetime_usec ->
+        DateTime.add(
+          build_utc_datetime_usec(),
+          -@one_day_in_seconds,
+          :second,
+          Calendar.UTCOnlyTimeZoneDatabase
+        )
+
+      :naive_datetime ->
+        NaiveDateTime.add(build_utc_naive_datetime(), -@one_day_in_seconds)
+
+      :naive_datetime_usec ->
+        NaiveDateTime.add(build_utc_naive_datetime_usec(), -@one_day_in_seconds)
+
+      _ ->
+        "some #{key}"
     end
   end
+
   defp type_to_default(key, t, :update) do
     case t do
-        {:array, type}  -> build_array_values(type, :update)
-        {:enum, values} -> build_enum_values(values, :update)
-        :integer        -> 43
-        :float          -> 456.7
-        :decimal        -> "456.7"
-        :boolean        -> false
-        :map            -> %{}
-        :text           -> "some updated #{key}"
-        :date           -> Date.utc_today()
-        :time           -> ~T[15:01:01]
-        :time_usec      -> ~T[15:01:01.000000]
-        :uuid           -> "7488a646-e31f-11e4-aace-600308960668"
-        :utc_datetime   -> build_utc_datetime()
-        :utc_datetime_usec -> build_utc_datetime_usec()
-        :naive_datetime -> build_utc_naive_datetime()
-        :naive_datetime_usec -> build_utc_naive_datetime_usec()
-        _               -> "some updated #{key}"
+      {:array, type} -> build_array_values(type, :update)
+      {:enum, values} -> build_enum_values(values, :update)
+      :integer -> 43
+      :float -> 456.7
+      :decimal -> "456.7"
+      :boolean -> false
+      :map -> %{}
+      :text -> "some updated #{key}"
+      :date -> Date.utc_today()
+      :time -> ~T[15:01:01]
+      :time_usec -> ~T[15:01:01.000000]
+      :uuid -> "7488a646-e31f-11e4-aace-600308960668"
+      :utc_datetime -> build_utc_datetime()
+      :utc_datetime_usec -> build_utc_datetime_usec()
+      :naive_datetime -> build_utc_naive_datetime()
+      :naive_datetime_usec -> build_utc_naive_datetime_usec()
+      _ -> "some updated #{key}"
     end
   end
 
   defp build_array_values(:string, :create),
-    do: Enum.map([1,2], &("option#{&1}"))
+    do: Enum.map([1, 2], &"option#{&1}")
+
   defp build_array_values(:integer, :create),
-    do: [1,2]
+    do: [1, 2]
+
   defp build_array_values(:string, :update),
     do: ["option1"]
+
   defp build_array_values(:integer, :update),
     do: [1]
+
   defp build_array_values(_, _),
     do: []
 
@@ -354,21 +409,26 @@ defmodule Mix.Phoenix.Schema do
   """
 
   defp validate_attr!({name, :datetime}), do: validate_attr!({name, :naive_datetime})
+
   defp validate_attr!({name, :array}) do
-    Mix.raise """
+    Mix.raise("""
     Phoenix generators expect the type of the array to be given to #{name}:array.
     For example:
 
         mix phx.gen.schema Post posts settings:array:string
-    """
+    """)
   end
-  defp validate_attr!({_name, :enum}), do: Mix.raise @enum_missing_value_error
+
+  defp validate_attr!({_name, :enum}), do: Mix.raise(@enum_missing_value_error)
   defp validate_attr!({_name, type} = attr) when type in @valid_types, do: attr
   defp validate_attr!({_name, {:enum, _vals}} = attr), do: attr
   defp validate_attr!({_name, {type, _}} = attr) when type in @valid_types, do: attr
+
   defp validate_attr!({_, type}) do
-    Mix.raise "Unknown type `#{inspect type}` given to generator. " <>
-              "The supported types are: #{@valid_types |> Enum.sort() |> Enum.join(", ")}"
+    Mix.raise(
+      "Unknown type `#{inspect(type)}` given to generator. " <>
+        "The supported types are: #{@valid_types |> Enum.sort() |> Enum.join(", ")}"
+    )
   end
 
   defp partition_attrs_and_assocs(schema_module, attrs) do
@@ -376,14 +436,17 @@ defmodule Mix.Phoenix.Schema do
       Enum.split_with(attrs, fn
         {_, {:references, _}} ->
           true
+
         {key, :references} ->
-          Mix.raise """
+          Mix.raise("""
           Phoenix generators expect the table to be given to #{key}:references.
           For example:
 
               mix phx.gen.schema Comment comments body:text post_id:references:posts
-          """
-        _ -> false
+          """)
+
+        _ ->
+          false
       end)
 
     assocs =
@@ -399,8 +462,8 @@ defmodule Mix.Phoenix.Schema do
 
   defp schema_defaults(attrs) do
     Enum.into(attrs, %{}, fn
-      {key, :boolean}  -> {key, ", default: false"}
-      {key, _}         -> {key, ""}
+      {key, :boolean} -> {key, ", default: false"}
+      {key, _} -> {key, ""}
     end)
   end
 
@@ -428,9 +491,10 @@ defmodule Mix.Phoenix.Schema do
 
   defp schema_type(:text), do: :string
   defp schema_type(:uuid), do: Ecto.UUID
+
   defp schema_type(val) do
     if Code.ensure_loaded?(Ecto.Type) and not Ecto.Type.primitive?(val) do
-      Mix.raise "Unknown type `#{val}` given to generator"
+      Mix.raise("Unknown type `#{val}` given to generator")
     else
       val
     end
@@ -444,14 +508,14 @@ defmodule Mix.Phoenix.Schema do
     |> Enum.uniq_by(fn {key, _} -> key end)
     |> Enum.map(fn
       {key, false} -> "create index(:#{table}, [:#{key}])"
-      {key, true}  -> "create unique_index(:#{table}, [:#{key}])"
+      {key, true} -> "create unique_index(:#{table}, [:#{key}])"
     end)
   end
 
   defp migration_defaults(attrs) do
     Enum.into(attrs, %{}, fn
-      {key, :boolean}  -> {key, ", default: false, null: false"}
-      {key, _}         -> {key, ""}
+      {key, :boolean} -> {key, ", default: false, null: false"}
+      {key, _} -> {key, ""}
     end)
   end
 
@@ -482,7 +546,7 @@ defmodule Mix.Phoenix.Schema do
   defp migration_module do
     case Application.get_env(:ecto_sql, :migration_module, Ecto.Migration) do
       migration_module when is_atom(migration_module) -> migration_module
-      other -> Mix.raise "Expected :migration_module to be a module, got: #{inspect(other)}"
+      other -> Mix.raise("Expected :migration_module to be a module, got: #{inspect(other)}")
     end
   end
 
@@ -521,17 +585,19 @@ defmodule Mix.Phoenix.Schema do
             {function_def, true}
         end
 
-
       {attr, {function_name, function_def, needs_impl?}}
     end)
   end
 
   defp fixture_params(attrs, fixture_unique_functions) do
-    Map.new(attrs, fn {attr, type} ->
-      case Map.fetch(fixture_unique_functions, attr) do
-        {:ok, {function_name, _function_def, _needs_impl?}} ->
+    attrs
+    |> Enum.sort()
+    |> Enum.map(fn {attr, type} ->
+      case fixture_unique_functions do
+        %{^attr => {function_name, _function_def, _needs_impl?}} ->
           {attr, "#{function_name}()"}
-        :error ->
+
+        %{} ->
           {attr, inspect(type_to_default(attr, type, :create))}
       end
     end)
diff --git a/lib/mix/tasks/phx.gen.context.ex b/lib/mix/tasks/phx.gen.context.ex
index 934a08fa1..7bbf7aed4 100644
--- a/lib/mix/tasks/phx.gen.context.ex
+++ b/lib/mix/tasks/phx.gen.context.ex
@@ -78,16 +78,26 @@ defmodule Mix.Tasks.Phx.Gen.Context do
   alias Mix.Phoenix.{Context, Schema}
   alias Mix.Tasks.Phx.Gen
 
-  @switches [binary_id: :boolean, table: :string, web: :string,
-             schema: :boolean, context: :boolean, context_app: :string,
-             merge_with_existing_context: :boolean, prefix: :string, live: :boolean]
+  @switches [
+    binary_id: :boolean,
+    table: :string,
+    web: :string,
+    schema: :boolean,
+    context: :boolean,
+    context_app: :string,
+    merge_with_existing_context: :boolean,
+    prefix: :string,
+    live: :boolean
+  ]
 
   @default_opts [schema: true, context: true]
 
   @doc false
   def run(args) do
     if Mix.Project.umbrella?() do
-      Mix.raise "mix phx.gen.context must be invoked from within your *_web application root directory"
+      Mix.raise(
+        "mix phx.gen.context must be invoked from within your *_web application root directory"
+      )
     end
 
     {context, schema} = build(args)
@@ -120,6 +130,7 @@ defmodule Mix.Tasks.Phx.Gen.Context do
 
   defp parse_opts(args) do
     {opts, parsed, invalid} = OptionParser.parse(args, switches: @switches)
+
     merged_opts =
       @default_opts
       |> Keyword.merge(opts)
@@ -127,7 +138,9 @@ defmodule Mix.Tasks.Phx.Gen.Context do
 
     {merged_opts, parsed, invalid}
   end
+
   defp put_context_app(opts, nil), do: opts
+
   defp put_context_app(opts, string) do
     Keyword.put(opts, :context_app, String.to_atom(string))
   end
@@ -154,7 +167,10 @@ defmodule Mix.Tasks.Phx.Gen.Context do
   @doc false
   def ensure_context_file_exists(%Context{file: file} = context, paths, binding) do
     unless Context.pre_existing?(context) do
-      Mix.Generator.create_file(file, Mix.Phoenix.eval_from(paths, "priv/templates/phx.gen.context/context.ex", binding))
+      Mix.Generator.create_file(
+        file,
+        Mix.Phoenix.eval_from(paths, "priv/templates/phx.gen.context/context.ex", binding)
+      )
     end
   end
 
@@ -162,7 +178,10 @@ defmodule Mix.Tasks.Phx.Gen.Context do
     ensure_context_file_exists(context, paths, binding)
 
     paths
-    |> Mix.Phoenix.eval_from("priv/templates/phx.gen.context/#{schema_access_template(context)}", binding)
+    |> Mix.Phoenix.eval_from(
+      "priv/templates/phx.gen.context/#{schema_access_template(context)}",
+      binding
+    )
     |> inject_eex_before_final_end(file, binding)
   end
 
@@ -173,7 +192,10 @@ defmodule Mix.Tasks.Phx.Gen.Context do
   @doc false
   def ensure_test_file_exists(%Context{test_file: test_file} = context, paths, binding) do
     unless Context.pre_existing_tests?(context) do
-      Mix.Generator.create_file(test_file, Mix.Phoenix.eval_from(paths, "priv/templates/phx.gen.context/context_test.exs", binding))
+      Mix.Generator.create_file(
+        test_file,
+        Mix.Phoenix.eval_from(paths, "priv/templates/phx.gen.context/context_test.exs", binding)
+      )
     end
   end
 
@@ -186,13 +208,24 @@ defmodule Mix.Tasks.Phx.Gen.Context do
   end
 
   @doc false
-  def ensure_test_fixtures_file_exists(%Context{test_fixtures_file: test_fixtures_file} = context, paths, binding) do
+  def ensure_test_fixtures_file_exists(
+        %Context{test_fixtures_file: test_fixtures_file} = context,
+        paths,
+        binding
+      ) do
     unless Context.pre_existing_test_fixtures?(context) do
-      Mix.Generator.create_file(test_fixtures_file, Mix.Phoenix.eval_from(paths, "priv/templates/phx.gen.context/fixtures_module.ex", binding))
+      Mix.Generator.create_file(
+        test_fixtures_file,
+        Mix.Phoenix.eval_from(paths, "priv/templates/phx.gen.context/fixtures_module.ex", binding)
+      )
     end
   end
 
-  defp inject_test_fixture(%Context{test_fixtures_file: test_fixtures_file} = context, paths, binding) do
+  defp inject_test_fixture(
+         %Context{test_fixtures_file: test_fixtures_file} = context,
+         paths,
+         binding
+       ) do
     ensure_test_fixtures_file_exists(context, paths, binding)
 
     paths
@@ -214,20 +247,14 @@ defmodule Mix.Tasks.Phx.Gen.Context do
       )
 
     if Enum.any?(fixture_functions_needing_implementations) do
-      Mix.shell.info(
-        """
-
-        Some of the generated database columns are unique. Please provide
-        unique implementations for the following fixture function(s) in
-        #{context.test_fixtures_file}:
-
-        #{
-          fixture_functions_needing_implementations
-          |> Enum.map_join(&indent(&1, 2))
-          |> String.trim_trailing()
-        }
-        """
-      )
+      Mix.shell().info("""
+
+      Some of the generated database columns are unique. Please provide
+      unique implementations for the following fixture function(s) in
+      #{context.test_fixtures_file}:
+
+      #{fixture_functions_needing_implementations |> Enum.map_join(&indent(&1, 2)) |> String.trim_trailing()}
+      """)
     end
   end
 
@@ -237,11 +264,11 @@ defmodule Mix.Tasks.Phx.Gen.Context do
     string
     |> String.split("\n")
     |> Enum.map_join(fn line ->
-        if String.trim(line) == "" do
-          "\n"
-        else
-          indent_string <> line <> "\n"
-        end
+      if String.trim(line) == "" do
+        "\n"
+      else
+        indent_string <> line <> "\n"
+      end
     end)
   end
 
@@ -283,27 +310,38 @@ defmodule Mix.Tasks.Phx.Gen.Context do
   defp validate_args!([context, schema, _plural | _] = args, help) do
     cond do
       not Context.valid?(context) ->
-        help.raise_with_help "Expected the context, #{inspect context}, to be a valid module name"
+        help.raise_with_help(
+          "Expected the context, #{inspect(context)}, to be a valid module name"
+        )
+
       not Schema.valid?(schema) ->
-        help.raise_with_help "Expected the schema, #{inspect schema}, to be a valid module name"
+        help.raise_with_help("Expected the schema, #{inspect(schema)}, to be a valid module name")
+
       context == schema ->
-        help.raise_with_help "The context and schema should have different names"
+        help.raise_with_help("The context and schema should have different names")
+
       context == Mix.Phoenix.base() ->
-        help.raise_with_help "Cannot generate context #{context} because it has the same name as the application"
+        help.raise_with_help(
+          "Cannot generate context #{context} because it has the same name as the application"
+        )
+
       schema == Mix.Phoenix.base() ->
-        help.raise_with_help "Cannot generate schema #{schema} because it has the same name as the application"
+        help.raise_with_help(
+          "Cannot generate schema #{schema} because it has the same name as the application"
+        )
+
       true ->
         args
     end
   end
 
   defp validate_args!(_, help) do
-    help.raise_with_help "Invalid arguments"
+    help.raise_with_help("Invalid arguments")
   end
 
   @doc false
   def raise_with_help(msg) do
-    Mix.raise """
+    Mix.raise("""
     #{msg}
 
     mix phx.gen.html, phx.gen.json, phx.gen.live, and phx.gen.context
@@ -319,11 +357,12 @@ defmodule Mix.Tasks.Phx.Gen.Context do
     The context serves as the API boundary for the given resource.
     Multiple resources may belong to a context and a resource may be
     split over distinct contexts (such as Accounts.User and Payments.User).
-    """
+    """)
   end
 
   @doc false
   def prompt_for_code_injection(%Context{generate?: false}), do: :ok
+
   def prompt_for_code_injection(%Context{} = context) do
     if Context.pre_existing?(context) && !merge_with_existing_context?(context) do
       System.halt()
diff --git a/lib/phoenix/code_reloader.ex b/lib/phoenix/code_reloader.ex
index 931a7ccc5..d1c48fb54 100644
--- a/lib/phoenix/code_reloader.ex
+++ b/lib/phoenix/code_reloader.ex
@@ -33,7 +33,9 @@ defmodule Phoenix.CodeReloader do
 
   ## Options
 
-    * `:reloadable_args` - additional CLI args to pass to the compiler tasks
+    * `:reloadable_args` - additional CLI args to pass to the compiler tasks.
+      Defaults to `["--no-all-warnings"]` so only warnings related to the
+      files being compiled are printed
 
   """
   @spec reload(module, keyword) :: :ok | {:error, binary()}
@@ -64,22 +66,29 @@ defmodule Phoenix.CodeReloader do
     primary: "#EB532D",
     accent: "#a0b0c0",
     text_color: "#304050",
-    logo: "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNzEgNDgiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPgoJPHBhdGggZD0ibTI2LjM3MSAzMy40NzctLjU1Mi0uMWMtMy45Mi0uNzI5LTYuMzk3LTMuMS03LjU3LTYuODI5LS43MzMtMi4zMjQuNTk3LTQuMDM1IDMuMDM1LTQuMTQ4IDEuOTk1LS4wOTIgMy4zNjIgMS4wNTUgNC41NyAyLjM5IDEuNTU3IDEuNzIgMi45ODQgMy41NTggNC41MTQgNS4zMDUgMi4yMDIgMi41MTUgNC43OTcgNC4xMzQgOC4zNDcgMy42MzQgMy4xODMtLjQ0OCA1Ljk1OC0xLjcyNSA4LjM3MS0zLjgyOC4zNjMtLjMxNi43NjEtLjU5MiAxLjE0NC0uODg2bC0uMjQxLS4yODRjLTIuMDI3LjYzLTQuMDkzLjg0MS02LjIwNS43MzUtMy4xOTUtLjE2LTYuMjQtLjgyOC04Ljk2NC0yLjU4Mi0yLjQ4Ni0xLjYwMS00LjMxOS0zLjc0Ni01LjE5LTYuNjExLS43MDQtMi4zMTUuNzM2LTMuOTM0IDMuMTM1LTMuNi45NDguMTMzIDEuNzQ2LjU2IDIuNDYzIDEuMTY1LjU4My40OTMgMS4xNDMgMS4wMTUgMS43MzggMS40OTMgMi44IDIuMjUgNi43MTIgMi4zNzUgMTAuMjY1LS4wNjgtNS44NDItLjAyNi05LjgxNy0zLjI0LTEzLjMwOC03LjMxMy0xLjM2Ni0xLjU5NC0yLjctMy4yMTYtNC4wOTUtNC43ODUtMi42OTgtMy4wMzYtNS42OTItNS43MS05Ljc5LTYuNjIzQzEyLjgtLjYyMyA3Ljc0NS4xNCAyLjg5MyAyLjM2MSAxLjkyNiAyLjgwNC45OTcgMy4zMTkgMCA0LjE0OWMuNDk0IDAgLjc2My4wMDYgMS4wMzIgMCAyLjQ0Ni0uMDY0IDQuMjggMS4wMjMgNS42MDIgMy4wMjQuOTYyIDEuNDU3IDEuNDE1IDMuMTA0IDEuNzYxIDQuNzk4LjUxMyAyLjUxNS4yNDcgNS4wNzguNTQ0IDcuNjA1Ljc2MSA2LjQ5NCA0LjA4IDExLjAyNiAxMC4yNiAxMy4zNDYgMi4yNjcuODUyIDQuNTkxIDEuMTM1IDcuMTcyLjU1NVpNMTAuNzUxIDMuODUyYy0uOTc2LjI0Ni0xLjc1Ni0uMTQ4LTIuNTYtLjk2MiAxLjM3Ny0uMzQzIDIuNTkyLS40NzYgMy44OTctLjUyOC0uMTA3Ljg0OC0uNjA3IDEuMzA2LTEuMzM2IDEuNDlabTMyLjAwMiAzNy45MjRjLS4wODUtLjYyNi0uNjItLjkwMS0xLjA0LTEuMjI4LTEuODU3LTEuNDQ2LTQuMDMtMS45NTgtNi4zMzMtMi0xLjM3NS0uMDI2LTIuNzM1LS4xMjgtNC4wMzEtLjYxLS41OTUtLjIyLTEuMjYtLjUwNS0xLjI0NC0xLjI3Mi4wMTUtLjc4LjY5My0xIDEuMzEtMS4xODQuNTA1LS4xNSAxLjAyNi0uMjQ3IDEuNi0uMzgyLTEuNDYtLjkzNi0yLjg4Ni0xLjA2NS00Ljc4Ny0uMy0yLjk5MyAxLjIwMi01Ljk0MyAxLjA2LTguOTI2LS4wMTctMS42ODQtLjYwOC0zLjE3OS0xLjU2My00LjczNS0yLjQwOGwtLjA0My4wM2EyLjk2IDIuOTYgMCAwIDAgLjA0LS4wMjljLS4wMzgtLjExNy0uMTA3LS4xMi0uMTk3LS4wNTRsLjEyMi4xMDdjMS4yOSAyLjExNSAzLjAzNCAzLjgxNyA1LjAwNCA1LjI3MSAzLjc5MyAyLjggNy45MzYgNC40NzEgMTIuNzg0IDMuNzNBNjYuNzE0IDY2LjcxNCAwIDAgMSAzNyA0MC44NzdjMS45OC0uMTYgMy44NjYuMzk4IDUuNzUzLjg5OVptLTkuMTQtMzAuMzQ1Yy0uMTA1LS4wNzYtLjIwNi0uMjY2LS40Mi0uMDY5IDEuNzQ1IDIuMzYgMy45ODUgNC4wOTggNi42ODMgNS4xOTMgNC4zNTQgMS43NjcgOC43NzMgMi4wNyAxMy4yOTMuNTEgMy41MS0xLjIxIDYuMDMzLS4wMjggNy4zNDMgMy4zOC4xOS0zLjk1NS0yLjEzNy02LjgzNy01Ljg0My03LjQwMS0yLjA4NC0uMzE4LTQuMDEuMzczLTUuOTYyLjk0LTUuNDM0IDEuNTc1LTEwLjQ4NS43OTgtMTUuMDk0LTIuNTUzWm0yNy4wODUgMTUuNDI1Yy43MDguMDU5IDEuNDE2LjEyMyAyLjEyNC4xODUtMS42LTEuNDA1LTMuNTUtMS41MTctNS41MjMtMS40MDQtMy4wMDMuMTctNS4xNjcgMS45MDMtNy4xNCAzLjk3Mi0xLjczOSAxLjgyNC0zLjMxIDMuODctNS45MDMgNC42MDQuMDQzLjA3OC4wNTQuMTE3LjA2Ni4xMTcuMzUuMDA1LjY5OS4wMjEgMS4wNDcuMDA1IDMuNzY4LS4xNyA3LjMxNy0uOTY1IDEwLjE0LTMuNy44OS0uODYgMS42ODUtMS44MTcgMi41NDQtMi43MS43MTYtLjc0NiAxLjU4NC0xLjE1OSAyLjY0NS0xLjA3Wm0tOC43NTMtNC42N2MtMi44MTIuMjQ2LTUuMjU0IDEuNDA5LTcuNTQ4IDIuOTQzLTEuNzY2IDEuMTgtMy42NTQgMS43MzgtNS43NzYgMS4zNy0uMzc0LS4wNjYtLjc1LS4xMTQtMS4xMjQtLjE3bC0uMDEzLjE1NmMuMTM1LjA3LjI2NS4xNTEuNDA1LjIwNy4zNTQuMTQuNzAyLjMwOCAxLjA3LjM5NSA0LjA4My45NzEgNy45OTIuNDc0IDExLjUxNi0xLjgwMyAyLjIyMS0xLjQzNSA0LjUyMS0xLjcwNyA3LjAxMy0xLjMzNi4yNTIuMDM4LjUwMy4wODMuNzU2LjEwNy4yMzQuMDIyLjQ3OS4yNTUuNzk1LjAwMy0yLjE3OS0xLjU3NC00LjUyNi0yLjA5Ni03LjA5NC0xLjg3MlptLTEwLjA0OS05LjU0NGMxLjQ3NS4wNTEgMi45NDMtLjE0MiA0LjQ4Ni0xLjA1OS0uNDUyLjA0LS42NDMuMDQtLjgyNy4wNzYtMi4xMjYuNDI0LTQuMDMzLS4wNC01LjczMy0xLjM4My0uNjIzLS40OTMtMS4yNTctLjk3NC0xLjg4OS0xLjQ1Ny0yLjUwMy0xLjkxNC01LjM3NC0yLjU1NS04LjUxNC0yLjUuMDUuMTU0LjA1NC4yNi4xMDguMzE1IDMuNDE3IDMuNDU1IDcuMzcxIDUuODM2IDEyLjM2OSA2LjAwOFptMjQuNzI3IDE3LjczMWMtMi4xMTQtMi4wOTctNC45NTItMi4zNjctNy41NzgtLjUzNyAxLjczOC4wNzggMy4wNDMuNjMyIDQuMTAxIDEuNzI4LjM3NC4zODguNzYzLjc2OCAxLjE4MiAxLjEwNiAxLjYgMS4yOSA0LjMxMSAxLjM1MiA1Ljg5Ni4xNTUtMS44NjEtLjcyNi0xLjg2MS0uNzI2LTMuNjAxLTIuNDUyWm0tMjEuMDU4IDE2LjA2Yy0xLjg1OC0zLjQ2LTQuOTgxLTQuMjQtOC41OS00LjAwOGE5LjY2NyA5LjY2NyAwIDAgMSAyLjk3NyAxLjM5Yy44NC41ODYgMS41NDcgMS4zMTEgMi4yNDMgMi4wNTUgMS4zOCAxLjQ3MyAzLjUzNCAyLjM3NiA0Ljk2MiAyLjA3LS42NTYtLjQxMi0xLjIzOC0uODQ4LTEuNTkyLTEuNTA3Wm0xNy4yOS0xOS4zMmMwLS4wMjMuMDAxLS4wNDUuMDAzLS4wNjhsLS4wMDYuMDA2LjAwNi0uMDA2LS4wMzYtLjAwNC4wMjEuMDE4LjAxMi4wNTNabS0yMCAxNC43NDRhNy42MSA3LjYxIDAgMCAwLS4wNzItLjA0MS4xMjcuMTI3IDAgMCAwIC4wMTUuMDQzYy4wMDUuMDA4LjAzOCAwIC4wNTgtLjAwMlptLS4wNzItLjA0MS0uMDA4LS4wMzQtLjAwOC4wMS4wMDgtLjAxLS4wMjItLjAwNi4wMDUuMDI2LjAyNC4wMTRaIgogICAgICAgICAgICBmaWxsPSIjRkQ0RjAwIiAvPgo8L3N2Zz4K",
+    logo:
+      "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNzEgNDgiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPgoJPHBhdGggZD0ibTI2LjM3MSAzMy40NzctLjU1Mi0uMWMtMy45Mi0uNzI5LTYuMzk3LTMuMS03LjU3LTYuODI5LS43MzMtMi4zMjQuNTk3LTQuMDM1IDMuMDM1LTQuMTQ4IDEuOTk1LS4wOTIgMy4zNjIgMS4wNTUgNC41NyAyLjM5IDEuNTU3IDEuNzIgMi45ODQgMy41NTggNC41MTQgNS4zMDUgMi4yMDIgMi41MTUgNC43OTcgNC4xMzQgOC4zNDcgMy42MzQgMy4xODMtLjQ0OCA1Ljk1OC0xLjcyNSA4LjM3MS0zLjgyOC4zNjMtLjMxNi43NjEtLjU5MiAxLjE0NC0uODg2bC0uMjQxLS4yODRjLTIuMDI3LjYzLTQuMDkzLjg0MS02LjIwNS43MzUtMy4xOTUtLjE2LTYuMjQtLjgyOC04Ljk2NC0yLjU4Mi0yLjQ4Ni0xLjYwMS00LjMxOS0zLjc0Ni01LjE5LTYuNjExLS43MDQtMi4zMTUuNzM2LTMuOTM0IDMuMTM1LTMuNi45NDguMTMzIDEuNzQ2LjU2IDIuNDYzIDEuMTY1LjU4My40OTMgMS4xNDMgMS4wMTUgMS43MzggMS40OTMgMi44IDIuMjUgNi43MTIgMi4zNzUgMTAuMjY1LS4wNjgtNS44NDItLjAyNi05LjgxNy0zLjI0LTEzLjMwOC03LjMxMy0xLjM2Ni0xLjU5NC0yLjctMy4yMTYtNC4wOTUtNC43ODUtMi42OTgtMy4wMzYtNS42OTItNS43MS05Ljc5LTYuNjIzQzEyLjgtLjYyMyA3Ljc0NS4xNCAyLjg5MyAyLjM2MSAxLjkyNiAyLjgwNC45OTcgMy4zMTkgMCA0LjE0OWMuNDk0IDAgLjc2My4wMDYgMS4wMzIgMCAyLjQ0Ni0uMDY0IDQuMjggMS4wMjMgNS42MDIgMy4wMjQuOTYyIDEuNDU3IDEuNDE1IDMuMTA0IDEuNzYxIDQuNzk4LjUxMyAyLjUxNS4yNDcgNS4wNzguNTQ0IDcuNjA1Ljc2MSA2LjQ5NCA0LjA4IDExLjAyNiAxMC4yNiAxMy4zNDYgMi4yNjcuODUyIDQuNTkxIDEuMTM1IDcuMTcyLjU1NVpNMTAuNzUxIDMuODUyYy0uOTc2LjI0Ni0xLjc1Ni0uMTQ4LTIuNTYtLjk2MiAxLjM3Ny0uMzQzIDIuNTkyLS40NzYgMy44OTctLjUyOC0uMTA3Ljg0OC0uNjA3IDEuMzA2LTEuMzM2IDEuNDlabTMyLjAwMiAzNy45MjRjLS4wODUtLjYyNi0uNjItLjkwMS0xLjA0LTEuMjI4LTEuODU3LTEuNDQ2LTQuMDMtMS45NTgtNi4zMzMtMi0xLjM3NS0uMDI2LTIuNzM1LS4xMjgtNC4wMzEtLjYxLS41OTUtLjIyLTEuMjYtLjUwNS0xLjI0NC0xLjI3Mi4wMTUtLjc4LjY5My0xIDEuMzEtMS4xODQuNTA1LS4xNSAxLjAyNi0uMjQ3IDEuNi0uMzgyLTEuNDYtLjkzNi0yLjg4Ni0xLjA2NS00Ljc4Ny0uMy0yLjk5MyAxLjIwMi01Ljk0MyAxLjA2LTguOTI2LS4wMTctMS42ODQtLjYwOC0zLjE3OS0xLjU2My00LjczNS0yLjQwOGwtLjA0My4wM2EyLjk2IDIuOTYgMCAwIDAgLjA0LS4wMjljLS4wMzgtLjExNy0uMTA3LS4xMi0uMTk3LS4wNTRsLjEyMi4xMDdjMS4yOSAyLjExNSAzLjAzNCAzLjgxNyA1LjAwNCA1LjI3MSAzLjc5MyAyLjggNy45MzYgNC40NzEgMTIuNzg0IDMuNzNBNjYuNzE0IDY2LjcxNCAwIDAgMSAzNyA0MC44NzdjMS45OC0uMTYgMy44NjYuMzk4IDUuNzUzLjg5OVptLTkuMTQtMzAuMzQ1Yy0uMTA1LS4wNzYtLjIwNi0uMjY2LS40Mi0uMDY5IDEuNzQ1IDIuMzYgMy45ODUgNC4wOTggNi42ODMgNS4xOTMgNC4zNTQgMS43NjcgOC43NzMgMi4wNyAxMy4yOTMuNTEgMy41MS0xLjIxIDYuMDMzLS4wMjggNy4zNDMgMy4zOC4xOS0zLjk1NS0yLjEzNy02LjgzNy01Ljg0My03LjQwMS0yLjA4NC0uMzE4LTQuMDEuMzczLTUuOTYyLjk0LTUuNDM0IDEuNTc1LTEwLjQ4NS43OTgtMTUuMDk0LTIuNTUzWm0yNy4wODUgMTUuNDI1Yy43MDguMDU5IDEuNDE2LjEyMyAyLjEyNC4xODUtMS42LTEuNDA1LTMuNTUtMS41MTctNS41MjMtMS40MDQtMy4wMDMuMTctNS4xNjcgMS45MDMtNy4xNCAzLjk3Mi0xLjczOSAxLjgyNC0zLjMxIDMuODctNS45MDMgNC42MDQuMDQzLjA3OC4wNTQuMTE3LjA2Ni4xMTcuMzUuMDA1LjY5OS4wMjEgMS4wNDcuMDA1IDMuNzY4LS4xNyA3LjMxNy0uOTY1IDEwLjE0LTMuNy44OS0uODYgMS42ODUtMS44MTcgMi41NDQtMi43MS43MTYtLjc0NiAxLjU4NC0xLjE1OSAyLjY0NS0xLjA3Wm0tOC43NTMtNC42N2MtMi44MTIuMjQ2LTUuMjU0IDEuNDA5LTcuNTQ4IDIuOTQzLTEuNzY2IDEuMTgtMy42NTQgMS43MzgtNS43NzYgMS4zNy0uMzc0LS4wNjYtLjc1LS4xMTQtMS4xMjQtLjE3bC0uMDEzLjE1NmMuMTM1LjA3LjI2NS4xNTEuNDA1LjIwNy4zNTQuMTQuNzAyLjMwOCAxLjA3LjM5NSA0LjA4My45NzEgNy45OTIuNDc0IDExLjUxNi0xLjgwMyAyLjIyMS0xLjQzNSA0LjUyMS0xLjcwNyA3LjAxMy0xLjMzNi4yNTIuMDM4LjUwMy4wODMuNzU2LjEwNy4yMzQuMDIyLjQ3OS4yNTUuNzk1LjAwMy0yLjE3OS0xLjU3NC00LjUyNi0yLjA5Ni03LjA5NC0xLjg3MlptLTEwLjA0OS05LjU0NGMxLjQ3NS4wNTEgMi45NDMtLjE0MiA0LjQ4Ni0xLjA1OS0uNDUyLjA0LS42NDMuMDQtLjgyNy4wNzYtMi4xMjYuNDI0LTQuMDMzLS4wNC01LjczMy0xLjM4My0uNjIzLS40OTMtMS4yNTctLjk3NC0xLjg4OS0xLjQ1Ny0yLjUwMy0xLjkxNC01LjM3NC0yLjU1NS04LjUxNC0yLjUuMDUuMTU0LjA1NC4yNi4xMDguMzE1IDMuNDE3IDMuNDU1IDcuMzcxIDUuODM2IDEyLjM2OSA2LjAwOFptMjQuNzI3IDE3LjczMWMtMi4xMTQtMi4wOTctNC45NTItMi4zNjctNy41NzgtLjUzNyAxLjczOC4wNzggMy4wNDMuNjMyIDQuMTAxIDEuNzI4LjM3NC4zODguNzYzLjc2OCAxLjE4MiAxLjEwNiAxLjYgMS4yOSA0LjMxMSAxLjM1MiA1Ljg5Ni4xNTUtMS44NjEtLjcyNi0xLjg2MS0uNzI2LTMuNjAxLTIuNDUyWm0tMjEuMDU4IDE2LjA2Yy0xLjg1OC0zLjQ2LTQuOTgxLTQuMjQtOC41OS00LjAwOGE5LjY2NyA5LjY2NyAwIDAgMSAyLjk3NyAxLjM5Yy44NC41ODYgMS41NDcgMS4zMTEgMi4yNDMgMi4wNTUgMS4zOCAxLjQ3MyAzLjUzNCAyLjM3NiA0Ljk2MiAyLjA3LS42NTYtLjQxMi0xLjIzOC0uODQ4LTEuNTkyLTEuNTA3Wm0xNy4yOS0xOS4zMmMwLS4wMjMuMDAxLS4wNDUuMDAzLS4wNjhsLS4wMDYuMDA2LjAwNi0uMDA2LS4wMzYtLjAwNC4wMjEuMDE4LjAxMi4wNTNabS0yMCAxNC43NDRhNy42MSA3LjYxIDAgMCAwLS4wNzItLjA0MS4xMjcuMTI3IDAgMCAwIC4wMTUuMDQzYy4wMDUuMDA4LjAzOCAwIC4wNTgtLjAwMlptLS4wNzItLjA0MS0uMDA4LS4wMzQtLjAwOC4wMS4wMDgtLjAxLS4wMjItLjAwNi4wMDUuMDI2LjAyNC4wMTRaIgogICAgICAgICAgICBmaWxsPSIjRkQ0RjAwIiAvPgo8L3N2Zz4K",
     monospace_font: "menlo, consolas, monospace"
   }
 
   @doc """
   API used by Plug to start the code reloader.
   """
-  def init(opts), do: Keyword.put_new(opts, :reloader, &Phoenix.CodeReloader.reload/1)
+  def init(opts) do
+    case Keyword.pop(opts, :reloader) do
+      {nil, opts} -> &Phoenix.CodeReloader.reload(&1, opts)
+      {fun, _opts} -> fun
+    end
+  end
 
   @doc """
   API used by Plug to invoke the code reloader on every request.
   """
-  def call(conn, opts) do
-    case opts[:reloader].(conn.private.phoenix_endpoint) do
+  def call(conn, reloader) do
+    case reloader.(conn.private.phoenix_endpoint) do
       :ok ->
         conn
+
       {:error, output} ->
         conn
         |> put_resp_content_type("text/html")
@@ -270,7 +279,7 @@ defmodule Phoenix.CodeReloader do
 
   defp format_output(output) do
     output
-    |> String.trim
-    |> Plug.HTML.html_escape
+    |> String.trim()
+    |> Plug.HTML.html_escape()
   end
 end
diff --git a/lib/phoenix/code_reloader/server.ex b/lib/phoenix/code_reloader/server.ex
index e1146c4a1..ec18b3c4c 100644
--- a/lib/phoenix/code_reloader/server.ex
+++ b/lib/phoenix/code_reloader/server.ex
@@ -61,7 +61,7 @@ defmodule Phoenix.CodeReloader.Server do
   def handle_call({:reload!, endpoint, opts}, from, state) do
     compilers = endpoint.config(:reloadable_compilers)
     apps = endpoint.config(:reloadable_apps) || default_reloadable_apps()
-    args = Keyword.get(opts, :reloadable_args, [])
+    args = Keyword.get(opts, :reloadable_args, ["--no-all-warnings"])
 
     # We do a backup of the endpoint in case compilation fails.
     # If so we can bring it back to finish the request handling.
diff --git a/lib/phoenix/controller.ex b/lib/phoenix/controller.ex
index 259123215..87c51c9b6 100644
--- a/lib/phoenix/controller.ex
+++ b/lib/phoenix/controller.ex
@@ -1043,6 +1043,7 @@ defmodule Phoenix.Controller do
             put_layout(conn, #{inspect(good_value)})
 
         In this case, the layout without format will always win.
+        Passing the layout without a format is currently soft-deprecated.
         If you use layouts with formats, make sure that they are
         used everywhere. Also remember to configure your controller
         to use layouts with formats:
diff --git a/lib/phoenix/socket/transport.ex b/lib/phoenix/socket/transport.ex
index 277d9732b..5a1397144 100644
--- a/lib/phoenix/socket/transport.ex
+++ b/lib/phoenix/socket/transport.ex
@@ -294,9 +294,7 @@ defmodule Phoenix.Socket.Transport do
   """
   def code_reload(conn, endpoint, opts) do
     if Keyword.get(opts, :code_reloader, endpoint.config(:code_reloader)) do
-      # If the WebSocket reconnects, then often the page has already been reloaded
-      # and we don't want to print warnings twice, so we disable all warnings.
-      Phoenix.CodeReloader.reload(endpoint, reloadable_args: ~w(--no-all-warnings))
+      Phoenix.CodeReloader.reload(endpoint)
     end
 
     conn
diff --git a/priv/templates/phx.gen.html/controller.ex b/priv/templates/phx.gen.html/controller.ex
index 3dd7aab2b..de3e013ed 100644
--- a/priv/templates/phx.gen.html/controller.ex
+++ b/priv/templates/phx.gen.html/controller.ex
@@ -6,7 +6,7 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web
 
   def index(conn, _params) do
     <%= schema.plural %> = <%= inspect context.alias %>.list_<%= schema.plural %>()
-    render(conn, :index, <%= schema.plural %>: <%= schema.plural %>)
+    render(conn, :index, <%= schema.collection %>: <%= schema.plural %>)
   end
 
   def new(conn, _params) do
diff --git a/priv/templates/phx.gen.live/core_components.ex b/priv/templates/phx.gen.live/core_components.ex
index f9f817bb2..7490d6189 100644
--- a/priv/templates/phx.gen.live/core_components.ex
+++ b/priv/templates/phx.gen.live/core_components.ex
@@ -115,7 +115,7 @@ defmodule <%= @web_namespace %>.CoreComponents do
       phx-click={JS.push("lv:clear-flash", value: %{key: @kind}) |> hide("##{@id}")}
       role="alert"
       class={[
-        "fixed top-2 right-2 w-80 sm:w-96 z-50 rounded-lg p-3 ring-1",
+        "fixed top-2 right-2 mr-2 w-80 sm:w-96 z-50 rounded-lg p-3 ring-1",
         @kind == :info && "bg-emerald-50 text-emerald-800 ring-emerald-500 fill-cyan-900",
         @kind == :error && "bg-rose-50 text-rose-900 shadow-md ring-rose-500 fill-rose-900"
       ]}


Test patch:
diff --git a/installer/test/phx_new_test.exs b/installer/test/phx_new_test.exs
index be2e4db81..d9b96c5e6 100644
--- a/installer/test/phx_new_test.exs
+++ b/installer/test/phx_new_test.exs
@@ -55,6 +55,7 @@ defmodule Mix.Tasks.Phx.NewTest do
       assert_file("phx_blog/config/config.exs", fn file ->
         assert file =~ "ecto_repos: [PhxBlog.Repo]"
         assert file =~ "config :phoenix, :json_library, Jason"
+        assert file =~ ~s[cd: Path.expand("../assets", __DIR__),]
         refute file =~ "namespace: PhxBlog"
         refute file =~ "config :phx_blog, :generators"
       end)
@@ -149,7 +150,11 @@ defmodule Mix.Tasks.Phx.NewTest do
 
       # tailwind
       assert_file("phx_blog/assets/css/app.css")
-      assert_file("phx_blog/assets/tailwind.config.js")
+      assert_file "phx_blog/assets/tailwind.config.js", fn file ->
+        assert file =~ "phx_blog_web.ex"
+        assert file =~ "phx_blog_web/**/*.ex"
+      end
+
       assert_file("phx_blog/assets/vendor/heroicons/LICENSE.md")
       assert_file("phx_blog/assets/vendor/heroicons/UPGRADE.md")
       assert_file("phx_blog/assets/vendor/heroicons/optimized/24/outline/cake.svg")
@@ -598,7 +603,16 @@ defmodule Mix.Tasks.Phx.NewTest do
           assert file =~ "deps_path: \"../../deps\""
           assert file =~ "lockfile: \"../../mix.lock\""
         end)
+
+        refute_file("phx_blog/config/config.exs")
       end)
+
+      assert_file("config/config.exs", fn file ->
+        assert file =~ "PhxBlogWeb.Endpoint"
+        assert file =~ ~s[cd: Path.expand("../apps/phx_blog/assets", __DIR__),]
+      end)
+
+      assert_file("config/config.exs", "PhxBlogWeb.Endpoint")
     end)
   end
 
diff --git a/installer/test/phx_new_umbrella_test.exs b/installer/test/phx_new_umbrella_test.exs
index 1ca8a943f..5dca16ba0 100644
--- a/installer/test/phx_new_umbrella_test.exs
+++ b/installer/test/phx_new_umbrella_test.exs
@@ -183,6 +183,11 @@ defmodule Mix.Tasks.Phx.New.UmbrellaTest do
       assert_file(web_path(@app, ".gitignore"), ~r/\n$/)
       assert_file(web_path(@app, "assets/css/app.css"))
 
+      assert_file web_path(@app, "assets/tailwind.config.js"), fn file ->
+        assert file =~ "phx_umb_web.ex"
+        assert file =~ "phx_umb_web/**/*.ex"
+      end
+
       assert_file(web_path(@app, "priv/static/favicon.ico"))
 
       refute File.exists?(web_path(@app, "priv/static/assets/app.css"))
diff --git a/installer/test/phx_new_web_test.exs b/installer/test/phx_new_web_test.exs
index 2f13f4cda..7b6e7114c 100644
--- a/installer/test/phx_new_web_test.exs
+++ b/installer/test/phx_new_web_test.exs
@@ -63,4 +63,15 @@ defmodule Mix.Tasks.Phx.New.WebTest do
       assert_received {:mix_shell, :info, ["Start your Phoenix app" <> _]}
     end
   end
+
+  test "app_name is included in tailwind config" do
+    in_tmp_umbrella_project "new with defaults", fn ->
+      Mix.Tasks.Phx.New.Web.run(["testweb"])
+
+      assert_file "testweb/assets/tailwind.config.js", fn file ->
+        assert file =~ "testweb.ex"
+        assert file =~ "testweb/**/*.ex"
+      end
+    end
+  end
 end
diff --git a/lib/phoenix/test/channel_test.ex b/lib/phoenix/test/channel_test.ex
index a8337b69b..4ca2ccc99 100644
--- a/lib/phoenix/test/channel_test.ex
+++ b/lib/phoenix/test/channel_test.ex
@@ -692,6 +692,8 @@ defmodule Phoenix.ChannelTest do
     do: struct
   def __stringify__(%{} = params),
     do: Enum.into(params, %{}, &stringify_kv/1)
+  def __stringify__(params) when is_list(params),
+    do: Enum.map(params, &__stringify__/1)
   def __stringify__(other),
     do: other
 
diff --git a/test/mix/tasks/phx.gen.html_test.exs b/test/mix/tasks/phx.gen.html_test.exs
index ab3395586..13723b146 100644
--- a/test/mix/tasks/phx.gen.html_test.exs
+++ b/test/mix/tasks/phx.gen.html_test.exs
@@ -324,6 +324,15 @@ defmodule Mix.Tasks.Phx.Gen.HtmlTest do
     end)
   end
 
+  test "with a matching plural and singular term", config do
+    in_tmp_project(config.test, fn ->
+      Gen.Html.run(~w(Tracker Series series value:integer))
+      assert_file("lib/phoenix_web/controllers/series_controller.ex", fn file ->
+        assert file =~ "render(conn, :index, series_collection: series)"
+      end)
+    end)
+  end
+
   test "with --no-context no warning is emitted when context exists", config do
     in_tmp_project(config.test, fn ->
       Gen.Html.run(~w(Blog Post posts title:string))
diff --git a/test/phoenix/socket/v1_json_serializer_test.exs b/test/phoenix/socket/v1_json_serializer_test.exs
index 7f0cfc9eb..62652d3f2 100644
--- a/test/phoenix/socket/v1_json_serializer_test.exs
+++ b/test/phoenix/socket/v1_json_serializer_test.exs
@@ -7,8 +7,6 @@ defmodule Phoenix.Socket.V1.JSONSerializerTest do
   @serializer V1.JSONSerializer
   @v1_msg_json "{\"event\":\"e\",\"payload\":\"m\",\"ref\":null,\"topic\":\"t\"}"
   @v1_bad_json "[null,null,\"t\",\"e\",{\"m\":1}]"
-  @v1_reply_json "{\"event\":\"phx_reply\",\"payload\":{\"response\":null,\"status\":null},\"ref\":\"null\",\"topic\":\"t\"}"
-  @v1_fastlane_json "{\"event\":\"e\",\"payload\":\"m\",\"ref\":null,\"topic\":\"t\"}"
 
   def encode!(serializer, msg) do
     {:socket_push, :text, encoded} = serializer.encode!(msg)
@@ -24,12 +22,26 @@ defmodule Phoenix.Socket.V1.JSONSerializerTest do
 
   test "encode!/1 encodes `Phoenix.Socket.Message` as JSON" do
     msg = %Message{topic: "t", event: "e", payload: "m"}
-    assert encode!(@serializer, msg) == @v1_msg_json
+    encoded = encode!(@serializer, msg)
+
+    assert Jason.decode!(encoded) == %{
+             "event" => "e",
+             "payload" => "m",
+             "ref" => nil,
+             "topic" => "t"
+           }
   end
 
   test "encode!/1 encodes `Phoenix.Socket.Reply` as JSON" do
     msg = %Reply{topic: "t", ref: "null"}
-    assert encode!(@serializer, msg) == @v1_reply_json
+    encoded = encode!(@serializer, msg)
+
+    assert Jason.decode!(encoded) == %{
+             "event" => "phx_reply",
+             "payload" => %{"response" => nil, "status" => nil},
+             "ref" => "null",
+             "topic" => "t"
+           }
   end
 
   test "decode!/2 decodes `Phoenix.Socket.Message` from JSON" do
@@ -47,6 +59,13 @@ defmodule Phoenix.Socket.V1.JSONSerializerTest do
 
   test "fastlane!/1 encodes a broadcast into a message as JSON" do
     msg = %Broadcast{topic: "t", event: "e", payload: "m"}
-    assert fastlane!(@serializer, msg) == @v1_fastlane_json
+    encoded = fastlane!(@serializer, msg)
+
+    assert Jason.decode!(encoded) == %{
+             "event" => "e",
+             "payload" => "m",
+             "ref" => nil,
+             "topic" => "t"
+           }
   end
 end
diff --git a/test/phoenix/socket/v2_json_serializer_test.exs b/test/phoenix/socket/v2_json_serializer_test.exs
index 4723eb727..c534ff47d 100644
--- a/test/phoenix/socket/v2_json_serializer_test.exs
+++ b/test/phoenix/socket/v2_json_serializer_test.exs
@@ -4,7 +4,6 @@ defmodule Phoenix.Socket.V2.JSONSerializerTest do
 
   @serializer V2.JSONSerializer
   @v2_fastlane_json "[null,null,\"t\",\"e\",{\"m\":1}]"
-  @v2_reply_json "[null,null,\"t\",\"phx_reply\",{\"response\":{\"m\":1},\"status\":null}]"
   @v2_msg_json "[null,null,\"t\",\"e\",{\"m\":1}]"
 
   @client_push <<
@@ -101,7 +100,15 @@ defmodule Phoenix.Socket.V2.JSONSerializerTest do
 
   test "encode!/1 encodes `Phoenix.Socket.Reply` as JSON" do
     msg = %Reply{topic: "t", payload: %{m: 1}}
-    assert encode!(@serializer, msg) == @v2_reply_json
+    encoded = encode!(@serializer, msg)
+
+    assert Jason.decode!(encoded) == [
+             nil,
+             nil,
+             "t",
+             "phx_reply",
+             %{"response" => %{"m" => 1}, "status" => nil}
+           ]
   end
 
   test "decode!/2 decodes `Phoenix.Socket.Message` from JSON" do
@@ -225,7 +232,6 @@ defmodule Phoenix.Socket.V2.JSONSerializerTest do
           payload: {:binary, <<101, 102, 103>>}
         })
       end
-
     end
   end
 
diff --git a/test/phoenix/test/channel_test.exs b/test/phoenix/test/channel_test.exs
index fd7078a0f..de69baf31 100644
--- a/test/phoenix/test/channel_test.exs
+++ b/test/phoenix/test/channel_test.exs
@@ -290,6 +290,12 @@ defmodule Phoenix.Test.ChannelTest do
     assert_reply ref, :ok, %{"resp" => "foo"}
   end
 
+  test "works with list data structures" do
+    {:ok, _, socket} = join(socket(UserSocket), Channel, "foo:ok")
+    ref = push(socket, "reply", %{req: [%{bar: "baz"}, %{bar: "foo"}]})
+    assert_reply ref, :ok, %{"resp" => [%{"bar" => "baz"}, %{"bar" => "foo"}]}
+  end
+
   test "receives async replies" do
     {:ok, _, socket} = join(socket(UserSocket), Channel, "foo:ok")