Repository: phoenixframework/phoenix
Pull request #5157 (2022-12-07 17:52:03)

Bump qs from 6.5.2 to 6.5.3 in /assets
Bumps [qs](https://github.com/ljharb/qs) from 6.5.2 to 6.5.3.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a href="https://github.com/ljharb/qs/blob/main/CHANGELOG.md">qs's changelog</a>.</em></p>
<blockquote>
<h2><strong>6.5.3</strong></h2>
<ul>
<li>[Fix] <code>parse</code>: ignore <code>__proto__</code> keys (<a href="https://github-redirect.dependabot.com/ljharb/qs/issues/428">#428</a>)</li>
<li>[Fix]<code> </code>utils.merge`: avoid a crash with a null target and a truthy non-array source</li>
<li>[Fix] correctly parse nested arrays</li>
<li>[Fix] <code>stringify</code>: fix a crash with <code>strictNullHandling</code> and a custom <code>filter</code>/<code>serializeDate</code> (<a href="https://github-redirect.dependabot.com/ljharb/qs/issues/279">#279</a>)</li>
<li>[Fix] <code>utils</code>: <code>merge</code>: fix crash when <code>source</code> is a truthy primitive &amp; no options are provided</li>
<li>[Fix] when <code>parseArrays</code> is false, properly handle keys ending in <code>[]</code></li>
<li>[Fix] fix for an impossible situation: when the formatter is called with a non-string value</li>
<li>[Fix] <code>utils.merge</code>: avoid a crash with a null target and an array source</li>
<li>[Refactor] <code>utils</code>: reduce observable [[Get]]s</li>
<li>[Refactor] use cached <code>Array.isArray</code></li>
<li>[Refactor] <code>stringify</code>: Avoid arr = arr.concat(...), push to the existing instance (<a href="https://github-redirect.dependabot.com/ljharb/qs/issues/269">#269</a>)</li>
<li>[Refactor] <code>parse</code>: only need to reassign the var once</li>
<li>[Robustness] <code>stringify</code>: avoid relying on a global <code>undefined</code> (<a href="https://github-redirect.dependabot.com/ljharb/qs/issues/427">#427</a>)</li>
<li>[readme] remove travis badge; add github actions/codecov badges; update URLs</li>
<li>[Docs] Clean up license text so it’s properly detected as BSD-3-Clause</li>
<li>[Docs] Clarify the need for &quot;arrayLimit&quot; option</li>
<li>[meta] fix README.md (<a href="https://github-redirect.dependabot.com/ljharb/qs/issues/399">#399</a>)</li>
<li>[meta] add FUNDING.yml</li>
<li>[actions] backport actions from main</li>
<li>[Tests] always use <code>String(x)</code> over <code>x.toString()</code></li>
<li>[Tests] remove nonexistent tape option</li>
<li>[Dev Deps] backport from main</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/ljharb/qs/commit/298bfa55d6db00ddea78dd0333509aadf9bb3077"><code>298bfa5</code></a> v6.5.3</li>
<li><a href="https://github.com/ljharb/qs/commit/ed0f5dcbef4b168a8ae299d78b1e4a2e9b1baf1f"><code>ed0f5dc</code></a> [Fix] <code>parse</code>: ignore <code>__proto__</code> keys (<a href="https://github-redirect.dependabot.com/ljharb/qs/issues/428">#428</a>)</li>
<li><a href="https://github.com/ljharb/qs/commit/691e739cfa40cd42604dc05a54e6154371a429ab"><code>691e739</code></a> [Robustness] <code>stringify</code>: avoid relying on a global <code>undefined</code> (<a href="https://github-redirect.dependabot.com/ljharb/qs/issues/427">#427</a>)</li>
<li><a href="https://github.com/ljharb/qs/commit/1072d57d38a690e1ad7616dced44390bffedcbb2"><code>1072d57</code></a> [readme] remove travis badge; add github actions/codecov badges; update URLs</li>
<li><a href="https://github.com/ljharb/qs/commit/12ac1c403aaa04d1a34844f514ed9f9abfb76e64"><code>12ac1c4</code></a> [meta] fix README.md (<a href="https://github-redirect.dependabot.com/ljharb/qs/issues/399">#399</a>)</li>
<li><a href="https://github.com/ljharb/qs/commit/0338716b09fdbd4711823eeb0a14e556a2498e7a"><code>0338716</code></a> [actions] backport actions from main</li>
<li><a href="https://github.com/ljharb/qs/commit/5639c20ce0a7c1332200a3181339331483e5a3a1"><code>5639c20</code></a> Clean up license text so it’s properly detected as BSD-3-Clause</li>
<li><a href="https://github.com/ljharb/qs/commit/51b8a0b1b213596dd1702b837f5e7dec2229793d"><code>51b8a0b</code></a> add FUNDING.yml</li>
<li><a href="https://github.com/ljharb/qs/commit/45f675936e742d92fac8d4dae5cfc385c576a977"><code>45f6759</code></a> [Fix] fix for an impossible situation: when the formatter is called with a no...</li>
<li><a href="https://github.com/ljharb/qs/commit/f814a7f8f2af059f8158f7e4b2bf8b46aeb62cd3"><code>f814a7f</code></a> [Dev Deps] backport from main</li>
<li>Additional commits viewable in <a href="https://github.com/ljharb/qs/compare/v6.5.2...v6.5.3">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=qs&package-manager=npm_and_yarn&previous-version=6.5.2&new-version=6.5.3)](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 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)
You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/phoenixframework/phoenix/network/alerts).

</details>


Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 6c1657dd0..720f167a6 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -21,7 +21,7 @@ jobs:
             lint: true
             installer: true
 
-    runs-on: ubuntu-latest
+    runs-on: ubuntu-20.04
 
     steps:
     - name: Checkout
diff --git a/assets/package-lock.json b/assets/package-lock.json
index b168be3c2..19a2a833f 100644
--- a/assets/package-lock.json
+++ b/assets/package-lock.json
@@ -7015,9 +7015,9 @@
       }
     },
     "node_modules/qs": {
-      "version": "6.5.2",
-      "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
-      "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==",
+      "version": "6.5.3",
+      "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz",
+      "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==",
       "dev": true,
       "engines": {
         "node": ">=0.6"
@@ -14623,9 +14623,9 @@
       "dev": true
     },
     "qs": {
-      "version": "6.5.2",
-      "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
-      "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==",
+      "version": "6.5.3",
+      "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz",
+      "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==",
       "dev": true
     },
     "querystringify": {
diff --git a/guides/controllers.md b/guides/controllers.md
index 14b21474c..aeabe8d47 100644
--- a/guides/controllers.md
+++ b/guides/controllers.md
@@ -181,8 +181,8 @@ What it doesn't have is a view for rendering JSON. Phoenix Controller hands off
   def controller do
     quote do
       use Phoenix.Controller,
-        namespace: HelloWeb,
-        formats: [:html, :json]
+        formats: [:html, :json],
+        layouts: [html: HelloWeb.Layouts]
       ...
     end
   end
diff --git a/guides/ecto.md b/guides/ecto.md
index c2846bccc..67be98a37 100644
--- a/guides/ecto.md
+++ b/guides/ecto.md
@@ -562,7 +562,7 @@ The last change is to open up `lib/hello_phoenix/repo.ex` and make sure to set t
 Now all we need to do is fetch our new dependency, and we'll be ready to go.
 
 ```console
-$ mix do deps.get, compile
+$ mix deps.get
 ```
 
 With our new adapter installed and configured, we're ready to create our database.
diff --git a/guides/howto/custom_error_pages.md b/guides/howto/custom_error_pages.md
index 385e76fa6..bd94ea93c 100644
--- a/guides/howto/custom_error_pages.md
+++ b/guides/howto/custom_error_pages.md
@@ -14,10 +14,10 @@ defmodule HelloWeb.ErrorHTML do
   # uncomment the embed_templates/1 call below
   # and add pages to the error directory:
   #
-  #   * lib/<%= @lib_web_name %>/controllers/error/404.html.heex
-  #   * lib/<%= @lib_web_name %>/controllers/error/500.html.heex
+  #   * lib/<%= @lib_web_name %>/controllers/error_html/404.html.heex
+  #   * lib/<%= @lib_web_name %>/controllers/error_html/500.html.heex
   #
-  # embed_templates "error/*"
+  # embed_templates "error_html/*"
 
   # The default is to render a plain text page based on
   # the template name. For example, "404.html" becomes
diff --git a/guides/routing.md b/guides/routing.md
index 472b2f5b5..cead676be 100644
--- a/guides/routing.md
+++ b/guides/routing.md
@@ -28,7 +28,7 @@ defmodule HelloWeb.Router do
   scope "/", HelloWeb do
     pipe_through :browser
 
-    get "/", PageController, :index
+    get "/", PageController, :home
   end
 
   # Other scopes may use custom stacks.
@@ -48,7 +48,7 @@ Scopes have their own section in this guide, so we won't spend time on the `scop
 Inside the scope block, however, we have our first actual route:
 
 ```elixir
-get "/", PageController, :index
+get "/", PageController, :home
 ```
 
 `get` is a Phoenix macro that corresponds to the HTTP verb GET. Similar macros exist for other HTTP verbs, including POST, PUT, PATCH, DELETE, OPTIONS, CONNECT, TRACE, and HEAD.
@@ -61,11 +61,11 @@ Let's see how this works. Go to the root of a newly-generated Phoenix applicatio
 
 ```console
 $ mix phx.routes
-GET  /  HelloWeb.PageController :index
+GET  /  HelloWeb.PageController :home
 ...
 ```
 
-The route above tells us that any HTTP GET request for the root of the application will be handled by the `index` action of the `HelloWeb.PageController`.
+The route above tells us that any HTTP GET request for the root of the application will be handled by the `home` action of the `HelloWeb.PageController`.
 
 ## Resources
 
@@ -75,7 +75,7 @@ The router supports other macros besides those for HTTP verbs like [`get`](`Phoe
 scope "/", HelloWeb do
   pipe_through :browser
 
-  get "/", PageController, :index
+  get "/", PageController, :home
   resources "/users", UserController
   ...
 end
@@ -469,7 +469,7 @@ defmodule HelloWeb.Router do
   scope "/", HelloWeb do
     pipe_through :browser
 
-    get "/", PageController, :index
+    get "/", PageController, :home
   end
 
   # Other scopes may use custom stacks.
@@ -484,7 +484,7 @@ end
 
 When the server accepts a request, the request will always first pass through the plugs in our endpoint, after which it will attempt to match on the path and HTTP verb.
 
-Let's say that the request matches our first route: a GET to `/`. The router will first pipe that request through the `:browser` pipeline - which will fetch the session data, fetch the flash, and execute forgery protection - before it dispatches the request to `PageController`'s `index` action.
+Let's say that the request matches our first route: a GET to `/`. The router will first pipe that request through the `:browser` pipeline - which will fetch the session data, fetch the flash, and execute forgery protection - before it dispatches the request to `PageController`'s `home` action.
 
 Conversely, suppose the request matches any of the routes defined by the [`resources/2`](`Phoenix.Router.resources/2`) macro. In that case, the router will pipe it through the `:api` pipeline — which currently only performs content negotiation — before it dispatches further to the correct action of the `HelloWeb.ReviewController`.
 
diff --git a/guides/telemetry.md b/guides/telemetry.md
index f8f1c4731..fe6f82776 100644
--- a/guides/telemetry.md
+++ b/guides/telemetry.md
@@ -518,6 +518,7 @@ Library authors are actively encouraged to send a PR adding
 their own (in alphabetical order, please):
 
 * [Absinthe](https://hexdocs.pm/absinthe) - [Events](https://hexdocs.pm/absinthe/telemetry.html)
+* [Ash Framework](https://hexdocs.pm/ash) - [Events](https://hexdocs.pm/ash/monitoring.html)
 * [Broadway](https://hexdocs.pm/broadway) - [Events](https://hexdocs.pm/broadway/Broadway.html#module-telemetry)
 * [Ecto](https://hexdocs.pm/ecto) - [Events](https://hexdocs.pm/ecto/Ecto.Repo.html#module-telemetry-events)
 * [Oban](https://hexdocs.pm/oban) - [Events](https://hexdocs.pm/oban/Oban.Telemetry.html)
diff --git a/installer/lib/mix/tasks/phx.new.ex b/installer/lib/mix/tasks/phx.new.ex
index 6afef65e4..cbc1fd406 100644
--- a/installer/lib/mix/tasks/phx.new.ex
+++ b/installer/lib/mix/tasks/phx.new.ex
@@ -170,8 +170,25 @@ defmodule Mix.Tasks.Phx.New do
     maybe_cd(path, fn ->
       mix_step = install_mix(project, install?)
 
-      if mix_step == [] and rebar_available?() do
-        cmd(project, "mix deps.compile")
+      if mix_step == [] do
+        tasks =
+          if Keyword.get(project.opts, :assets, true) do
+            # First compile only esbuild and tailwind so we can install in parallel
+            cmd(project, "mix deps.compile esbuild tailwind")
+
+            Enum.map(
+              ["mix tailwind.install", "mix esbuild.install"],
+              &Task.async(fn -> cmd(project, &1) end)
+            )
+          else
+            []
+          end
+
+        if rebar_available?() do
+          cmd(project, "mix deps.compile")
+        end
+
+        Task.await_many(tasks, :infinity)
       end
 
       print_missing_steps(cd_step ++ mix_step)
@@ -208,7 +225,11 @@ defmodule Mix.Tasks.Phx.New do
   defp switch_to_string({name, val}), do: name <> "=" <> val
 
   defp install_mix(project, install?) do
-    maybe_cmd(project, "mix deps.get", true, install? && hex_available?())
+    if install? && hex_available?() do
+      cmd(project, "mix deps.get")
+    else
+      ["$ mix deps.get"]
+    end
   end
 
   # TODO: Elixir v1.15 automatically installs Hex/Rebar if missing, so we can simplify this.
@@ -266,24 +287,12 @@ defmodule Mix.Tasks.Phx.New do
 
   ## Helpers
 
-  defp maybe_cmd(project, cmd, should_run?, can_run?) do
-    cond do
-      should_run? && can_run? ->
-        cmd(project, cmd)
-      should_run? ->
-        ["$ #{cmd}"]
-      true ->
-        []
-    end
-  end
-
   defp cmd(%Project{} = project, cmd) do
     Mix.shell().info [:green, "* running ", :reset, cmd]
+
     case Mix.shell().cmd(cmd, cmd_opts(project)) do
-      0 ->
-        []
-      _ ->
-        ["$ #{cmd}"]
+      0 -> []
+      _ -> ["$ #{cmd}"]
     end
   end
 
diff --git a/installer/lib/phx_new/single.ex b/installer/lib/phx_new/single.ex
index 649ebc271..9e0d77ddb 100644
--- a/installer/lib/phx_new/single.ex
+++ b/installer/lib/phx_new/single.ex
@@ -54,7 +54,7 @@ defmodule Phx.New.Single do
      "phx_web/components/layouts/app.html.heex":
        "lib/:lib_web_name/components/layouts/app.html.heex",
      "phx_web/components/layouts.ex": "lib/:lib_web_name/components/layouts.ex"},
-    {:eex, :web, "phx_live/assets/topbar.js": "assets/vendor/topbar.js"}
+    {:eex, :web, "phx_assets/topbar.js": "assets/vendor/topbar.js"}
   ])
 
   template(:ecto, [
diff --git a/installer/lib/phx_new/web.ex b/installer/lib/phx_new/web.ex
index 87a593979..2605d0386 100644
--- a/installer/lib/phx_new/web.ex
+++ b/installer/lib/phx_new/web.ex
@@ -51,7 +51,7 @@ defmodule Phx.New.Web do
        "lib/:web_app/controllers/page_html/home.html.heex",
      "phx_test/controllers/page_controller_test.exs":
        "test/:web_app/controllers/page_controller_test.exs",
-     "phx_live/assets/topbar.js": "assets/vendor/topbar.js",
+     "phx_assets/topbar.js": "assets/vendor/topbar.js",
      "phx_web/components/layouts/root.html.heex": "lib/:web_app/components/layouts/root.html.heex",
      "phx_web/components/layouts/app.html.heex": "lib/:web_app/components/layouts/app.html.heex"}
   ])
diff --git a/installer/templates/phx_live/assets/topbar.js b/installer/templates/phx_assets/topbar.js
similarity index 100%
rename from installer/templates/phx_live/assets/topbar.js
rename to installer/templates/phx_assets/topbar.js
diff --git a/installer/templates/phx_single/README.md b/installer/templates/phx_single/README.md
index cb0a1617b..5c96eb63a 100644
--- a/installer/templates/phx_single/README.md
+++ b/installer/templates/phx_single/README.md
@@ -2,8 +2,7 @@
 
 To start your Phoenix server:
 
-  * Install dependencies with `mix deps.get`<%= if @ecto do %>
-  * Create and migrate your database with `mix ecto.setup`<% end %>
+  * Run `mix setup` to install and setup dependencies
   * Start Phoenix endpoint with `mix phx.server` or inside IEx with `iex -S mix phx.server`
 
 Now you can visit [`localhost:4000`](http://localhost:4000) from your browser.
diff --git a/installer/templates/phx_single/lib/app_name_web.ex b/installer/templates/phx_single/lib/app_name_web.ex
index bf3382b4f..a2d52e1d3 100644
--- a/installer/templates/phx_single/lib/app_name_web.ex
+++ b/installer/templates/phx_single/lib/app_name_web.ex
@@ -39,7 +39,6 @@ defmodule <%= @web_namespace %> do
   def controller do
     quote do
       use Phoenix.Controller,
-        namespace: <%= @web_namespace %>,
         formats: [:html, :json],
         layouts: [html: <%= @web_namespace %>.Layouts]
 
diff --git a/installer/templates/phx_single/mix.exs b/installer/templates/phx_single/mix.exs
index bea1b5f68..9519dd745 100644
--- a/installer/templates/phx_single/mix.exs
+++ b/installer/templates/phx_single/mix.exs
@@ -66,7 +66,7 @@ defmodule <%= @app_module %>.MixProject do
   # See the documentation for `Mix` for more info on aliases.
   defp aliases do
     [
-      setup: ["deps.get"<%= if @ecto do %>, "ecto.setup"<% end %>]<%= if @ecto do %>,
+      setup: ["deps.get"<%= if @ecto do %>, "ecto.setup"<% end %><%= if @assets do %>, "tailwind.install --if-missing", "esbuild.install --if-missing"<% end %>]<%= if @ecto do %>,
       "ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"],
       "ecto.reset": ["ecto.drop", "ecto.setup"],
       test: ["ecto.create --quiet", "ecto.migrate --quiet", "test"]<% end %><%= if @assets do %>,
diff --git a/installer/templates/phx_umbrella/apps/app_name_web/README.md b/installer/templates/phx_umbrella/apps/app_name_web/README.md
index d462ec3e8..fdb87dfcf 100644
--- a/installer/templates/phx_umbrella/apps/app_name_web/README.md
+++ b/installer/templates/phx_umbrella/apps/app_name_web/README.md
@@ -2,8 +2,7 @@
 
 To start your Phoenix server:
 
-  * Install dependencies with `mix deps.get`<%= if @ecto do %>
-  * Create and migrate your database with `mix ecto.setup`<% end %>
+  * Run `mix setup` to install and setup dependencies
   * Start Phoenix endpoint with `mix phx.server`
 
 Now you can visit [`localhost:4000`](http://localhost:4000) from your browser.
diff --git a/installer/templates/phx_umbrella/apps/app_name_web/lib/app_name.ex b/installer/templates/phx_umbrella/apps/app_name_web/lib/app_name.ex
index bf3382b4f..a2d52e1d3 100644
--- a/installer/templates/phx_umbrella/apps/app_name_web/lib/app_name.ex
+++ b/installer/templates/phx_umbrella/apps/app_name_web/lib/app_name.ex
@@ -39,7 +39,6 @@ defmodule <%= @web_namespace %> do
   def controller do
     quote do
       use Phoenix.Controller,
-        namespace: <%= @web_namespace %>,
         formats: [:html, :json],
         layouts: [html: <%= @web_namespace %>.Layouts]
 
diff --git a/installer/templates/phx_umbrella/apps/app_name_web/mix.exs b/installer/templates/phx_umbrella/apps/app_name_web/mix.exs
index 711b02cf4..516cd1f9f 100644
--- a/installer/templates/phx_umbrella/apps/app_name_web/mix.exs
+++ b/installer/templates/phx_umbrella/apps/app_name_web/mix.exs
@@ -60,7 +60,7 @@ defmodule <%= @web_namespace %>.MixProject do
   # See the documentation for `Mix` for more info on aliases.
   defp aliases do
     [
-      setup: ["deps.get"]<%= if @ecto do %>,
+      setup: ["deps.get"<%= if @assets do %>, "tailwind.install --if-missing", "esbuild.install --if-missing"<% end %>]<%= if @ecto do %>,
       test: ["ecto.create --quiet", "ecto.migrate --quiet", "test"]<% end %><%= if @assets do %>,
       "assets.deploy": ["tailwind default --minify", "esbuild default --minify", "phx.digest"]<% end %>
     ]
diff --git a/installer/templates/phx_web/components/core_components.ex b/installer/templates/phx_web/components/core_components.ex
index f9264a307..284246445 100644
--- a/installer/templates/phx_web/components/core_components.ex
+++ b/installer/templates/phx_web/components/core_components.ex
@@ -338,9 +338,7 @@ defmodule <%= @web_namespace %>.CoreComponents do
           "phx-no-feedback:border-zinc-300 phx-no-feedback:focus:border-zinc-400 phx-no-feedback:focus:ring-zinc-800/5"
         ]}
         {@rest}
-      >
-
-    <%%= @value %></textarea>
+      ><%%= @value %></textarea>
       <.error :for={msg <- @errors}><%%= msg %></.error>
     </div>
     """
diff --git a/installer/templates/phx_web/controllers/error_html.ex b/installer/templates/phx_web/controllers/error_html.ex
index 0a9df6221..9d575e7da 100644
--- a/installer/templates/phx_web/controllers/error_html.ex
+++ b/installer/templates/phx_web/controllers/error_html.ex
@@ -5,10 +5,10 @@ defmodule <%= @web_namespace %>.ErrorHTML do
   # uncomment the embed_templates/1 call below
   # and add pages to the error directory:
   #
-  #   * lib/<%= @lib_web_name %>/controllers/error/404.html.heex
-  #   * lib/<%= @lib_web_name %>/controllers/error/500.html.heex
+  #   * lib/<%= @lib_web_name %>/controllers/error_html/404.html.heex
+  #   * lib/<%= @lib_web_name %>/controllers/error_html/500.html.heex
   #
-  # embed_templates "error/*"
+  # embed_templates "error_html/*"
 
   # The default is to render a plain text page based on
   # the template name. For example, "404.html" becomes
diff --git a/lib/mix/phoenix/schema.ex b/lib/mix/phoenix/schema.ex
index 4777a31bf..17bf41da0 100644
--- a/lib/mix/phoenix/schema.ex
+++ b/lib/mix/phoenix/schema.ex
@@ -279,7 +279,7 @@ defmodule Mix.Phoenix.Schema do
 
   defp type_to_default(key, t, :create) do
     case t do
-        {:array, _}     -> []
+        {:array, type} -> build_array_values(type, :create)
         {:enum, values} -> build_enum_values(values, :create)
         :integer        -> 42
         :float          -> 120.5
@@ -300,7 +300,7 @@ defmodule Mix.Phoenix.Schema do
   end
   defp type_to_default(key, t, :update) do
     case t do
-        {:array, _}     -> []
+        {:array, type}  -> build_array_values(type, :update)
         {:enum, values} -> build_enum_values(values, :update)
         :integer        -> 43
         :float          -> 456.7
@@ -320,6 +320,17 @@ defmodule Mix.Phoenix.Schema do
     end
   end
 
+  defp build_array_values(:string, :create),
+    do: Enum.map([1,2], &("option#{&1}"))
+  defp build_array_values(:integer, :create),
+    do: [1,2]
+  defp build_array_values(:string, :update),
+    do: ["option1"]
+  defp build_array_values(:integer, :update),
+    do: [1]
+  defp build_array_values(_, _),
+    do: []
+
   defp build_enum_values(values, action) do
     case {action, translate_enum_vals(values)} do
       {:create, vals} -> hd(vals)
diff --git a/lib/mix/tasks/phx.gen.html.ex b/lib/mix/tasks/phx.gen.html.ex
index f2af40499..bd2d598e0 100644
--- a/lib/mix/tasks/phx.gen.html.ex
+++ b/lib/mix/tasks/phx.gen.html.ex
@@ -201,17 +201,14 @@ defmodule Mix.Tasks.Phx.Gen.Html do
       {key, :naive_datetime} ->
         ~s(<.input field={{f, #{inspect(key)}}} type="datetime-local" label="#{label(key)}" />)
 
-      {key, {:array, :integer}} ->
-        ~s(<.input field={{f, #{inspect(key)}}} type="datetime-local" label="#{label(key)}" />)
-
-      {key, {:array, _}} ->
+      {key, {:array, _} = type} ->
         ~s"""
         <.input
           field={{f, #{inspect(key)}}}
           type="select"
           multiple
           label="#{label(key)}"
-          options={[{"Option 1", "option1"}, {"Option 2", "option2"}]}
+          options={#{inspect(default_options(type))}}
         />
         """
 
@@ -231,6 +228,14 @@ defmodule Mix.Tasks.Phx.Gen.Html do
     end)
   end
 
+  defp default_options({:array, :string}),
+    do: Enum.map([1,2], &({"Option #{&1}", "option#{&1}"}))
+
+  defp default_options({:array, :integer}),
+    do: Enum.map([1,2], &({"#{&1}", &1}))
+
+  defp default_options({:array, _}), do: []
+
   defp label(key), do: Phoenix.Naming.humanize(to_string(key))
 
   @doc false
diff --git a/lib/mix/tasks/phx.gen.json.ex b/lib/mix/tasks/phx.gen.json.ex
index 432b0cd0f..e2c4a8a00 100644
--- a/lib/mix/tasks/phx.gen.json.ex
+++ b/lib/mix/tasks/phx.gen.json.ex
@@ -97,7 +97,13 @@ defmodule Mix.Tasks.Phx.Gen.Json do
     {context, schema} = Gen.Context.build(args)
     Gen.Context.prompt_for_code_injection(context)
 
-    binding = [context: context, schema: schema]
+    binding = [
+      context: context,
+      schema: schema,
+      core_components?: Code.ensure_loaded?(Module.concat(context.web_module, "CoreComponents")),
+      gettext?: Code.ensure_loaded?(Module.concat(context.web_module, "Gettext"))
+    ]
+
     paths = Mix.Phoenix.generator_paths()
 
     prompt_for_conflicts(context)
diff --git a/lib/phoenix/channel.ex b/lib/phoenix/channel.ex
index 8636e734f..8080aff3c 100644
--- a/lib/phoenix/channel.ex
+++ b/lib/phoenix/channel.ex
@@ -68,7 +68,7 @@ defmodule Phoenix.Channel do
       end
 
   ## Broadcasts
- 
+
   Here's an example of receiving an incoming `"new_msg"` event from one client,
   and broadcasting the message to all topic subscribers for this socket.
 
@@ -125,7 +125,7 @@ defmodule Phoenix.Channel do
   `reply/2`.
 
   ## Pushes
-  
+
   Calling `push/3` allows you to send a message to the client which is not a
   reply to a specific client message. Because it is not a reply, a pushed
   message does not contain a client message `ref`; there is no prior client
@@ -228,12 +228,12 @@ defmodule Phoenix.Channel do
   `terminate/2`, however, won't be invoked in case of errors nor in
   case of exits. This is the same behaviour as you find in Elixir
   abstractions like `GenServer` and others. Similar to `GenServer`,
-  it would also be possible `:trap_exit` to guarantee that `terminate/2`
+  it would also be possible to `:trap_exit` to guarantee that `terminate/2`
   is invoked. This practice is not encouraged though.
 
-  Typically speaking, if you want to clean something up, it is better to
+  Generally speaking, if you want to clean something up, it is better to
   monitor your channel process and do the clean up from another process.
-  All channel callbacks including `join/3` are called from within the
+  All channel callbacks, including `join/3`, are called from within the
   channel process. Therefore, `self()` in any of them returns the PID to
   be monitored.
 
@@ -324,12 +324,13 @@ defmodule Phoenix.Channel do
 
   ## Shutdown
 
-  You can configure the shutdown of each channel used when your application
-  is shutting down by setting the `:shutdown` value on use:
+  You can configure the shutdown behavior of each channel used when your
+  application is shutting down by setting the `:shutdown` value on use:
 
       use Phoenix.Channel, shutdown: 5_000
 
-  It defaults to 5_000.
+  It defaults to 5_000. The supported values are described under the
+  in the `Supervisor` module docs.
 
   ## Logging
 
diff --git a/lib/phoenix/controller.ex b/lib/phoenix/controller.ex
index 98f20d571..53bfdd4f4 100644
--- a/lib/phoenix/controller.ex
+++ b/lib/phoenix/controller.ex
@@ -6,7 +6,7 @@ defmodule Phoenix.Controller do
   require Phoenix.Endpoint
 
   @unsent [:unset, :set, :set_chunked, :set_file]
-  
+
   @type view :: atom()
   @type layout :: {module(), layout_name :: atom()} | false
 
@@ -25,7 +25,7 @@ defmodule Phoenix.Controller do
 
         def show(conn, %{"id" => id}) do
           user = Repo.get(User, id)
-          render(conn, "show.html", user: user)
+          render(conn, :show, user: user)
         end
       end
 
@@ -33,16 +33,9 @@ defmodule Phoenix.Controller do
   and the request parameters as arguments. The connection is a
   `Plug.Conn` struct, as specified by the Plug library.
 
-  ## Options
-
-  When used, the controller supports the following options:
-
-    * `:namespace` - sets the namespace to properly inflect
-      the layout view. By default it uses the base alias
-      in your controller name
-
-    * `:put_default_views` - controls whether the default view
-      and layout should be set or not
+  Then we invoke `render/3`, passing the connection, the template
+  to render (typically named after the action), and the `user: user`
+  as assigns. We will explore all of those concepts next.
 
   ## Connection
 
@@ -61,6 +54,73 @@ defmodule Phoenix.Controller do
   without fully implementing the controller, you can import both
   modules directly instead of `use Phoenix.Controller`.
 
+  ## Rendering and layouts
+
+  One of the main features provided by controllers is the ability
+  to perform content negotiation and render templates based on
+  information sent by the client.
+
+  There are two ways to render content in a controller. One option
+  is to invoke format-specific functions, such as `html/2` and `json/2`.
+
+  However, most commonly controllers invoke custom modules called
+  views. Views are modules capable of rendering a custom format.
+  This is done by specifying the option `:formats` when defining
+  the controller:
+
+      use Phoenix.Controller,
+        formats: [:html, :json]
+
+   Now, when invoking `render/3`, a controller named `MyAppWeb.UserController`
+   will invoke `MyAppWeb.UserHTML` and `MyAppWeb.UserJSON` respectively
+   when rendering each format:
+
+      def show(conn, %{"id" => id}) do
+        user = Repo.get(User, id)
+        # Will invoke UserHTML.show(%{user: user}) for html requests
+        # Will invoke UserJSON.show(%{user: user}) for json requests
+        render(conn, :show, user: user)
+      end
+
+  Some formats are also handy to have layouts, which render content
+  shared across all pages. We can also specify layouts on `use`:
+
+      use Phoenix.Controller,
+        formats: [:html, :json],
+        layouts: [html: MyAppWeb.Layouts]
+
+  You can also specify formats and layouts to render by calling
+  `put_view/2` and `put_layout/2` directly with a connection.
+  The line above can also be written directly in your actions as:
+
+      conn
+      |> put_view(html: MyAppWeb.UserHTML, json: MyAppWeb.UserJSON)
+      |> put_layout(html: MyAppWeb.Layouts)
+
+  ### Options
+
+  When used, the controller supports the following options to customize
+  template rendering:
+
+    * `:formats` - the formats this controller will render
+      by default. For example, specifying `formats: [:html, :json]`
+      for a controller named `MyAppWeb.UserController` will
+      invoke `MyAppWeb.UserHTML` and `MyAppWeb.UserJSON` when
+      respectively rendering each format. If `:formats` is not
+      set, the default view is set to `MyAppWeb.UserView`
+
+    * `:layouts` - which layouts to render for each format,
+      for example: `[html: DemoWeb.Layouts]`
+
+  Deprecated options:
+
+    * `:namespace` - sets the namespace for the layout. Use
+      `:layouts` instead
+
+    * `:put_default_views` - controls whether the default view
+      and layout should be set or not. Set `formats: []` and
+      `layouts: []` instead
+
   ## Plug pipeline
 
   As with routers, controllers also have their own plug pipeline.
@@ -150,18 +210,15 @@ defmodule Phoenix.Controller do
         # ...
       end
 
-  ## Rendering and layouts
-
-  One of the main features provided by controllers is the ability
-  to perform content negotiation and render templates based on
-  information sent by the client. Read `render/3` to learn more.
-
-  It is also important not to confuse `Phoenix.Controller.render/3`
-  with `Phoenix.View.render/3`. The former expects
-  a connection and relies on content negotiation while the latter is
-  connection-agnostic and typically invoked from your views.
   """
   defmacro __using__(opts) do
+    opts =
+      if Macro.quoted_literal?(opts) do
+        Macro.prewalk(opts, &expand_alias(&1, __CALLER__))
+      else
+        opts
+      end
+
     quote bind_quoted: [opts: opts] do
       import Phoenix.Controller
 
@@ -177,6 +234,11 @@ defmodule Phoenix.Controller do
     end
   end
 
+  defp expand_alias({:__aliases__, _, _} = alias, env),
+    do: Macro.expand(alias, %{env | function: {:action, 2}})
+
+  defp expand_alias(other, _env), do: other
+
   @doc """
   Registers the plug to call as a fallback to the controller action.
 
@@ -446,15 +508,15 @@ defmodule Phoenix.Controller do
   Stores the view for rendering.
 
   Raises `Plug.Conn.AlreadySentError` if `conn` is already sent.
-  
+
   ## Examples
-  
+
       # Use single view module
       iex> put_view(conn, AppView)
-      
+
       # Use multiple view module for content negotiation
       iex> put_view(conn, html: AppHTML, json: AppJSON)
-      
+
   """
   @spec put_view(Plug.Conn.t(), [{format :: atom, view}] | view) :: Plug.Conn.t()
   def put_view(%Plug.Conn{state: state} = conn, formats) when state in @unsent do
@@ -536,15 +598,11 @@ defmodule Phoenix.Controller do
       iex> layout(conn)
       false
 
-      iex> conn = put_layout conn, {AppView, "application.html"}
-      iex> layout(conn)
-      {AppView, "application.html"}
-
-      iex> conn = put_layout conn, "print.html"
+      iex> conn = put_layout(conn, {AppView, :application})
       iex> layout(conn)
-      {AppView, "print.html"}
+      {AppView, :application}
 
-      iex> conn = put_layout conn, :print
+      iex> conn = put_layout(conn, :print)
       iex> layout(conn)
       {AppView, :print}
 
@@ -635,15 +693,11 @@ defmodule Phoenix.Controller do
       iex> root_layout(conn)
       false
 
-      iex> conn = put_root_layout conn, {AppView, "root.html"}
-      iex> root_layout(conn)
-      {AppView, "root.html"}
-
-      iex> conn = put_root_layout conn, "bare.html"
+      iex> conn = put_root_layout(conn, {AppView, :root})
       iex> root_layout(conn)
-      {AppView, "bare.html"}
+      {AppView, :root}
 
-      iex> conn = put_root_layout conn, :bare
+      iex> conn = put_root_layout(conn, :bare)
       iex> root_layout(conn)
       {AppView, :bare}
 
@@ -1758,7 +1812,7 @@ defmodule Phoenix.Controller do
 
           other ->
             raise ArgumentError, """
-            expected :layouts to be a list of format module pairs of the form: [html: DemoWEb.Layouts] or [html: {DemoWeb.Layouts, :app}]
+            expected :layouts to be a list of format module pairs of the form: [html: DemoWeb.Layouts] or [html: {DemoWeb.Layouts, :app}]
 
             Got: #{inspect(other)}
             """
diff --git a/lib/phoenix/verified_routes.ex b/lib/phoenix/verified_routes.ex
index 858e2e371..69e734bd0 100644
--- a/lib/phoenix/verified_routes.ex
+++ b/lib/phoenix/verified_routes.ex
@@ -111,8 +111,14 @@ defmodule Phoenix.VerifiedRoutes do
 
   defmacro __using__(opts) do
     opts =
-      if Macro.quoted_literal?(opts) do
-        Macro.prewalk(opts, &expand_alias(&1, __CALLER__))
+      if Keyword.keyword?(opts) do
+        for {k, v} <- opts do
+          if Macro.quoted_literal?(v) do
+            {k, Macro.prewalk(v, &expand_alias(&1, __CALLER__))}
+          else
+            {k, v}
+          end
+        end
       else
         opts
       end
diff --git a/priv/templates/phx.gen.json/changeset_json.ex b/priv/templates/phx.gen.json/changeset_json.ex
index 77f511f6d..89b1aa1d1 100644
--- a/priv/templates/phx.gen.json/changeset_json.ex
+++ b/priv/templates/phx.gen.json/changeset_json.ex
@@ -1,20 +1,39 @@
 defmodule <%= inspect context.web_module %>.ChangesetJSON do
-  @doc """
-  Traverses and translates changeset errors.
-
-  See `Ecto.Changeset.traverse_errors/2` and
-  `<%= inspect context.web_module %>.CoreCompponents.translate_error/1` for more details.
-  """
-  def translate_errors(changeset) do
-    Ecto.Changeset.traverse_errors(changeset, &<%= inspect context.web_module %>.CoreComponents.translate_error/1)
-  end
-
   @doc """
   Renders changeset errors.
-  """
+  """<%= if core_components? do %>
+  def error(%{changeset: changeset}) do
+    # When encoded, the changeset returns its errors
+    # as a JSON object. So we just pass it forward.
+    %{errors: Ecto.Changeset.traverse_errors(changeset, &<%= inspect context.web_module %>.CoreComponents.translate_error/1)}
+  end<% else %>
   def error(%{changeset: changeset}) do
     # When encoded, the changeset returns its errors
     # as a JSON object. So we just pass it forward.
-    %{errors: translate_errors(changeset)}
+    %{errors: Ecto.Changeset.traverse_errors(changeset, &translate_error/1)}
+  end
+<%= if gettext? do %>
+  defp translate_error({msg, opts}) do
+    # set by Ecto and indicates we should also apply plural rules.
+    if count = opts[:count] do
+      Gettext.dngettext(<%= inspect context.web_module %>.Gettext, "errors", msg, msg, count, opts)
+    else
+      Gettext.dgettext(<%= inspect context.web_module %>.Gettext, "errors", msg, opts)
+    end
   end
+<% else %>
+  defp translate_error({msg, opts}) do
+    # You can make use of gettext to translate error messages by
+    # uncommenting and adjusting the following code:
+
+    # if count = opts[:count] do
+    #   Gettext.dngettext(<%= inspect context.web_module %>.Gettext, "errors", msg, msg, count, opts)
+    # else
+    #   Gettext.dgettext(<%= inspect context.web_module %>.Gettext, "errors", msg, opts)
+    # end
+
+    Enum.reduce(opts, msg, fn {key, value}, acc ->
+      String.replace(acc, "%{#{key}}", fn _ -> to_string(value) end)
+    end)
+  end<% end %><% end %>
 end
diff --git a/priv/templates/phx.gen.presence/presence.ex b/priv/templates/phx.gen.presence/presence.ex
index 204ef333c..de5d98d61 100644
--- a/priv/templates/phx.gen.presence/presence.ex
+++ b/priv/templates/phx.gen.presence/presence.ex
@@ -5,6 +5,7 @@ defmodule <%= module %> do
   See the [`Phoenix.Presence`](https://hexdocs.pm/phoenix/Phoenix.Presence.html)
   docs for more details.
   """
-  use Phoenix.Presence, otp_app: <%= inspect otp_app %>,
-                        pubsub_server: <%= inspect pubsub_server %>
+  use Phoenix.Presence,
+    otp_app: <%= inspect otp_app %>,
+    pubsub_server: <%= inspect pubsub_server %>
 end


Test patch:
diff --git a/installer/test/phx_new_test.exs b/installer/test/phx_new_test.exs
index 39667c712..71b9f1e4a 100644
--- a/installer/test/phx_new_test.exs
+++ b/installer/test/phx_new_test.exs
@@ -576,11 +576,6 @@ defmodule Mix.Tasks.Phx.NewTest do
       assert_file("custom_path/mix.exs", ~r/app: :phx_blog/)
       assert_file("custom_path/lib/phx_blog_web/endpoint.ex", ~r/app: :phx_blog/)
       assert_file("custom_path/config/config.exs", ~r/namespace: PhoteuxBlog/)
-
-      assert_file(
-        "custom_path/lib/phx_blog_web.ex",
-        ~r/use Phoenix.Controller,\n.*namespace: PhoteuxBlogWeb/
-      )
     end)
   end
 
diff --git a/installer/test/phx_new_umbrella_test.exs b/installer/test/phx_new_umbrella_test.exs
index c7d870211..99bd4f160 100644
--- a/installer/test/phx_new_umbrella_test.exs
+++ b/installer/test/phx_new_umbrella_test.exs
@@ -556,11 +556,6 @@ defmodule Mix.Tasks.Phx.New.UmbrellaTest do
         ~r/app: :#{@app}_web/
       )
 
-      assert_file(
-        "custom_path_umbrella/apps/phx_umb_web/lib/#{@app}_web.ex",
-        ~r/use Phoenix.Controller,\n.*namespace: PhoteuxBlogWeb/
-      )
-
       assert_file(
         "custom_path_umbrella/apps/phx_umb/lib/phx_umb/application.ex",
         ~r/defmodule PhoteuxBlog.Application/
diff --git a/lib/phoenix/test/conn_test.ex b/lib/phoenix/test/conn_test.ex
index 013af32ca..520938bbf 100644
--- a/lib/phoenix/test/conn_test.ex
+++ b/lib/phoenix/test/conn_test.ex
@@ -589,6 +589,7 @@ defmodule Phoenix.ConnTest do
   def redirected_params(%Plug.Conn{} = conn, status \\ 302) do
     router = Phoenix.Controller.router_module(conn)
     %URI{path: path, host: host} = conn |> redirected_to(status) |> URI.parse()
+    path = remove_script_name(conn, router, path)
 
     case Phoenix.Router.route_info(router, "GET", path, host || conn.host) do
       :error ->
@@ -598,6 +599,17 @@ defmodule Phoenix.ConnTest do
     end
   end
 
+  defp remove_script_name(conn, router, path) do
+    case conn.private[router] do
+      [_ | _] = list ->
+        script_name = "/" <> Enum.join(list, ",")
+        String.replace_leading(path, script_name, "")
+
+      _ ->
+        path
+    end
+  end
+
   @doc """
   Returns the matched params of the URL for the `%Plug.Conn{}`'s router.
 
diff --git a/test/mix/phoenix_test.exs b/test/mix/phoenix_test.exs
index 2fde37a23..fce3de3f6 100644
--- a/test/mix/phoenix_test.exs
+++ b/test/mix/phoenix_test.exs
@@ -18,6 +18,7 @@ defmodule Mix.PhoenixTest do
   test "attrs/1 defaults each type" do
     attrs = [
       "logins:array:string",
+      "lottery_numbers:array:integer",
       "age:integer",
       "temp:float",
       "temp_2:decimal",
@@ -32,6 +33,7 @@ defmodule Mix.PhoenixTest do
 
     assert Mix.Phoenix.Schema.attrs(attrs) == [
              logins: {:array, :string},
+             lottery_numbers: {:array, :integer},
              age: :integer,
              temp: :float,
              temp_2: :decimal,
@@ -71,7 +73,7 @@ defmodule Mix.PhoenixTest do
     ]
 
     assert Mix.Phoenix.Schema.params(params) == %{
-             logins: [],
+             logins: ["option1", "option2"],
              age: 42,
              temp: 120.5,
              temp_2: "120.5",
@@ -110,6 +112,9 @@ defmodule Mix.PhoenixTest do
     assert Mix.Phoenix.Schema.live_form_value(~U[2020-10-09T14:00:00Z]) ==
              "2020-10-09T14:00:00Z"
 
+    assert Mix.Phoenix.Schema.live_form_value([1]) == [1]
+    assert Mix.Phoenix.Schema.live_form_value(["option1"]) == ["option1"]
+
     assert Mix.Phoenix.Schema.live_form_value(:value) == :value
   end
 
diff --git a/test/mix/tasks/phx.gen.cert_test.exs b/test/mix/tasks/phx.gen.cert_test.exs
index 7e8c66090..06d5a30c9 100644
--- a/test/mix/tasks/phx.gen.cert_test.exs
+++ b/test/mix/tasks/phx.gen.cert_test.exs
@@ -56,7 +56,7 @@ defmodule Mix.Tasks.Phx.CertTest do
 
       # We don't actually verify the server cert contents, we just check that
       # the client and server are able to complete the TLS handshake
-      assert {:ok, client} = :ssl.connect('localhost', port, [], @timeout)
+      assert {:ok, client} = :ssl.connect('localhost', port, [verify: :verify_none], @timeout)
       :ssl.close(client)
       :ssl.close(server)
     end)
diff --git a/test/mix/tasks/phx.gen.json_test.exs b/test/mix/tasks/phx.gen.json_test.exs
index cc9475a45..40999e5b4 100644
--- a/test/mix/tasks/phx.gen.json_test.exs
+++ b/test/mix/tasks/phx.gen.json_test.exs
@@ -181,6 +181,10 @@ defmodule Mix.Tasks.Phx.Gen.JsonTest do
         assert file =~ "defmodule PhoenixWeb.Blog.PostJSON"
       end)
 
+      assert_file("lib/phoenix_web/controllers/blog/changeset_json.ex", fn file ->
+        assert file =~ "Ecto.Changeset.traverse_errors(changeset, &translate_error/1)"
+      end)
+
       assert_receive {:mix_shell, :info,
                       [
                         """
@@ -326,4 +330,28 @@ defmodule Mix.Tasks.Phx.Gen.JsonTest do
       end)
     end
   end
+
+  test "with existing core_components.ex file", config do
+    in_tmp_project(config.test, fn ->
+      File.mkdir_p!("lib/phoenix_web/components")
+
+      File.write!("lib/phoenix_web/components/core_components.ex", """
+      defmodule PhoenixWeb.CoreComponents do
+      end
+      """)
+
+      [{module, _}] = Code.compile_file("lib/phoenix_web/components/core_components.ex")
+
+      Gen.Json.run(~w(Blog Post posts title:string --web Blog))
+
+      assert_file("lib/phoenix_web/controllers/blog/changeset_json.ex", fn file ->
+        assert file =~
+                 "Ecto.Changeset.traverse_errors(changeset, &PhoenixWeb.CoreComponents.translate_error/1)"
+      end)
+
+      # Clean up test case specific compile artifact so it doesn't leak to other test cases
+      :code.purge(module)
+      :code.delete(module)
+    end)
+  end
 end
diff --git a/test/phoenix/test/conn_test.exs b/test/phoenix/test/conn_test.exs
index 36dc360b8..1041794e1 100644
--- a/test/phoenix/test/conn_test.exs
+++ b/test/phoenix/test/conn_test.exs
@@ -39,6 +39,7 @@ defmodule Phoenix.Test.ConnTest.Router do
   scope "/" do
     pipe_through :browser
     get "/stat", CatchAll, :stat, private: %{route: :stat}
+    forward "/redir", Phoenix.Test.ConnTest.RedirRouter
     forward "/", CatchAll
   end
 
@@ -438,6 +439,16 @@ defmodule Phoenix.Test.ConnTest do
       assert redirected_params(conn) == %{id: "123"}
     end
 
+    test "with matching forwarded route" do
+      conn =
+        build_conn(:get, "/redir")
+        |> Router.call(Router.init([]))
+        |> put_resp_header("location", "/redir/posts/123")
+        |> send_resp(302, "foo")
+
+      assert redirected_params(conn) == %{id: "123"}
+    end
+
     test "raises Phoenix.Router.NoRouteError for unmatched location" do
       conn =
         build_conn(:get, "/")