Repository: phoenixframework/phoenix
Pull request #6200 (2025-04-17 15:50:49)

Update scopes guide
Here are some edits to the scopes guide, with the intent to make the introductory paragraphs a bit more concise.


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 17eb15196..0df3e83e8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,7 +6,7 @@ This release requires Erlang/OTP 25+.
 
   * Extend tailwindcss support in new apps with [daisyUI](https://daisyui.com/) for light/dark/system mode support for entire app, including core components
   * Simplify layout handling for new apps. Now there is only a single `root.html.heex` which wraps the render pipeline. Other dynamic layouts, like `app.html.heex` are called as needed within templates as regular function components
-  * Simplify core components and live generators to more closely match basic `phx.gen.html` crud. This serves as a better base for seasoned devs to start with, and lessons the amount of code newcomers need to get up to speed with on the basics
+  * Simplify core components and live generators to more closely match basic `phx.gen.html` crud. This serves as a better base for seasoned devs to start with, and lessens the amount of code newcomers need to get up to speed with on the basics
   * Introduce magic links (passwordless auth) and "sudo mode" to `mix phx.gen.auth` while simplifying the generated structure
   * Introduce scopes to Phoenix generators, designed to make secure data access the *default*, not something you remember (or forget) to do later
 
@@ -25,6 +25,13 @@ This release introduces deprecation warnings for several features that have been
   * Specifying layouts without modules, such as `put_layout(conn, :print)` or `put_layout(conn, html: :print)` is deprecated
   * The `:trailing_slash` option in `Phoenix.Router` has been deprecated in favor of using `Phoenix.VerifiedRoutes`. The overall usage of helpers will be deprecated in the future
 
+## 1.8.0-rc.2 (2025-05-29)
+
+## Big Fixes
+  - [phx.gen.live] only subscribe to pubsub if connected
+  - [phx.gen.auth] remove unused current_password field
+  - [phx.gen.auth] use context_app for scopes to fix generated scopes in umbrella apps
+
 ## 1.8.0-rc.1 (2025-04-16)
 
 ## Enhancements
diff --git a/guides/authn_authz/mix_phx_gen_auth.md b/guides/authn_authz/mix_phx_gen_auth.md
index 843a05803..de5b74336 100644
--- a/guides/authn_authz/mix_phx_gen_auth.md
+++ b/guides/authn_authz/mix_phx_gen_auth.md
@@ -64,7 +64,7 @@ The following are notes about the generated authentication system.
 The generated code ships with an authentication module with a handful of plugs that fetch the current user, require authentication and so on. For instance, in an app named MyApp which had `mix phx.gen.auth Accounts User users` run on it, you will find a module named `MyAppWeb.UserAuth` with plugs such as:
 
   * `fetch_current_scope_for_user` - fetches the current user information if available and stores it as `:current_scope` assign
-  * `require_authenticated_user` - must be invoked after `fetch_current_user` and requires that a current user exists and is authenticated
+  * `require_authenticated_user` - must be invoked after `fetch_current_scope_for_user` and requires that a current user exists and is authenticated
   * `redirect_if_user_is_authenticated` - used for the few pages that must not be available to authenticated users (only generated for controller based authentication)
   * `require_sudo_mode` - used for pages that contain sensitive operations and enforces recent authentication
 
@@ -172,4 +172,4 @@ The following links describe the original implementation of the authentication s
   * José Valim's blog post - [An upcoming authentication solution for Phoenix](https://dashbit.co/blog/a-new-authentication-solution-for-phoenix)
   * Berenice Medel's blog post on generating LiveViews for authentication (rather than conventional Controllers & Views) - [Bringing Phoenix Authentication to Life](https://fly.io/phoenix-files/phx-gen-auth/)
   * [Original design spec](https://github.com/dashbitco/mix_phx_gen_auth_demo/blob/auth/README.md)
-  * [Pull request on bare Phoenix app][https://github.com/dashbitco/mix_phx_gen_auth_demo/pull/1]
+  * [Pull request on bare Phoenix app](https://github.com/dashbitco/mix_phx_gen_auth_demo/pull/1)
diff --git a/guides/authn_authz/scopes.md b/guides/authn_authz/scopes.md
index 0d5e4940e..c40066fa1 100644
--- a/guides/authn_authz/scopes.md
+++ b/guides/authn_authz/scopes.md
@@ -1,12 +1,10 @@
 # Scopes
 
-A scope is a data structure used to keep information about the current request or session, such as the current user logged in, the organization/company it belongs to, permissions, and so on. Think about it as a container that holds information that is required in the huge majority of pages in your application. It can also hold important request metadata, such as IP addresses.
+A scope is a data structure used to keep information about the current request or session, such as the current user, the user's organization/company, permissions, and so on. Think of a scope as a container with information required by the majority of pages in your application. A scope can also hold request metadata, such as IP addresses.
 
-Scopes also play a very important role in security. OWASP (Open Worldwide Application Security Project) lists "Broken access control" as the biggest security risk in web applications. That's because most data in an application is not publicly available. Instead, it most often belongs to a user, a team, or an organization. Therefore, it is extremely important that, when you query the database, your queries, inserts, updates, and deletes are properly scoped to the current user/team/organization.
+Scopes play an important role in security. [OWASP](https://owasp.org/) lists "Broken access control" as a [top-10 security risk](https://owasp.org/Top10/). Most application data is private for a user, a team, or an organization. Your database CRUD operations must be properly scoped to the current user/team/organization. Phoenix generators such as `mix phx.gen.html`, `mix phx.gen.json`, and `mix phx.gen.live` automatically use your custom scopes.
 
-By using scopes, you have a single data structure that contains all relevant information, which is then passed around so all of your operations are properly scoped. By defining your own scopes, Phoenix generators such as `mix phx.gen.html`, `mix phx.gen.json`, and `mix phx.gen.live` will automatically make sure all operations pertain to that scope, ensuring that all generated code is safe by default.
-
-Scopes are also flexible: you can have more than one scope in your application and choose the relevant scope when invoking the relevant generator. When you run `mix phx.gen.auth`, it will automatically generate a scope for you, but you may also add your own.
+Scopes are flexible. You can have more than one scope in your application and choose the specific scope when invoking a generator. When you run `mix phx.gen.auth`, it will automatically generate a scope for you, but you may also add your own.
 
 This guide will:
 
@@ -17,7 +15,7 @@ This guide will:
 
 ## phx.gen.auth
 
-When you invoke `mix phx.gen.auth`, it will generate a default scope for you. This scope ties the generated resources to the currently authenticated user. Let's see it in action:
+The task `mix phx.gen.auth` will generate a default scope. This scope ties the generated resources to the currently authenticated user. Let's see it in action:
 
 ```console
 $ mix phx.gen.auth Accounts User users
@@ -84,7 +82,7 @@ end
 
 ## Integration of scopes in the Phoenix generators
 
-If a default scope is defined in your application's config, the generators will generate scoped resources by default. The generated LiveViews / Controllers will automatically pass the scope to the context functions. `mix phx.gen.auth` automatically sets its scope as default, if there is not already a default scope defined:
+If a default scope is defined in your application's config, the generators will build scoped resources by default. The generated LiveViews / Controllers will automatically pass the scope to the context functions. `mix phx.gen.auth` automatically sets its scope as default, if there is not already a default scope defined:
 
 ```elixir
 # config/config.exs
diff --git a/guides/deployment/fly.md b/guides/deployment/fly.md
index 2417b57bb..00fbafb00 100644
--- a/guides/deployment/fly.md
+++ b/guides/deployment/fly.md
@@ -1,7 +1,8 @@
 # Deploying on Fly.io
 
+The main goal for this guide is to get a Phoenix application running on [Fly.io](https://fly.io).
 
-Fly.io maintains their own guide for Elixir/Phoenix here: [Fly.io/docs/elixir/getting-started/](https://fly.io/docs/elixir/getting-started/) we will keep this guide up but for the latest and greatest check with them!
+Fly.io maintains their own guide for Elixir/Phoenix here: [Fly.io/docs/elixir/getting-started/](https://fly.io/docs/elixir/getting-started/). We will keep this guide up but for the latest and greatest check with them!
 
 ## What we'll need
 
@@ -13,10 +14,6 @@ You can just:
 $ mix phx.new my_app
 ```
 
-## Goals
-
-The main goal for this guide is to get a Phoenix application running on [Fly.io](https://fly.io).
-
 ## Sections
 
 Let's separate this process into a few steps, so we can keep track of where we are.
@@ -24,6 +21,7 @@ Let's separate this process into a few steps, so we can keep track of where we a
 - Install the Fly.io CLI
 - Sign up for Fly.io
 - Deploy the app to Fly.io
+- Clustering your application
 - Extra Fly.io tips
 - Helpful Fly.io resources
 
@@ -117,6 +115,32 @@ If everything looks good, open your app on Fly
 $ fly open
 ```
 
+## Clustering your application
+
+Elixir and the Erlang VM have the incredible ability to be clustered together and pass messages seamlessly between nodes. Phoenix comes with all of the knobs in place, you only need to set the appropriate environment variables before deploying.
+
+If you used `fly launch` to deploy your app, those environment variables are already in place, if not, open up `rel/env.ssh.eex` and add:
+
+```sh
+export ERL_AFLAGS="-proto_dist inet6_tcp"
+export RELEASE_DISTRIBUTION="name"
+export RELEASE_NODE="${FLY_APP_NAME}-${FLY_IMAGE_REF##*-}@${FLY_PRIVATE_IP}"
+
+export ECTO_IPV6="true"
+export DNS_CLUSTER_QUERY="${FLY_APP_NAME}.internal"
+```
+
+The first three environment variables are managed by Elixir and the Erlang/VM:
+
+  * `ERL_AFLAGS` - configures Erlang to use IPv6 for its distribution
+  * `RELEASE_DISTRIBUTION` - configures Erlang to named nodes
+  * `RELEASE_NODE` - attaches a name to the node, using Fly's app name and deploy reference
+
+The last two are handled by your `config/runtime.exs`:
+
+  * `ECTO_IPV6` - connect to the database using IPv6
+  * `DNS_CLUSTER_QUERY` - configures your app to find other nodes using the given DNS query
+
 ## Extra Fly.io tips
 
 ### Getting an IEx shell into a running node
@@ -151,32 +175,6 @@ iex(my_app@fdaa:0:1da8:a7b:ac4:b204:7e29:2)1>
 
 Now we have a running IEx shell into our node! You can safely disconnect using CTRL+C, CTRL+C.
 
-### Clustering your application
-
-Elixir and the BEAM have the incredible ability to be clustered together and pass messages seamlessly between nodes. Phoenix comes with all of the knobs in place, you only need to set the appropriate environment variables before deploying.
-
-If you used `fly launch` to deploy your app, those environement variables are already in place, if not, open up `rel/env.ssh.eex` and add:
-
-```sh
-export ERL_AFLAGS="-proto_dist inet6_tcp"
-export RELEASE_DISTRIBUTION="name"
-export RELEASE_NODE="${FLY_APP_NAME}-${FLY_IMAGE_REF##*-}@${FLY_PRIVATE_IP}"
-
-export ECTO_IPV6="true"
-export DNS_CLUSTER_QUERY="${FLY_APP_NAME}.internal"
-```
-
-The first three environment variables are managed by Elixir and the Erlang/VM:
-
-  * `ERL_AFLAGS` - configures Erlang to use IPv6 for its distribution
-  * `RELEASE_DISTRIBUTION` - configures Erlang to named nodes
-  * `RELEASE_NODE` - attaches a name to the node, using Fly's app name and deploy reference
-
-The last two are handled by your `config/runtime.exs`:
-
-  * `ECTO_IPV6` - connect to the database using IPv6
-  * `DNS_CLUSTER_QUERY` - configures your app to find other nodes using the given DNS query
-
 #### Running multiple instances
 
 There are two ways to run multiple instances.
diff --git a/guides/deployment/gigalixir.md b/guides/deployment/gigalixir.md
index 9eb7d1ed0..d074e704c 100644
--- a/guides/deployment/gigalixir.md
+++ b/guides/deployment/gigalixir.md
@@ -1,13 +1,11 @@
 # Deploying on Gigalixir
 
+Our main goal for this guide is to get a Phoenix application running on Gigalixir.
+
 ## What we'll need
 
 The only thing we'll need for this guide is a working Phoenix application. For those of us who need a simple application to deploy, please follow the [Up and Running guide](https://hexdocs.pm/phoenix/up_and_running.html).
 
-## Goals
-
-Our main goal for this guide is to get a Phoenix application running on Gigalixir.
-
 ## Steps
 
 Let's separate this process into a few steps, so we can keep track of where we are.
@@ -130,31 +128,17 @@ $ gigalixir config
 
 There's nothing we need to do to get our app running on Gigalixir, but for a production app, you probably want to enforce SSL.
 
-### Force HTTPS/SSL Connections
-
-To do that, see [Force SSL](https://hexdocs.pm/phoenix/using_ssl.html#force-ssl)
-
 ### Database Connection Security
-You may also want to use SSL for your database connection.
 
-For a free tier database, update your `Repo` config with:
+You may also want to use SSL for your database connection. In your `config/runtime.exs`:
 
 ```elixir
-ssl: true,
-ssl_opts: [
-  verify: :verify_none,
+ssl: [
+  verify: :verify_peer,
   cacerts: :public_key.cacerts_get()
 ]
 ```
 
-For a production database, use this instead:
-
-```elixir
-ssl: true,
-allowed_tls_versions: [:"tlsv1.2"],
-ssl_opts: [verify: :verify_none]
-```
-
 ## Deploy Time!
 
 Our project is now ready to be deployed on Gigalixir.
diff --git a/guides/deployment/heroku.md b/guides/deployment/heroku.md
index b65d13bef..832ef19ca 100644
--- a/guides/deployment/heroku.md
+++ b/guides/deployment/heroku.md
@@ -1,13 +1,11 @@
 # Deploying on Heroku
 
+Our main goal for this guide is to get a Phoenix application running on Heroku.
+
 ## What we'll need
 
 The only thing we'll need for this guide is a working Phoenix application. For those of us who need a simple application to deploy, please follow the [Up and Running guide](https://hexdocs.pm/phoenix/up_and_running.html).
 
-## Goals
-
-Our main goal for this guide is to get a Phoenix application running on Heroku.
-
 ## Limitations
 
 Heroku is a great platform and Elixir performs well on it. However, you may run into limitations if you plan to leverage advanced features provided by Elixir and Phoenix, such as:
@@ -26,7 +24,7 @@ Heroku is a great platform and Elixir performs well on it. However, you may run
 
 If you are just getting started, or you don't expect to use the features above, Heroku should be enough for your needs. For instance, if you are migrating an existing application running on Heroku to Phoenix, keeping a similar set of features, Elixir will perform just as well or even better than your current stack.
 
-If you want a platform-as-a-service without these limitations, try [Gigalixir](gigalixir.html). If you would rather deploy to a cloud platform, such as EC2, Google Cloud, etc, consider using `mix release`.
+If you want a platform-as-a-service without these limitations, there are alternatives listed in the sidebar and also generally available elsewhere. If you would rather deploy to a cloud platform, such as EC2, Google Cloud, etc, consider using `mix release`.
 
 ## Steps
 
diff --git a/guides/deployment/releases.md b/guides/deployment/releases.md
index 2d9a28887..ba3399872 100644
--- a/guides/deployment/releases.md
+++ b/guides/deployment/releases.md
@@ -1,13 +1,11 @@
 # Deploying with Releases
 
+Our main goal for this guide is to package your Phoenix application into a self-contained directory that includes the Erlang VM, Elixir, all of your code and dependencies. This package can then be dropped into a production machine.
+
 ## What we'll need
 
 The only thing we'll need for this guide is a working Phoenix application. For those of us who need a simple application to deploy, please follow the [Up and Running guide](up_and_running.html).
 
-## Goals
-
-Our main goal for this guide is to package your Phoenix application into a self-contained directory that includes the Erlang VM, Elixir, all of your code and dependencies. This package can then be dropped into a production machine.
-
 ## Releases, assemble!
 
 If you are not familiar with Elixir releases yet, we recommend you to read [Elixir's excellent docs](https://hexdocs.pm/mix/Mix.Tasks.Release.html) before continuing.
@@ -94,11 +92,9 @@ You can start the release by calling `_build/prod/rel/my_app/bin/my_app start`,
 
 Now you can get all of the files under the `_build/prod/rel/my_app` directory, package it, and run it in any production machine with the same OS and architecture as the one that assembled the release. For more details, check the [docs for `mix release`](https://hexdocs.pm/mix/Mix.Tasks.Release.html).
 
-But before we finish this guide, there is one more feature from releases that most Phoenix application will use, so let's talk about that.
-
-## Ecto migrations and custom commands
+## Ecto migrations
 
-A common need in production systems is to execute custom commands required to set up the production environment. One of such commands is precisely migrating the database. Since we don't have `Mix`, a *build* tool, inside releases, which are a production artifact, we need to bring said commands directly into the release.
+A common need in production systems is to execute custom commands required to set up the production environment. One of such commands is precisely migrating the database. Since we don't have `Mix`, a *build* tool, inside releases, which are production artifacts, we need to bring said commands directly into the release.
 
 The `phx.gen.release` command created the following `release.ex` file in your project `lib/my_app/release.ex`, with the following content:
 
@@ -124,6 +120,7 @@ defmodule MyApp.Release do
   end
 
   defp load_app do
+    Application.ensure_all_started(:ssl)
     Application.ensure_loaded(@app)
   end
 end
@@ -137,9 +134,13 @@ Now you can assemble a new release with `MIX_ENV=prod mix release` and you can i
 $ _build/prod/rel/my_app/bin/my_app eval "MyApp.Release.migrate"
 ```
 
-And that's it! If you peek inside the `migrate` script, you'll see it wraps exactly this invocation.
+And that's it! If you peek inside the `migrate` script, you'll see it wraps exactly this invocation. Depending on where you are deploying your application, you can invoke the `migrate` command separately, or you may want to change the `server` script to migrate your database before starting your app.
 
-You can use this approach to create any custom command to run in production. In this case, we used `load_app`, which calls `Application.ensure_loaded/1` to load the current application without starting it. However, you may want to write a custom command that starts the whole application. In such cases, `Application.ensure_all_started/1` must be used. Keep in mind, starting the application will start all processes for the current application, including the Phoenix endpoint. This can be circumvented by changing your supervision tree to not start certain children under certain conditions. For example, in the release commands file you could do:
+## Custom commands
+
+You can use the same approach used for migrations to create any custom command to run in production. The idea is that each command invokes `load_app`, which calls `Application.ensure_loaded/1` to load the current application without starting it.
+
+However, some commands may need to start the whole application. In such cases, `Application.ensure_all_started/1` must be used instead of `Application.load/1`. Keep in mind starting the application will all processes in its supervision tree, including the Phoenix endpoint. This can be circumvented by changing your supervision tree to not start certain children under certain conditions. For example, in the release commands file you could do:
 
 ```elixir
 defp start_app do
@@ -261,6 +262,82 @@ Where `my_app` is the name of your app. At the end, you will have an application
 
 A few points about configuring a containerized application:
 
-- If you run your app in a container, the `Endpoint` needs to be configured to listen on a "public" `:ip` address (like `0.0.0.0`) so that the app can be reached from outside the container. Whether the host should publish the container's ports to its own public IP or to localhost depends on your needs.
 - The more configuration you can provide at runtime (using `config/runtime.exs`), the more reusable your images will be across environments. In particular, secrets like database credentials and API keys should not be compiled into the image, but rather should be provided when creating containers based on that image. This is why the `Endpoint`'s `:secret_key_base` is configured in `config/runtime.exs` by default.
+
 - If possible, any environment variables that are needed at runtime should be read in `config/runtime.exs`, not scattered throughout your code. Having them all visible in one place will make it easier to ensure the containers get what they need, especially if the person doing the infrastructure work does not work on the Elixir code. Libraries in particular should never directly read environment variables; all their configuration should be handed to them by the top-level application, preferably [without using the application environment](https://hexdocs.pm/elixir/library-guidelines.html#avoid-application-configuration).
+
+## Clustering
+
+Elixir and the Erlang VM have the incredible ability to be clustered together and pass messages seamlessly between nodes. To enable clustering, we need two distinct features:
+
+* Node connection: different instances of the same service should communicate with each other. This is a feature of the Erlang VM.
+
+* Service discovery: for a given service, you must be able to find the IP address of all instances. Phoenix ships with `dns_cluster` to provide out-of-the-box DNS-based service discovery but alternative methods may be used.
+
+### DNS Discovery
+
+Your clustering configuration is typically added to `rel/env.sh.eex`. This is a file that is executed before you release starts, and it is a perfect place to configure your application runtime based on your deployment environment. Here is a general skeleton:
+
+```sh
+# Uncomment if IPv6 is required
+# export ECTO_IPV6="true"
+# export ERL_AFLAGS="-proto_dist inet6_tcp"
+
+# Erlang uses a port mapper daemon on each node,
+# it by default runs on port 4369
+export ERL_EPMD_PORT=4369
+
+# Use the ports 4370-4372 for nodes to communicate.
+export ERL_AFLAGS="-kernel inet_dist_listen_min 4370 inet_dist_listen_max 4372"
+
+export RELEASE_DISTRIBUTION="name"
+export RELEASE_NODE="app-${PLATFORM_DEPLOYMENT_SHA}@${PLATFORM_DEPLOYMENT_IP}"
+export DNS_CLUSTER_QUERY="your-app.internal"
+```
+
+The script above is doing a couple things:
+
+* It configures your app to use ports 4369, 4370, 4371, and 4372 for communication. You may need to explicitly expose those as internal TCP ports in your deployment platform (in addition to the HTTP port of your choice)
+
+* It then configures your app to use fully qualified names. The name of each app will include the current deployment sha as `PLATFORM_DEPLOYMENT_SHA` (the name of the exact environment variable is platform dependent), so each deployment establishes its own cluster, and the current IP as `PLATFORM_DEPLOYMENT_IP` (also platform specific). If the IP is not available, you may be able to compute it as `NODE_IP=hostname | tr -d ' '`
+
+* Then finally you define a DNS query which will be used to find the IPs of the other instances
+
+Some platforms, such as [Fly.io](https://fly.io/docs/networking/private-networking/), [Railway](https://docs.railway.com/guides/private-networking), and [Render](https://render.com/docs/private-network#direct-ip-communication-advanced), provide private networks with DNS querying out of the box. You only need to adapt the `DNS_CLUSTER_QUERY` variable accordingly.
+
+Other platforms, such as [Digital Ocean App Platform](https://www.digitalocean.com/products/app-platform) and [Northflank](https://northflank.com/features/platform), allow nodes to directly connect to each other, but they do not provide DNS service discovery. In this next section, we explore different service discovery mechanisms.
+
+### Alternative discovery mechanisms
+
+While not all platforms support DNS queries for service discovery, there are many alternative strategies for connecting your nodes together. Please checkout the following libraries:
+
+  * [libcluster](https://github.com/bitwalker/libcluster) - provides strategies for connecting your nodes using gossip protocols, kubernetes, ec2, and others
+
+  * [libcluster_postgres](https://github.com/supabase/libcluster_postgres/) - a plugin for `libcluster` which uses PostgreSQL for node discovery. Given most applications already use a database, and likely PostgreSQL, this is a suitable option which does not require additional setup
+
+When using the libraries above, you can likely remove `dns_query` from your application dependencies.
+
+### `epmd`-less deployment
+
+In the snippet above, we used ports 4369, 4370, 4371, and 4372. However, the Erlang VM allows running the distribution over a fixed port, also known as `epmd`-less deployments. To enable such, do this.
+
+Remove the lines:
+
+```sh
+export ERL_EPMD_PORT=4369
+export ERL_AFLAGS="-kernel inet_dist_listen_min 4370 inet_dist_listen_max 4372"
+```
+
+Add a file rel/vm.args.eex with the following:
+
+```
+-start_epmd false -erl_epmd_port 6789
+```
+
+Add a file rel/remote.vm.args.eex with the following:
+
+```
+-start_epmd false -erl_epmd_port 6789 -dist_listen false
+```
+
+And now only port 6789 (in addition to the HTTP one) needs to be exposed internally between instances.
\ No newline at end of file
diff --git a/guides/real_time/channels.md b/guides/real_time/channels.md
index 67319c21b..7436cda32 100644
--- a/guides/real_time/channels.md
+++ b/guides/real_time/channels.md
@@ -42,7 +42,7 @@ All the client needs is a suitable library; see the [Client Libraries](#client-l
 Each client library communicates using one of the "transports" that Channels understand.
 Currently, that's either Websockets or long polling, but other transports may be added in the future.
 
-Unlike stateless HTTP connections, Channels support long-lived connections, each backed by a lightweight BEAM process, working in parallel and maintaining its own state.
+Unlike stateless HTTP connections, Channels support long-lived connections, each backed by a lightweight Erlang VM process, working in parallel and maintaining its own state.
 
 This architecture scales well; Phoenix Channels [can support millions of subscribers with reasonable latency on a single box](https://phoenixframework.org/blog/the-road-to-2-million-websocket-connections), passing hundreds of thousands of messages per second.
 And that capacity can be multiplied by adding more nodes to the cluster.
@@ -158,7 +158,7 @@ PubSub is provided by the `Phoenix.PubSub` module. Interested parties can receiv
 This is useful to broadcast messages on channel and also for application development in general. For instance, letting all connected [live views](https://github.com/phoenixframework/phoenix_live_view) to know that a new comment has been added to a post.
 
 The PubSub system takes care of getting messages from one node to another so that they can be sent to all subscribers across the cluster.
-By default, this is done using [Phoenix.PubSub.PG2](https://hexdocs.pm/phoenix_pubsub/Phoenix.PubSub.PG2.html), which uses native BEAM messaging.
+By default, this is done using [Phoenix.PubSub.PG2](https://hexdocs.pm/phoenix_pubsub/Phoenix.PubSub.PG2.html), which uses native Erlang VM messaging.
 
 If your deployment environment does not support distributed Elixir or direct communication between servers, Phoenix also ships with a [Redis Adapter](https://hexdocs.pm/phoenix_pubsub_redis/Phoenix.PubSub.Redis.html) that uses Redis to exchange PubSub data. Please see the [Phoenix.PubSub docs](https://hexdocs.pm/phoenix_pubsub/Phoenix.PubSub.html) for more information.
 
diff --git a/installer/mix.exs b/installer/mix.exs
index 491420fba..de29070c6 100644
--- a/installer/mix.exs
+++ b/installer/mix.exs
@@ -6,7 +6,7 @@ end
 defmodule Phx.New.MixProject do
   use Mix.Project
 
-  @version "1.8.0-rc.1"
+  @version "1.8.0-rc.2"
   @scm_url "https://github.com/phoenixframework/phoenix"
 
   # If the elixir requirement is updated, we need to update:
diff --git a/installer/templates/phx_assets/app.css b/installer/templates/phx_assets/app.css
index b20471303..50e9202dc 100644
--- a/installer/templates/phx_assets/app.css
+++ b/installer/templates/phx_assets/app.css
@@ -79,8 +79,8 @@
   --color-success-content: oklch(98% 0.014 180.72);
   --color-warning: oklch(66% 0.179 58.318);
   --color-warning-content: oklch(98% 0.022 95.277);
-  --color-error: oklch(65% 0.241 354.308);
-  --color-error-content: oklch(97% 0.014 343.198);
+  --color-error: oklch(58% 0.253 17.585);
+  --color-error-content: oklch(96% 0.015 12.422);
   --radius-selector: 0.25rem;
   --radius-field: 0.25rem;
   --radius-box: 0.5rem;
diff --git a/installer/templates/phx_single/config/dev.exs b/installer/templates/phx_single/config/dev.exs
index e5d9f666a..5b5a2e52b 100644
--- a/installer/templates/phx_single/config/dev.exs
+++ b/installer/templates/phx_single/config/dev.exs
@@ -53,7 +53,7 @@ config :<%= @app_name %>, <%= @endpoint_module %>,
     patterns: [
       ~r"priv/static/(?!uploads/).*(js|css|png|jpeg|jpg|gif|svg)$",<%= if @gettext do %>
       ~r"priv/gettext/.*(po)$",<% end %>
-      ~r"lib/<%= @lib_web_name %>/(controllers|live|components)/.*(ex|heex)$"
+      ~r"lib/<%= @lib_web_name %>/(?:controllers|live|components|router)/?.*\.(ex|heex)$"
     ]
   ]<% end %>
 
diff --git a/installer/templates/phx_static/default.css b/installer/templates/phx_static/default.css
index f78adc7ae..40e1e8b93 100644
--- a/installer/templates/phx_static/default.css
+++ b/installer/templates/phx_static/default.css
@@ -1488,10 +1488,10 @@
     width: 1rem;
     height: 1rem;
   }
-  .hero-exclamation-circle-mini {
-    --hero-exclamation-circle-mini: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2020%2020%22%20fill%3D%22currentColor%22%20aria-hidden%3D%22true%22%20data-slot%3D%22icon%22%3E%20%20%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M18%2010a8%208%200%201%201-16%200%208%208%200%200%201%2016%200Zm-8-5a.75.75%200%200%201%20.75.75v4.5a.75.75%200%200%201-1.5%200v-4.5A.75.75%200%200%201%2010%205Zm0%2010a1%201%200%201%200%200-2%201%201%200%200%200%200%202Z%22%20clip-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E');
-    -webkit-mask: var(--hero-exclamation-circle-mini);
-    mask: var(--hero-exclamation-circle-mini);
+  .hero-exclamation-circle {
+    --hero-exclamation-circle: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2020%2020%22%20fill%3D%22currentColor%22%20aria-hidden%3D%22true%22%20data-slot%3D%22icon%22%3E%20%20%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M18%2010a8%208%200%201%201-16%200%208%208%200%200%201%2016%200Zm-8-5a.75.75%200%200%201%20.75.75v4.5a.75.75%200%200%201-1.5%200v-4.5A.75.75%200%200%201%2010%205Zm0%2010a1%201%200%201%200%200-2%201%201%200%200%200%200%202Z%22%20clip-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E');
+    -webkit-mask: var(--hero-exclamation-circle);
+    mask: var(--hero-exclamation-circle);
     mask-repeat: no-repeat;
     background-color: currentColor;
     vertical-align: middle;
@@ -1499,10 +1499,10 @@
     width: 1.25rem;
     height: 1.25rem;
   }
-  .hero-information-circle-mini {
-    --hero-information-circle-mini: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2020%2020%22%20fill%3D%22currentColor%22%20aria-hidden%3D%22true%22%20data-slot%3D%22icon%22%3E%20%20%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M18%2010a8%208%200%201%201-16%200%208%208%200%200%201%2016%200Zm-7-4a1%201%200%201%201-2%200%201%201%200%200%201%202%200ZM9%209a.75.75%200%200%200%200%201.5h.253a.25.25%200%200%201%20.244.304l-.459%202.066A1.75%201.75%200%200%200%2010.747%2015H11a.75.75%200%200%200%200-1.5h-.253a.25.25%200%200%201-.244-.304l.459-2.066A1.75%201.75%200%200%200%209.253%209H9Z%22%20clip-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E');
-    -webkit-mask: var(--hero-information-circle-mini);
-    mask: var(--hero-information-circle-mini);
+  .hero-information-circle {
+    --hero-information-circle: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2020%2020%22%20fill%3D%22currentColor%22%20aria-hidden%3D%22true%22%20data-slot%3D%22icon%22%3E%20%20%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M18%2010a8%208%200%201%201-16%200%208%208%200%200%201%2016%200Zm-7-4a1%201%200%201%201-2%200%201%201%200%200%201%202%200ZM9%209a.75.75%200%200%200%200%201.5h.253a.25.25%200%200%201%20.244.304l-.459%202.066A1.75%201.75%200%200%200%2010.747%2015H11a.75.75%200%200%200%200-1.5h-.253a.25.25%200%200%201-.244-.304l.459-2.066A1.75%201.75%200%200%200%209.253%209H9Z%22%20clip-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E');
+    -webkit-mask: var(--hero-information-circle);
+    mask: var(--hero-information-circle);
     mask-repeat: no-repeat;
     background-color: currentColor;
     vertical-align: middle;
@@ -1554,10 +1554,10 @@
     width: 1rem;
     height: 1rem;
   }
-  .hero-x-mark-solid {
-    --hero-x-mark-solid: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22currentColor%22%20aria-hidden%3D%22true%22%20data-slot%3D%22icon%22%3E%20%20%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M5.47%205.47a.75.75%200%200%201%201.06%200L12%2010.94l5.47-5.47a.75.75%200%201%201%201.06%201.06L13.06%2012l5.47%205.47a.75.75%200%201%201-1.06%201.06L12%2013.06l-5.47%205.47a.75.75%200%200%201-1.06-1.06L10.94%2012%205.47%206.53a.75.75%200%200%201%200-1.06Z%22%20clip-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E');
-    -webkit-mask: var(--hero-x-mark-solid);
-    mask: var(--hero-x-mark-solid);
+  .hero-x-mark {
+    --hero-x-mark: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22currentColor%22%20aria-hidden%3D%22true%22%20data-slot%3D%22icon%22%3E%20%20%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M5.47%205.47a.75.75%200%200%201%201.06%200L12%2010.94l5.47-5.47a.75.75%200%201%201%201.06%201.06L13.06%2012l5.47%205.47a.75.75%200%201%201-1.06%201.06L12%2013.06l-5.47%205.47a.75.75%200%200%201-1.06-1.06L10.94%2012%205.47%206.53a.75.75%200%200%201%200-1.06Z%22%20clip-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E');
+    -webkit-mask: var(--hero-x-mark);
+    mask: var(--hero-x-mark);
     mask-repeat: no-repeat;
     background-color: currentColor;
     vertical-align: middle;
@@ -2460,8 +2460,8 @@
     --color-success-content: oklch(98% 0.014 180.72);
     --color-warning: oklch(66% 0.179 58.318);
     --color-warning-content: oklch(98% 0.022 95.277);
-    --color-error: oklch(65% 0.241 354.308);
-    --color-error-content: oklch(97% 0.014 343.198);
+    --color-error: oklch(58% 0.253 17.585);
+    --color-error-content: oklch(96% 0.015 12.422);
     --radius-selector: 0.25rem;
     --radius-field: 0.25rem;
     --radius-box: 0.5rem;
diff --git a/installer/templates/phx_umbrella/apps/app_name_web/config/dev.exs b/installer/templates/phx_umbrella/apps/app_name_web/config/dev.exs
index 2d2e54a6e..3006e76fd 100644
--- a/installer/templates/phx_umbrella/apps/app_name_web/config/dev.exs
+++ b/installer/templates/phx_umbrella/apps/app_name_web/config/dev.exs
@@ -53,7 +53,7 @@ config :<%= @web_app_name %>, <%= @endpoint_module %>,
     patterns: [
       ~r"priv/static/(?!uploads/).*(js|css|png|jpeg|jpg|gif|svg)$",<%= if @gettext do %>
       ~r"priv/gettext/.*(po)$",<% end %>
-      ~r"lib/<%= @web_app_name %>/(controllers|live|components)/.*(ex|heex)$"
+      ~r"lib/<%= @web_app_name %>/(?:controllers|live|components|router)/?.*\.(ex|heex)$"
     ]
   ]<% end %>
 
diff --git a/installer/templates/phx_web/components/core_components.ex b/installer/templates/phx_web/components/core_components.ex
index 40e1a4a92..f16622700 100644
--- a/installer/templates/phx_web/components/core_components.ex
+++ b/installer/templates/phx_web/components/core_components.ex
@@ -64,15 +64,15 @@ defmodule <%= @web_namespace %>.CoreComponents do
         @kind == :info && "alert-info",
         @kind == :error && "alert-error"
       ]}>
-        <.icon :if={@kind == :info} name="hero-information-circle-mini" class="size-5 shrink-0" />
-        <.icon :if={@kind == :error} name="hero-exclamation-circle-mini" class="size-5 shrink-0" />
+        <.icon :if={@kind == :info} name="hero-information-circle" class="size-5 shrink-0" />
+        <.icon :if={@kind == :error} name="hero-exclamation-circle" class="size-5 shrink-0" />
         <div>
           <p :if={@title} class="font-semibold">{@title}</p>
           <p>{msg}</p>
         </div>
         <div class="flex-1" />
         <button type="button" class="group self-start cursor-pointer" aria-label=<%= maybe_heex_attr_gettext.("close", @gettext) %>>
-          <.icon name="hero-x-mark-solid" class="size-5 opacity-40 group-hover:opacity-70" />
+          <.icon name="hero-x-mark" class="size-5 opacity-40 group-hover:opacity-70" />
         </button>
       </div>
     </div>
@@ -268,7 +268,7 @@ defmodule <%= @web_namespace %>.CoreComponents do
   defp error(assigns) do
     ~H"""
     <p class="mt-1.5 flex gap-2 items-center text-sm text-error">
-      <.icon name="hero-exclamation-circle-mini" class="size-5" />
+      <.icon name="hero-exclamation-circle" class="size-5" />
       {render_slot(@inner_block)}
     </p>
     """
@@ -404,7 +404,7 @@ defmodule <%= @web_namespace %>.CoreComponents do
 
   ## Examples
 
-      <.icon name="hero-x-mark-solid" />
+      <.icon name="hero-x-mark" />
       <.icon name="hero-arrow-path" class="ml-1 size-3 motion-safe:animate-spin" />
   """
   attr :name, :string, required: true
diff --git a/installer/templates/phx_web/components/layouts.ex b/installer/templates/phx_web/components/layouts.ex
index 3981b63b6..9f284b715 100644
--- a/installer/templates/phx_web/components/layouts.ex
+++ b/installer/templates/phx_web/components/layouts.ex
@@ -11,11 +11,29 @@ defmodule <%= @web_namespace %>.Layouts do
 
   embed_templates "layouts/*"
 
+  @doc """
+  Renders the app layout
+
+  ## Examples
+
+      <Layouts.app flash={@flash}>
+        <h1>Content</h1>
+      </Layout.app>
+      
+  """
+  attr :flash, :map, required: true, doc: "the map of flash messages"
+
+  attr :current_scope, :map,
+    default: nil,
+    doc: "the current [scope](https://hexdocs.pm/phoenix/scopes.html)"
+
+  slot :inner_block, required: true
+
   def app(assigns) do
     ~H"""
     <header class="navbar px-4 sm:px-6 lg:px-8">
       <div class="flex-1">
-        <a href="/" class="flex-1 flex items-center gap-2">
+        <a href="/" class="flex-1 flex w-fit items-center gap-2">
           <img src={~p"/images/logo.svg"} width="36" />
           <span class="text-sm font-semibold">v{Application.spec(:phoenix, :vsn)}</span>
         </a>
@@ -75,19 +93,19 @@ defmodule <%= @web_namespace %>.Layouts do
         hidden
       >
         <%= maybe_eex_gettext.("Attempting to reconnect", @gettext) %>
-        <.icon name="hero-arrow-path" class="ml-1 h-3 w-3 motion-safe:animate-spin" />
+        <.icon name="hero-arrow-path" class="ml-1 size-3 motion-safe:animate-spin" />
       </.flash>
 
       <.flash
         id="server-error"
         kind={:error}
         title=<%= maybe_heex_attr_gettext.("Something went wrong!", @gettext) %>
-        phx-disconnected={show(".phx-client-error #client-error") |> JS.remove_attribute("hidden")}
-        phx-connected={hide("#client-error") |> JS.set_attribute({"hidden", ""})}
+        phx-disconnected={show(".phx-server-error #server-error") |> JS.remove_attribute("hidden")}
+        phx-connected={hide("#server-error") |> JS.set_attribute({"hidden", ""})}
         hidden
       >
-        <%= maybe_eex_gettext.("Hang in there while we get back on track", @gettext) %>
-        <.icon name="hero-arrow-path" class="ml-1 h-3 w-3 motion-safe:animate-spin" />
+        <%= maybe_eex_gettext.("Attempting to reconnect", @gettext) %>
+        <.icon name="hero-arrow-path" class="ml-1 size-3 motion-safe:animate-spin" />
       </.flash>
     </div>
     """
diff --git a/installer/templates/phx_web/components/layouts/root.html.heex b/installer/templates/phx_web/components/layouts/root.html.heex
index cb10aaa05..ef859d6ae 100644
--- a/installer/templates/phx_web/components/layouts/root.html.heex
+++ b/installer/templates/phx_web/components/layouts/root.html.heex
@@ -22,7 +22,9 @@
             document.documentElement.setAttribute("data-theme", theme);
           }
         };
-        setTheme(localStorage.getItem("phx:theme") || "system");
+        if (!document.documentElement.hasAttribute("data-theme")) {
+          setTheme(localStorage.getItem("phx:theme") || "system");
+        }
         window.addEventListener("storage", (e) => e.key === "phx:theme" && setTheme(e.newValue || "system"));
         window.addEventListener("phx:set-theme", ({ detail: { theme } }) => setTheme(theme));
       })();
diff --git a/lib/mix/phoenix/schema.ex b/lib/mix/phoenix/schema.ex
index cd4692c86..25aaf45b9 100644
--- a/lib/mix/phoenix/schema.ex
+++ b/lib/mix/phoenix/schema.ex
@@ -83,7 +83,7 @@ defmodule Mix.Phoenix.Schema do
     repo_alias = if String.ends_with?(Atom.to_string(repo), ".Repo"), do: "", else: ", as: Repo"
     file = Mix.Phoenix.context_lib_path(ctx_app, basename <> ".ex")
     table = opts[:table] || schema_plural
-    scope = Mix.Phoenix.Scope.scope_from_opts(otp_app, opts[:scope], opts[:no_scope])
+    scope = Mix.Phoenix.Scope.scope_from_opts(ctx_app, opts[:scope], opts[:no_scope])
     {cli_attrs, uniques, redacts} = extract_attr_flags(cli_attrs)
     {assocs, attrs} = partition_attrs_and_assocs(module, attrs(cli_attrs))
     types = types(attrs)
diff --git a/lib/phoenix/channel.ex b/lib/phoenix/channel.ex
index 0f902dc6b..d1b83bdd9 100644
--- a/lib/phoenix/channel.ex
+++ b/lib/phoenix/channel.ex
@@ -34,8 +34,8 @@ defmodule Phoenix.Channel do
 
   The first argument is the topic, the second argument is a map payload given by
   the client, and the third argument is an instance of `Phoenix.Socket`. The
-  `socket` to all channel callbacks, so check its module and documentation to
-  learn its fields and the different ways to interact with it.
+  `socket` is provided to all channel callbacks, so check its module and
+  documentation to learn its fields and the different ways to interact with it.
 
   ## Authorization
 
diff --git a/lib/phoenix/endpoint/supervisor.ex b/lib/phoenix/endpoint/supervisor.ex
index 8f8a52bcf..75027af9d 100644
--- a/lib/phoenix/endpoint/supervisor.ex
+++ b/lib/phoenix/endpoint/supervisor.ex
@@ -33,8 +33,26 @@ defmodule Phoenix.Endpoint.Supervisor do
       cond do
         Code.ensure_loaded?(mod) and function_exported?(mod, :init, 2) ->
           IO.warn(
-            "#{inspect(mod)}.init/2 is deprecated, use config/runtime.exs instead " <>
-              "or pass additional options when starting the endpoint in your supervision tree"
+            """
+            your #{inspect(mod)} defines a init/2 callback, which is now deprecated. \
+            This callback is invoked when your endpoint is initialized as part of your supervision tree. \
+            Instead, you should either:
+
+            1. Move all dynamic configuration to config/runtime.exs (preferred). For example:
+
+                # config/runtime.exs
+                import Config
+
+                if config_env() == :prod do
+                  config #{inspect(otp_app)}, #{inspect(mod)},
+                    http: [:inet6, port: System.fetch_env!("PORT")]
+                end
+
+            2. Pass the configuration you returned from the `init/2` callback \
+            as additional options when starting the endpoint in your supervision tree. \
+            For example: {#{inspect(mod)}, some_extra_options: true}
+            """,
+            []
           )
 
           {:ok, init_conf} = mod.init(:supervisor, env_conf)
@@ -88,6 +106,7 @@ defmodule Phoenix.Endpoint.Supervisor do
         server_children(mod, conf, server?) ++
         socket_children(mod, conf, :drainer_spec) ++
         watcher_children(mod, conf, server?)
+
     Supervisor.init(children, strategy: :one_for_one)
   end
 
diff --git a/mix.exs b/mix.exs
index 064c48ae3..1d218b96e 100644
--- a/mix.exs
+++ b/mix.exs
@@ -8,7 +8,7 @@ defmodule Phoenix.MixProject do
     end
   end
 
-  @version "1.8.0-rc.1"
+  @version "1.8.0-rc.2"
   @scm_url "https://github.com/phoenixframework/phoenix"
 
   # If the elixir requirement is updated, we need to make the installer
diff --git a/package.json b/package.json
index 41a691a1c..b58b027b3 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "phoenix",
-  "version": "1.8.0-rc.1",
+  "version": "1.8.0-rc.2",
   "description": "The official JavaScript client for the Phoenix web framework.",
   "license": "MIT",
   "module": "./priv/static/phoenix.mjs",
diff --git a/priv/templates/phx.gen.auth/login_live.ex b/priv/templates/phx.gen.auth/login_live.ex
index 60abd025b..fd6f83552 100644
--- a/priv/templates/phx.gen.auth/login_live.ex
+++ b/priv/templates/phx.gen.auth/login_live.ex
@@ -23,7 +23,7 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web
         </.header>
 
         <div :if={local_mail_adapter?()} class="alert alert-info">
-          <.icon name="hero-information-circle" class="w-6 h-6 shrink-0" />
+          <.icon name="hero-information-circle" class="size-6 shrink-0" />
           <div>
             <p>You are running the local mail adapter.</p>
             <p>
diff --git a/priv/templates/phx.gen.auth/schema.ex b/priv/templates/phx.gen.auth/schema.ex
index 1ee64e0e6..28d55502f 100644
--- a/priv/templates/phx.gen.auth/schema.ex
+++ b/priv/templates/phx.gen.auth/schema.ex
@@ -7,7 +7,6 @@ defmodule <%= inspect schema.module %> do
     field :email, :string
     field :password, :string, virtual: true, redact: true
     field :hashed_password, :string, redact: true
-    field :current_password, :string, virtual: true, redact: true
     field :confirmed_at, <%= inspect schema.timestamp_type %>
     field :authenticated_at, <%= inspect schema.timestamp_type %>, virtual: true
 
diff --git a/priv/templates/phx.gen.auth/session_new.html.heex b/priv/templates/phx.gen.auth/session_new.html.heex
index 5cf03d59f..1831483ea 100644
--- a/priv/templates/phx.gen.auth/session_new.html.heex
+++ b/priv/templates/phx.gen.auth/session_new.html.heex
@@ -16,7 +16,7 @@
     </.header>
 
     <div :if={local_mail_adapter?()} class="alert alert-info">
-      <.icon name="hero-information-circle" class="w-6 h-6 shrink-0" />
+      <.icon name="hero-information-circle" class="size-6 shrink-0" />
       <div>
         <p>You are running the local mail adapter.</p>
         <p>
diff --git a/priv/templates/phx.gen.live/index.ex b/priv/templates/phx.gen.live/index.ex
index 922872436..18ddcb1da 100644
--- a/priv/templates/phx.gen.live/index.ex
+++ b/priv/templates/phx.gen.live/index.ex
@@ -43,7 +43,9 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web
 
   @impl true
   def mount(_params, _session, socket) do<%= if scope do %>
-    <%= inspect context.alias %>.subscribe_<%= schema.plural %>(<%= socket_scope %>)
+    if connected?(socket) do
+      <%= inspect context.alias %>.subscribe_<%= schema.plural %>(<%= socket_scope %>)
+    end
 <% end %>
     {:ok,
      socket
diff --git a/priv/templates/phx.gen.live/show.ex b/priv/templates/phx.gen.live/show.ex
index 6d7722673..bf9e9e80f 100644
--- a/priv/templates/phx.gen.live/show.ex
+++ b/priv/templates/phx.gen.live/show.ex
@@ -29,7 +29,9 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web
 
   @impl true
   def mount(%{"<%= primary_key %>" => <%= primary_key %>}, _session, socket) do<%= if scope do %>
-    <%= inspect context.alias %>.subscribe_<%= schema.plural %>(<%= socket_scope %>)
+    if connected?(socket) do
+      <%= inspect context.alias %>.subscribe_<%= schema.plural %>(<%= socket_scope %>)
+    end
 <% end %>
     {:ok,
      socket
diff --git a/priv/templates/phx.gen.release/release.ex b/priv/templates/phx.gen.release/release.ex
index 521d17768..86d00a153 100644
--- a/priv/templates/phx.gen.release/release.ex
+++ b/priv/templates/phx.gen.release/release.ex
@@ -23,6 +23,8 @@ defmodule <%= app_namespace %>.Release do
   end
 
   defp load_app do
+    # Many platforms require SSL when connecting to the database
+    Application.ensure_all_started(:ssl)
     Application.ensure_loaded(@app)
   end
 end


Test patch:
diff --git a/installer/test/phx_new_test.exs b/installer/test/phx_new_test.exs
index bb47c040e..6766a85ad 100644
--- a/installer/test/phx_new_test.exs
+++ b/installer/test/phx_new_test.exs
@@ -141,7 +141,7 @@ defmodule Mix.Tasks.Phx.NewTest do
 
       assert_file("phx_blog/config/dev.exs", fn file ->
         assert file =~ "esbuild: {Esbuild,"
-        assert file =~ "lib/phx_blog_web/(controllers|live|components)/.*(ex|heex)"
+        assert file =~ "lib/phx_blog_web/(?:controllers|live|components|router)/?.*\\.(ex|heex)$"
         assert file =~ "http: [ip: {127, 0, 0, 1}"
       end)
 
diff --git a/installer/test/phx_new_umbrella_test.exs b/installer/test/phx_new_umbrella_test.exs
index 7d652c660..fd298a47c 100644
--- a/installer/test/phx_new_umbrella_test.exs
+++ b/installer/test/phx_new_umbrella_test.exs
@@ -71,7 +71,7 @@ defmodule Mix.Tasks.Phx.New.UmbrellaTest do
 
       assert_file(root_path(@app, "config/dev.exs"), fn file ->
         assert file =~ ~r[esbuild: {Esbuild]
-        assert file =~ "lib/#{@app}_web/(controllers|live|components)/.*(ex|heex)"
+        assert file =~ "lib/#{@app}_web/(?:controllers|live|components|router)/?.*\\.(ex|heex)$"
         assert file =~ "config :#{@app}_web, dev_routes: true"
       end)
 
diff --git a/test/mix/tasks/phx.gen.schema_test.exs b/test/mix/tasks/phx.gen.schema_test.exs
index 407c73091..20ba172e0 100644
--- a/test/mix/tasks/phx.gen.schema_test.exs
+++ b/test/mix/tasks/phx.gen.schema_test.exs
@@ -213,9 +213,7 @@ defmodule Mix.Tasks.Phx.Gen.SchemaTest do
 
   test "allows a custom migration dir", config do
     in_tmp_project(config.test, fn ->
-      Gen.Schema.run(
-        ~w(Blog.Post blog_posts title:string --migration-dir priv/custom_dir)
-      )
+      Gen.Schema.run(~w(Blog.Post blog_posts title:string --migration-dir priv/custom_dir))
 
       assert [migration] = Path.wildcard("priv/custom_dir/*_create_blog_posts.exs")
 
@@ -240,9 +238,7 @@ defmodule Mix.Tasks.Phx.Gen.SchemaTest do
 
   test "does not add maps to the required list", config do
     in_tmp_project(config.test, fn ->
-      Gen.Schema.run(
-        ~w(Blog.Post blog_posts title:string tags:map published_at:naive_datetime)
-      )
+      Gen.Schema.run(~w(Blog.Post blog_posts title:string tags:map published_at:naive_datetime))
 
       assert_file("lib/phoenix/blog/post.ex", fn file ->
         assert file =~ "cast(attrs, [:title, :tags, :published_at]"
@@ -283,9 +279,7 @@ defmodule Mix.Tasks.Phx.Gen.SchemaTest do
 
   test "generates unique indices", config do
     in_tmp_project(config.test, fn ->
-      Gen.Schema.run(
-        ~w(Blog.Post posts title:unique secret:redact unique_int:integer:unique)
-      )
+      Gen.Schema.run(~w(Blog.Post posts title:unique secret:redact unique_int:integer:unique))
 
       assert [migration] = Path.wildcard("priv/repo/migrations/*_create_posts.exs")
 
@@ -556,6 +550,48 @@ defmodule Mix.Tasks.Phx.Gen.SchemaTest do
         assert [_] = Path.wildcard("another_app/priv/repo/migrations/*_create_blog_posts.exs")
       end)
     end
+
+    test "generates scoped schema", config do
+      in_tmp_umbrella_project(config.test, fn ->
+        Application.put_env(:phoenix, :generators, context_app: {:another_app, "another_app"})
+
+        with_scope_env(
+          :another_app,
+          [
+            org: [
+              default: true,
+              module: MyApp.Accounts.Scope,
+              assign_key: :current_scope,
+              access_path: [:user, :org_id],
+              schema_key: :org_id,
+              schema_type: :id,
+              schema_table: :organizations
+            ]
+          ],
+          fn ->
+            Gen.Schema.run(~w(Blog.Post blog_posts title:string --scope org --binary-id))
+
+            assert_file("another_app/lib/another_app/blog/post.ex", fn file ->
+              assert file =~ "@primary_key {:id, :binary_id, autogenerate: true}"
+              assert file =~ "field :org_id, :id"
+            end)
+
+            assert [migration] =
+                     Path.wildcard("another_app/priv/repo/migrations/*_create_blog_posts.exs")
+
+            assert_file(migration, fn file ->
+              assert file =~ "create table(:blog_posts, primary_key: false) do"
+              assert file =~ "add :id, :binary_id, primary_key: true"
+
+              assert file =~
+                       "add :org_id, references(:organizations, type: :id, on_delete: :delete_all)"
+
+              assert file =~ "create index(:blog_posts, [:org_id])"
+            end)
+          end
+        )
+      end)
+    end
   end
 
   test "generates scoped schema", config do