Repository: phoenixframework/phoenix
Pull request #5090 (2022-11-08 17:24:37)

Unify `gen.auth` and `phx.new` menu styles
Hi all,
Pulled the 1.7rc, and noticed that the `gen.auth` templates aren't stylistically aligned with the awesome work that the tailwind devs applied to the rest of the `phx.new` templates. There are a handful of additional updates I would make, but I wanted to keep the PRs small, so this one just updates the auth-related link styles so that they're identical to the link styles from `phx.new`.

## Before
<img width="1512" alt="before_styling_updates" src="https://user-images.githubusercontent.com/4386645/200632837-9b36a020-0e8d-47d9-92a5-da789e3c38d5.png">

## After

<img width="1512" alt="after_styling_updates" src="https://user-images.githubusercontent.com/4386645/200633090-b54802b5-6f1f-447b-a956-23a7e78b3687.png">

## Possible Additional Update

I _think_ it would make sense to replace the `@phoenixframework`, `GitHub` and `Getting Started` links if a user has already taken the step to generate authentication, as that implies that the developer already has a level of fluency in the elixir/phoenix ecosystem. Then these registration links would sit in their place, inline with the phoenix logo, but that change feels like a bigger discussion and might be non-trivial given the existing structure of the generators and templates. 



derpycoder: Here’s my take on a Unfied Auth look:

[](https://elixirforum.com/t/phoenix-1-7-0-rc-0-is-out/51625/15?u=derpycoder) 

![6D736FEE-5975-4FD3-8F11-BC9FC8D83D23](https://user-images.githubusercontent.com/25662120/210178356-e8b6da2d-d52a-4fcf-96e4-fdedb987634a.png)
![4FE852EB-F605-49F7-8329-17B80F0E7D8A](https://user-images.githubusercontent.com/25662120/210178359-3b4f6c97-46ad-4d0d-b24a-b2778354b014.png)
![A069A580-D0E8-4B76-822E-9F9858ACB300](https://user-images.githubusercontent.com/25662120/210178360-43f6ed52-a95e-4e5a-82e1-4368c90eebcc.png)

derpycoder: ![3108B327-A1A8-4E8E-A000-626B05E41BC3](https://user-images.githubusercontent.com/25662120/210178415-6595ee4f-1d71-4a21-ad01-dd8c338f19ca.png)

Eiji7: Any update for this? @derpycoder proposal looks great and I'm looking forward to test `phx.new` with this update.
derpycoder: I am a beginner, I just made some styling changes in the front end and showed them here. I did not contribute to the project, sorry about that.

I just thought I can influence @vanderhoop to make a slight modification, as he took the initiative to make this look awesome.

I don't know if the design I proposed, will work with the phoenix auth generator or not, so I left it to experienced developers.
vanderhoop: hey @derpycoder + @Eiji7! I've been punting on this, as I was waiting word on how the maintainers wanted the tests updated (see [comment](https://github.com/phoenixframework/phoenix/pull/5090#discussion_r1016927569) above). That said, I'll pull the latest RC and see if I can implement some of the styles proposed without much of a lift, as this would close issue #5176. Updating the test assertions in the snapshot sense would be good enough, as that's the status quo, and assertion updates could follow at any time.
vanderhoop: hey @derpycoder + @Eiji7! I'm going to keep this PR scoped to just updating the link styles, and avoid changing their placement in the layouts for now, as the injector's logic for placement is non-trivial and getting the various CI test suites to pass took more time than I expected 😅.

If and when _this_ PR gets merged, I think I can open a follow-up getting these links get added to the _existing_ set of links in the header. It won't _quite_ meet the design you proposed up above for the header, but I think it would be an overall aesthetic improvement.

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c99776178..b33adce0e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -38,7 +38,7 @@ will continue to work and be generated. You can disable router helpers by passin
 ## 1.7.0-rc.4
 
 ### Bug Fixes
-  * Fix race conditions in the longpoll trasnport by batching messages
+  * Fix race conditions in the longpoll transport by batching messages
 
 ## 1.7.0-rc.3 (2023-02-15)
 
diff --git a/guides/components.md b/guides/components.md
index 31e1ce95a..af07635bc 100644
--- a/guides/components.md
+++ b/guides/components.md
@@ -34,7 +34,7 @@ That's simple enough. There's only two lines, `use HelloWeb, :html`. This line c
 
 All of the imports and aliases we make in our module will also be available in our templates. That's because templates are effectively compiled into functions inside their respective module. For example, if you define a function in your module, you will be able to invoke it directly from the template. Let's see this in practice.
 
-Imagine we want to refactor our `show.html.heex` to move the rendering of `<h2>Hello World, from <%= @messenger %>!</h2>` to its own function. We can move it to a function component inside `HelloHTML`:
+Imagine we want to refactor our `show.html.heex` to move the rendering of `<h2>Hello World, from <%= @messenger %>!</h2>` to its own function. We can move it to a function component inside `HelloHTML`, let's do so:
 
 ```elixir
 defmodule HelloWeb.HelloHTML do
@@ -52,7 +52,7 @@ defmodule HelloWeb.HelloHTML do
 end
 ```
 
-We declared the attributes we accept via `attr` provided by `Phoenix.Component`, then we defined our `title/1` function which returns the HEEx template. When we reload our home page, we should see our new title. Since templates are compiled inside the view, we can invoke the view function simply as `<.greet messenger="..." />`, but we can also type `<HelloWeb.HelloHTML.greet messenger="..." />` if the component was defined elsewhere.
+In the example above, we defined a `greet/1` function which returns the HEEx template. Above the function, we called `attr`, provided by `Phoenix.Component`, which defines the attributes/assigns that function expects. Since templates are embedded inside the `HelloHTML` module, we can invoke the our component simply as `<.greet messenger="..." />`, but we can also type `<HelloWeb.HelloHTML.greet messenger="..." />` if the component was defined elsewhere.
 
 By declaring attributes, Phoenix will warn if we call the `<.greet />` component without passing attributes. If an attribute is optional, you can specify the `:default` option with a value:
 
@@ -183,13 +183,13 @@ In other words, after rendering your page, the result is placed in the `@inner_c
 
 Phoenix provides all kinds of conveniences to control which layout should be rendered. For example, the `Phoenix.Controller` module provides the `put_root_layout/2` function for us to switch _root layouts_. This takes `conn` as its first argument and a keyword list of formats and their layouts. You can set it to `false` to disable the layout altogether.
 
-You can edit the `index` action of `PageController` in `lib/hello_web/controllers/page_controller.ex` to look like this.
+You can edit the `home` action of `PageController` in `lib/hello_web/controllers/page_controller.ex` to look like this.
 
 ```elixir
-def index(conn, _params) do
+def home(conn, _params) do
   conn
   |> put_root_layout(html: false)
-  |> render(:index)
+  |> render(:home)
 end
 ```
 
@@ -197,13 +197,13 @@ After reloading [http://localhost:4000/](http://localhost:4000/), we should see
 
 To customize the application layout, we invoke a similar function named `put_layout/2`. Let's actually create another layout and render the index template into it. As an example, let's say we had a different layout for the admin section of our application which didn't have the logo image. To do this, copy the existing `app.html.heex` to a new file `admin.html.heex` in the same directory `lib/hello_web/components/layouts`. Then remove everything inside the `<header>...</header>` tags (or change it to whatever you desire) in the new file.
 
-Now, in the `index` action of the controller of `lib/hello_web/controllers/page_controller.ex`, add the following:
+Now, in the `home` action of the controller of `lib/hello_web/controllers/page_controller.ex`, add the following:
 
 ```elixir
-def index(conn, _params) do
+def home(conn, _params) do
   conn
   |> put_layout(html: :admin)
-  |> render(:index)
+  |> render(:home)
 end
 ```
 
diff --git a/installer/lib/phx_new/generator.ex b/installer/lib/phx_new/generator.ex
index 015f8c52e..64f201a7a 100644
--- a/installer/lib/phx_new/generator.ex
+++ b/installer/lib/phx_new/generator.ex
@@ -356,7 +356,8 @@ defmodule Phx.New.Generator do
           For example: ecto://USER:PASS@HOST/DATABASE
           \"""
 
-      maybe_ipv6 = if System.get_env("ECTO_IPV6"), do: [:inet6], else: []
+      maybe_ipv6 = if System.get_env("ECTO_IPV6") in ~w(true 1), do: [:inet6], else: []
+
       """,
       prod_config: """
       # ssl: true,
diff --git a/lib/mix/tasks/phx.gen.auth/injector.ex b/lib/mix/tasks/phx.gen.auth/injector.ex
index 98d2bebd3..c290a1cfc 100644
--- a/lib/mix/tasks/phx.gen.auth/injector.ex
+++ b/lib/mix/tasks/phx.gen.auth/injector.ex
@@ -149,6 +149,7 @@ defmodule Mix.Tasks.Phx.Gen.Auth.Injector do
   """
   def app_layout_menu_help_text(file_path, %Schema{} = schema) do
     {_dup_check, code} = app_layout_menu_code_to_inject(schema)
+
     """
     Add the following #{schema.singular} menu items to your #{Path.relative_to_cwd(file_path)} layout file:
 
@@ -162,24 +163,48 @@ defmodule Mix.Tasks.Phx.Gen.Auth.Injector do
   def app_layout_menu_code_to_inject(%Schema{} = schema, padding \\ 4, newline \\ "\n") do
     already_injected_str = "#{schema.route_prefix}/log_in"
 
+    base_tailwind_classes = "text-[0.8125rem] leading-6 text-zinc-900"
+    link_tailwind_classes = "#{base_tailwind_classes} font-semibold hover:text-zinc-700"
+
     template = """
-    <ul>
+    <ul class="flex items-center gap-4 px-4 sm:px-6 lg:px-8 justify-end">
       <%= if @current_#{schema.singular} do %>
-        <li>
+        <li class="#{base_tailwind_classes}">
           <%= @current_#{schema.singular}.email %>
         </li>
         <li>
-          <.link href={~p"#{schema.route_prefix}/settings"}>Settings</.link>
+          <.link
+            href={~p"#{schema.route_prefix}/settings"}
+            class="#{link_tailwind_classes}"
+          >
+            Settings
+          </.link>
         </li>
         <li>
-          <.link href={~p"#{schema.route_prefix}/log_out"} method="delete">Log out</.link>
+          <.link
+            href={~p"#{schema.route_prefix}/log_out"}
+            method="delete"
+            class="#{link_tailwind_classes}"
+          >
+            Log out
+          </.link>
         </li>
       <% else %>
         <li>
-          <.link href={~p"#{schema.route_prefix}/register"}>Register</.link>
+          <.link
+            href={~p"#{schema.route_prefix}/register"}
+            class="#{link_tailwind_classes}"
+          >
+            Register
+          </.link>
         </li>
         <li>
-          <.link href={~p"#{schema.route_prefix}/log_in"}>Log in</.link>
+          <.link
+            href={~p"#{schema.route_prefix}/log_in"}
+            class="#{link_tailwind_classes}"
+          >
+            Log in
+          </.link>
         </li>
       <% end %>
     </ul>\
diff --git a/lib/mix/tasks/phx.gen.release.ex b/lib/mix/tasks/phx.gen.release.ex
index e0cf66944..dc97c567e 100644
--- a/lib/mix/tasks/phx.gen.release.ex
+++ b/lib/mix/tasks/phx.gen.release.ex
@@ -113,7 +113,7 @@ defmodule Mix.Tasks.Phx.Gen.Release do
 
       Add the following to your config/runtime.exs:
 
-          maybe_ipv6 = if System.get_env("ECTO_IPV6"), do: [:inet6], else: []
+          maybe_ipv6 = if System.get_env("ECTO_IPV6") in ~w(true 1), do: [:inet6], else: []
 
           config :#{app}, #{app_namespace}.Repo,
             ...,
diff --git a/lib/phoenix/controller.ex b/lib/phoenix/controller.ex
index 802a2c915..2b15e8e44 100644
--- a/lib/phoenix/controller.ex
+++ b/lib/phoenix/controller.ex
@@ -690,7 +690,7 @@ defmodule Phoenix.Controller do
 
   Raises `Plug.Conn.AlreadySentError` if `conn` is already sent.
   """
-  @spec put_new_layout(Plug.Conn.t(), [{format :: atom, layout}]) :: Plug.Conn.t()
+  @spec put_new_layout(Plug.Conn.t(), [{format :: atom, layout}] | layout) :: Plug.Conn.t()
   def put_new_layout(%Plug.Conn{state: state} = conn, layout)
       when (is_tuple(layout) and tuple_size(layout) == 2) or is_list(layout) or layout == false do
     unless state in @unsent, do: raise(AlreadySentError)
diff --git a/lib/phoenix/socket/serializers/v1_json_serializer.ex b/lib/phoenix/socket/serializers/v1_json_serializer.ex
index 174f70c35..cd96fe8cc 100644
--- a/lib/phoenix/socket/serializers/v1_json_serializer.ex
+++ b/lib/phoenix/socket/serializers/v1_json_serializer.ex
@@ -28,9 +28,15 @@ defmodule Phoenix.Socket.V1.JSONSerializer do
 
   @impl true
   def decode!(message, _opts) do
-    message
-    |> Phoenix.json_library().decode!()
-    |> Phoenix.Socket.Message.from_map!()
+    payload = Phoenix.json_library().decode!(message)
+
+    case payload do
+      %{} ->
+        Phoenix.Socket.Message.from_map!(payload)
+
+      other ->
+        raise "V1 JSON Serializer expected a map, got #{inspect(other)}"
+    end
   end
 
   defp encode_v1_fields_only(%Message{} = msg) do
diff --git a/priv/templates/phx.gen.auth/auth.ex b/priv/templates/phx.gen.auth/auth.ex
index 6dcf8ff68..9aa6a753e 100644
--- a/priv/templates/phx.gen.auth/auth.ex
+++ b/priv/templates/phx.gen.auth/auth.ex
@@ -173,15 +173,11 @@ defmodule <%= inspect auth_module %> do
   end
 
   defp mount_current_<%= schema.singular %>(session, socket) do
-    case session do
-      %{"<%= schema.singular %>_token" => <%= schema.singular %>_token} ->
-        Phoenix.Component.assign_new(socket, :current_<%= schema.singular %>, fn ->
-          <%= inspect context.alias %>.get_<%= schema.singular %>_by_session_token(<%= schema.singular %>_token)
-        end)
-
-      %{} ->
-        Phoenix.Component.assign_new(socket, :current_<%= schema.singular %>, fn -> nil end)
-    end
+    Phoenix.Component.assign_new(socket, :current_<%= schema.singular %>, fn ->
+      if <%= schema.singular %>_token = session["<%= schema.singular %>_token"] do
+        <%= inspect context.alias %>.get_<%= schema.singular %>_by_session_token(<%= schema.singular %>_token)
+      end
+    end)
   end
 
   @doc """
diff --git a/priv/templates/phx.gen.auth/schema.ex b/priv/templates/phx.gen.auth/schema.ex
index 81a33e83a..2624e96f3 100644
--- a/priv/templates/phx.gen.auth/schema.ex
+++ b/priv/templates/phx.gen.auth/schema.ex
@@ -54,6 +54,7 @@ defmodule <%= inspect schema.module %> do
     changeset
     |> validate_required([:password])
     |> validate_length(:password, min: 12, max: 72)
+    # Examples of additional password validation:
     # |> validate_format(:password, ~r/[a-z]/, message: "at least one lower case character")
     # |> validate_format(:password, ~r/[A-Z]/, message: "at least one upper case character")
     # |> validate_format(:password, ~r/[!?@#$%^&*_0-9]/, message: "at least one digit or punctuation character")
@@ -68,6 +69,8 @@ defmodule <%= inspect schema.module %> do
       changeset<%= if hashing_library.name == :bcrypt do %>
       # If using Bcrypt, then further validate it is at most 72 bytes long
       |> validate_length(:password, max: 72, count: :bytes)<% end %>
+      # Hashing could be done with `Ecto.Changeset.prepare_changes/2`, but that
+      # would keep the database transaction open longer and hurt performance.
       |> put_change(:hashed_password, <%= inspect hashing_library.module %>.hash_pwd_salt(password))
       |> delete_change(:password)
     else


Test patch:
diff --git a/installer/test/phx_new_test.exs b/installer/test/phx_new_test.exs
index f73d11fe9..9600d3320 100644
--- a/installer/test/phx_new_test.exs
+++ b/installer/test/phx_new_test.exs
@@ -178,7 +178,10 @@ defmodule Mix.Tasks.Phx.NewTest do
 
       assert_file("phx_blog/config/runtime.exs", fn file ->
         assert file =~ config
-        assert file =~ ~S|maybe_ipv6 = if System.get_env("ECTO_IPV6"), do: [:inet6], else: []|
+
+        assert file =~
+                 ~S|maybe_ipv6 = if System.get_env("ECTO_IPV6") in ~w(true 1), do: [:inet6], else: []|
+
         assert file =~ ~S|socket_options: maybe_ipv6|
 
         assert file =~ """
diff --git a/priv/templates/phx.gen.auth/forgot_password_live_test.exs b/priv/templates/phx.gen.auth/forgot_password_live_test.exs
index 8e011d332..ef417c907 100644
--- a/priv/templates/phx.gen.auth/forgot_password_live_test.exs
+++ b/priv/templates/phx.gen.auth/forgot_password_live_test.exs
@@ -9,11 +9,11 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web
 
   describe "Forgot password page" do
     test "renders email page", %{conn: conn} do
-      {:ok, _lv, html} = live(conn, ~p"<%= schema.route_prefix %>/reset_password")
+      {:ok, lv, html} = live(conn, ~p"<%= schema.route_prefix %>/reset_password")
 
       assert html =~ "Forgot your password?"
-      assert html =~ "Register</a>"
-      assert html =~ "Log in</a>"
+      assert has_element?(lv, ~s|a[href="#{~p"/users/register"}"]|, "Register")
+      assert has_element?(lv, ~s|a[href="#{~p"/users/log_in"}"]|, "Log in")
     end
 
     test "redirects if already logged in", %{conn: conn} do
diff --git a/priv/templates/phx.gen.auth/registration_controller_test.exs b/priv/templates/phx.gen.auth/registration_controller_test.exs
index c1a769084..06d5defd7 100644
--- a/priv/templates/phx.gen.auth/registration_controller_test.exs
+++ b/priv/templates/phx.gen.auth/registration_controller_test.exs
@@ -8,8 +8,8 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web
       conn = get(conn, ~p"<%= schema.route_prefix %>/register")
       response = html_response(conn, 200)
       assert response =~ "Register"
-      assert response =~ "Log in</a>"
-      assert response =~ "Register</a>"
+      assert response =~ ~p"/users/log_in"
+      assert response =~ ~p"/users/register"
     end
 
     test "redirects if already logged in", %{conn: conn} do
@@ -36,8 +36,8 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web
       conn = get(conn, ~p"/")
       response = html_response(conn, 200)
       assert response =~ email
-      assert response =~ "Settings</a>"
-      assert response =~ "Log out</a>"
+      assert response =~ ~p"/users/settings"
+      assert response =~ ~p"/users/log_out"
     end
 
     test "render errors for invalid data", %{conn: conn} do
diff --git a/priv/templates/phx.gen.auth/session_controller_test.exs b/priv/templates/phx.gen.auth/session_controller_test.exs
index 18fe5bfef..a6700ac43 100644
--- a/priv/templates/phx.gen.auth/session_controller_test.exs
+++ b/priv/templates/phx.gen.auth/session_controller_test.exs
@@ -12,7 +12,7 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web
       conn = get(conn, ~p"<%= schema.route_prefix %>/log_in")
       response = html_response(conn, 200)
       assert response =~ "Log in"
-      assert response =~ "Register</a>"
+      assert response =~ ~p"/users/register"
       assert response =~ "Forgot your password?"
     end
 
@@ -36,8 +36,8 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web
       conn = get(conn, ~p"/")
       response = html_response(conn, 200)
       assert response =~ <%= schema.singular %>.email
-      assert response =~ "Settings</a>"
-      assert response =~ "Log out</a>"
+      assert response =~ ~p"/users/settings"
+      assert response =~ ~p"/users/log_out"
     end
 
     test "logs the <%= schema.singular %> in with remember me", %{conn: conn, <%= schema.singular %>: <%= schema.singular %>} do
diff --git a/test/mix/tasks/phx.gen.auth/injector_test.exs b/test/mix/tasks/phx.gen.auth/injector_test.exs
index 679b0e91b..c1eb6f5a4 100644
--- a/test/mix/tasks/phx.gen.auth/injector_test.exs
+++ b/test/mix/tasks/phx.gen.auth/injector_test.exs
@@ -665,23 +665,44 @@ defmodule Mix.Tasks.Phx.Gen.Auth.InjectorTest do
                              <li><.link href={Routes.live_dashboard_path(@conn, :home)}>LiveDashboard</.link></li>
                            <% end %>
                          </ul>
-                         <ul>
+                         <ul class="flex items-center gap-4 px-4 sm:px-6 lg:px-8 justify-end">
                            <%= if @current_user do %>
-                             <li>
+                             <li class="text-[0.8125rem] leading-6 text-zinc-900">
                                <%= @current_user.email %>
                              </li>
                              <li>
-                               <.link href={~p"/users/settings"}>Settings</.link>
+                               <.link
+                                 href={~p"/users/settings"}
+                                 class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
+                               >
+                                 Settings
+                               </.link>
                              </li>
                              <li>
-                               <.link href={~p"/users/log_out"} method="delete">Log out</.link>
+                               <.link
+                                 href={~p"/users/log_out"}
+                                 method="delete"
+                                 class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
+                               >
+                                 Log out
+                               </.link>
                              </li>
                            <% else %>
                              <li>
-                               <.link href={~p"/users/register"}>Register</.link>
+                               <.link
+                                 href={~p"/users/register"}
+                                 class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
+                               >
+                                 Register
+                               </.link>
                              </li>
                              <li>
-                               <.link href={~p"/users/log_in"}>Log in</.link>
+                               <.link
+                                 href={~p"/users/log_in"}
+                                 class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
+                               >
+                                 Log in
+                               </.link>
                              </li>
                            <% end %>
                          </ul>
@@ -738,23 +759,44 @@ defmodule Mix.Tasks.Phx.Gen.Auth.InjectorTest do
                              <li><.link href={Routes.live_dashboard_path(@conn, :home)}>LiveDashboard</.link></li>\r
                            <% end %>\r
                          </ul>\r
-                         <ul>\r
+                         <ul class="flex items-center gap-4 px-4 sm:px-6 lg:px-8 justify-end">\r
                            <%= if @current_user do %>\r
-                             <li>\r
+                             <li class="text-[0.8125rem] leading-6 text-zinc-900">\r
                                <%= @current_user.email %>\r
                              </li>\r
                              <li>\r
-                               <.link href={~p"/users/settings"}>Settings</.link>\r
+                               <.link\r
+                                 href={~p"/users/settings"}\r
+                                 class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"\r
+                               >\r
+                                 Settings\r
+                               </.link>\r
                              </li>\r
                              <li>\r
-                               <.link href={~p"/users/log_out"} method="delete">Log out</.link>\r
+                               <.link\r
+                                 href={~p"/users/log_out"}\r
+                                 method="delete"\r
+                                 class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"\r
+                               >\r
+                                 Log out\r
+                               </.link>\r
                              </li>\r
                            <% else %>\r
                              <li>\r
-                               <.link href={~p"/users/register"}>Register</.link>\r
+                               <.link\r
+                                 href={~p"/users/register"}\r
+                                 class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"\r
+                               >\r
+                                 Register\r
+                               </.link>\r
                              </li>\r
                              <li>\r
-                               <.link href={~p"/users/log_in"}>Log in</.link>\r
+                               <.link\r
+                                 href={~p"/users/log_in"}\r
+                                 class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"\r
+                               >\r
+                                 Log in\r
+                               </.link>\r
                              </li>\r
                            <% end %>\r
                          </ul>\r
@@ -795,23 +837,44 @@ defmodule Mix.Tasks.Phx.Gen.Auth.InjectorTest do
                    <title>Demo · Phoenix Framework</title>
                  </head>
                  <body>
-                   <ul>
+                   <ul class="flex items-center gap-4 px-4 sm:px-6 lg:px-8 justify-end">
                      <%= if @current_user do %>
-                       <li>
+                       <li class="text-[0.8125rem] leading-6 text-zinc-900">
                          <%= @current_user.email %>
                        </li>
                        <li>
-                         <.link href={~p"/users/settings"}>Settings</.link>
+                         <.link
+                           href={~p"/users/settings"}
+                           class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
+                         >
+                           Settings
+                         </.link>
                        </li>
                        <li>
-                         <.link href={~p"/users/log_out"} method="delete">Log out</.link>
+                         <.link
+                           href={~p"/users/log_out"}
+                           method="delete"
+                           class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
+                         >
+                           Log out
+                         </.link>
                        </li>
                      <% else %>
                        <li>
-                         <.link href={~p"/users/register"}>Register</.link>
+                         <.link
+                           href={~p"/users/register"}
+                           class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
+                         >
+                           Register
+                         </.link>
                        </li>
                        <li>
-                         <.link href={~p"/users/log_in"}>Log in</.link>
+                         <.link
+                           href={~p"/users/log_in"}
+                           class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
+                         >
+                           Log in
+                         </.link>
                        </li>
                      <% end %>
                    </ul>
@@ -854,23 +917,44 @@ defmodule Mix.Tasks.Phx.Gen.Auth.InjectorTest do
                    <title>Demo · Phoenix Framework</title>\r
                  </head>\r
                  <body>\r
-                   <ul>\r
+                   <ul class="flex items-center gap-4 px-4 sm:px-6 lg:px-8 justify-end">\r
                      <%= if @current_user do %>\r
-                       <li>\r
+                       <li class="text-[0.8125rem] leading-6 text-zinc-900">\r
                          <%= @current_user.email %>\r
                        </li>\r
                        <li>\r
-                         <.link href={~p"/users/settings"}>Settings</.link>\r
+                         <.link\r
+                           href={~p"/users/settings"}\r
+                           class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"\r
+                         >\r
+                           Settings\r
+                         </.link>\r
                        </li>\r
                        <li>\r
-                         <.link href={~p"/users/log_out"} method="delete">Log out</.link>\r
+                         <.link\r
+                           href={~p"/users/log_out"}\r
+                           method="delete"\r
+                           class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"\r
+                         >\r
+                           Log out\r
+                         </.link>\r
                        </li>\r
                      <% else %>\r
                        <li>\r
-                         <.link href={~p"/users/register"}>Register</.link>\r
+                         <.link\r
+                           href={~p"/users/register"}\r
+                           class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"\r
+                         >\r
+                           Register\r
+                         </.link>\r
                        </li>\r
                        <li>\r
-                         <.link href={~p"/users/log_in"}>Log in</.link>\r
+                         <.link\r
+                           href={~p"/users/log_in"}\r
+                           class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"\r
+                         >\r
+                           Log in\r
+                         </.link>\r
                        </li>\r
                      <% end %>\r
                    </ul>\r
diff --git a/test/mix/tasks/phx.gen.auth_test.exs b/test/mix/tasks/phx.gen.auth_test.exs
index ba8c7c2b3..aff07e8d3 100644
--- a/test/mix/tasks/phx.gen.auth_test.exs
+++ b/test/mix/tasks/phx.gen.auth_test.exs
@@ -192,16 +192,16 @@ defmodule Mix.Tasks.Phx.Gen.AuthTest do
 
       assert_file("lib/my_app_web/components/layouts/root.html.heex", fn file ->
         assert file =~
-                 ~S|<.link href={~p"/users/settings"}>Settings</.link>|
+                 ~r|<.link.*href={~p"/users/settings"}.*>|s
 
         assert file =~
-                 ~S|<.link href={~p"/users/log_out"} method="delete">Log out</.link>|
+                 ~r|<.link.*href={~p"/users/log_out"}.*method="delete".*>|s
 
         assert file =~
-                 ~S|<.link href={~p"/users/register"}>Register</.link>|
+                 ~r|<.link.*href={~p"/users/register"}.*>|s
 
         assert file =~
-                 ~S|<.link href={~p"/users/log_in"}>Log in</.link>|
+                 ~r|<.link.*href={~p"/users/log_in"}.*>|s
       end)
 
       assert_file("test/support/conn_case.ex", fn file ->
@@ -327,16 +327,16 @@ defmodule Mix.Tasks.Phx.Gen.AuthTest do
 
       assert_file("lib/my_app_web/components/layouts/root.html.heex", fn file ->
         assert file =~
-                 ~S|<.link href={~p"/users/settings"}>Settings</.link>|
+                 ~r|<\.link.*href={~p"/users/settings"}.*>|s
 
         assert file =~
-                 ~S|<.link href={~p"/users/log_out"} method="delete">Log out</.link>|
+                 ~r|<\.link.*href={~p"/users/log_out"}.*method="delete".*>|s
 
         assert file =~
-                 ~S|<.link href={~p"/users/register"}>Register</.link>|
+                 ~r|<\.link.*href={~p"/users/register"}.*>|s
 
         assert file =~
-                 ~S|<.link href={~p"/users/log_in"}>Log in</.link>|
+                 ~r|<\.link.*href={~p"/users/log_in"}.*>|s
       end)
 
       assert_file("test/support/conn_case.ex", fn file ->
@@ -366,16 +366,16 @@ defmodule Mix.Tasks.Phx.Gen.AuthTest do
 
       assert_file("lib/my_app_web/components/layouts/root.html.heex", fn file ->
         assert file =~
-                 ~S|<.link href={~p"/users/settings"}>Settings</.link>|
+                 ~r|<.link.*href={~p"/users/settings"}.*>|s
 
         assert file =~
-                 ~S|<.link href={~p"/users/log_out"} method="delete">Log out</.link>|
+                 ~r|<.link.*href={~p"/users/log_out"}.*method="delete".*>|s
 
         assert file =~
-                 ~S|<.link href={~p"/users/register"}>Register</.link>|
+                 ~r|<.link.*href={~p"/users/register"}.*>|s
 
         assert file =~
-                 ~S|<.link href={~p"/users/log_in"}>Log in</.link>|
+                 ~r|<.link.*href={~p"/users/log_in"}.*>|s
       end)
 
       assert_file("lib/my_app_web/router.ex", fn file ->
@@ -435,16 +435,16 @@ defmodule Mix.Tasks.Phx.Gen.AuthTest do
 
       assert_file("lib/my_app_web/components/layouts/root.html.heex", fn file ->
         assert file =~
-                 ~S|<.link href={~p"/users/settings"}>Settings</.link>|
+                 ~r|<.link.*href={~p"/users/settings"}.*>|s
 
         assert file =~
-                 ~S|<.link href={~p"/users/log_out"} method="delete">Log out</.link>|
+                 ~r|<.link.*href={~p"/users/log_out"}.*method="delete".*>|s
 
         assert file =~
-                 ~S|<.link href={~p"/users/register"}>Register</.link>|
+                 ~r|<.link.*href={~p"/users/register"}.*>|s
 
         assert file =~
-                 ~S|<.link href={~p"/users/log_in"}>Log in</.link>|
+                 ~r|<.link.*href={~p"/users/log_in"}.*>|s
       end)
 
       assert_file("lib/my_app_web/router.ex", fn file ->
@@ -528,10 +528,10 @@ defmodule Mix.Tasks.Phx.Gen.AuthTest do
                  ~S(<.simple_form :let={f} for={@conn.params["user"]} as={:user} action={~p"/warehouse/users/confirm"}>)
 
         assert file =~
-                 ~S|<.link href={~p"/warehouse/users/register"}>Register</.link>|
+                 ~r|<\.link.*href={~p"/warehouse/users/register"}.*>|s
 
         assert file =~
-                 ~S|<.link href={~p"/warehouse/users/log_in"}>Log in</.link>|
+                 ~r|<\.link.*href={~p"/warehouse/users/log_in"}.*>|s
       end)
 
       assert_file(
@@ -550,16 +550,16 @@ defmodule Mix.Tasks.Phx.Gen.AuthTest do
 
       assert_file("lib/my_app_web/components/layouts/root.html.heex", fn file ->
         assert file =~
-                 ~S|<.link href={~p"/warehouse/users/settings"}>Settings</.link>|
+                 ~r|<\.link.*href={~p"/warehouse/users/settings"}.*>|s
 
         assert file =~
-                 ~S|<.link href={~p"/warehouse/users/log_out"} method="delete">Log out</.link>|
+                 ~r|<\.link.*href={~p"/warehouse/users/log_out"}.*method="delete".*>|s
 
         assert file =~
-                 ~S|<.link href={~p"/warehouse/users/register"}>Register</.link>|
+                 ~r|<\.link.*href={~p"/warehouse/users/register"}.*>|s
 
         assert file =~
-                 ~S|<.link href={~p"/warehouse/users/log_in"}>Log in</.link>|
+                 ~r|<\.link.*href={~p"/warehouse/users/log_in"}.*>|s
       end)
 
       assert_file(
@@ -1356,23 +1356,44 @@ defmodule Mix.Tasks.Phx.Gen.AuthTest do
         layout file, please add the following code to it where you'd
         like the user menu items to be rendered.
 
-            <ul>
+            <ul class="flex items-center gap-4 px-4 sm:px-6 lg:px-8 justify-end">
               <%= if @current_user do %>
-                <li>
+                <li class="text-[0.8125rem] leading-6 text-zinc-900">
                   <%= @current_user.email %>
                 </li>
                 <li>
-                  <.link href={~p"/users/settings"}>Settings</.link>
+                  <.link
+                    href={~p"/users/settings"}
+                    class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
+                  >
+                    Settings
+                  </.link>
                 </li>
                 <li>
-                  <.link href={~p"/users/log_out"} method="delete">Log out</.link>
+                  <.link
+                    href={~p"/users/log_out"}
+                    method="delete"
+                    class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
+                  >
+                    Log out
+                  </.link>
                 </li>
               <% else %>
                 <li>
-                  <.link href={~p"/users/register"}>Register</.link>
+                  <.link
+                    href={~p"/users/register"}
+                    class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
+                  >
+                    Register
+                  </.link>
                 </li>
                 <li>
-                  <.link href={~p"/users/log_in"}>Log in</.link>
+                  <.link
+                    href={~p"/users/log_in"}
+                    class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
+                  >
+                    Log in
+                  </.link>
                 </li>
               <% end %>
             </ul>
@@ -1400,23 +1421,44 @@ defmodule Mix.Tasks.Phx.Gen.AuthTest do
 
         Add the following user menu items to your lib/my_app_web/components/layouts/root.html.heex layout file:
 
-            <ul>
+            <ul class="flex items-center gap-4 px-4 sm:px-6 lg:px-8 justify-end">
               <%= if @current_user do %>
-                <li>
+                <li class="text-[0.8125rem] leading-6 text-zinc-900">
                   <%= @current_user.email %>
                 </li>
                 <li>
-                  <.link href={~p"/users/settings"}>Settings</.link>
+                  <.link
+                    href={~p"/users/settings"}
+                    class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
+                  >
+                    Settings
+                  </.link>
                 </li>
                 <li>
-                  <.link href={~p"/users/log_out"} method="delete">Log out</.link>
+                  <.link
+                    href={~p"/users/log_out"}
+                    method="delete"
+                    class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
+                  >
+                    Log out
+                  </.link>
                 </li>
               <% else %>
                 <li>
-                  <.link href={~p"/users/register"}>Register</.link>
+                  <.link
+                    href={~p"/users/register"}
+                    class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
+                  >
+                    Register
+                  </.link>
                 </li>
                 <li>
-                  <.link href={~p"/users/log_in"}>Log in</.link>
+                  <.link
+                    href={~p"/users/log_in"}
+                    class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
+                  >
+                    Log in
+                  </.link>
                 </li>
               <% end %>
             </ul>
diff --git a/test/phoenix/controller/render_test.exs b/test/phoenix/controller/render_test.exs
index b1b7c53e2..3ddcf1b07 100644
--- a/test/phoenix/controller/render_test.exs
+++ b/test/phoenix/controller/render_test.exs
@@ -175,27 +175,21 @@ defmodule Phoenix.Controller.RenderTest do
     ]
 
     setup context do
-      test_pid = self()
-      test_name = context.test
-
-      :telemetry.attach_many(
-        test_name,
-        @render_events,
-        fn event, measures, metadata, config ->
-          send(test_pid, {:telemetry_event, event, {measures, metadata, config}})
-        end,
-        nil
-      )
+      :telemetry.attach_many(context.test, @render_events, &__MODULE__.message_pid/4, self())
+    end
+
+    def message_pid(event, measures, metadata, test_pid) do
+      send(test_pid, {:telemetry_event, event, {measures, metadata}})
     end
 
     test "phoenix.controller.render.start and .stop are emitted on success" do
       render(conn(), "index.html", title: "Hello")
 
       assert_received {:telemetry_event, [:phoenix, :controller, :render, :start],
-                       {_, %{format: "html", template: "index", view: MyApp.UserView}, _}}
+                       {_, %{format: "html", template: "index", view: MyApp.UserView}}}
 
       assert_received {:telemetry_event, [:phoenix, :controller, :render, :stop],
-                       {_, %{format: "html", template: "index", view: MyApp.UserView}, _}}
+                       {_, %{format: "html", template: "index", view: MyApp.UserView}}}
 
       refute_received {:telemetry_event, [:phoenix, :controller, :render, :exception], _}
     end
@@ -210,7 +204,7 @@ defmodule Phoenix.Controller.RenderTest do
         end
 
       assert_received {:telemetry_event, [:phoenix, :controller, :render, :start],
-                       {_, %{format: "html", template: "index", view: MyApp.UserView}, _}}
+                       {_, %{format: "html", template: "index", view: MyApp.UserView}}}
 
       refute_received {:telemetry_event, [:phoenix, :controller, :render, :stop], _}
 
@@ -222,7 +216,7 @@ defmodule Phoenix.Controller.RenderTest do
                           view: MyApp.UserView,
                           kind: :error,
                           reason: %ArgumentError{}
-                        }, _}}
+                        }}}
     end
   end
 end
diff --git a/test/phoenix/socket/v1_json_serializer_test.exs b/test/phoenix/socket/v1_json_serializer_test.exs
index 9e25aabaf..7f0cfc9eb 100644
--- a/test/phoenix/socket/v1_json_serializer_test.exs
+++ b/test/phoenix/socket/v1_json_serializer_test.exs
@@ -6,6 +6,7 @@ defmodule Phoenix.Socket.V1.JSONSerializerTest do
   # v1 responses must not contain join_ref
   @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\"}"
 
@@ -33,7 +34,15 @@ defmodule Phoenix.Socket.V1.JSONSerializerTest do
 
   test "decode!/2 decodes `Phoenix.Socket.Message` from JSON" do
     assert %Message{topic: "t", event: "e", payload: "m"} ==
-      decode!(@serializer, @v1_msg_json, opcode: :text)
+             decode!(@serializer, @v1_msg_json, opcode: :text)
+  end
+
+  test "decode!/2 raise a PayloadFormatException if the JSON doesn't contain a map" do
+    assert_raise(
+      RuntimeError,
+      "V1 JSON Serializer expected a map, got [nil, nil, \"t\", \"e\", %{\"m\" => 1}]",
+      fn -> decode!(@serializer, @v1_bad_json, opcode: :text) end
+    )
   end
 
   test "fastlane!/1 encodes a broadcast into a message as JSON" do