diff --git a/.github/BUILD.md b/.github/BUILD.md
index 5f962a8911..12b64b287f 100644
--- a/.github/BUILD.md
+++ b/.github/BUILD.md
@@ -1,33 +1,99 @@
-# Umbraco CMS Build
+# Umbraco CMS Build
 
 ## Are you sure?
 
 In order to use Umbraco as a CMS and build your website with it, you should not build it yourself. If you're reading this then you're trying to contribute to Umbraco or you're debugging a complex issue.
 
-- Are you about to create a pull request for Umbraco?
+- Are you about to [create a pull request for Umbraco][contribution guidelines]?
 - Are you trying to get to the bottom of a problem in your existing Umbraco installation?
 
 If the answer is yes, please read on. Otherwise, make sure to head on over [to the download page](https://our.umbraco.com/download) and start using Umbraco CMS as intended.
 
-**Table of contents**
+## Table of contents
 
-[Building from source](#building-from-source)
-  * [The quick build](#quick)
-  * [Build infrastructure](#build-infrastructure)
-    * [Properties](#properties)
-    * [GetUmbracoVersion](#getumbracoversion)
-    * [SetUmbracoVersion](#setumbracoversion)
-    * [Build](#build)
-    * [Build-UmbracoDocs](#build-umbracodocs)
-    * [Verify-NuGet](#verify-nuget)
-  * [Cleaning up](#cleaning-up)
+↖️ You can jump to any section by using the "table of contents" button ( ![Table of contents icon](img/tableofcontentsicon.svg) ) above.
 
-[Azure DevOps](#azure-devops)
 
-[Quirks](#quirks)
-  * [Powershell quirks](#powershell-quirks)
-  * [Git quirks](#git-quirks)
+## Debugging source locally
 
+Did you read ["Are you sure"](#are-you-sure)?
+
+[More details about contributing to Umbraco and how to use the GitHub tooling can be found in our guide to contributing.][contribution guidelines]
+
+If you want to run a build without debugging, see [Building from source](#building-from-source) below. This runs the build in the same way it is run on our build servers.
+
+#### Debugging with VS Code
+
+In order to build the Umbraco source code locally with Visual Studio Code, first make sure you have the following installed.
+
+  * [Visual Studio Code](https://code.visualstudio.com/)
+  * [dotnet SDK v6.0.2+](https://dotnet.microsoft.com/en-us/download)
+  * [Node.js v14+](https://nodejs.org/en/download/)
+  * npm v7+ (installed with Node.js)
+  * [Git command line](https://git-scm.com/download/)
+
+Open the root folder of the repository in Visual Studio Code.
+
+To build the front end you'll need to open the command pallet (<kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>P</kbd>) and run `>Tasks: Run Task` followed by `Client Watch` and then run the `Client Build` task in the same way.
+
+You can also run the tasks manually on the command line:
+
+```
+cd src\Umbraco.Web.UI.Client
+npm install
+npm run dev
+```
+
+or
+
+```
+cd src\Umbraco.Web.UI.Client
+npm install
+gulp dev
+```
+
+**The initial Gulp build might take a long time - don't worry, this will be faster on subsequent runs.**
+
+You might run into [Gulp quirks](#gulp-quirks).
+
+The caching for the back office has been described as 'aggressive' so we often find it's best when making back office changes to [disable caching in the browser (check "Disable cache" on the "Network" tab of developer tools)][disable browser caching] to help you to see the changes you're making.
+
+To run the C# portion of the project, either hit <kbd>F5</kbd> to begin debugging, or manually using the command line:
+
+```
+dotnet watch --project .\src\Umbraco.Web.UI\Umbraco.Web.UI.csproj
+```
+
+**The initial C# build might take a _really_ long time (seriously, go and make a cup of coffee!) - but don't worry, this will be faster on subsequent runs.**
+
+When the page eventually loads in your web browser, you can follow the installer to set up a database for debugging. You may also wish to install a [starter kit][starter kits] to ease your debugging.
+
+#### Debugging with Visual Studio
+
+In order to build the Umbraco source code locally with Visual Studio, first make sure you have the following installed.
+
+  * [Visual Studio 2019 v16.8+ with .NET 6.0.2+](https://visualstudio.microsoft.com/vs/) ([the community edition is free](https://www.visualstudio.com/thank-you-downloading-visual-studio/?sku=Community&rel=15) for you to use to contribute to Open Source projects)
+  * [Node.js v14+](https://nodejs.org/en/download/)
+  * npm v7+ (installed with Node.js)
+  * [Git command line](https://git-scm.com/download/)
+
+The easiest way to get started is to open `umbraco.sln` in Visual Studio.
+
+To build the front end, you'll first need to run `cd src\Umbraco.Web.UI.Client && npm install`  in the command line (or `cd src\Umbraco.Web.UI.Client; npm install` in PowerShell). Then find the Task Runner Explorer (View → Other Windows → Task Runner Explorer) and run the `build` task under `Gulpfile.js`. You may need to refresh the Task Runner Explorer before the tasks load.
+
+If you're working on the backoffice, you may wish to run the `dev` command instead while you're working with it, so changes are copied over to the appropriate directories and you can refresh your browser to view the results of your changes.
+
+**The initial Gulp build might take a long time - don't worry, this will be faster on subsequent runs.**
+
+You might run into [Gulp quirks](#gulp-quirks).
+
+The caching for the back office has been described as 'aggressive' so we often find it's best when making back office changes to [disable caching in the browser (check "Disable cache" on the "Network" tab of developer tools)][disable browser caching] to help you to see the changes you're making.
+
+"The rest" is a C# based codebase, which is mostly ASP.NET Core MVC based. You can make changes, build them in Visual Studio, and hit <kbd>F5</kbd> to see the result.
+
+**The initial C# build might take a _really_ long time (seriously, go and make a cup of coffee!) - but don't worry, this will be faster on subsequent runs.**
+
+When the page eventually loads in your web browser, you can follow the installer to set up a database for debugging. You may also wish to install a [starter kit][starter kits] to ease your debugging.
 
 ## Building from source
 
@@ -38,13 +104,14 @@ Did you read ["Are you sure"](#are-you-sure)?
 To build Umbraco, fire up PowerShell and move to Umbraco's repository root (the directory that contains `src`, `build`, `LICENSE.md`...). There, trigger the build with the following command:
 
     build/build.ps1
-    
+
 If you only see a build.bat-file, you're probably on the wrong branch. If you switch to the correct branch (v8/contrib) the file will appear and you can build it.
 
 You might run into [Powershell quirks](#powershell-quirks).
 
 If it runs without errors; Hooray! Now you can continue with [the next step](CONTRIBUTING.md#how-do-i-begin) and open the solution and build it.
 
+
 ### Build Infrastructure
 
 The Umbraco Build infrastructure relies on a PowerShell object. The object can be retrieved with:
@@ -145,7 +212,7 @@ To perform a more complete clear, you will want to also delete the content of th
 The following command will force remove all untracked files and directories, whether they are ignored by Git or not. Combined with `git reset` it can recreate a pristine working directory. 
 
     git clean -xdf .
-    
+
 For git documentation see:
 * git [clean](<https://git-scm.com/docs/git-clean>)
 * git [reset](<https://git-scm.com/docs/git-reset>)
@@ -214,3 +281,19 @@ The best solution is to unblock the Zip file before un-zipping: right-click the
 ### Git Quirks
 
 Git might have issues dealing with long file paths during build. You may want/need to enable `core.longpaths` support (see [this page](https://github.com/msysgit/msysgit/wiki/Git-cannot-create-a-file-or-directory-with-a-long-path) for details).
+
+### Gulp Quirks
+
+You may need to run the following commands to set up gulp properly:
+
+  ```
+npm cache clean --force
+npm ci
+npm run build
+  ```
+
+
+
+[ contribution guidelines]: CONTRIBUTING.md	"Read the guide to contributing for more details on contributing to Umbraco"
+[ starter kits ]: https://our.umbraco.com/packages/?category=Starter%20Kits&version=9	"Browse starter kits available for v9 on Our "
+[ disable browser caching ]: https://techwiser.com/disable-cache-google-chrome-firefox "Instructions on how to disable browser caching in Chrome and Firefox"
diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index e8b378fb15..28618fb548 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -2,192 +2,131 @@
 
 👍🎉 First off, thanks for taking the time to contribute! 🎉👍
 
-The following is a set of guidelines, for contributing to Umbraco CMS.
+These contribution guidelines are mostly just that - guidelines, not rules. This is what we've found to work best over the years, but if you choose to ignore them, we still love you! 💖 Use your best judgement, and feel free to propose changes to this document in a pull request.
 
-These are mostly guidelines, not rules. Use your best judgement, and feel free to propose changes to this document in a pull request.
+## Coding not your thing? Or want more ways to contribute?
 
-Remember, we're a friendly bunch and are happy with whatever contribution you might provide. Below are guidelines for success that we've gathered over the years. If you choose to ignore them then we still love you 💖.
+This document covers contributing to the codebase of the CMS but [the community site has plenty of inspiration for other ways to get involved.][get involved]
 
-**Code of conduct**
+If you don't feel you'd like to make code changes here, you can visit our [documentation repository][docs repo] and use your experience to contribute to making the docs we have, even better.
 
-This project and everyone participating in it, is governed by the [our Code of Conduct](https://github.com/umbraco/.github/blob/main/.github/CODE_OF_CONDUCT.md).
+We also encourage community members to feel free to comment on others' pull requests and issues - the expertise we have is not limited to the Core Collaborators and HQ. So, if you see something on the issue tracker or pull requests you feel you can add to, please don't be shy.
 
-**Table of contents**
+## Table of contents
 
-[Contributing code changes](#contributing-code-changes)
-  * [Guidelines for contributions we welcome](#guidelines-for-contributions-we-welcome)
-    * [Ownership and copyright](#ownership-and-copyright)
-  * [What can I start with?](#what-can-i-start-with)
-  * [How do I begin?](#how-do-i-begin)
-  * [Pull requests](#pull-requests)
+- [Before you start](#before-you-start)
+  * [Code of Conduct](#code-of-conduct)
+  * [What can I contribute?](#what-can-i-contribute)
+    + [Making larger changes](#making-larger-changes)
+    + [Pull request or package?](#pull-request-or-package)
+    + [Ownership and copyright](#ownership-and-copyright)
+- [Finding your first issue: Up for grabs](#finding-your-first-issue-up-for-grabs)
+- [Making your changes](#making-your-changes)
+    + [Keeping your Umbraco fork in sync with the main repository](#keeping-your-umbraco-fork-in-sync-with-the-main-repository)
+    + [Style guide](#style-guide)
+    + [Questions?](#questions)
+- [Creating a pull request](#creating-a-pull-request)
+- [The review process](#the-review-process)
+  * [Dealing with requested changes](#dealing-with-requested-changes)
+    + [No longer available?](#no-longer-available)
+  * [The Core Collaborators team](#the-core-collaborators-team)
 
-[Reviews](#reviews)
-  * [Styleguides](#styleguides)
-  * [The Core Contributors](#the-core-contributors-team)
-  * [Questions?](#questions)
+## Before you start
 
-[Working with the code](#working-with-the-code)
-  * [Building Umbraco from source code](#building-umbraco-from-source-code)
-  * [Working with the source code](#working-with-the-source-code)
-  * [Making changes after the PR is open](#making-changes-after-the-pr-is-open)
-  * [Which branch should I target for my contributions?](#which-branch-should-i-target-for-my-contributions)
-  * [Keeping your Umbraco fork in sync with the main repository](#keeping-your-umbraco-fork-in-sync-with-the-main-repository)
 
-## Contributing code changes
+### Code of Conduct
 
-This document gives you a quick overview on how to get started.
+This project and everyone participating in it, is governed by the [our Code of Conduct][code of conduct].
 
-### Guidelines for contributions we welcome
+### What can I contribute?
 
-Not all changes are wanted, so on occasion we might close a PR without merging it. We will give you feedback why we can't accept your changes and we'll be nice about it, thanking you for spending your valuable time.
+We categorise pull requests (PRs) into two categories:
 
-We have [documented what we consider small and large changes](CONTRIBUTION_GUIDELINES.md). Make sure to talk to us before making large changes, so we can ensure that you don't put all your hard work into something we would not be able to merge.
+| PR type   | Definition                                                   |
+| --------- | ------------------------------------------------------------ |
+| Small PRs | Bug fixes and small improvements - can be recognized by seeing a small number of changes and possibly a small number of new files. |
+| Large PRs | New features and large refactorings - can be recognized by seeing a large number of changes, plenty of new files, updates to package manager files (NuGet’s packages.config, NPM’s packages.json, etc.). |
 
-Remember, it is always worth working on an issue from the `Up for grabs` list or even asking for some feedback before you send us a PR. This way, your PR will not be closed as unwanted.
+We’re usually able to handle small PRs pretty quickly. A community volunteer will do the initial review and flag it for Umbraco HQ as “community tested”. If everything looks good, it will be merged pretty quickly [as per the described process][review process].
+
+We would love to follow the same process for larger PRs but this is not always possible due to time limitations and priorities that need to be aligned. We don’t want to put up any barriers, but this document should set the correct expectations. 
+
+Not all changes are wanted, so on occasion we might close a PR without merging it but if we do, we will give you feedback why we can't accept your changes. **So make sure to [talk to us before making large changes][making larger changes]**, so we can ensure that you don't put all your hard work into something we would not be able to merge.
+
+#### Making larger changes
+
+[making larger changes]: #making-larger-changes
+
+Please make sure to describe your larger ideas in an [issue (bugs)][issues] or [discussion (new features)][discussions], it helps to put in mock up screenshots or videos. If the change makes sense for HQ to include in Umbraco CMS we will leave you some feedback on how we’d like to see it being implemented. 
+
+If a larger pull request is encouraged by Umbraco HQ, the process will be similar to what is described in the small PRs process above, we strive to feedback within 14 days. Finalizing and merging the PR might take longer though as it will likely need to be picked up by the development team to make sure everything is in order. We’ll keep you posted on the progress.
+
+#### Pull request or package?
+
+[pr or package]: #pull-request-or-package
+
+If you're unsure about whether your changes belong in the core Umbraco CMS or if you should turn your idea into a package instead, make sure to [talk to us][making larger changes].
+
+If it doesn’t fit in CMS right now, we will likely encourage you to make it into a package instead. A package is a great way to check out popularity of a feature, learn how people use it, validate good usability and fix bugs. Eventually, a package could "graduate" to be included in the CMS.
 
 #### Ownership and copyright
 
-It is your responsibility to make sure that you're allowed to share the code you're providing us.
-For example, you should have permission from your employer or customer to share code.
+It is your responsibility to make sure that you're allowed to share the code you're providing us. For example, you should have permission from your employer or customer to share code.
 
 Similarly, if your contribution is copied or adapted from somewhere else, make sure that the license allows you to reuse that for a contribution to Umbraco-CMS.
 
 If you're not sure, leave a note on your contribution and we will be happy to guide you.
 
-When your contribution has been accepted, it will be [MIT licensed](https://github.com/umbraco/Umbraco-CMS/blob/v8/contrib/LICENSE.md) from that time onwards.
+When your contribution has been accepted, it will be [MIT licensed][MIT license] from that time onwards.
 
-### What can I start with?
+## Finding your first issue: Up for grabs
 
-Unsure where to begin contributing to Umbraco? You can start by looking through [these `Up for grabs` issues](https://github.com/umbraco/Umbraco-CMS/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+label%3Acommunity%2Fup-for-grabs+)
+Umbraco HQ will regularly mark newly created issues on the issue tracker with [the `community/up-for-grabs` tag][up for grabs issues]. This means that the proposed changes are wanted in Umbraco but the HQ does not have the time to make them at this time. We encourage anyone to pick them up and help out.
 
-### How do I begin?
+If you do start working on something, make sure to leave a small comment on the issue saying something like: "I'm working on this". That way other people stumbling upon the issue know they don't need to pick it up, someone already has.
+
+## Making your changes
 
 Great question! The short version goes like this:
 
-  * **Fork** - create a fork of [`Umbraco-CMS` on GitHub](https://github.com/umbraco/Umbraco-CMS)
+1. **Fork**
 
-  ![Fork the repository](img/forkrepository.png)
+    Create a fork of [`Umbraco-CMS` on GitHub][Umbraco CMS repo]
+    
+    ![Fork the repository](img/forkrepository.png)
+    
+1. **Clone**
 
-  * **Clone** - when GitHub has created your fork, you can clone it in your favorite Git tool
+    When GitHub has created your fork, you can clone it in your favorite Git tool
+    
+    ![Clone the fork](img/clonefork.png) 
+    
+1. **Switch to the correct branch**
 
-  ![Clone the fork](img/clonefork.png)
+    Switch to the `v10/contrib` branch
 
-  * **Switch to the correct branch** - switch to the `v9/contrib` branch
-  * **Build** - build your fork of Umbraco locally as described in [building Umbraco from source code](BUILD.md)
-  * **Change** - make your changes, experiment, have fun, explore and learn, and don't be afraid. We welcome all contributions and will [happily give feedback](#questions)
-  * **Commit** - done? Yay! 🎉 **Important:** create a new branch now and name it after the issue you're fixing, we usually follow the format: `temp-12345`. This means it's a temporary branch for the particular issue you're working on, in this case `12345`. When you have a branch, commit your changes. Don't commit to `v9/contrib`, create a new branch first.
-  * **Push** - great, now you can push the changes up to your fork on GitHub
-  * **Create pull request** - exciting! You're ready to show us your changes (or not quite ready, you just need some feedback to progress - you can now make use of GitHub's draft pull request status, detailed [here](https://github.blog/2019-02-14-introducing-draft-pull-requests/)). GitHub has picked up on the new branch you've pushed and will offer to create a Pull Request. Click that green button and away you go.
+1. **Build**
 
-  ![Create a pull request](img/createpullrequest.png)
+    Build your fork of Umbraco locally as described in the build documentation: you can [debug with Visual Studio Code][build - debugging with code] or [with Visual Studio][build - debugging with vs].
 
-### Pull requests
-The most successful pull requests usually look a like this:
+1. **Branch**
 
-  * Fill in the required template (shown when starting a PR on GitHub), and link your pull request to an issue on the [issue tracker,](https://github.com/umbraco/Umbraco-CMS/issues) if applicable.
-  * Include screenshots and animated GIFs in your pull request whenever possible.
-  * Unit tests, while optional, are awesome. Thank you!
-  * New code is commented with documentation from which [the reference documentation](https://our.umbraco.com/documentation/Reference/) is generated.
+    Create a new branch now and name it after the issue you're fixing, we usually follow the format: `temp-12345`. This means it's a temporary branch for the particular issue you're working on, in this case issue number `12345`.  Don't commit to `v10/contrib`, create a new branch first.
 
-Again, these are guidelines, not strict requirements. However, the more information that you give to us, the more we have to work with when considering your contributions. Good documentation of a pull request can really speed up the time it takes to review and merge your work!
+1. **Change**
 
-## Reviews
+    Make your changes, experiment, have fun, explore and learn, and don't be afraid. We welcome all contributions and will [happily give feedback][questions].
 
-You've sent us your first contribution - congratulations! Now what?
+1. **Commit and push**
 
-The [pull request team](#the-pr-team) can now start reviewing your proposed changes and give you feedback on them. If it's not perfect, we'll either fix up what we need or we can request that you make some additional changes.
+    Done? Yay! 🎉
 
-We have [a process in place which you can read all about](REVIEW_PROCESS.md). The very abbreviated version is:
+    Remember to commit to your new `temp` branch, and don't commit to `v10/contrib`. Then you can push the changes up to your fork on GitHub.
 
-- Your PR will get a reply within 48 hours
-- An in-depth reply will be added within at most 2 weeks
-- The PR will be either merged or rejected within at most 4 weeks
-- Sometimes it is difficult to meet these timelines and we'll talk to you if this is the case.
+#### Keeping your Umbraco fork in sync with the main repository
+[sync fork]: #keeping-your-umbraco-fork-in-sync-with-the-main-repository
 
-### Styleguides
-
-To be honest, we don't like rules very much. We trust you have the best of intentions and we encourage you to create working code. If it doesn't look perfect then we'll happily help clean it up.
-
-That said, the Umbraco development team likes to follow the hints that ReSharper gives us (no problem if you don't have this installed) and we've added a `.editorconfig` file so that Visual Studio knows what to do with whitespace, line endings, etc.
-
-### The Core Contributors team
-
-The Core Contributors team consists of one member of Umbraco HQ, [Sebastiaan](https://github.com/nul800sebastiaan), who gets assistance from the following community members who have comitted to volunteering their free time:
-
-- [Nathan Woulfe](https://github.com/nathanwoulfe)
-- [Joe Glombek](https://github.com/glombek)
-- [Laura Weatherhead](https://github.com/lssweatherhead)
-- [Michael Latouche](https://github.com/mikecp)
-- [Owain Williams](https://github.com/OwainWilliams)
-
-
-These wonderful people aim to provide you with a first reply to your PR, review and test out your changes and on occasions, they might ask more questions. If they are happy with your work, they'll let Umbraco HQ know by approving the PR. Hq will have final sign-off and will check the work again before it is merged.
-
-### Questions?
-
-You can get in touch with [the core contributors team](#the-core-contributors-team) in multiple ways; we love open conversations and we are a friendly bunch. No question you have is stupid. Any question you have usually helps out multiple people with the same question. Ask away:
-
-- If there's an existing issue on the issue tracker then that's a good place to leave questions and discuss how to start or move forward.
-- Unsure where to start? Did something not work as expected? Try leaving a note in the ["Contributing to Umbraco"](https://our.umbraco.com/forum/contributing-to-umbraco-cms/) forum. The team monitors that one closely, so one of us will be on hand and ready to point you in the right direction.
-
-## Working with the code
-
-### Building Umbraco from source code
-
-In order to build the Umbraco source code locally, first make sure you have the following installed.
-
-  * [Visual Studio 2019 v16.8+ (with .NET Core 3.0)](https://visualstudio.microsoft.com/vs/)
-  * [Node.js v10+](https://nodejs.org/en/download/)
-  * npm v6.4.1+ (installed with Node.js)
-  * [Git command line](https://git-scm.com/download/)
-
-The easiest way to get started is to open `src\umbraco.sln` in Visual Studio 2019 (version 16.3 or higher, [the community edition is free](https://www.visualstudio.com/thank-you-downloading-visual-studio/?sku=Community&rel=15) for you to use to contribute to Open Source projects). In Visual Studio, find the Task Runner Explorer (in the View menu under Other Windows) and run the build task under the gulpfile.
-
-Alternatively, you can run `build.ps1` from the Powershell command line, which will build both the backoffice (also known as "Belle") and the Umbraco core. You can then easily start debugging from Visual Studio, or if you need to debug Belle you can run `gulp dev` in `src\Umbraco.Web.UI.Client`. See [this page](BUILD.md) for more details.
-
-![Gulp build in Visual Studio](img/gulpbuild.png)
-
-After this build completes, you should be able to hit `F5` in Visual Studio to build and run the project. A IISExpress webserver will start and the Umbraco installer will pop up in your browser. Follow the directions there to get a working Umbraco install up and running.
-
-### Working with the source code
-
-Some parts of our source code are over 10 years old now. And when we say "old", we mean "mature" of course!
-
-There are two big areas that you should know about:
-
-  1. The Umbraco backoffice is a extensible AngularJS app and requires you to run a `gulp dev` command while you're working with it, so changes are copied over to the appropriate directories and you can refresh your browser to view the results of your changes.
-  You may need to run the following commands to set up gulp properly:
-  ```
-  npm cache clean --force
-  npm ci
-  npm run build
-  ```
-  The caching for the back office has been described as 'aggressive' so we often find it's best when making back office changes to disable caching in the browser to help you to see the changes you're making.
-
-  2. "The rest" is a C# based codebase, which is mostly ASP.NET MVC based. You can make changes, build them in Visual Studio, and hit `F5` to see the result.
-
-To find the general areas for something you're looking to fix or improve, have a look at the following two parts of the API documentation.
-
-  * [The AngularJS based backoffice files](https://apidocs.umbraco.com/v9/ui#/api) (to be found  in `src\Umbraco.Web.UI.Client\src`)
-  * [The C# application](https://apidocs.umbraco.com/v9/csharp/)
-
-### Which branch should I target for my contributions?
-
-We like to use [Gitflow as much as possible](https://jeffkreeftmeijer.com/git-flow/), but don't worry if you are not familiar with it. The most important thing you need to know is that when you fork the Umbraco repository, the default branch is set to something, usually `v9/contrib`. If you are working on v9, this is the branch you should be targetting. For v8 contributions, please target 'v8/contrib'
-
-Please note: we are no longer accepting features for v7 but will continue to merge bug fixes as and when they arise.
-
-![Which branch should I target?](img/defaultbranch.png)
-
-### Making changes after the PR is open
-
-If you make the corrections we ask for in the same branch and push them to your fork again, the pull request automatically updates with the additional commit(s) so we can review it again. If all is well, we'll merge the code and your commits are forever part of Umbraco!
-
-### Keeping your Umbraco fork in sync with the main repository
-
-We recommend you to sync with our repository before you submit your pull request. That way, you can fix any potential merge conflicts and make our lives a little bit easier.
-
-Also, if you have submitted a pull request three weeks ago and want to work on something new, you'll want to get the latest code to build against of course.
+Once you've already got a fork and cloned your fork locally, you can skip steps 1 and 2 going forward. Just remember to keep your fork up to date before making further changes.
 
 To sync your fork with this original one, you'll have to add the upstream url. You only have to do this once:
 
@@ -199,13 +138,110 @@ Then when you want to get the changes from the main repository:
 
 ```
 git fetch upstream
-git rebase upstream/v9/contrib
+git rebase upstream/v10/contrib
 ```
 
-In this command we're syncing with the `v9/contrib` branch, but you can of course choose another one if needed.
+In this command we're syncing with the `v10/contrib` branch, but you can of course choose another one if needed.
 
-(More info on how this works: [http://robots.thoughtbot.com/post/5133345960/keeping-a-git-fork-updated](http://robots.thoughtbot.com/post/5133345960/keeping-a-git-fork-updated))
+[More information on how this works can be found on the thoughtbot blog.][sync fork ext]
 
-### And finally
+#### Style guide
 
-We welcome all kinds of contributions to this repository. If you don't feel you'd like to make code changes here, you can visit our [documentation repository](https://github.com/umbraco/UmbracoDocs) and use your experience to contribute to making the docs we have, even better. We also encourage community members to feel free to comment on others' pull requests and issues - the expertise we have is not limited to the Core Contributors and HQ. So, if you see something on the issue tracker or pull requests you feel you can add to, please don't be shy.
+To be honest, we don't like rules very much. We trust you have the best of intentions and we encourage you to create working code. If it doesn't look perfect then we'll happily help clean it up.
+
+That said, the Umbraco development team likes to follow the hints that ReSharper gives us (no problem if you don't have this installed) and we've added a `.editorconfig` file so that Visual Studio knows what to do with whitespace, line endings, etc.
+
+#### Questions?
+[questions]: #questions
+
+You can get in touch with [the core contributors team][core collabs] in multiple ways; we love open conversations and we are a friendly bunch. No question you have is stupid. Any question you have usually helps out multiple people with the same question. Ask away:
+
+- If there's an existing issue on the issue tracker then that's a good place to leave questions and discuss how to start or move forward.
+- If you want to ask questions on some code you've already written you can create a draft pull request, [detailed in a GitHub blog post][draft prs].
+- Unsure where to start? Did something not work as expected? Try leaving a note in the ["Contributing to Umbraco"][contrib forum] forum. The team monitors that one closely, so one of us will be on hand and ready to point you in the right direction.
+
+## Creating a pull request
+
+Exciting! You're ready to show us your changes.
+
+We recommend you to [sync with our repository][sync fork] before you submit your pull request. That way, you can fix any potential merge conflicts and make our lives a little bit easier.
+
+GitHub will have picked up on the new branch you've pushed and will offer to create a Pull Request. Click that green button and away you go.
+![Create a pull request](img/createpullrequest.png)
+
+We like to use [git flow][git flow] as much as possible, but don't worry if you are not familiar with it. The most important thing you need to know is that when you fork the Umbraco repository, the default branch is set to something, usually `v10/contrib`. If you are working on v9, this is the branch you should be targeting.
+
+Please note: we are no longer accepting features for v8 and below but will continue to merge security fixes as and when they arise.
+
+## The review process
+[review process]: #the-review-process
+
+You've sent us your first contribution - congratulations! Now what?
+
+The [Core Collaborators team][Core collabs] can now start reviewing your proposed changes and give you feedback on them. If it's not perfect, we'll either fix up what we need or we can request that you make some additional changes.
+
+You will get an initial automated reply from our [Friendly Umbraco Robot, Umbrabot][Umbrabot], to acknowledge that we’ve seen your PR and we’ll pick it up as soon as we can. You can take this opportunity to double check everything is in order based off the handy checklist Umbrabot provides.
+
+You will get feedback as soon as the [Core Collaborators team][Core collabs] can after opening the PR. You’ll most likely get feedback within a couple of weeks. Then there are a few possible outcomes:
+
+- Your proposed change is awesome! We merge it in and it will be included in the next minor release of Umbraco
+- If the change is a high priority bug fix, we will cherry-pick it into the next patch release as well so that we can release it as soon as possible
+- Your proposed change is awesome but needs a bit more work, we’ll give you feedback on the changes we’d like to see
+- Your proposed change is awesome but... not something we’re looking to include at this point. We’ll close your PR and the related issue (we’ll be nice about it!). See [making larger changes][making larger changes] and [pull request or package?][pr or package]
+
+### Dealing with requested changes
+
+If you make the corrections we ask for in the same branch and push them to your fork again, the pull request automatically updates with the additional commit(s) so we can review it again. If all is well, we'll merge the code and your commits are forever part of Umbraco!
+
+#### No longer available?
+
+We understand you have other things to do and can't just drop everything to help us out.
+
+So if we’re asking for your help to improve the PR we’ll wait for two weeks to give you a fair chance to make changes. We’ll ask for an update if we don’t hear back from you after that time.  
+
+If we don’t hear back from you for 4 weeks, we’ll close the PR so that it doesn’t just hang around forever. You’re very welcome to re-open it once you have some more time to spend on it.  
+
+There will be times that we really like your proposed changes and we’ll finish the final improvements we’d like to see ourselves. You still get the credits and your commits will live on in the git repository.
+
+### The Core Collaborators team
+[Core collabs]: #the-core-collaborators-team
+
+The Core Contributors team consists of one member of Umbraco HQ, [Sebastiaan][Sebastiaan], who gets assistance from the following community members who have committed to volunteering their free time:
+
+- [Nathan Woulfe][Nathan Woulfe]
+- [Joe Glombek][Joe Glombek]
+- [Laura Weatherhead][Laura Weatherhead]
+- [Michael Latouche][Michael Latouche]
+- [Owain Williams][Owain Williams]
+
+
+These wonderful people aim to provide you with a reply to your PR, review and test out your changes and on occasions, they might ask more questions. If they are happy with your work, they'll let Umbraco HQ know by approving the PR. HQ will have final sign-off and will check the work again before it is merged.
+
+<!-- Reference links for easy updating -->
+
+<!-- Local -->
+
+[MIT license]: ../LICENSE.md	"Umbraco's license declaration"
+[build - debugging with vs]: BUILD.md#debugging-with-visual-studio	"Details on building and debugging Umbraco with Visual Studio"
+[build - debugging with code]: BUILD.md#debugging-with-vs-code	"Details on building and debugging Umbraco with Visual Studio Code"
+
+<!-- External -->
+
+[Nathan Woulfe]: https://github.com/nathanwoulfe	"Nathan's GitHub profile"
+[Joe Glombek]: https://github.com/glombek	"Joe's GitHub profile"
+[Laura Weatherhead]: https://github.com/lssweatherhead	"Laura's GitHub profile"
+[Michael Latouche]: https://github.com/mikecp	"Michael's GitHub profile"
+[Owain Williams]: https://github.com/OwainWilliams	"Owain's GitHub profile"
+[Sebastiaan]: https://github.com/nul800sebastiaan	"Senastiaan's GitHub profile"
+[ Umbrabot ]: https://github.com/umbrabot
+[git flow]: https://jeffkreeftmeijer.com/git-flow/	"An explanation of git flow"
+[sync fork ext]: http://robots.thoughtbot.com/post/5133345960/keeping-a-git-fork-updated	"Details on keeping a git fork updated"
+[draft prs]: https://github.blog/2019-02-14-introducing-draft-pull-requests/	"Github's blog post providing details on draft pull requests"
+[contrib forum]: https://our.umbraco.com/forum/contributing-to-umbraco-cms/
+[get involved]: https://community.umbraco.com/get-involved/
+[docs repo]: https://github.com/umbraco/UmbracoDocs
+[code of conduct]: https://github.com/umbraco/.github/blob/main/.github/CODE_OF_CONDUCT.md
+[up for grabs issues]: https://github.com/umbraco/Umbraco-CMS/issues?q=is%3Aissue+is%3Aopen+label%3Acommunity%2Fup-for-grabs
+[Umbraco CMS repo]: https://github.com/umbraco/Umbraco-CMS
+[issues]: https://github.com/umbraco/Umbraco-CMS/issues
+[discussions]: https://github.com/umbraco/Umbraco-CMS/discussions
diff --git a/.github/CONTRIBUTION_GUIDELINES.md b/.github/CONTRIBUTION_GUIDELINES.md
deleted file mode 100644
index 0ac35e6897..0000000000
--- a/.github/CONTRIBUTION_GUIDELINES.md
+++ /dev/null
@@ -1,35 +0,0 @@
-# Contributing to Umbraco CMS
-
-When you’re considering creating a pull request for Umbraco CMS, we will categorize them in two different sizes, small and large.
-
-The process for both sizes is very similar, as [explained in the contribution document](CONTRIBUTING.md#how-do-i-begin).
-
-## Small PRs
-Bug fixes and small improvements - can be recognized by seeing a small number of changes and possibly a small number of new files.
-
-We’re usually able to handle small PRs pretty quickly. A community volunteer will do the initial review and flag it for Umbraco HQ as “community tested”. If everything looks good, it will be merged pretty quickly [as per the described process](REVIEW_PROCESS.md).
-
-### Up for grabs
-
-Umbraco HQ will regularly mark newly created issues on the issue tracker with the `Up for grabs` tag. This means that the proposed changes are wanted in Umbraco but the HQ does not have the time to make them at this time. We encourage anyone to pick them up and help out.
-
-If you do start working on something, make sure to leave a small comment on the issue saying something like: "I'm working on this". That way other people stumbling upon the issue know they don't need to pick it up, someone already has.
-
-## Large PRs
-New features and large refactorings - can be recognized by seeing a large number of changes, plenty of new files, updates to package manager files (NuGet’s packages.config, NPM’s packages.json, etc.).  
-
-We would love to follow the same process for larger PRs but this is not always possible due to time limitations and priorities that need to be aligned. We don’t want to put up any barriers, but this document should set the correct expectations.  
-
-Please make sure to describe your idea in an issue, it helps to put in mockup screenshots or videos.  
-
-If the change makes sense for HQ to include in Umbraco CMS we will leave you some feedback on how we’d like to see it being implemented. 
-
-If a larger pull request is encouraged by Umbraco HQ, the process will be similar to what is described in the [small PRs process](#small-prs) above, we strive to feedback within 14 days. Finalizing and merging the PR might take longer though as it will likely need to be picked up by the development team to make sure everything is in order. We’ll keep you posted on the progress.
-
-It is highly recommended that you speak to the HQ before making large, complex changes. 
-
-### Pull request or package?
-
-If it doesn’t fit in CMS right now, we will likely encourage you to make it into a package instead. A package is a great way to check out popularity of a feature, learn how people use it, validate good usability and fix bugs.  
-
-Eventually, a package could "graduate" to be included in the CMS.  
diff --git a/.github/REVIEW_PROCESS.md b/.github/REVIEW_PROCESS.md
deleted file mode 100644
index 917d25b090..0000000000
--- a/.github/REVIEW_PROCESS.md
+++ /dev/null
@@ -1,25 +0,0 @@
-# Review process
-
-You're an awesome person and have sent us your contribution in the form of a pull request! It's now time to relax for a bit and wait for our response. 
-
-In order to set some expectations, here's what happens next.
-
-## Review process
-
-You will get an initial reply within 48 hours (workdays) to acknowledge that we’ve seen your PR and we’ll pick it up as soon as we can.
-
-You will get feedback within at most 14 days after opening the PR. You’ll most likely get feedback sooner though. Then there are a few possible outcomes:
-
-- Your proposed change is awesome! We merge it in and it will be included in the next minor release of Umbraco
-- If the change is a high priority bug fix, we will cherry-pick it into the next patch release as well so that we can release it as soon as possible
-- Your proposed change is awesome but needs a bit more work, we’ll give you feedback on the changes we’d like to see
-- Your proposed change is awesome but.. not something we’re looking to include at this point. We’ll close your PR and the related issue (we’ll be nice about it!)
-
-## Are you still available?
-
-We understand you have other things to do and can't just drop everything to help us out.   
-So if we’re asking for your help to improve the PR we’ll wait for two weeks to give you a fair chance to make changes. We’ll ask for an update if we don’t hear back from you after that time.  
-
-If we don’t hear back from you for 4 weeks, we’ll close the PR so that it doesn’t just hang around forever. You’re very welcome to re-open it once you have some more time to spend on it.  
-
-There will be times that we really like your proposed changes and we’ll finish the final improvements we’d like to see ourselves. You still get the credits and your commits will live on in the git repository.
\ No newline at end of file
diff --git a/.github/img/tableofcontentsicon.svg b/.github/img/tableofcontentsicon.svg
new file mode 100644
index 0000000000..a08c8742d3
--- /dev/null
+++ b/.github/img/tableofcontentsicon.svg
@@ -0,0 +1,3 @@
+<svg xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16">
+    <path fill-rule="evenodd" d="M2 4a1 1 0 100-2 1 1 0 000 2zm3.75-1.5a.75.75 0 000 1.5h8.5a.75.75 0 000-1.5h-8.5zm0 5a.75.75 0 000 1.5h8.5a.75.75 0 000-1.5h-8.5zm0 5a.75.75 0 000 1.5h8.5a.75.75 0 000-1.5h-8.5zM3 8a1 1 0 11-2 0 1 1 0 012 0zm-1 6a1 1 0 100-2 1 1 0 000 2z" fill="#57606A"></path>
+</svg>
\ No newline at end of file
diff --git a/.github/workflows/add-issues-to-review-project.yml b/.github/workflows/add-issues-to-review-project.yml
new file mode 100644
index 0000000000..4590c173fc
--- /dev/null
+++ b/.github/workflows/add-issues-to-review-project.yml
@@ -0,0 +1,53 @@
+name: Add issues to review project
+
+on:
+  issues:
+    types:
+      - opened
+
+jobs:
+  get-user-type:
+    runs-on: ubuntu-latest
+    outputs:
+      ignored: ${{ steps.set-output.outputs.ignored }}
+    steps:
+      - name: Install dependencies
+        run: |
+          npm install node-fetch@2
+      - uses: actions/github-script@v5
+        name: "Determing HQ user or not"
+        id: set-output
+        with:
+          script: |
+            const fetch = require('node-fetch');
+            const response = await fetch('https://collaboratorsv2.euwest01.umbraco.io/umbraco/api/users/IsIgnoredUser', {
+              method: 'post',
+              body: JSON.stringify('${{ github.event.issue.user.login }}'),
+              headers: {
+                'Authorization': 'Bearer ${{ secrets.OUR_BOT_API_TOKEN }}',
+                'Content-Type': 'application/json'
+              }
+            });
+
+            var isIgnoredUser = true;
+            try {                
+                if(response.status === 200) {
+                    const data = await response.text();
+                    isIgnoredUser = data === "true";
+                } else {
+                    console.log("Returned data not indicate success:", response.status);
+                }
+            } catch(error) {
+                console.log(error);
+            };
+            core.setOutput("ignored", isIgnoredUser);
+            console.log("Ignored is", isIgnoredUser);
+  add-to-project:
+    if: needs.get-user-type.outputs.ignored == 'false'
+    runs-on: ubuntu-latest
+    needs: [get-user-type]
+    steps:
+      - uses: actions/add-to-project@main
+        with:
+          project-url: https://github.com/orgs/${{ github.repository_owner }}/projects/21
+          github-token: ${{ secrets.ADD_TO_PROJECT_PAT }}
diff --git a/.github/workflows/pr-first-response.yml b/.github/workflows/pr-first-response.yml
index 0969a883c9..991a5d0808 100644
--- a/.github/workflows/pr-first-response.yml
+++ b/.github/workflows/pr-first-response.yml
@@ -4,12 +4,12 @@ on:
   pull_request_target:
     types: [opened]
 
-jobs: 
+jobs:
   send-response:
     runs-on: ubuntu-latest
     permissions:
       issues: write
-      pull-requests: write    
+      pull-requests: write
     steps:
       - name: Install dependencies
         run: |
@@ -45,8 +45,17 @@ jobs:
                         body: data
                     });
                 } else {
-                    console.log("Status code did not indicate success:", response.status);
+                    console.log("Returned data not indicate success.");
+                    
+                    if(response.status !== 200) {
+                      console.log("Status code:", response.status)
+                    }
+
                     console.log("Returned data:", data);
+                    
+                    if(data === '') {
+                      console.log("An empty response usually indicates that either no comment was found or the actor user was not eligible for getting an automated response (HQ users are not getting auto-responses).")
+                    }
                 }
             } catch(error) {
                 console.log(error);
diff --git a/.github/workflows/up-for-grabs-response.yml b/.github/workflows/up-for-grabs-response.yml
new file mode 100644
index 0000000000..eb16eb7779
--- /dev/null
+++ b/.github/workflows/up-for-grabs-response.yml
@@ -0,0 +1,63 @@
+name: labeled-up-for-grabs-first-comment
+
+on:
+  issues:
+    types:
+      - labeled
+
+jobs:
+  send-response:
+    if: github.event.label.name == 'community/up-for-grabs'
+    runs-on: ubuntu-latest
+    permissions:
+      issues: write
+      pull-requests: write
+    steps:
+      - name: Install dependencies
+        run: |
+          npm install node-fetch@2
+      - name: Fetch comment 🗣️ and add it to the issue
+        uses: actions/github-script@v6
+        with:
+          script: |
+            const fetch = require('node-fetch');
+            const response = await fetch('https://collaboratorsv2.euwest01.umbraco.io/umbraco/api/comments/PostComment', {
+              method: 'post',
+              body: JSON.stringify({
+                repo: '${{ github.repository }}',
+                number: '${{ github.event.issue.number }}',
+                actor: '${{ github.event.issue.user.login }}',
+                commentType: 'labeled-up-for-grabs-first-comment'
+              }),
+              headers: {
+                'Authorization': 'Bearer ${{ secrets.OUR_BOT_API_TOKEN }}',
+                'Content-Type': 'application/json'
+              }
+            });
+
+            try {
+                const data = await response.text();
+
+                if(response.status === 200 && data !== '') {
+                    github.rest.issues.createComment({
+                        issue_number: context.issue.number,
+                        owner: context.repo.owner,
+                        repo: context.repo.repo,
+                        body: data
+                    });
+                } else {
+                    console.log("Returned data not indicate success.");
+
+                    if(response.status !== 200) {
+                      console.log("Status code:", response.status)
+                    }
+
+                    console.log("Returned data:", data);
+                    
+                    if(data === '') {
+                      console.log("An empty response usually indicates that either no comment was found or the actor user was not eligible for getting an automated response (HQ users are not getting auto-responses).")
+                    }
+                }
+            } catch(error) {
+                console.log(error);
+            };
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index 1d4324a34d..99876bc77e 100644
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -33,6 +33,18 @@
                 "$gulp-tsc"
             ]
         },
+        {
+            "label": "Client Watch",
+            "detail": "runs npm run dev for Umbraco.Web.UI.Client",
+            "promptOnClose": true,
+            "group": "build",
+            "type": "npm",
+            "script": "dev",
+            "path": "src/Umbraco.Web.UI.Client/",
+            "problemMatcher": [
+                "$gulp-tsc"
+            ]
+        },
         {
             "label": "Dotnet build",
             "detail": "Dotnet build of SLN",
diff --git a/build/azure-pipelines.yml b/build/azure-pipelines.yml
index ae8ca25f52..6289a9bfc4 100644
--- a/build/azure-pipelines.yml
+++ b/build/azure-pipelines.yml
@@ -305,7 +305,7 @@ stages:
           - powershell: sqllocaldb start mssqllocaldb
             displayName: Start localdb (Windows only)
             condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
-          - powershell: docker run --name mssql -d -p 1433:1433 -e ACCEPT_EULA=Y -e SA_PASSWORD=$(SA_PASSWORD) -e MSSQL_PID=Developer mcr.microsoft.com/mssql/server:2019-latest 
+          - powershell: docker run --name mssql -d -p 1433:1433 -e ACCEPT_EULA=Y -e SA_PASSWORD=$(SA_PASSWORD) -e MSSQL_PID=Developer mcr.microsoft.com/mssql/server:2019-latest
             displayName: Start SQL Server (Linux only)
             condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
           - task: DotNetCoreCLI@2
@@ -494,7 +494,6 @@ stages:
     displayName: NuGet release
     dependsOn:
       - Deploy_MyGet
-      - Build_Docs
     condition: and(succeeded(), or(startsWith(variables['Build.SourceBranch'], 'refs/heads/release/'), ${{parameters.nuGetDeploy}}))
     jobs:
       - job:
@@ -523,6 +522,7 @@ stages:
     dependsOn:
       - Build
       - Deploy_NuGet
+      - Build_Docs
     condition: and(succeeded(), or(startsWith(variables['Build.SourceBranch'], 'refs/heads/release/'), ${{parameters.uploadApiDocs}}))
     jobs:
       - job:
diff --git a/src/Directory.Build.props b/src/Directory.Build.props
index b7d39cc6f5..ce54e08edd 100644
--- a/src/Directory.Build.props
+++ b/src/Directory.Build.props
@@ -42,4 +42,12 @@
   <PropertyGroup Condition="'$(TF_BUILD)' == 'true'">
     <ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
   </PropertyGroup>
+
+  <!-- Package Validation -->
+  <PropertyGroup>
+    <EnablePackageValidation>true</EnablePackageValidation>
+    <PackageValidationBaselineVersion>10.0.0</PackageValidationBaselineVersion>
+    <EnableStrictModeForCompatibleFrameworksInPackage>true</EnableStrictModeForCompatibleFrameworksInPackage>
+    <EnableStrictModeForCompatibleTfms>true</EnableStrictModeForCompatibleTfms>
+  </PropertyGroup>
 </Project>
diff --git a/src/JsonSchema/JsonSchema.csproj b/src/JsonSchema/JsonSchema.csproj
index 5ab8db1d99..ea0ce9b7c3 100644
--- a/src/JsonSchema/JsonSchema.csproj
+++ b/src/JsonSchema/JsonSchema.csproj
@@ -4,6 +4,7 @@
     <TargetFramework>net6.0</TargetFramework>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
     <IsPackable>false</IsPackable>
+    <EnablePackageValidation>false</EnablePackageValidation>
   </PropertyGroup>
 
     <ItemGroup>
@@ -34,5 +35,4 @@
     </ItemGroup>
     <Copy SourceFiles="@(PackageReferenceFiles)" DestinationFolder="$(OutDir)" />
   </Target>
-
 </Project>
diff --git a/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoInstall/Index.cshtml b/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoInstall/Index.cshtml
index 92e458c3b0..406c311312 100644
--- a/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoInstall/Index.cshtml
+++ b/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoInstall/Index.cshtml
@@ -26,7 +26,7 @@
 
     <div id="overlay" ng-cloak ng-animate="'fade'" ng-show="installer.done"></div>
 
-    <div id="installer" class="absolute-center clearfix"
+    <div id="installer"
          ng-cloak
          ng-animate="'fade'"
          ng-show="installer.configuring">
diff --git a/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoWebsite/NoNodes.cshtml b/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoWebsite/NoNodes.cshtml
index 69d8318a1f..e773e81516 100644
--- a/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoWebsite/NoNodes.cshtml
+++ b/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoWebsite/NoNodes.cshtml
@@ -36,10 +36,10 @@
 
                 <div class="row">
                     <div class="col">
-                        <h2>Easy start with Umbraco.tv</h2>
+                        <h2>Easy start with Umbraco Learning Base</h2>
                         <p>We have created a bunch of 'how-to' videos, to get you easily started with Umbraco. Learn how to build projects in just a couple of minutes. Easiest CMS in the world.</p>
 
-                        <a href="https://umbraco.tv?ref=tvFromInstaller" target="_blank" rel="noopener">Umbraco.tv &rarr;</a>
+                        <a href="https://www.youtube.com/channel/UCbGfwSAPflebnadyhEPw-wA?ref=tvFromInstaller" target="_blank" rel="noopener">Umbraco Learning Base &rarr;</a>
                     </div>
 
                     <div class="col">
diff --git a/src/Umbraco.Core/Actions/ActionAssignDomain.cs b/src/Umbraco.Core/Actions/ActionAssignDomain.cs
index b8fd8a4650..3bd946837f 100644
--- a/src/Umbraco.Core/Actions/ActionAssignDomain.cs
+++ b/src/Umbraco.Core/Actions/ActionAssignDomain.cs
@@ -24,7 +24,7 @@ public class ActionAssignDomain : IAction
     public string Category => Constants.Conventions.PermissionCategories.AdministrationCategory;
 
     /// <inheritdoc />
-    public string Icon => "home";
+    public string Icon => "icon-home";
 
     /// <inheritdoc />
     public bool ShowInNotifier => false;
diff --git a/src/Umbraco.Core/Actions/ActionCopy.cs b/src/Umbraco.Core/Actions/ActionCopy.cs
index 02bb17166f..f7d9d699a5 100644
--- a/src/Umbraco.Core/Actions/ActionCopy.cs
+++ b/src/Umbraco.Core/Actions/ActionCopy.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Umbraco.
+﻿// Copyright (c) Umbraco.
 // See LICENSE for more details.
 
 namespace Umbraco.Cms.Core.Actions;
@@ -23,7 +23,7 @@ public class ActionCopy : IAction
     public string Category => Constants.Conventions.PermissionCategories.StructureCategory;
 
     /// <inheritdoc />
-    public string Icon => "documents";
+    public string Icon => "icon-documents";
 
     /// <inheritdoc />
     public bool ShowInNotifier => true;
diff --git a/src/Umbraco.Core/Actions/ActionCreateBlueprintFromContent.cs b/src/Umbraco.Core/Actions/ActionCreateBlueprintFromContent.cs
index 85490b42f8..ee249d9ef8 100644
--- a/src/Umbraco.Core/Actions/ActionCreateBlueprintFromContent.cs
+++ b/src/Umbraco.Core/Actions/ActionCreateBlueprintFromContent.cs
@@ -18,7 +18,7 @@ public class ActionCreateBlueprintFromContent : IAction
     public bool CanBePermissionAssigned => true;
 
     /// <inheritdoc />
-    public string Icon => "blueprint";
+    public string Icon => Constants.Icons.Blueprint;
 
     /// <inheritdoc />
     public string Alias => "createblueprint";
diff --git a/src/Umbraco.Core/Actions/ActionDelete.cs b/src/Umbraco.Core/Actions/ActionDelete.cs
index 7d9c4e6a03..055b2a8f98 100644
--- a/src/Umbraco.Core/Actions/ActionDelete.cs
+++ b/src/Umbraco.Core/Actions/ActionDelete.cs
@@ -28,7 +28,7 @@ public class ActionDelete : IAction
     public string Category => Constants.Conventions.PermissionCategories.ContentCategory;
 
     /// <inheritdoc/>
-    public string Icon => "delete";
+    public string Icon => "icon-delete";
 
     /// <inheritdoc/>
     public bool ShowInNotifier => true;
diff --git a/src/Umbraco.Core/Actions/ActionMove.cs b/src/Umbraco.Core/Actions/ActionMove.cs
index a40d03d096..c1f6089793 100644
--- a/src/Umbraco.Core/Actions/ActionMove.cs
+++ b/src/Umbraco.Core/Actions/ActionMove.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Umbraco.
+﻿// Copyright (c) Umbraco.
 // See LICENSE for more details.
 
 namespace Umbraco.Cms.Core.Actions;
@@ -23,7 +23,7 @@ public class ActionMove : IAction
     public string Category => Constants.Conventions.PermissionCategories.StructureCategory;
 
     /// <inheritdoc />
-    public string Icon => "enter";
+    public string Icon => "icon-enter";
 
     /// <inheritdoc />
     public bool ShowInNotifier => true;
diff --git a/src/Umbraco.Core/Actions/ActionNew.cs b/src/Umbraco.Core/Actions/ActionNew.cs
index 31056632ed..bd08a95ac5 100644
--- a/src/Umbraco.Core/Actions/ActionNew.cs
+++ b/src/Umbraco.Core/Actions/ActionNew.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Umbraco.
+﻿// Copyright (c) Umbraco.
 // See LICENSE for more details.
 
 namespace Umbraco.Cms.Core.Actions;
@@ -25,7 +25,7 @@ public class ActionNew : IAction
     public string Alias => ActionAlias;
 
     /// <inheritdoc />
-    public string Icon => "add";
+    public string Icon => "icon-add";
 
     /// <inheritdoc />
     public bool ShowInNotifier => true;
diff --git a/src/Umbraco.Core/Actions/ActionNotify.cs b/src/Umbraco.Core/Actions/ActionNotify.cs
index 9d1975b852..10845b01b9 100644
--- a/src/Umbraco.Core/Actions/ActionNotify.cs
+++ b/src/Umbraco.Core/Actions/ActionNotify.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Umbraco.
+﻿// Copyright (c) Umbraco.
 // See LICENSE for more details.
 
 namespace Umbraco.Cms.Core.Actions;
@@ -18,7 +18,7 @@ public class ActionNotify : IAction
     public bool CanBePermissionAssigned => true;
 
     /// <inheritdoc />
-    public string Icon => "megaphone";
+    public string Icon => "icon-megaphone";
 
     /// <inheritdoc />
     public string Alias => "notify";
diff --git a/src/Umbraco.Core/Actions/ActionProtect.cs b/src/Umbraco.Core/Actions/ActionProtect.cs
index 0a5ac8ace8..7b54a0ec98 100644
--- a/src/Umbraco.Core/Actions/ActionProtect.cs
+++ b/src/Umbraco.Core/Actions/ActionProtect.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Umbraco.
+﻿// Copyright (c) Umbraco.
 // See LICENSE for more details.
 
 namespace Umbraco.Cms.Core.Actions;
@@ -23,7 +23,7 @@ public class ActionProtect : IAction
     public string Category => Constants.Conventions.PermissionCategories.AdministrationCategory;
 
     /// <inheritdoc />
-    public string Icon => "lock";
+    public string Icon => "icon-lock";
 
     /// <inheritdoc />
     public bool ShowInNotifier => true;
diff --git a/src/Umbraco.Core/Actions/ActionRestore.cs b/src/Umbraco.Core/Actions/ActionRestore.cs
index 79e00f4464..395c678fe4 100644
--- a/src/Umbraco.Core/Actions/ActionRestore.cs
+++ b/src/Umbraco.Core/Actions/ActionRestore.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Umbraco.
+﻿// Copyright (c) Umbraco.
 // See LICENSE for more details.
 
 namespace Umbraco.Cms.Core.Actions;
@@ -23,7 +23,7 @@ public class ActionRestore : IAction
     public string? Category => null;
 
     /// <inheritdoc />
-    public string Icon => "undo";
+    public string Icon => "icon-undo";
 
     /// <inheritdoc />
     public bool ShowInNotifier => true;
diff --git a/src/Umbraco.Core/Actions/ActionRights.cs b/src/Umbraco.Core/Actions/ActionRights.cs
index 08afe7e2db..4cd8674122 100644
--- a/src/Umbraco.Core/Actions/ActionRights.cs
+++ b/src/Umbraco.Core/Actions/ActionRights.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Umbraco.
+﻿// Copyright (c) Umbraco.
 // See LICENSE for more details.
 
 namespace Umbraco.Cms.Core.Actions;
@@ -23,7 +23,7 @@ public class ActionRights : IAction
     public string Category => Constants.Conventions.PermissionCategories.ContentCategory;
 
     /// <inheritdoc />
-    public string Icon => "vcard";
+    public string Icon => "icon-vcard";
 
     /// <inheritdoc />
     public bool ShowInNotifier => true;
diff --git a/src/Umbraco.Core/Actions/ActionRollback.cs b/src/Umbraco.Core/Actions/ActionRollback.cs
index 5aada555d3..63021a2ae3 100644
--- a/src/Umbraco.Core/Actions/ActionRollback.cs
+++ b/src/Umbraco.Core/Actions/ActionRollback.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Umbraco.
+﻿// Copyright (c) Umbraco.
 // See LICENSE for more details.
 
 namespace Umbraco.Cms.Core.Actions;
@@ -23,7 +23,7 @@ public class ActionRollback : IAction
     public string Category => Constants.Conventions.PermissionCategories.AdministrationCategory;
 
     /// <inheritdoc />
-    public string Icon => "undo";
+    public string Icon => "icon-undo";
 
     /// <inheritdoc />
     public bool ShowInNotifier => true;
diff --git a/src/Umbraco.Core/Actions/ActionSort.cs b/src/Umbraco.Core/Actions/ActionSort.cs
index b77a44c730..23e65d7533 100644
--- a/src/Umbraco.Core/Actions/ActionSort.cs
+++ b/src/Umbraco.Core/Actions/ActionSort.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Umbraco.
+﻿// Copyright (c) Umbraco.
 // See LICENSE for more details.
 
 namespace Umbraco.Cms.Core.Actions;
@@ -23,7 +23,7 @@ public class ActionSort : IAction
     public string Category => Constants.Conventions.PermissionCategories.StructureCategory;
 
     /// <inheritdoc />
-    public string Icon => "navigation-vertical";
+    public string Icon => "icon-navigation-vertical";
 
     /// <inheritdoc />
     public bool ShowInNotifier => true;
diff --git a/src/Umbraco.Core/Actions/ActionToPublish.cs b/src/Umbraco.Core/Actions/ActionToPublish.cs
index bf15ee4e3b..8df53b3e4a 100644
--- a/src/Umbraco.Core/Actions/ActionToPublish.cs
+++ b/src/Umbraco.Core/Actions/ActionToPublish.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Umbraco.
+﻿// Copyright (c) Umbraco.
 // See LICENSE for more details.
 
 namespace Umbraco.Cms.Core.Actions;
@@ -23,7 +23,7 @@ public class ActionToPublish : IAction
     public string Category => Constants.Conventions.PermissionCategories.ContentCategory;
 
     /// <inheritdoc />
-    public string Icon => "outbox";
+    public string Icon => "icon-outbox";
 
     /// <inheritdoc />
     public bool ShowInNotifier => true;
diff --git a/src/Umbraco.Core/Actions/ActionUnpublish.cs b/src/Umbraco.Core/Actions/ActionUnpublish.cs
index c8a83f002e..f8ebb918f9 100644
--- a/src/Umbraco.Core/Actions/ActionUnpublish.cs
+++ b/src/Umbraco.Core/Actions/ActionUnpublish.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Umbraco.
+﻿// Copyright (c) Umbraco.
 // See LICENSE for more details.
 
 namespace Umbraco.Cms.Core.Actions;
@@ -23,7 +23,7 @@ public class ActionUnpublish : IAction
     public string Category => Constants.Conventions.PermissionCategories.ContentCategory;
 
     /// <inheritdoc />
-    public string Icon => "circle-dotted";
+    public string Icon => "icon-circle-dotted";
 
     /// <inheritdoc />
     public bool ShowInNotifier => false;
diff --git a/src/Umbraco.Core/Actions/ActionUpdate.cs b/src/Umbraco.Core/Actions/ActionUpdate.cs
index 4af2410cc4..2d01ef176c 100644
--- a/src/Umbraco.Core/Actions/ActionUpdate.cs
+++ b/src/Umbraco.Core/Actions/ActionUpdate.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Umbraco.
+﻿// Copyright (c) Umbraco.
 // See LICENSE for more details.
 
 namespace Umbraco.Cms.Core.Actions;
@@ -23,7 +23,7 @@ public class ActionUpdate : IAction
     public string Category => Constants.Conventions.PermissionCategories.ContentCategory;
 
     /// <inheritdoc />
-    public string Icon => "save";
+    public string Icon => "icon-save";
 
     /// <inheritdoc />
     public bool ShowInNotifier => true;
diff --git a/src/Umbraco.Core/Constants-Telemetry.cs b/src/Umbraco.Core/Constants-Telemetry.cs
index 5f3783a774..c40df0f8bd 100644
--- a/src/Umbraco.Core/Constants-Telemetry.cs
+++ b/src/Umbraco.Core/Constants-Telemetry.cs
@@ -1,4 +1,4 @@
-namespace Umbraco.Cms.Core;
+﻿namespace Umbraco.Cms.Core;
 
 public static partial class Constants
 {
@@ -26,5 +26,6 @@ public static partial class Constants
         public static string AspEnvironment = "AspEnvironment";
         public static string IsDebug = "IsDebug";
         public static string DatabaseProvider = "DatabaseProvider";
+        public static string CurrentServerRole = "CurrentServerRole";
     }
 }
diff --git a/src/Umbraco.Core/DependencyInjection/ServiceCollectionExtensions.cs b/src/Umbraco.Core/DependencyInjection/ServiceCollectionExtensions.cs
index 3d74261abf..ec26080e76 100644
--- a/src/Umbraco.Core/DependencyInjection/ServiceCollectionExtensions.cs
+++ b/src/Umbraco.Core/DependencyInjection/ServiceCollectionExtensions.cs
@@ -47,19 +47,19 @@ public static class ServiceCollectionExtensions
     public static void AddMultipleUnique<TService1, TService2, TImplementing>(
         this IServiceCollection services)
         where TService1 : class
-            where TService2 : class
-            where TImplementing : class, TService1, TService2
-            => services.AddMultipleUnique<TService1, TService2, TImplementing>(ServiceLifetime.Singleton);
+        where TService2 : class
+        where TImplementing : class, TService1, TService2
+        => services.AddMultipleUnique<TService1, TService2, TImplementing>(ServiceLifetime.Singleton);
 
-        /// <summary>
-        /// Adds services of types <typeparamref name="TService1"/> &amp; <typeparamref name="TService2"/> with a shared implementation type of <typeparamref name="TImplementing"/> to the specified <see cref="IServiceCollection"/>.
-        /// </summary>
-        /// <remarks>
-        /// Removes all previous registrations for the types <typeparamref name="TService1"/> &amp; <typeparamref name="TService2"/>.
-        /// </remarks>
-        public static void AddMultipleUnique<TService1, TService2, TImplementing>(
-            this IServiceCollection services,
-            ServiceLifetime lifetime)
+    /// <summary>
+    /// Adds services of types <typeparamref name="TService1"/> &amp; <typeparamref name="TService2"/> with a shared implementation type of <typeparamref name="TImplementing"/> to the specified <see cref="IServiceCollection"/>.
+    /// </summary>
+    /// <remarks>
+    /// Removes all previous registrations for the types <typeparamref name="TService1"/> &amp; <typeparamref name="TService2"/>.
+    /// </remarks>
+    public static void AddMultipleUnique<TService1, TService2, TImplementing>(
+        this IServiceCollection services,
+        ServiceLifetime lifetime)
         where TService1 : class
         where TService2 : class
         where TImplementing : class, TService1, TService2
@@ -88,18 +88,18 @@ public static class ServiceCollectionExtensions
         this IServiceCollection services,
         Func<IServiceProvider, TService> factory)
         where TService : class
-            => services.AddUnique(factory, ServiceLifetime.Singleton);
+        => services.AddUnique(factory, ServiceLifetime.Singleton);
 
-        /// <summary>
-        /// Adds a service of type <typeparamref name="TService"/> with an implementation factory method to the specified <see cref="IServiceCollection"/>.
-        /// </summary>
-        /// <remarks>
-        /// Removes all previous registrations for the type <typeparamref name="TService"/>.
-        /// </remarks>
-        public static void AddUnique<TService>(
-            this IServiceCollection services,
-            Func<IServiceProvider, TService> factory,
-            ServiceLifetime lifetime)
+    /// <summary>
+    /// Adds a service of type <typeparamref name="TService"/> with an implementation factory method to the specified <see cref="IServiceCollection"/>.
+    /// </summary>
+    /// <remarks>
+    /// Removes all previous registrations for the type <typeparamref name="TService"/>.
+    /// </remarks>
+    public static void AddUnique<TService>(
+        this IServiceCollection services,
+        Func<IServiceProvider, TService> factory,
+        ServiceLifetime lifetime)
         where TService : class
     {
         services.RemoveAll<TService>();
diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.CollectionBuilders.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.CollectionBuilders.cs
index bd8114bab9..c409a849ec 100644
--- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.CollectionBuilders.cs
+++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.CollectionBuilders.cs
@@ -84,7 +84,7 @@ public static partial class UmbracoBuilderExtensions
         return builder;
     }
 
-    [Obsolete("Use AddEmbedProvider instead. This will be removed in Umbraco 10")]
+    [Obsolete("Use AddEmbedProvider instead. This will be removed in Umbraco 11")]
     public static IUmbracoBuilder AddOEmbedProvider<T>(this IUmbracoBuilder builder)
         where T : class, IEmbedProvider => AddEmbedProvider<T>(builder);
 
diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs
index 5ad759de68..c699e09743 100644
--- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs
+++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs
@@ -204,20 +204,12 @@ public static partial class UmbracoBuilderExtensions
         => builder.WithCollectionBuilder<DashboardCollectionBuilder>();
 
     /// <summary>
-    /// Gets the partial view snippets collection builder.
-    /// </summary>
-    /// <param name="builder">The builder.</param>
     public static PartialViewSnippetCollectionBuilder? PartialViewSnippets(this IUmbracoBuilder builder)
         => builder.WithCollectionBuilder<PartialViewSnippetCollectionBuilder>();
 
-    /// <summary>
-    /// Gets the partial view macro snippets collection builder.
-    /// </summary>
-    /// <param name="builder">The builder.</param>
     public static PartialViewMacroSnippetCollectionBuilder? PartialViewMacroSnippets(this IUmbracoBuilder builder)
         => builder.WithCollectionBuilder<PartialViewMacroSnippetCollectionBuilder>();
 
-    /// <summary>
     /// Gets the cache refreshers collection builder.
     /// </summary>
     /// <param name="builder">The builder.</param>
diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/da.xml b/src/Umbraco.Core/EmbeddedResources/Lang/da.xml
index dcfd040713..b03fa9d884 100644
--- a/src/Umbraco.Core/EmbeddedResources/Lang/da.xml
+++ b/src/Umbraco.Core/EmbeddedResources/Lang/da.xml
@@ -1029,12 +1029,14 @@
     <key alias="resetPasswordEmailCopySubject">Umbraco: Nulstil adgangskode</key>
     <key alias="resetPasswordEmailCopyFormat">
       <![CDATA[<p>Dit brugernavn til at logge på Umbraco backoffice er: <strong>%0%</strong></p><p>Klik <a href="%1%"><strong>her</strong></a> for at nulstille din adgangskode eller kopier/indsæt denne URL i din browser:</p><p><em>%1%</em></p>]]></key>
+    <key alias="mfaSecurityCodeSubject">Umbraco: Sikkerhedskode</key>
+    <key alias="mfaSecurityCodeMessage">Din sikkerhedskode er: %0%</key>
     <key alias="2faTitle">Sidste skridt</key>
-    <key alias="2faText">Det er påkrævet at du verificerer din identitet.</key>
-    <key alias="2faMultipleText">Vælg venligst en autentificeringsmetode</key>
+    <key alias="2faText">Det er påkrævet at du bekræfter din identitet.</key>
+    <key alias="2faMultipleText">Vælg venligst en godkendelsesmetode</key>
     <key alias="2faCodeInput">Kode</key>
-    <key alias="2faCodeInputHelp">Indtast venligst koden fra dit device</key>
-    <key alias="2faInvalidCode">Koden kunne ikke genkendes</key>
+    <key alias="2faCodeInputHelp">Indtast venligst bekræftelseskoden</key>
+    <key alias="2faInvalidCode">Ugyldig kode indtastet</key>
   </area>
   <area alias="main">
     <key alias="dashboard">Skrivebord</key>
diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/en.xml b/src/Umbraco.Core/EmbeddedResources/Lang/en.xml
index 7cf8b5233a..70a458b45f 100644
--- a/src/Umbraco.Core/EmbeddedResources/Lang/en.xml
+++ b/src/Umbraco.Core/EmbeddedResources/Lang/en.xml
@@ -36,6 +36,7 @@
     <key alias="restore" version="7.3.0">Restore</key>
     <key alias="chooseWhereToCopy">Choose where to copy</key>
     <key alias="chooseWhereToMove">Choose where to move</key>
+    <key alias="chooseWhereToImport">Choose where to import</key>
     <key alias="toInTheTreeStructureBelow">to in the tree structure below</key>
     <key alias="infiniteEditorChooseWhereToCopy">Choose where to copy the selected item(s)</key>
     <key alias="infiniteEditorChooseWhereToMove">Choose where to move the selected item(s)</key>
@@ -570,9 +571,14 @@
     <key alias="deletingALayout">Modifying layout will result in loss of data for any existing content that is based on this configuration.</key>
   </area>
   <area alias="dictionary">
+    <key alias="importDictionaryItemHelp">
+      To import a dictionary item, find the ".udt" file on your computer by clicking the
+      "Import" button (you'll be asked for confirmation on the next screen)
+    </key>
     <key alias="itemDoesNotExists">Dictionary item does not exist.</key>
     <key alias="parentDoesNotExists">Parent item does not exist.</key>
     <key alias="noItems">There are no dictionary items.</key>
+    <key alias="noItemsInFile">There are no dictionary items in this file.</key>
     <key alias="createNew">Create dictionary item</key>
   </area>
   <area alias="dictionaryItem">
@@ -1593,6 +1599,9 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
     <key alias="resendInviteSuccess">Invitation has been re-sent to %0%</key>
     <key alias="documentTypeExportedSuccess">Document Type was exported to file</key>
     <key alias="documentTypeExportedError">An error occurred while exporting the Document Type</key>
+    <key alias="dictionaryItemExportedSuccess">Dictionary item(s) was exported to file</key>
+    <key alias="dictionaryItemExportedError">An error occurred while exporting the dictionary item(s)</key>
+    <key alias="dictionaryItemImported">The following dictionary item(s) has been imported!</key>
     <key alias="publishWithNoDomains">Domains are not configured for multilingual site, please contact an administrator,
       see log for more information
     </key>
@@ -1622,6 +1631,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
     <key alias="tabRules">Editor</key>
   </area>
   <area alias="template">
+    <key alias="runtimeModeProduction"><![CDATA[Content is not editable when using runtime mode <code>Production</code>.]]></key>
     <key alias="deleteByIdFailed">Failed to delete template with ID %0%</key>
     <key alias="edittemplate">Edit template</key>
     <key alias="insertSections">Sections</key>
diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml b/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml
index d89ef0020e..73dbed2cea 100644
--- a/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml
+++ b/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml
@@ -37,6 +37,7 @@
     <key alias="restore" version="7.3.0">Restore</key>
     <key alias="chooseWhereToCopy">Choose where to copy</key>
     <key alias="chooseWhereToMove">Choose where to move</key>
+    <key alias="chooseWhereToImport">Choose where to import</key>
     <key alias="toInTheTreeStructureBelow">to in the tree structure below</key>
     <key alias="infiniteEditorChooseWhereToCopy">Choose where to copy the selected item(s)</key>
     <key alias="infiniteEditorChooseWhereToMove">Choose where to move the selected item(s)</key>
@@ -585,9 +586,14 @@
     <key alias="deletingALayout">Modifying layout will result in loss of data for any existing content that is based on this configuration.</key>
   </area>
   <area alias="dictionary">
+    <key alias="importDictionaryItemHelp">
+      To import a dictionary item, find the ".udt" file on your computer by clicking the
+      "Import" button (you'll be asked for confirmation on the next screen)
+    </key>
     <key alias="itemDoesNotExists">Dictionary item does not exist.</key>
     <key alias="parentDoesNotExists">Parent item does not exist.</key>
     <key alias="noItems">There are no dictionary items.</key>
+    <key alias="noItemsInFile">There are no dictionary items in this file.</key>
     <key alias="createNew">Create dictionary item</key>
   </area>
   <area alias="dictionaryItem">
@@ -1641,6 +1647,9 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
     <key alias="contentCultureValidationError">Validation failed for language '%0%'</key>
     <key alias="documentTypeExportedSuccess">Document Type was exported to file</key>
     <key alias="documentTypeExportedError">An error occurred while exporting the Document Type</key>
+    <key alias="dictionaryItemExportedSuccess">Dictionary item(s) was exported to file</key>
+    <key alias="dictionaryItemExportedError">An error occurred while exporting the dictionary item(s)</key>
+    <key alias="dictionaryItemImported">The following dictionary item(s) has been imported!</key>
     <key alias="scheduleErrReleaseDate1">The release date cannot be in the past</key>
     <key alias="scheduleErrReleaseDate2">Cannot schedule the document for publishing since the required '%0%' is not
       published
@@ -1681,6 +1690,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
     <key alias="tabRules">Rich Text Editor</key>
   </area>
   <area alias="template">
+    <key alias="runtimeModeProduction"><![CDATA[Content is not editable when using runtime mode <code>Production</code>.]]></key>
     <key alias="deleteByIdFailed">Failed to delete template with ID %0%</key>
     <key alias="edittemplate">Edit template</key>
     <key alias="insertSections">Sections</key>
@@ -2528,7 +2538,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
     <key alias="settingsPublishedStatus">Published Status</key>
     <key alias="settingsModelsBuilder">Models Builder</key>
     <key alias="settingsHealthCheck">Health Check</key>
-    <key alias="settingsAnalytics">Analytics</key>
+    <key alias="settingsAnalytics">Telemetry data</key>
     <key alias="settingsProfiler">Profiling</key>
     <key alias="memberIntro">Getting Started</key>
     <key alias="formsInstall">Install Umbraco Forms</key>
@@ -2886,8 +2896,8 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
     <key alias="searchResults">items returned</key>
   </area>
   <area alias="analytics">
-    <key alias="consentForAnalytics">Consent for analytics</key>
-    <key alias="analyticsLevelSavedSuccess">Analytics level saved!</key>
+    <key alias="consentForAnalytics">Consent for telemetry data</key>
+    <key alias="analyticsLevelSavedSuccess">Telemetry level saved!</key>
     <key alias="analyticsDescription">
       <![CDATA[
       In order to improve Umbraco and add new functionality based on as relevant information as possible,
@@ -2903,12 +2913,13 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
     <key alias="detailedLevelDescription">
       <![CDATA[
           We will send:
-          <br>- Anonymized site ID, umbraco version, and packages installed.
-          <br>- Number of: Root nodes, Content nodes, Macros, Media, Document Types, Templates, Languages, Domains, User Group, Users, Members, and Property Editors in use.
-          <br>- System information: Webserver, server OS, server framework, server OS language, and database provider.
-          <br>- Configuration settings: Modelsbuilder mode, if custom Umbraco path exists, ASP environment, and if you are in debug mode.
-          <br>
-          <br><i>We might change what we send on the Detailed level in the future. If so, it will be listed above.
+          <ul>
+            <li>Anonymized site ID, umbraco version, and packages installed.</li>
+            <li>Number of: Root nodes, Content nodes, Macros, Media, Document Types, Templates, Languages, Domains, User Group, Users, Members, and Property Editors in use.</li>
+            <li>System information: Webserver, server OS, server framework, server OS language, and database provider.</li>
+            <li>Configuration settings: Modelsbuilder mode, if custom Umbraco path exists, ASP environment, and if you are in debug mode.</li>
+          </ul>
+          <i>We might change what we send on the Detailed level in the future. If so, it will be listed above.
           <br>By choosing "Detailed" you agree to current and future anonymized information being collected.</i>
        ]]>
     </key>
diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/nl.xml b/src/Umbraco.Core/EmbeddedResources/Lang/nl.xml
index 8f94e8694d..1d76ac06f9 100644
--- a/src/Umbraco.Core/EmbeddedResources/Lang/nl.xml
+++ b/src/Umbraco.Core/EmbeddedResources/Lang/nl.xml
@@ -996,6 +996,14 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je
     <key alias="resetPasswordEmailCopySubject">Umbraco: Wachtwoord Reset</key>
     <key alias="resetPasswordEmailCopyFormat">
       <![CDATA[<p>De gebruikersnaam om in te loggen bij jouw Umbraco omgeving is: <strong>%0%</strong></p><p>Klik <a href="%1%"><strong>hier</strong></a> om je wachtwoord te resetten of knip/plak deze URL in je browser:</p><p><em>%1%</em></p>]]></key>
+    <key alias="mfaSecurityCodeSubject">Umbraco: Beveiligingscode</key>
+    <key alias="mfaSecurityCodeMessage">Jouw beveiligingscode is: %0%</key>
+    <key alias="2faTitle">Laatste stap</key>
+    <key alias="2faText">Je hebt tweestapsverificatie ingeschakeld en moet je identiteit verifiëren.</key>
+    <key alias="2faMultipleText">Kies een tweestapsverificatie aanbieder</key>
+    <key alias="2faCodeInput">Verificatiecode</key>
+    <key alias="2faCodeInputHelp">Vul de verificatiecode in</key>
+    <key alias="2faInvalidCode">Ongeldige code ingevoerd</key>
   </area>
   <area alias="main">
     <key alias="dashboard">Dashboard</key>
@@ -1436,6 +1444,7 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je
     <key alias="tabRules">Rich Text Editor</key>
   </area>
   <area alias="template">
+    <key alias="runtimeModeProduction"><![CDATA[Inhoud kan niet worden bewerkt in de runtime-modus <code>Production</code>.]]></key>
     <key alias="deleteByIdFailed">Kan sjabloon met ID %0% niet verwijderen</key>
     <key alias="edittemplate">Sjabloon aanpassen</key>
     <key alias="insertSections">Secties</key>
@@ -1934,6 +1943,7 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je
     </key>
     <key alias="userinviteAvatarMessage">Wijzig je foto zodat andere gebruikers je makkelijk kunnen herkennen.</key>
     <key alias="writer">Auteur</key>
+    <key alias="configureTwoFactor">Configureer tweestapsverificatie</key>
     <key alias="change">Wijzig</key>
     <key alias="yourProfile" version="7.0">Je profiel</key>
     <key alias="yourHistory" version="7.0">Je recente historie</key>
@@ -2287,6 +2297,7 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je
     <key alias="labelUsedByMediaTypes">Gebruikt in Mediatypes</key>
     <key alias="labelUsedByMemberTypes">Gebruikt in Ledentypes</key>
     <key alias="usedByProperties">Gebruikt door</key>
+    <key alias="labelUsedByItems">Heeft verwijzingen vanuit de volgende items</key>
     <key alias="labelUsedByDocuments">Gebruikt in Documenten</key>
     <key alias="labelUsedByMembers">Gebruikt in Leden</key>
     <key alias="labelUsedByMedia">Gebruikt in Media</key>
diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/sv.xml b/src/Umbraco.Core/EmbeddedResources/Lang/sv.xml
index ad6f18b4cd..af3f157bf4 100644
--- a/src/Umbraco.Core/EmbeddedResources/Lang/sv.xml
+++ b/src/Umbraco.Core/EmbeddedResources/Lang/sv.xml
@@ -123,6 +123,7 @@
     <key alias="listBullet">Punktlista</key>
     <key alias="listNumeric">Numrerad lista</key>
     <key alias="macroInsert">Infoga macro</key>
+    <key alias="noDocumentTypesCreateNew">Skapa en ny dokumenttyp</key>
     <key alias="pictureInsert">Infoga bild</key>
     <key alias="publishAndClose">Publicera och stäng</key>
     <key alias="relations">Ändra relation</key>
diff --git a/src/Umbraco.Core/FireAndForgetRunner.cs b/src/Umbraco.Core/FireAndForgetRunner.cs
new file mode 100644
index 0000000000..8c466bd439
--- /dev/null
+++ b/src/Umbraco.Core/FireAndForgetRunner.cs
@@ -0,0 +1,41 @@
+﻿using Microsoft.Extensions.Logging;
+
+namespace Umbraco.Cms.Core;
+
+
+public class FireAndForgetRunner : IFireAndForgetRunner
+{
+    private readonly ILogger<FireAndForgetRunner> _logger;
+
+    public FireAndForgetRunner(ILogger<FireAndForgetRunner> logger) => _logger = logger;
+
+    public void RunFireAndForget(Func<Task> task) => ExecuteBackgroundTask(task);
+
+    private Task ExecuteBackgroundTask(Func<Task> fn)
+    {
+        // it is also possible to use UnsafeQueueUserWorkItem which does not flow the execution context,
+        // however that seems more difficult to use for async operations.
+
+        // Do not flow AsyncLocal to the child thread
+        using (ExecutionContext.SuppressFlow())
+        {
+            // NOTE: ConfigureAwait(false) is irrelevant here, it is not needed because this is not being
+            // awaited. ConfigureAwait(false) is only relevant when awaiting to prevent the SynchronizationContext
+            // (very different from the ExecutionContext!) from running the continuation on the calling thread.
+            return Task.Run(LoggingWrapper(fn));
+        }
+    }
+
+    private Func<Task> LoggingWrapper(Func<Task> fn) =>
+        async () =>
+        {
+            try
+            {
+                await fn();
+            }
+            catch (Exception e)
+            {
+                _logger.LogError(e, "Exception thrown in a background thread");
+            }
+        };
+}
diff --git a/src/Umbraco.Core/IFireAndForgetRunner.cs b/src/Umbraco.Core/IFireAndForgetRunner.cs
new file mode 100644
index 0000000000..b28a777990
--- /dev/null
+++ b/src/Umbraco.Core/IFireAndForgetRunner.cs
@@ -0,0 +1,6 @@
+﻿namespace Umbraco.Cms.Core;
+
+public interface IFireAndForgetRunner
+{
+    void RunFireAndForget(Func<Task> task);
+}
diff --git a/src/Umbraco.Core/Models/DictionaryImportModel.cs b/src/Umbraco.Core/Models/DictionaryImportModel.cs
new file mode 100644
index 0000000000..2507a6a1ec
--- /dev/null
+++ b/src/Umbraco.Core/Models/DictionaryImportModel.cs
@@ -0,0 +1,14 @@
+using System.Runtime.Serialization;
+
+namespace Umbraco.Cms.Core.Models
+{
+    [DataContract(Name = "dictionaryImportModel")]
+    public class DictionaryImportModel
+    {
+        [DataMember(Name = "dictionaryItems")]
+        public List<DictionaryPreviewImportModel>? DictionaryItems { get; set; }
+
+        [DataMember(Name = "tempFileName")]
+        public string? TempFileName { get; set; }
+    }
+}
diff --git a/src/Umbraco.Core/Models/DictionaryPreviewImportModel.cs b/src/Umbraco.Core/Models/DictionaryPreviewImportModel.cs
new file mode 100644
index 0000000000..530d49b013
--- /dev/null
+++ b/src/Umbraco.Core/Models/DictionaryPreviewImportModel.cs
@@ -0,0 +1,14 @@
+using System.Runtime.Serialization;
+
+namespace Umbraco.Cms.Core.Models
+{
+    [DataContract(Name = "dictionaryPreviewImportModel")]
+    public class DictionaryPreviewImportModel
+    {
+        [DataMember(Name = "name")]
+        public string? Name { get; set; }
+
+        [DataMember(Name = "level")]
+        public int Level { get; set; }
+    }
+}
diff --git a/src/Umbraco.Core/Models/Property.cs b/src/Umbraco.Core/Models/Property.cs
index 195772be3a..e49edc9217 100644
--- a/src/Umbraco.Core/Models/Property.cs
+++ b/src/Umbraco.Core/Models/Property.cs
@@ -1,4 +1,4 @@
-using System.Collections;
+﻿using System.Collections;
 using System.Runtime.Serialization;
 using Umbraco.Cms.Core.Collections;
 using Umbraco.Cms.Core.Models.Entities;
@@ -198,8 +198,8 @@ public class Property : EntityBase, IProperty
         // and match the specified culture and segment (or anything when '*').
         IEnumerable<IPropertyValue> pvalues = _vvalues.Where(x =>
                 PropertyType.SupportsVariation(x.Value.Culture, x.Value.Segment, true) && // the value variation is ok
-                (culture == "*" || (x.Value.Culture?.InvariantEquals(culture) ?? false)) && // the culture matches
-                (segment == "*" || (x.Value.Segment?.InvariantEquals(segment) ?? false))) // the segment matches
+                    (culture == "*" || x.Value.Culture.InvariantEquals(culture)) && // the culture matches
+                    (segment == "*" || x.Value.Segment.InvariantEquals(segment))) // the segment matches
             .Select(x => x.Value);
 
         foreach (IPropertyValue pvalue in pvalues)
diff --git a/src/Umbraco.Core/Models/Trees/ActionMenuItem.cs b/src/Umbraco.Core/Models/Trees/ActionMenuItem.cs
index 87fe72a0fb..7fdcc4f76f 100644
--- a/src/Umbraco.Core/Models/Trees/ActionMenuItem.cs
+++ b/src/Umbraco.Core/Models/Trees/ActionMenuItem.cs
@@ -1,4 +1,4 @@
-using Umbraco.Cms.Core.Services;
+﻿using Umbraco.Cms.Core.Services;
 using Umbraco.Extensions;
 
 namespace Umbraco.Cms.Core.Models.Trees;
diff --git a/src/Umbraco.Core/Models/Trees/CreateChildEntity.cs b/src/Umbraco.Core/Models/Trees/CreateChildEntity.cs
index 41c8c6f0de..7b3d8f2932 100644
--- a/src/Umbraco.Core/Models/Trees/CreateChildEntity.cs
+++ b/src/Umbraco.Core/Models/Trees/CreateChildEntity.cs
@@ -1,26 +1,30 @@
-using Umbraco.Cms.Core.Actions;
+﻿using Umbraco.Cms.Core.Actions;
 using Umbraco.Cms.Core.Services;
 
 namespace Umbraco.Cms.Core.Models.Trees;
 
 /// <summary>
-///     Represents the refresh node menu item
+    /// Represents the refresh node menu item
 /// </summary>
 public sealed class CreateChildEntity : ActionMenuItem
 {
+    private const string icon = "icon-add";
+
     public CreateChildEntity(string name, bool separatorBefore = false)
         : base(ActionNew.ActionAlias, name)
     {
-        Icon = "add";
+        Icon = icon;
         Name = name;
         SeparatorBefore = separatorBefore;
+        UseLegacyIcon = false;
     }
 
     public CreateChildEntity(ILocalizedTextService textService, bool separatorBefore = false)
         : base(ActionNew.ActionAlias, textService)
     {
-        Icon = "add";
+        Icon = icon;
         SeparatorBefore = separatorBefore;
+        UseLegacyIcon = false;
     }
 
     public override string AngularServiceName => "umbracoMenuActions";
diff --git a/src/Umbraco.Core/Models/Trees/ExportMember.cs b/src/Umbraco.Core/Models/Trees/ExportMember.cs
index 3f11ef4b05..2039a827cf 100644
--- a/src/Umbraco.Core/Models/Trees/ExportMember.cs
+++ b/src/Umbraco.Core/Models/Trees/ExportMember.cs
@@ -1,4 +1,4 @@
-using Umbraco.Cms.Core.Services;
+﻿using Umbraco.Cms.Core.Services;
 
 namespace Umbraco.Cms.Core.Models.Trees;
 
@@ -8,7 +8,11 @@ namespace Umbraco.Cms.Core.Models.Trees;
 public sealed class ExportMember : ActionMenuItem
 {
     public ExportMember(ILocalizedTextService textService)
-        : base("export", textService) => Icon = "download-alt";
+        : base("export", textService)
+    {
+        Icon = "icon-download-alt";
+        UseLegacyIcon = false;
+    }
 
     public override string AngularServiceName => "umbracoMenuActions";
 }
diff --git a/src/Umbraco.Core/Models/Trees/MenuItem.cs b/src/Umbraco.Core/Models/Trees/MenuItem.cs
index 3f77ccf2b6..e99e0d598a 100644
--- a/src/Umbraco.Core/Models/Trees/MenuItem.cs
+++ b/src/Umbraco.Core/Models/Trees/MenuItem.cs
@@ -8,7 +8,7 @@ using Umbraco.Extensions;
 namespace Umbraco.Cms.Core.Models.Trees;
 
 /// <summary>
-///     A context menu item
+    /// A context menu item
 /// </summary>
 [DataContract(Name = "menuItem", Namespace = "")]
 public class MenuItem
@@ -18,7 +18,7 @@ public class MenuItem
     public MenuItem()
     {
         AdditionalData = new Dictionary<string, object>();
-        Icon = "folder";
+        Icon = Constants.Icons.Folder;
     }
 
     public MenuItem(string alias, string name)
@@ -33,11 +33,11 @@ public class MenuItem
     {
         Alias = alias;
         Name = textService.Localize("actions", Alias);
-        TextDescription = textService.Localize("visuallyHiddenTexts", alias + "_description", Thread.CurrentThread.CurrentUICulture);
+            TextDescription =  textService.Localize("visuallyHiddenTexts", alias + "_description", Thread.CurrentThread.CurrentUICulture);
     }
 
     /// <summary>
-    ///     Create a menu item based on an <see cref="IAction" /> definition
+        /// Create a menu item based on an <see cref="IAction"/> definition
     /// </summary>
     /// <param name="action"></param>
     /// <param name="name"></param>
@@ -81,16 +81,26 @@ public class MenuItem
     public string? TextDescription { get; set; }
 
     /// <summary>
-    ///     Ensures a menu separator will exist before this menu item
+        /// Ensures a menu separator will exist before this menu item
     /// </summary>
     [DataMember(Name = "separator")]
     public bool SeparatorBefore { get; set; }
 
-    [DataMember(Name = "cssclass")]
+    /// <summary>
+    /// Icon to use at action menu item.
+    /// </summary>
+    [DataMember(Name = "icon")]
     public string Icon { get; set; }
 
     /// <summary>
-    ///     Used in the UI to inform the user that the menu item will open a dialog/confirmation
+    /// Used in the UI to indicate whether icons should be prefixed with "icon-".
+    /// If not legacy icon full icon name should be specified.
+    /// </summary>
+    [DataMember(Name = "useLegacyIcon")]
+    public bool UseLegacyIcon { get; set; } = true;
+
+        /// <summary>
+        /// Used in the UI to inform the user that the menu item will open a dialog/confirmation
     /// </summary>
     [DataMember(Name = "opensDialog")]
     public bool OpensDialog { get; set; }
diff --git a/src/Umbraco.Core/Models/Trees/RefreshNode.cs b/src/Umbraco.Core/Models/Trees/RefreshNode.cs
index befbec019e..c10cdfcdf5 100644
--- a/src/Umbraco.Core/Models/Trees/RefreshNode.cs
+++ b/src/Umbraco.Core/Models/Trees/RefreshNode.cs
@@ -1,4 +1,4 @@
-using Umbraco.Cms.Core.Services;
+﻿using Umbraco.Cms.Core.Services;
 
 namespace Umbraco.Cms.Core.Models.Trees;
 
@@ -8,18 +8,22 @@ namespace Umbraco.Cms.Core.Models.Trees;
 /// </summary>
 public sealed class RefreshNode : ActionMenuItem
 {
+    private const string icon = "icon-refresh";
+
     public RefreshNode(string name, bool separatorBefore = false)
         : base("refreshNode", name)
     {
-        Icon = "refresh";
+        Icon = icon;
         SeparatorBefore = separatorBefore;
+        UseLegacyIcon = false;
     }
 
     public RefreshNode(ILocalizedTextService textService, bool separatorBefore = false)
         : base("refreshNode", textService)
     {
-        Icon = "refresh";
+        Icon = icon;
         SeparatorBefore = separatorBefore;
+        UseLegacyIcon = false;
     }
 
     public override string AngularServiceName => "umbracoMenuActions";
diff --git a/src/Umbraco.Core/Persistence/Repositories/IRedirectUrlRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IRedirectUrlRepository.cs
index b6393dfcc0..b7f29edc92 100644
--- a/src/Umbraco.Core/Persistence/Repositories/IRedirectUrlRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/IRedirectUrlRepository.cs
@@ -1,3 +1,4 @@
+﻿using System.Threading.Tasks;
 using Umbraco.Cms.Core.Models;
 
 namespace Umbraco.Cms.Core.Persistence.Repositories;
@@ -44,10 +45,25 @@ public interface IRedirectUrlRepository : IReadWriteQueryRepository<Guid, IRedir
     ///     Gets the most recent redirect URL corresponding to an Umbraco redirect URL route.
     /// </summary>
     /// <param name="url">The Umbraco redirect URL route.</param>
+    /// <returns>The most recent redirect URL corresponding to the route.</returns>
+    Task<IRedirectUrl?> GetMostRecentUrlAsync(string url) => Task.FromResult(GetMostRecentUrl(url));
+
+    /// <summary>
+    /// Gets the most recent redirect URL corresponding to an Umbraco redirect URL route.
+    /// </summary>
+    /// <param name="url">The Umbraco redirect URL route.</param>
     /// <param name="culture">The culture the domain is associated with</param>
     /// <returns>The most recent redirect URL corresponding to the route.</returns>
     IRedirectUrl? GetMostRecentUrl(string url, string culture);
 
+    /// <summary>
+    /// Gets the most recent redirect URL corresponding to an Umbraco redirect URL route.
+    /// </summary>
+    /// <param name="url">The Umbraco redirect URL route.</param>
+    /// <param name="culture">The culture the domain is associated with</param>
+    /// <returns>The most recent redirect URL corresponding to the route.</returns>
+    Task<IRedirectUrl?> GetMostRecentUrlAsync(string url, string culture) => Task.FromResult(GetMostRecentUrl(url, culture));
+
     /// <summary>
     ///     Gets all redirect URLs for a content item.
     /// </summary>
diff --git a/src/Umbraco.Core/PropertyEditors/NestedContentConfiguration.cs b/src/Umbraco.Core/PropertyEditors/NestedContentConfiguration.cs
index a22eb352c0..fdef902b58 100644
--- a/src/Umbraco.Core/PropertyEditors/NestedContentConfiguration.cs
+++ b/src/Umbraco.Core/PropertyEditors/NestedContentConfiguration.cs
@@ -1,4 +1,4 @@
-using System.Runtime.Serialization;
+﻿using System.Runtime.Serialization;
 
 namespace Umbraco.Cms.Core.PropertyEditors;
 
@@ -22,6 +22,9 @@ public class NestedContentConfiguration
     [ConfigurationField("showIcons", "Show Icons", "boolean", Description = "Show the Element Type icons.")]
     public bool ShowIcons { get; set; } = true;
 
+    [ConfigurationField("expandsOnLoad", "Expands on load", "boolean", Description = "A single item is automatically expanded")]
+    public bool ExpandsOnLoad { get; set; } = true;
+
     [ConfigurationField("hideLabel", "Hide Label", "boolean", Description = "Hide the property label and let the item list span the full width of the editor window.")]
     public bool HideLabel { get; set; }
 
diff --git a/src/Umbraco.Core/Routing/ContentFinderByRedirectUrl.cs b/src/Umbraco.Core/Routing/ContentFinderByRedirectUrl.cs
index 99103d8cb6..6b5e1590c1 100644
--- a/src/Umbraco.Core/Routing/ContentFinderByRedirectUrl.cs
+++ b/src/Umbraco.Core/Routing/ContentFinderByRedirectUrl.cs
@@ -45,11 +45,11 @@ public class ContentFinderByRedirectUrl : IContentFinder
     ///     Optionally, can also assign the template or anything else on the document request, although that is not
     ///     required.
     /// </remarks>
-    public Task<bool> TryFindContent(IPublishedRequestBuilder frequest)
+    public async Task<bool> TryFindContent(IPublishedRequestBuilder frequest)
     {
         if (!_umbracoContextAccessor.TryGetUmbracoContext(out IUmbracoContext? umbracoContext))
         {
-            return Task.FromResult(false);
+            return false;
         }
 
         var route = frequest.Domain != null
@@ -57,7 +57,7 @@ public class ContentFinderByRedirectUrl : IContentFinder
               DomainUtilities.PathRelativeToDomain(frequest.Domain.Uri, frequest.AbsolutePathDecoded)
             : frequest.AbsolutePathDecoded;
 
-        IRedirectUrl? redirectUrl = _redirectUrlService.GetMostRecentRedirectUrl(route, frequest.Culture);
+        IRedirectUrl? redirectUrl = await _redirectUrlService.GetMostRecentRedirectUrlAsync(route, frequest.Culture);
 
         if (redirectUrl == null)
         {
@@ -66,7 +66,7 @@ public class ContentFinderByRedirectUrl : IContentFinder
                 _logger.LogDebug("No match for route: {Route}", route);
             }
 
-            return Task.FromResult(false);
+            return false;
         }
 
         IPublishedContent? content = umbracoContext.Content?.GetById(redirectUrl.ContentId);
@@ -78,7 +78,7 @@ public class ContentFinderByRedirectUrl : IContentFinder
                 _logger.LogDebug("Route {Route} matches content {ContentId} which has no URL.", route, redirectUrl.ContentId);
             }
 
-            return Task.FromResult(false);
+            return false;
         }
 
         // Appending any querystring from the incoming request to the redirect URL
@@ -99,6 +99,6 @@ public class ContentFinderByRedirectUrl : IContentFinder
             .SetCacheExtensions(new List<string> { "no-store, must-revalidate" })
             .SetHeaders(new Dictionary<string, string> { { "Pragma", "no-cache" }, { "Expires", "0" } });
 
-        return Task.FromResult(true);
+        return true;
     }
 }
diff --git a/src/Umbraco.Core/Services/IFileService.cs b/src/Umbraco.Core/Services/IFileService.cs
index 3179a4491f..53d7d004b2 100644
--- a/src/Umbraco.Core/Services/IFileService.cs
+++ b/src/Umbraco.Core/Services/IFileService.cs
@@ -8,7 +8,8 @@ namespace Umbraco.Cms.Core.Services;
 /// </summary>
 public interface IFileService : IService
 {
-    [Obsolete("Please use SnippetCollection.GetPartialViewSnippetNames() or SnippetCollection.GetPartialViewMacroSnippetNames() instead. Scheduled for removal in V12.")]IEnumerable<string> GetPartialViewSnippetNames(params string[] filterNames);
+    [Obsolete("Please use SnippetCollection.GetPartialViewSnippetNames() or SnippetCollection.GetPartialViewMacroSnippetNames() instead. Scheduled for removal in V12.")]
+    IEnumerable<string> GetPartialViewSnippetNames(params string[] filterNames);
 
     void CreatePartialViewFolder(string folderPath);
 
@@ -308,12 +309,13 @@ public interface IFileService : IService
     /// <param name="snippetName">The name of the snippet</param>
     /// <returns></returns>
     [Obsolete("Please use SnippetCollection.GetPartialViewMacroSnippetContent instead. Scheduled for removal in V12.")]
-        string GetPartialViewMacroSnippetContent(string snippetName);
+    string GetPartialViewMacroSnippetContent(string snippetName);
 
     /// <summary>
     ///     Gets the content of a partial view snippet as a string.
     /// </summary>
     /// <param name="snippetName">The name of the snippet</param>
     /// <returns>The content of the partial view.</returns>
-    [Obsolete("Please use SnippetCollection.GetPartialViewSnippetContent instead. Scheduled for removal in V12.")]string GetPartialViewSnippetContent(string snippetName);
+    [Obsolete("Please use SnippetCollection.GetPartialViewSnippetContent instead. Scheduled for removal in V12.")]
+    string GetPartialViewSnippetContent(string snippetName);
 }
diff --git a/src/Umbraco.Core/Services/IRedirectUrlService.cs b/src/Umbraco.Core/Services/IRedirectUrlService.cs
index 9da327a600..f1897053f0 100644
--- a/src/Umbraco.Core/Services/IRedirectUrlService.cs
+++ b/src/Umbraco.Core/Services/IRedirectUrlService.cs
@@ -1,3 +1,4 @@
+﻿using System.Threading.Tasks;
 using Umbraco.Cms.Core.Models;
 
 namespace Umbraco.Cms.Core.Services;
@@ -54,6 +55,14 @@ public interface IRedirectUrlService : IService
     IRedirectUrl? GetMostRecentRedirectUrl(string url, string? culture);
 
     /// <summary>
+    /// Gets the most recent redirect URLs corresponding to an Umbraco redirect URL route.
+    /// </summary>
+    /// <param name="url">The Umbraco redirect URL route.</param>
+    /// <param name="culture">The culture of the request.</param>
+    /// <returns>The most recent redirect URLs corresponding to the route.</returns>
+    Task<IRedirectUrl?> GetMostRecentRedirectUrlAsync(string url, string? culture) => Task.FromResult(GetMostRecentRedirectUrl(url, culture));
+
+        /// <summary>
     ///     Gets all redirect URLs for a content item.
     /// </summary>
     /// <param name="contentKey">The content unique key.</param>
diff --git a/src/Umbraco.Core/Services/PropertyValidationService.cs b/src/Umbraco.Core/Services/PropertyValidationService.cs
index d93cbd4a7c..fc42b13232 100644
--- a/src/Umbraco.Core/Services/PropertyValidationService.cs
+++ b/src/Umbraco.Core/Services/PropertyValidationService.cs
@@ -182,8 +182,8 @@ public class PropertyValidationService : IPropertyValidationService
         var pvalues = property.Values.Where(x =>
                 x != pvalue && // don't revalidate pvalue
                 property.PropertyType.SupportsVariation(x.Culture, x.Segment, true) && // the value variation is ok
-                (culture == "*" || (x.Culture?.InvariantEquals(culture) ?? false)) && // the culture matches
-                (segment == "*" || (x.Segment?.InvariantEquals(segment) ?? false))) // the segment matches
+                    (culture == "*" || x.Culture.InvariantEquals(culture)) && // the culture matches
+                    (segment == "*" || x.Segment.InvariantEquals(segment))) // the segment matches
             .ToList();
 
         return pvalues.Count == 0 || pvalues.All(x => IsValidPropertyValue(property, x.EditedValue));
diff --git a/src/Umbraco.Core/Services/RedirectUrlService.cs b/src/Umbraco.Core/Services/RedirectUrlService.cs
index e68eed31e7..07646de3de 100644
--- a/src/Umbraco.Core/Services/RedirectUrlService.cs
+++ b/src/Umbraco.Core/Services/RedirectUrlService.cs
@@ -1,4 +1,5 @@
-﻿using Microsoft.Extensions.Logging;
+﻿using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
 using Umbraco.Cms.Core.Events;
 using Umbraco.Cms.Core.Models;
 using Umbraco.Cms.Core.Persistence.Repositories;
@@ -81,6 +82,14 @@ internal class RedirectUrlService : RepositoryService, IRedirectUrlService
         }
     }
 
+    public async Task<IRedirectUrl?> GetMostRecentRedirectUrlAsync(string url)
+    {
+        using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
+        {
+            return await _redirectUrlRepository.GetMostRecentUrlAsync(url);
+        }
+    }
+
     public IEnumerable<IRedirectUrl> GetContentRedirectUrls(Guid contentKey)
     {
         using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
@@ -125,4 +134,17 @@ internal class RedirectUrlService : RepositoryService, IRedirectUrlService
             return _redirectUrlRepository.GetMostRecentUrl(url, culture);
         }
     }
+
+    public async Task<IRedirectUrl?> GetMostRecentRedirectUrlAsync(string url, string? culture)
+    {
+        if (string.IsNullOrWhiteSpace(culture))
+        {
+            return await GetMostRecentRedirectUrlAsync(url);
+        }
+
+        using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
+        {
+            return await _redirectUrlRepository.GetMostRecentUrlAsync(url, culture);
+        }
+    }	
 }
diff --git a/src/Umbraco.Core/Trees/ISearchableTreeWithCulture.cs b/src/Umbraco.Core/Trees/ISearchableTreeWithCulture.cs
new file mode 100644
index 0000000000..6ac4ea3be1
--- /dev/null
+++ b/src/Umbraco.Core/Trees/ISearchableTreeWithCulture.cs
@@ -0,0 +1,21 @@
+using Umbraco.Cms.Core.Models.ContentEditing;
+
+namespace Umbraco.Cms.Core.Trees
+{
+    [Obsolete("This interface will be merged into ISearchableTree in Umbraco 12")]
+    public interface ISearchableTreeWithCulture : ISearchableTree
+    {
+        /// <summary>
+        /// Searches for results based on the entity type
+        /// </summary>
+        /// <param name="query"></param>
+        /// <param name="pageSize"></param>
+        /// <param name="pageIndex"></param>
+        /// <param name="searchFrom">
+        ///     A starting point for the search, generally a node id, but for members this is a member type alias
+        /// </param>
+        /// <param name="culture"></param>
+        /// <returns></returns>
+        Task<EntitySearchResults> SearchAsync(string query, int pageSize, long pageIndex, string? searchFrom = null, string? culture = null);
+    }
+}
diff --git a/src/Umbraco.Core/Trees/MenuItemList.cs b/src/Umbraco.Core/Trees/MenuItemList.cs
index a4cb1899e3..cb912a112b 100644
--- a/src/Umbraco.Core/Trees/MenuItemList.cs
+++ b/src/Umbraco.Core/Trees/MenuItemList.cs
@@ -32,9 +32,20 @@ public class MenuItemList : List<MenuItem>
     /// <param name="textService">The <see cref="ILocalizedTextService" /> used to localize the action name based on its alias</param>
     /// <param name="opensDialog">Whether or not this action opens a dialog</param>
     public MenuItem? Add<T>(ILocalizedTextService textService, bool hasSeparator = false, bool opensDialog = false)
+        where T : IAction => Add<T>(textService, hasSeparator, opensDialog, useLegacyIcon: true);
+
+    /// <summary>
+    ///     Adds a menu item with a dictionary which is merged to the AdditionalData bag
+    /// </summary>
+    /// <typeparam name="T"></typeparam>
+    /// <param name="hasSeparator"></param>
+    /// <param name="textService">The <see cref="ILocalizedTextService" /> used to localize the action name based on its alias</param>
+    /// <param name="opensDialog">Whether or not this action opens a dialog</param>
+    /// <param name="useLegacyIcon">Whether or not this action should use legacy icon prefixed with "icon-" or full icon name is specified.</param>
+    public MenuItem? Add<T>(ILocalizedTextService textService, bool hasSeparator = false, bool opensDialog = false, bool useLegacyIcon = true)
         where T : IAction
     {
-        MenuItem? item = CreateMenuItem<T>(textService, hasSeparator, opensDialog);
+        MenuItem? item = CreateMenuItem<T>(textService, hasSeparator, opensDialog, useLegacyIcon);
         if (item != null)
         {
             Add(item);
@@ -44,7 +55,7 @@ public class MenuItemList : List<MenuItem>
         return null;
     }
 
-    private MenuItem? CreateMenuItem<T>(ILocalizedTextService textService, bool hasSeparator = false, bool opensDialog = false)
+    private MenuItem? CreateMenuItem<T>(ILocalizedTextService textService, bool hasSeparator = false, bool opensDialog = false, bool useLegacyIcon = true)
         where T : IAction
     {
         T? item = _actionCollection.GetAction<T>();
@@ -61,6 +72,7 @@ public class MenuItemList : List<MenuItem>
             SeparatorBefore = hasSeparator,
             OpensDialog = opensDialog,
             TextDescription = textDescription,
+            UseLegacyIcon = useLegacyIcon,
         };
 
         return menuItem;
diff --git a/src/Umbraco.Examine.Lucene/CompatibilitySuppressions.xml b/src/Umbraco.Examine.Lucene/CompatibilitySuppressions.xml
new file mode 100644
index 0000000000..681c7cdab5
--- /dev/null
+++ b/src/Umbraco.Examine.Lucene/CompatibilitySuppressions.xml
@@ -0,0 +1,10 @@
+﻿<?xml version="1.0" encoding="utf-8"?>
+<Suppressions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
+  <Suppression>
+    <DiagnosticId>CP0008</DiagnosticId>
+    <Target>T:Umbraco.Cms.Infrastructure.Examine.UmbracoContentIndex</Target>
+    <Left>lib/net6.0/Umbraco.Examine.Lucene.dll</Left>
+    <Right>lib/net6.0/Umbraco.Examine.Lucene.dll</Right>
+    <IsBaselineSuppression>true</IsBaselineSuppression>
+  </Suppression>
+</Suppressions>
\ No newline at end of file
diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Collections.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Collections.cs
index bc83695d94..609c5305dc 100644
--- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Collections.cs
+++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Collections.cs
@@ -2,6 +2,7 @@ using Umbraco.Cms.Core.DependencyInjection;
 using Umbraco.Cms.Core.Packaging;
 using Umbraco.Cms.Infrastructure.Persistence;
 using Umbraco.Cms.Infrastructure.Persistence.Mappers;
+using Umbraco.Cms.Infrastructure.Runtime;
 
 namespace Umbraco.Extensions;
 
@@ -17,6 +18,10 @@ public static partial class UmbracoBuilderExtensions
     public static MapperCollectionBuilder? Mappers(this IUmbracoBuilder builder)
         => builder.WithCollectionBuilder<MapperCollectionBuilder>();
 
+    /// <summary>
+    ///     Gets the NPoco mappers collection builder.
+    /// </summary>
+    /// <param name="builder">The builder.</param>
     public static NPocoMapperCollectionBuilder? NPocoMappers(this IUmbracoBuilder builder)
         => builder.WithCollectionBuilder<NPocoMapperCollectionBuilder>();
 
@@ -26,4 +31,11 @@ public static partial class UmbracoBuilderExtensions
     /// <param name="builder">The builder.</param>
     public static PackageMigrationPlanCollectionBuilder? PackageMigrationPlans(this IUmbracoBuilder builder)
         => builder.WithCollectionBuilder<PackageMigrationPlanCollectionBuilder>();
+
+    /// <summary>
+    ///     Gets the runtime mode validators collection builder.
+    /// </summary>
+    /// <param name="builder">The builder.</param>
+    public static RuntimeModeValidatorCollectionBuilder RuntimeModeValidators(this IUmbracoBuilder builder)
+        => builder.WithCollectionBuilder<RuntimeModeValidatorCollectionBuilder>();
 }
diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs
index 1c874b2efa..62bafcd28e 100644
--- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs
+++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs
@@ -86,11 +86,12 @@ public static partial class UmbracoBuilderExtensions
 
         // Add runtime mode validation
         builder.Services.AddSingleton<IRuntimeModeValidationService, RuntimeModeValidationService>();
-        builder.Services.AddTransient<IRuntimeModeValidator, JITOptimizerValidator>();
-        builder.Services.AddTransient<IRuntimeModeValidator, UmbracoApplicationUrlValidator>();
-        builder.Services.AddTransient<IRuntimeModeValidator, UseHttpsValidator>();
-        builder.Services.AddTransient<IRuntimeModeValidator, RuntimeMinificationValidator>();
-        builder.Services.AddTransient<IRuntimeModeValidator, ModelsBuilderModeValidator>();
+        builder.RuntimeModeValidators()
+            .Add<JITOptimizerValidator>()
+            .Add<UmbracoApplicationUrlValidator>()
+            .Add<UseHttpsValidator>()
+            .Add<RuntimeMinificationValidator>()
+            .Add<ModelsBuilderModeValidator>();
 
         // composers
         builder
@@ -219,6 +220,8 @@ public static partial class UmbracoBuilderExtensions
 
         // Services required to run background jobs (with out the handler)
         builder.Services.AddSingleton<IBackgroundTaskQueue, BackgroundTaskQueue>();
+
+        builder.Services.AddTransient<IFireAndForgetRunner, FireAndForgetRunner>();
         return builder;
     }
 
diff --git a/src/Umbraco.Infrastructure/Install/InstallHelper.cs b/src/Umbraco.Infrastructure/Install/InstallHelper.cs
index 41e9d13ed8..85839e8757 100644
--- a/src/Umbraco.Infrastructure/Install/InstallHelper.cs
+++ b/src/Umbraco.Infrastructure/Install/InstallHelper.cs
@@ -1,3 +1,4 @@
+using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Options;
 using Umbraco.Cms.Core;
@@ -9,6 +10,7 @@ using Umbraco.Cms.Core.Services;
 using Umbraco.Cms.Core.Web;
 using Umbraco.Cms.Infrastructure.Migrations.Install;
 using Umbraco.Cms.Infrastructure.Persistence;
+using Umbraco.Cms.Web.Common.DependencyInjection;
 using Umbraco.Extensions;
 using Constants = Umbraco.Cms.Core.Constants;
 
@@ -24,6 +26,7 @@ namespace Umbraco.Cms.Infrastructure.Install
         private readonly ICookieManager _cookieManager;
         private readonly IUserAgentProvider _userAgentProvider;
         private readonly IUmbracoDatabaseFactory _umbracoDatabaseFactory;
+        private readonly IFireAndForgetRunner _fireAndForgetRunner;
         private InstallationType? _installationType;
 
         public InstallHelper(
@@ -34,7 +37,8 @@ namespace Umbraco.Cms.Infrastructure.Install
             IInstallationService installationService,
             ICookieManager cookieManager,
             IUserAgentProvider userAgentProvider,
-            IUmbracoDatabaseFactory umbracoDatabaseFactory)
+            IUmbracoDatabaseFactory umbracoDatabaseFactory,
+            IFireAndForgetRunner fireAndForgetRunner)
         {
             _logger = logger;
             _umbracoVersion = umbracoVersion;
@@ -44,11 +48,36 @@ namespace Umbraco.Cms.Infrastructure.Install
             _cookieManager = cookieManager;
             _userAgentProvider = userAgentProvider;
             _umbracoDatabaseFactory = umbracoDatabaseFactory;
+            _fireAndForgetRunner = fireAndForgetRunner;
 
             // We need to initialize the type already, as we can't detect later, if the connection string is added on the fly.
             GetInstallationType();
         }
 
+        [Obsolete("Please use constructor that takes an IFireAndForgetRunner instead, scheduled for removal in Umbraco 12")]
+        public InstallHelper(
+            DatabaseBuilder databaseBuilder,
+            ILogger<InstallHelper> logger,
+            IUmbracoVersion umbracoVersion,
+            IOptionsMonitor<ConnectionStrings> connectionStrings,
+            IInstallationService installationService,
+            ICookieManager cookieManager,
+            IUserAgentProvider userAgentProvider,
+            IUmbracoDatabaseFactory umbracoDatabaseFactory)
+            : this(
+                databaseBuilder,
+                logger,
+                umbracoVersion,
+                connectionStrings,
+                installationService,
+                cookieManager,
+                userAgentProvider,
+                umbracoDatabaseFactory,
+                StaticServiceProvider.Instance.GetRequiredService<IFireAndForgetRunner>())
+        {
+
+        }
+
         public InstallationType GetInstallationType() => _installationType ??= IsBrandNewInstall ? InstallationType.NewInstall : InstallationType.Upgrade;
 
         public async Task SetInstallStatusAsync(bool isCompleted, string errorMsg)
@@ -87,7 +116,7 @@ namespace Umbraco.Cms.Infrastructure.Install
                     userAgent: userAgent,
                     dbProvider: dbProvider);
 
-                await _installationService.LogInstall(installLog);
+                _fireAndForgetRunner.RunFireAndForget(() => _installationService.LogInstall(installLog));
             }
             catch (Exception ex)
             {
diff --git a/src/Umbraco.Infrastructure/Packaging/PackageDataInstallation.cs b/src/Umbraco.Infrastructure/Packaging/PackageDataInstallation.cs
index b9960ab153..b4f49ab76b 100644
--- a/src/Umbraco.Infrastructure/Packaging/PackageDataInstallation.cs
+++ b/src/Umbraco.Infrastructure/Packaging/PackageDataInstallation.cs
@@ -1387,6 +1387,12 @@ namespace Umbraco.Cms.Infrastructure.Packaging
             return ImportDictionaryItems(dictionaryItemElementList, languages, null, userId);
         }
 
+        public IEnumerable<IDictionaryItem> ImportDictionaryItem(XElement dictionaryItemElement, int userId, Guid? parentId)
+        {
+            var languages = _localizationService.GetAllLanguages().ToList();
+            return ImportDictionaryItem(dictionaryItemElement, languages, parentId, userId);
+        }
+
         private IReadOnlyList<IDictionaryItem> ImportDictionaryItems(IEnumerable<XElement> dictionaryItemElementList,
             List<ILanguage> languages, Guid? parentId, int userId)
         {
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CreatedPackageSchemaRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CreatedPackageSchemaRepository.cs
index 04c60261ea..9f921266ca 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CreatedPackageSchemaRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CreatedPackageSchemaRepository.cs
@@ -652,10 +652,13 @@ public class CreatedPackageSchemaRepository : ICreatedPackagesRepository
             // the media file path is different from the URL and is specifically
             // extracted using the property editor for this media file and the current media file system.
             Stream mediaStream = _mediaFileManager.GetFile(media, out var mediaFilePath);
-            xmlMedia.Add(new XAttribute("mediaFilePath", mediaFilePath!));
+            if (mediaFilePath is not null)
+            {
+                xmlMedia.Add(new XAttribute("mediaFilePath", mediaFilePath));
 
-            // add the stream to our outgoing stream
-            mediaStreams.Add(mediaFilePath!, mediaStream);
+                // add the stream to our outgoing stream
+                mediaStreams.Add(mediaFilePath, mediaStream);
+            }
         }
 
         IEnumerable<IMedia> medias = _mediaService.GetByIds(definition.MediaUdis);
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs
index 817a26aac1..bd08addddb 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs
@@ -781,6 +781,11 @@ public class MemberRepository : ContentRepositoryBase<int, IMember, MemberReposi
             changedCols.Add("passwordConfig");
         }
 
+        if (entity.IsPropertyDirty("EmailConfirmedDate"))
+        {
+            changedCols.Add("emailConfirmedDate");
+        }
+
         // If userlogin or the email has changed then need to reset security stamp
         if (changedCols.Contains("Email") || changedCols.Contains("LoginName"))
         {
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RedirectUrlRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RedirectUrlRepository.cs
index 512eed0ee9..1c2da42561 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RedirectUrlRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RedirectUrlRepository.cs
@@ -1,4 +1,5 @@
 using System.Security.Cryptography;
+using System.Threading.Tasks;
 using Microsoft.Extensions.Logging;
 using NPoco;
 using Umbraco.Cms.Core;
@@ -36,14 +37,28 @@ internal class RedirectUrlRepository : EntityRepositoryBase<Guid, IRedirectUrl>,
     public void Delete(Guid id) => Database.Delete<RedirectUrlDto>(id);
 
     public IRedirectUrl? GetMostRecentUrl(string url)
+    {
+        Sql<ISqlContext> sql = GetMostRecentSql(url);
+        List<RedirectUrlDto> dtos = Database.Fetch<RedirectUrlDto>(sql);
+        RedirectUrlDto? dto = dtos.FirstOrDefault();
+        return dto == null ? null : Map(dto);
+    }
+
+    public  async Task<IRedirectUrl?> GetMostRecentUrlAsync(string url)
+    {
+        Sql<ISqlContext> sql = GetMostRecentSql(url);
+        List<RedirectUrlDto> dtos = await Database.FetchAsync<RedirectUrlDto>(sql);
+        RedirectUrlDto? dto = dtos.FirstOrDefault();
+        return dto == null ? null : Map(dto);
+    }
+
+    private Sql<ISqlContext> GetMostRecentSql(string url)
     {
         var urlHash = url.GenerateHash<SHA1>();
         Sql<ISqlContext> sql = GetBaseQuery(false)
             .Where<RedirectUrlDto>(x => x.Url == url && x.UrlHash == urlHash)
             .OrderByDescending<RedirectUrlDto>(x => x.CreateDateUtc);
-        List<RedirectUrlDto> dtos = Database.Fetch<RedirectUrlDto>(sql);
-        RedirectUrlDto? dto = dtos.FirstOrDefault();
-        return dto == null ? null : Map(dto);
+        return sql;
     }
 
     public IRedirectUrl? GetMostRecentUrl(string url, string culture)
@@ -53,13 +68,40 @@ internal class RedirectUrlRepository : EntityRepositoryBase<Guid, IRedirectUrl>,
             return GetMostRecentUrl(url);
         }
 
+        Sql<ISqlContext> sql = GetMostRecentUrlSql(url, culture);
+
+        List<RedirectUrlDto> dtos = Database.Fetch<RedirectUrlDto>(sql);
+        RedirectUrlDto? dto = dtos.FirstOrDefault(f => f.Culture == culture.ToLower());
+
+        if (dto == null)
+        {
+            dto = dtos.FirstOrDefault(f => string.IsNullOrWhiteSpace(f.Culture));
+        }
+
+        return dto == null ? null : Map(dto);
+    }
+
+    private Sql<ISqlContext> GetMostRecentUrlSql(string url, string culture)
+    {
         var urlHash = url.GenerateHash<SHA1>();
         Sql<ISqlContext> sql = GetBaseQuery(false)
             .Where<RedirectUrlDto>(x => x.Url == url && x.UrlHash == urlHash &&
                                         (x.Culture == culture.ToLower() || x.Culture == null ||
                                          x.Culture == string.Empty))
             .OrderByDescending<RedirectUrlDto>(x => x.CreateDateUtc);
-        List<RedirectUrlDto> dtos = Database.Fetch<RedirectUrlDto>(sql);
+        return sql;
+    }
+
+    public async Task<IRedirectUrl?> GetMostRecentUrlAsync(string url, string culture)
+    {
+        if (string.IsNullOrWhiteSpace(culture))
+        {
+            return GetMostRecentUrl(url);
+        }
+
+        Sql<ISqlContext> sql = GetMostRecentUrlSql(url, culture);
+
+        List<RedirectUrlDto> dtos = await Database.FetchAsync<RedirectUrlDto>(sql);
         RedirectUrlDto? dto = dtos.FirstOrDefault(f => f.Culture == culture.ToLower());
 
         if (dto == null)
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TemplateRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TemplateRepository.cs
index 1a0a90269c..9e01320fdc 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TemplateRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TemplateRepository.cs
@@ -1,8 +1,10 @@
 using System.Text;
 using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
 using NPoco;
 using Umbraco.Cms.Core;
 using Umbraco.Cms.Core.Cache;
+using Umbraco.Cms.Core.Configuration.Models;
 using Umbraco.Cms.Core.IO;
 using Umbraco.Cms.Core.Models;
 using Umbraco.Cms.Core.Models.Entities;
@@ -24,17 +26,26 @@ internal class TemplateRepository : EntityRepositoryBase<int, ITemplate>, ITempl
 {
     private readonly IIOHelper _ioHelper;
     private readonly IShortStringHelper _shortStringHelper;
-    private readonly IViewHelper _viewHelper;
     private readonly IFileSystem? _viewsFileSystem;
+    private readonly IViewHelper _viewHelper;
+    private readonly IOptionsMonitor<RuntimeSettings> _runtimeSettings;
 
-    public TemplateRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger<TemplateRepository> logger,
-        FileSystems fileSystems, IIOHelper ioHelper, IShortStringHelper shortStringHelper, IViewHelper viewHelper)
+    public TemplateRepository(
+        IScopeAccessor scopeAccessor,
+        AppCaches cache,
+        ILogger<TemplateRepository> logger,
+        FileSystems fileSystems,
+        IIOHelper ioHelper,
+        IShortStringHelper shortStringHelper,
+        IViewHelper viewHelper,
+        IOptionsMonitor<RuntimeSettings> runtimeSettings)
         : base(scopeAccessor, cache, logger)
     {
         _ioHelper = ioHelper;
         _shortStringHelper = shortStringHelper;
         _viewsFileSystem = fileSystems.MvcViewsFileSystem;
         _viewHelper = viewHelper;
+        _runtimeSettings = runtimeSettings;
     }
 
     public Stream GetFileContentStream(string filepath)
@@ -421,8 +432,12 @@ internal class TemplateRepository : EntityRepositoryBase<int, ITemplate>, ITempl
         template.Id = nodeDto.NodeId; //Set Id on entity to ensure an Id is set
         template.Path = nodeDto.Path;
 
-        //now do the file work
-        SaveFile(template);
+        // Only save file when not in production runtime mode
+        if (_runtimeSettings.CurrentValue.Mode != RuntimeMode.Production)
+        {
+            //now do the file work
+            SaveFile(template);
+        }
 
         template.ResetDirtyProperties();
 
@@ -476,8 +491,12 @@ internal class TemplateRepository : EntityRepositoryBase<int, ITemplate>, ITempl
         IEnumerable<IUmbracoEntity> axisDefs = GetAxisDefinitions(dto);
         template.IsMasterTemplate = axisDefs.Any(x => x.ParentId == dto.NodeId);
 
-        //now do the file work
-        SaveFile((Template)entity, originalAlias);
+        // Only save file when not in production runtime mode
+        if (_runtimeSettings.CurrentValue.Mode != RuntimeMode.Production)
+        {
+            //now do the file work
+            SaveFile((Template)entity, originalAlias);
+        }
 
         entity.ResetDirtyProperties();
 
diff --git a/src/Umbraco.Infrastructure/PublishedContentQuery.cs b/src/Umbraco.Infrastructure/PublishedContentQuery.cs
index 891f938a2b..ef16aeccc6 100644
--- a/src/Umbraco.Infrastructure/PublishedContentQuery.cs
+++ b/src/Umbraco.Infrastructure/PublishedContentQuery.cs
@@ -18,11 +18,11 @@ namespace Umbraco.Cms.Infrastructure;
 /// <seealso cref="Umbraco.Cms.Core.IPublishedContentQuery" />
 public class PublishedContentQuery : IPublishedContentQuery
 {
-    private static readonly HashSet<string> _itemIdFieldNameHashSet = new() {ExamineFieldNames.ItemIdFieldName};
-
     private readonly IExamineManager _examineManager;
     private readonly IPublishedSnapshot _publishedSnapshot;
     private readonly IVariationContextAccessor _variationContextAccessor;
+    private static readonly HashSet<string> _returnedQueryFields =
+        new() { ExamineFieldNames.ItemIdFieldName, ExamineFieldNames.CategoryFieldName };
 
     /// <summary>
     ///     Initializes a new instance of the <see cref="PublishedContentQuery" /> class.
@@ -293,8 +293,8 @@ public class PublishedContentQuery : IPublishedContentQuery
             ordering = query.ManagedQuery(term, fields);
         }
 
-        // Only select item ID field, because results are loaded from the published snapshot based on this single value
-        IOrdering? queryExecutor = ordering.SelectFields(_itemIdFieldNameHashSet);
+            // Filter selected fields because results are loaded from the published snapshot based on these
+            IOrdering? queryExecutor = ordering.SelectFields(_returnedQueryFields);
 
 
         ISearchResults? results = skip == 0 && take == 0
@@ -328,8 +328,8 @@ public class PublishedContentQuery : IPublishedContentQuery
 
         if (query is IOrdering ordering)
         {
-            // Only select item ID field, because results are loaded from the published snapshot based on this single value
-            query = ordering.SelectFields(_itemIdFieldNameHashSet);
+                // Filter selected fields because results are loaded from the published snapshot based on these
+                query = ordering.SelectFields(_returnedQueryFields);
         }
 
         ISearchResults? results = skip == 0 && take == 0
diff --git a/src/Umbraco.Infrastructure/Runtime/RuntimeModeValidationService.cs b/src/Umbraco.Infrastructure/Runtime/RuntimeModeValidationService.cs
index 85eec91786..c4bbeb6902 100644
--- a/src/Umbraco.Infrastructure/Runtime/RuntimeModeValidationService.cs
+++ b/src/Umbraco.Infrastructure/Runtime/RuntimeModeValidationService.cs
@@ -29,11 +29,18 @@ internal class RuntimeModeValidationService : IRuntimeModeValidationService
         var validationMessages = new List<string>();
 
         // Runtime mode validators are registered transient, but this service is registered as singleton
-        foreach (var runtimeModeValidator in _serviceProvider.GetServices<IRuntimeModeValidator>())
+        using (var scope = _serviceProvider.CreateScope())
         {
-            if (runtimeModeValidator.Validate(runtimeMode, out var validationMessage) == false)
+            var runtimeModeValidators = scope.ServiceProvider.GetService<RuntimeModeValidatorCollection>();
+            if (runtimeModeValidators is not null)
             {
-                validationMessages.Add(validationMessage);
+                foreach (var runtimeModeValidator in runtimeModeValidators)
+                {
+                    if (runtimeModeValidator.Validate(runtimeMode, out var validationMessage) == false)
+                    {
+                        validationMessages.Add(validationMessage);
+                    }
+                }
             }
         }
 
diff --git a/src/Umbraco.Infrastructure/Runtime/RuntimeModeValidatorCollection.cs b/src/Umbraco.Infrastructure/Runtime/RuntimeModeValidatorCollection.cs
new file mode 100644
index 0000000000..97f25f3b8f
--- /dev/null
+++ b/src/Umbraco.Infrastructure/Runtime/RuntimeModeValidatorCollection.cs
@@ -0,0 +1,10 @@
+using Umbraco.Cms.Core.Composing;
+
+namespace Umbraco.Cms.Infrastructure.Runtime;
+
+public class RuntimeModeValidatorCollection : BuilderCollectionBase<IRuntimeModeValidator>
+{
+    public RuntimeModeValidatorCollection(Func<IEnumerable<IRuntimeModeValidator>> items)
+        : base(items)
+    { }
+}
diff --git a/src/Umbraco.Infrastructure/Runtime/RuntimeModeValidatorCollectionBuilder.cs b/src/Umbraco.Infrastructure/Runtime/RuntimeModeValidatorCollectionBuilder.cs
new file mode 100644
index 0000000000..4c755a6040
--- /dev/null
+++ b/src/Umbraco.Infrastructure/Runtime/RuntimeModeValidatorCollectionBuilder.cs
@@ -0,0 +1,11 @@
+using Microsoft.Extensions.DependencyInjection;
+using Umbraco.Cms.Core.Composing;
+
+namespace Umbraco.Cms.Infrastructure.Runtime;
+
+public class RuntimeModeValidatorCollectionBuilder : SetCollectionBuilderBase<RuntimeModeValidatorCollectionBuilder, RuntimeModeValidatorCollection, IRuntimeModeValidator>
+{
+    protected override ServiceLifetime CollectionLifetime => ServiceLifetime.Transient;
+
+    protected override RuntimeModeValidatorCollectionBuilder This => this;
+}
diff --git a/src/Umbraco.Infrastructure/Scoping/IScopeProvider.cs b/src/Umbraco.Infrastructure/Scoping/IScopeProvider.cs
index 19f07c210c..a21abccbf0 100644
--- a/src/Umbraco.Infrastructure/Scoping/IScopeProvider.cs
+++ b/src/Umbraco.Infrastructure/Scoping/IScopeProvider.cs
@@ -11,11 +11,6 @@ namespace Umbraco.Cms.Infrastructure.Scoping;
 /// </summary>
 public interface IScopeProvider : ICoreScopeProvider
 {
-    /// <summary>
-    ///     Gets the scope context.
-    /// </summary>
-    IScopeContext? Context { get; }
-
     /// <inheritdoc />
     ICoreScope ICoreScopeProvider.CreateCoreScope(
         IsolationLevel isolationLevel,
diff --git a/src/Umbraco.Infrastructure/Telemetry/Providers/SystemInformationTelemetryProvider.cs b/src/Umbraco.Infrastructure/Telemetry/Providers/SystemInformationTelemetryProvider.cs
index 84aa2a1bb3..8953b71ce1 100644
--- a/src/Umbraco.Infrastructure/Telemetry/Providers/SystemInformationTelemetryProvider.cs
+++ b/src/Umbraco.Infrastructure/Telemetry/Providers/SystemInformationTelemetryProvider.cs
@@ -1,4 +1,4 @@
-using System.Diagnostics;
+﻿using System.Diagnostics;
 using System.Runtime.InteropServices;
 using Microsoft.Extensions.Hosting;
 using Microsoft.Extensions.Options;
@@ -7,6 +7,7 @@ using Umbraco.Cms.Core.Configuration;
 using Umbraco.Cms.Core.Configuration.Models;
 using Umbraco.Cms.Core.Models;
 using Umbraco.Cms.Core.Services;
+using Umbraco.Cms.Core.Sync;
 using Umbraco.Cms.Infrastructure.Persistence;
 using Umbraco.Cms.Infrastructure.Telemetry.Interfaces;
 using Umbraco.Extensions;
@@ -22,6 +23,8 @@ internal class SystemInformationTelemetryProvider : IDetailedTelemetryProvider,
     private readonly ModelsBuilderSettings _modelsBuilderSettings;
     private readonly IUmbracoDatabaseFactory _umbracoDatabaseFactory;
     private readonly IUmbracoVersion _version;
+    private readonly IServerRoleAccessor _serverRoleAccessor;
+
 
     public SystemInformationTelemetryProvider(
         IUmbracoVersion version,
@@ -30,12 +33,14 @@ internal class SystemInformationTelemetryProvider : IDetailedTelemetryProvider,
         IOptionsMonitor<HostingSettings> hostingSettings,
         IOptionsMonitor<GlobalSettings> globalSettings,
         IHostEnvironment hostEnvironment,
-        IUmbracoDatabaseFactory umbracoDatabaseFactory)
+        IUmbracoDatabaseFactory umbracoDatabaseFactory,
+        IServerRoleAccessor serverRoleAccessor)
     {
         _version = version;
         _localizationService = localizationService;
         _hostEnvironment = hostEnvironment;
         _umbracoDatabaseFactory = umbracoDatabaseFactory;
+        _serverRoleAccessor = serverRoleAccessor;
 
         _globalSettings = globalSettings.CurrentValue;
         _hostingSettings = hostingSettings.CurrentValue;
@@ -60,6 +65,8 @@ internal class SystemInformationTelemetryProvider : IDetailedTelemetryProvider,
 
     private string DatabaseProvider => _umbracoDatabaseFactory.CreateDatabase().DatabaseType.GetProviderName();
 
+    private string CurrentServerRole => _serverRoleAccessor.CurrentServerRole.ToString();
+
     public IEnumerable<UsageInformation> GetInformation() =>
         new UsageInformation[]
         {
@@ -70,6 +77,7 @@ internal class SystemInformationTelemetryProvider : IDetailedTelemetryProvider,
             new(Constants.Telemetry.CustomUmbracoPath, UmbracoPathCustomized),
             new(Constants.Telemetry.AspEnvironment, AspEnvironment), new(Constants.Telemetry.IsDebug, IsDebug),
             new(Constants.Telemetry.DatabaseProvider, DatabaseProvider),
+            new(Constants.Telemetry.CurrentServerRole, CurrentServerRole),
         };
 
     public IEnumerable<UserData> GetUserData() =>
@@ -82,6 +90,7 @@ internal class SystemInformationTelemetryProvider : IDetailedTelemetryProvider,
             new("Current UI Culture", Thread.CurrentThread.CurrentUICulture.ToString()),
             new("Current Webserver", CurrentWebServer), new("Models Builder Mode", ModelsBuilderMode),
             new("Debug Mode", IsDebug.ToString()), new("Database Provider", DatabaseProvider),
+            new("Current Server Role", CurrentServerRole),
         };
 
     private bool IsRunningInProcessIIS()
diff --git a/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj b/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj
index fc41bec6c5..7d48468a96 100644
--- a/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj
+++ b/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj
@@ -48,7 +48,7 @@
       <PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" />
       <PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
       <PackageReference Include="Serilog.Sinks.Map" Version="1.0.2" />
-      <PackageReference Include="SixLabors.ImageSharp" Version="2.1.2" />
+      <PackageReference Include="SixLabors.ImageSharp" Version="2.1.3" />
       <PackageReference Include="System.IO.FileSystem.AccessControl" Version="5.0.0" />
       <PackageReference Include="System.Security.Cryptography.Pkcs" Version="6.0.1" />
       <PackageReference Include="System.Text.Encodings.Web" Version="6.0.0" /> <!-- Explicit updated this nested dependency due to this https://github.com/dotnet/announcements/issues/178-->
diff --git a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs
index e44219b681..898efe1938 100644
--- a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs
+++ b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs
@@ -598,7 +598,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
                 { "assemblyVersion", _umbracoVersion.AssemblyVersion?.ToString() }
             };
 
-
+            app.Add("runtimeMode", _runtimeSettings.Mode.ToString());
 
             //the value is the hash of the version, cdf version and the configured state
             app.Add("cacheBuster", $"{version}.{_runtimeState.Level}.{_runtimeMinifier.CacheBuster}".GenerateHash());
diff --git a/src/Umbraco.Web.BackOffice/Controllers/CodeFileController.cs b/src/Umbraco.Web.BackOffice/Controllers/CodeFileController.cs
index 8587939dfd..d1f92953ad 100644
--- a/src/Umbraco.Web.BackOffice/Controllers/CodeFileController.cs
+++ b/src/Umbraco.Web.BackOffice/Controllers/CodeFileController.cs
@@ -43,10 +43,10 @@ public class CodeFileController : BackOfficeNotificationsController
     private readonly IHostingEnvironment _hostingEnvironment;
 
     private readonly ILocalizedTextService _localizedTextService;
-    private readonly PartialViewMacroSnippetCollection _partialViewMacroSnippetCollection;
-    private readonly PartialViewSnippetCollection _partialViewSnippetCollection;
     private readonly IShortStringHelper _shortStringHelper;
     private readonly IUmbracoMapper _umbracoMapper;
+    private readonly PartialViewSnippetCollection _partialViewSnippetCollection;
+    private readonly PartialViewMacroSnippetCollection _partialViewMacroSnippetCollection;
 
     [ActivatorUtilitiesConstructor]
     public CodeFileController(
@@ -383,7 +383,7 @@ public class CodeFileController : BackOfficeNotificationsController
                     codeFileDisplay.VirtualPath = Constants.SystemDirectories.PartialViews;
                     if (snippetName.IsNullOrWhiteSpace() == false)
                     {
-                        codeFileDisplay.Content = _partialViewSnippetCollection.GetContentFromName(snippetName!);
+                            codeFileDisplay.Content = _partialViewSnippetCollection.GetContentFromName(snippetName!);
                     }
                 }
 
diff --git a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs
index bb8d6335d6..7481b4e1ff 100644
--- a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs
+++ b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs
@@ -528,6 +528,8 @@ public class ContentController : ContentControllerBase
         {
             display.DocumentType.Name =
                 _localizedTextService.UmbracoDictionaryTranslate(CultureDictionary, display.DocumentType.Name);
+            display.DocumentType.Description =
+                _localizedTextService.UmbracoDictionaryTranslate(CultureDictionary, display.DocumentType.Description);
         }
 
         //remove the listview app if it exists
@@ -1860,6 +1862,11 @@ public class ContentController : ContentControllerBase
 
     private IEnumerable<string> GetPublishedCulturesFromAncestors(IContent? content)
     {
+        if (content?.ParentId is not -1 && content?.HasIdentity is false)
+        {
+            content = _contentService.GetById(content.ParentId);
+        }
+
         if (content?.ParentId == -1)
         {
             return content.PublishedCultures;
diff --git a/src/Umbraco.Web.BackOffice/Controllers/DictionaryController.cs b/src/Umbraco.Web.BackOffice/Controllers/DictionaryController.cs
index ad55272587..b9508a501b 100644
--- a/src/Umbraco.Web.BackOffice/Controllers/DictionaryController.cs
+++ b/src/Umbraco.Web.BackOffice/Controllers/DictionaryController.cs
@@ -1,3 +1,4 @@
+using System.Xml;
 using System.Globalization;
 using System.Net.Mime;
 using System.Text;
@@ -10,11 +11,17 @@ using Umbraco.Cms.Core.Configuration.Models;
 using Umbraco.Cms.Core.Mapping;
 using Umbraco.Cms.Core.Models;
 using Umbraco.Cms.Core.Models.ContentEditing;
+using Umbraco.Cms.Core.Hosting;
 using Umbraco.Cms.Core.Security;
 using Umbraco.Cms.Core.Services;
 using Umbraco.Cms.Web.Common.Attributes;
 using Umbraco.Cms.Web.Common.Authorization;
 using Umbraco.Extensions;
+using Umbraco.Cms.Infrastructure.Packaging;
+using System.Xml.Linq;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.DependencyInjection;
+using Umbraco.Cms.Web.Common.DependencyInjection;
 
 namespace Umbraco.Cms.Web.BackOffice.Controllers;
 
@@ -38,7 +45,34 @@ public class DictionaryController : BackOfficeNotificationsController
     private readonly ILocalizedTextService _localizedTextService;
     private readonly ILogger<DictionaryController> _logger;
     private readonly IUmbracoMapper _umbracoMapper;
+    private readonly IEntityXmlSerializer _serializer;
+    private readonly IHostingEnvironment _hostingEnvironment;
+    private readonly PackageDataInstallation _packageDataInstallation;
 
+    [ActivatorUtilitiesConstructor]
+    public DictionaryController(
+        ILogger<DictionaryController> logger,
+        ILocalizationService localizationService,
+        IBackOfficeSecurityAccessor backofficeSecurityAccessor,
+        IOptionsSnapshot<GlobalSettings> globalSettings,
+        ILocalizedTextService localizedTextService,
+        IUmbracoMapper umbracoMapper,
+        IEntityXmlSerializer serializer,
+        IHostingEnvironment hostingEnvironment,
+        PackageDataInstallation packageDataInstallation)
+    {
+        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
+        _localizationService = localizationService ?? throw new ArgumentNullException(nameof(localizationService));
+        _backofficeSecurityAccessor = backofficeSecurityAccessor ?? throw new ArgumentNullException(nameof(backofficeSecurityAccessor));
+        _globalSettings = globalSettings.Value ?? throw new ArgumentNullException(nameof(globalSettings));
+        _localizedTextService = localizedTextService ?? throw new ArgumentNullException(nameof(localizedTextService));
+        _umbracoMapper = umbracoMapper ?? throw new ArgumentNullException(nameof(umbracoMapper));
+        _serializer = serializer ?? throw new ArgumentNullException(nameof(serializer));
+        _hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment));
+        _packageDataInstallation = packageDataInstallation ?? throw new ArgumentNullException(nameof(packageDataInstallation));
+    }
+
+    [Obsolete("Please use ctor that also takes an IEntityXmlSerializer, IHostingEnvironment & PackageDataInstallation instead, scheduled for removal in v12")]
     public DictionaryController(
         ILogger<DictionaryController> logger,
         ILocalizationService localizationService,
@@ -46,14 +80,17 @@ public class DictionaryController : BackOfficeNotificationsController
         IOptionsSnapshot<GlobalSettings> globalSettings,
         ILocalizedTextService localizedTextService,
         IUmbracoMapper umbracoMapper)
+    : this(
+        logger,
+        localizationService,
+        backofficeSecurityAccessor,
+        globalSettings,
+        localizedTextService,
+        umbracoMapper,
+        StaticServiceProvider.Instance.GetRequiredService<IEntityXmlSerializer>(),
+        StaticServiceProvider.Instance.GetRequiredService<IHostingEnvironment>(),
+        StaticServiceProvider.Instance.GetRequiredService<PackageDataInstallation>())
     {
-        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
-        _localizationService = localizationService ?? throw new ArgumentNullException(nameof(localizationService));
-        _backofficeSecurityAccessor = backofficeSecurityAccessor ??
-                                      throw new ArgumentNullException(nameof(backofficeSecurityAccessor));
-        _globalSettings = globalSettings.Value ?? throw new ArgumentNullException(nameof(globalSettings));
-        _localizedTextService = localizedTextService ?? throw new ArgumentNullException(nameof(localizedTextService));
-        _umbracoMapper = umbracoMapper ?? throw new ArgumentNullException(nameof(umbracoMapper));
     }
 
     /// <summary>
@@ -389,5 +426,134 @@ public class DictionaryController : BackOfficeNotificationsController
         }
     }
 
+    public IActionResult ExportDictionary(int id, bool includeChildren = false)
+    {
+        IDictionaryItem? dictionaryItem = _localizationService.GetDictionaryItemById(id);
+        if (dictionaryItem == null)
+        {
+            throw new NullReferenceException("No dictionary item found with id " + id);
+        }
+
+        XElement xml = _serializer.Serialize(dictionaryItem, includeChildren);
+
+        var fileName = $"{dictionaryItem.ItemKey}.udt";
+        // Set custom header so umbRequestHelper.downloadFile can save the correct filename
+        HttpContext.Response.Headers.Add("x-filename", fileName);
+
+        return File(Encoding.UTF8.GetBytes(xml.ToDataString()), MediaTypeNames.Application.Octet, fileName);
+    }
+
+    public IActionResult ImportDictionary(string file, int parentId)
+    {
+        if (string.IsNullOrWhiteSpace(file))
+        {
+            return NotFound();
+        }
+
+        var filePath = Path.Combine(_hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.Data), file);
+        if (!System.IO.File.Exists(filePath))
+        {
+            return NotFound();
+        }
+
+        var xd = new XmlDocument { XmlResolver = null };
+        xd.Load(filePath);
+
+        var userId = _backofficeSecurityAccessor.BackOfficeSecurity?.GetUserId().Result ?? 0;
+        var element = XElement.Parse(xd.InnerXml);
+
+        IDictionaryItem? parentDictionaryItem = _localizationService.GetDictionaryItemById(parentId);
+        IEnumerable<IDictionaryItem> dictionaryItems = _packageDataInstallation
+            .ImportDictionaryItem(element, userId, parentDictionaryItem?.Key);
+
+        // Try to clean up the temporary file.
+        try
+        {
+            System.IO.File.Delete(filePath);
+        }
+        catch (Exception ex)
+        {
+            _logger.LogError(ex, "Error cleaning up temporary udt file in {File}", filePath);
+        }
+
+        var model = _umbracoMapper.Map<IDictionaryItem, DictionaryDisplay>(dictionaryItems.FirstOrDefault());
+        return Content(model!.Path, MediaTypeNames.Text.Plain, Encoding.UTF8);
+    }
+
+    public ActionResult<DictionaryImportModel> Upload(IFormFile file)
+    {
+        if (file == null)
+        {
+            return ValidationProblem(
+                _localizedTextService.Localize("media", "failedFileUpload"),
+                _localizedTextService.Localize("speechBubbles", "fileErrorNotFound"));
+        }
+
+        var fileName = file.FileName.Trim(Constants.CharArrays.DoubleQuote);
+        var ext = fileName.Substring(fileName.LastIndexOf('.') + 1).ToLower();
+        var root = _hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.TempFileUploads);
+        var tempPath = Path.Combine(root, fileName);
+
+        if (!Path.GetFullPath(tempPath).StartsWith(Path.GetFullPath(root)))
+        {
+            return ValidationProblem(
+                _localizedTextService.Localize("media", "failedFileUpload"),
+                _localizedTextService.Localize("media", "invalidFileName"));
+        }
+
+        if (!ext.InvariantEquals("udt"))
+        {
+            return ValidationProblem(
+                _localizedTextService.Localize("media", "failedFileUpload"),
+                _localizedTextService.Localize("media", "disallowedFileType"));
+        }
+
+        using (FileStream stream = System.IO.File.Create(tempPath))
+        {
+            file.CopyToAsync(stream).GetAwaiter().GetResult();
+        }
+
+        var xd = new XmlDocument { XmlResolver = null };
+        xd.Load(tempPath);
+
+        if (xd.DocumentElement == null)
+        {
+            return ValidationProblem(
+                _localizedTextService.Localize("media", "failedFileUpload"),
+                _localizedTextService.Localize("speechBubbles", "fileErrorNotFound"));
+        }
+
+        var model = new DictionaryImportModel()
+        {
+            TempFileName = tempPath,
+            DictionaryItems = new List<DictionaryPreviewImportModel>(),
+        };
+
+        var level = 1;
+        var currentParent = string.Empty;
+        foreach (XmlNode dictionaryItem in xd.GetElementsByTagName("DictionaryItem"))
+        {
+            var name = dictionaryItem.Attributes?.GetNamedItem("Name")?.Value ?? string.Empty;
+            var parentKey = dictionaryItem?.ParentNode?.Attributes?.GetNamedItem("Key")?.Value ?? string.Empty;
+
+            if (parentKey != currentParent || level == 1)
+            {
+                level += 1;
+                currentParent = parentKey;
+            }
+
+            model.DictionaryItems.Add(new DictionaryPreviewImportModel() { Level = level, Name = name });
+        }
+
+        if (!model.DictionaryItems.Any())
+        {
+            return ValidationProblem(
+                _localizedTextService.Localize("media", "failedFileUpload"),
+                _localizedTextService.Localize("dictionary", "noItemsInFile"));
+        }
+
+        return model;
+    }
+
     private static Func<IDictionaryItem, string> ItemSort() => item => item.ItemKey;
 }
diff --git a/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs b/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs
index 3f51e909b9..c8bb55c44c 100644
--- a/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs
+++ b/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs
@@ -199,6 +199,7 @@ public class EntityController : UmbracoAuthorizedJsonController
             return result;
         }
 
+        var culture = ClientCulture();
         var allowedSections = _backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.AllowedSections.ToArray();
 
         var searchTasks = new List<Task>();
@@ -216,7 +217,7 @@ public class EntityController : UmbracoAuthorizedJsonController
                 var rootNodeDisplayName = Tree.GetRootNodeDisplayName(tree, _localizedTextService);
                 if (rootNodeDisplayName is not null)
                 {
-                    searchTasks.Add(ExecuteSearchAsync(query, searchableTree, rootNodeDisplayName, result));
+                    searchTasks.Add(ExecuteSearchAsync(query, culture, searchableTree, rootNodeDisplayName, result));
                 }
             }
         }
@@ -227,13 +228,22 @@ public class EntityController : UmbracoAuthorizedJsonController
 
     private static async Task ExecuteSearchAsync(
         string query,
+        string? culture,
         KeyValuePair<string, SearchableApplicationTree> searchableTree,
         string rootNodeDisplayName,
         ConcurrentDictionary<string, TreeSearchResult> result)
     {
+        ISearchableTree searcher = searchableTree.Value.SearchableTree;
+        const int pageSize = 200;
+        IEnumerable<SearchResultEntity> results = (
+            searcher is ISearchableTreeWithCulture searcherWithCulture
+                ? await searcherWithCulture.SearchAsync(query, pageSize, 0, culture: culture)
+                : await searcher.SearchAsync(query, pageSize, 0))
+        .WhereNotNull();
+
         var searchResult = new TreeSearchResult
         {
-            Results = (await searchableTree.Value.SearchableTree.SearchAsync(query, 200, 0)).WhereNotNull(),
+            Results = results,
             TreeAlias = searchableTree.Key,
             AppAlias = searchableTree.Value.AppAlias,
             JsFormatterService = searchableTree.Value.FormatterService,
diff --git a/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs b/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs
index 24e5a77a23..f734d8626b 100644
--- a/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs
+++ b/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs
@@ -179,8 +179,9 @@ public class UsersController : BackOfficeNotificationsController
         var fileName = file.FileName.Trim(new[] { '\"' }).TrimEnd();
         var safeFileName = fileName.ToSafeFileName(shortStringHelper);
         var ext = safeFileName.Substring(safeFileName.LastIndexOf('.') + 1).ToLower();
+        const string allowedAvatarFileTypes = "jpeg,jpg,gif,bmp,png,tiff,tif,webp";
 
-        if (contentSettings.DisallowedUploadFiles.Contains(ext) == false)
+        if (allowedAvatarFileTypes.Contains(ext) == true && contentSettings.DisallowedUploadFiles.Contains(ext) == false)
         {
             //generate a path of known data, we don't want this path to be guessable
             user.Avatar = "UserAvatars/" + (user.Id + safeFileName).GenerateHash<SHA1>() + "." + ext;
diff --git a/src/Umbraco.Web.BackOffice/Trees/ContentBlueprintTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/ContentBlueprintTreeController.cs
index db07717b40..d876fdc23e 100644
--- a/src/Umbraco.Web.BackOffice/Trees/ContentBlueprintTreeController.cs
+++ b/src/Umbraco.Web.BackOffice/Trees/ContentBlueprintTreeController.cs
@@ -143,7 +143,7 @@ public class ContentBlueprintTreeController : TreeController
         if (id == Constants.System.RootString)
         {
             // root actions
-            menu.Items.Add<ActionNew>(LocalizedTextService, opensDialog: true);
+            menu.Items.Add<ActionNew>(LocalizedTextService, opensDialog: true, useLegacyIcon: false);
             menu.Items.Add(new RefreshNode(LocalizedTextService, true));
             return menu;
         }
@@ -155,7 +155,7 @@ public class ContentBlueprintTreeController : TreeController
         {
             IContentType? ct = _contentTypeService.Get(cte.Id);
             MenuItem? createItem =
-                menu.Items.Add<ActionCreateBlueprintFromContent>(LocalizedTextService, opensDialog: true);
+                menu.Items.Add<ActionCreateBlueprintFromContent>(LocalizedTextService, opensDialog: true, useLegacyIcon: false);
             createItem?.NavigateToRoute("/settings/contentBlueprints/edit/-1?create=true&doctype=" + ct?.Alias);
 
             menu.Items.Add(new RefreshNode(LocalizedTextService, true));
@@ -163,7 +163,7 @@ public class ContentBlueprintTreeController : TreeController
             return menu;
         }
 
-        menu.Items.Add<ActionDelete>(LocalizedTextService, opensDialog: true);
+        menu.Items.Add<ActionDelete>(LocalizedTextService, opensDialog: true, useLegacyIcon: false);
 
         return menu;
     }
diff --git a/src/Umbraco.Web.BackOffice/Trees/ContentTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/ContentTreeController.cs
index ee1fde64f4..4728ebbca8 100644
--- a/src/Umbraco.Web.BackOffice/Trees/ContentTreeController.cs
+++ b/src/Umbraco.Web.BackOffice/Trees/ContentTreeController.cs
@@ -28,7 +28,7 @@ namespace Umbraco.Cms.Web.BackOffice.Trees;
 [PluginController(Constants.Web.Mvc.BackOfficeTreeArea)]
 [CoreTree]
 [SearchableTree("searchResultFormatter", "configureContentResult", 10)]
-public class ContentTreeController : ContentTreeControllerBase, ISearchableTree
+public class ContentTreeController : ContentTreeControllerBase, ISearchableTreeWithCulture
 {
     private readonly ActionCollection _actions;
     private readonly AppCaches _appCaches;
@@ -104,7 +104,6 @@ public class ContentTreeController : ContentTreeControllerBase, ISearchableTree
         return new EntitySearchResults(results, totalFound);
     }
 
-
     /// <inheritdoc />
     protected override TreeNode? GetSingleTreeNode(IEntitySlim entity, string parentId, FormCollection? queryStrings)
     {
@@ -196,8 +195,8 @@ public class ContentTreeController : ContentTreeControllerBase, ISearchableTree
                 .Select(x => new MenuItem(x));
 
             //these two are the standard items
-            menu.Items.Add<ActionNew>(LocalizedTextService, opensDialog: true);
-            menu.Items.Add<ActionSort>(LocalizedTextService, true, true);
+            menu.Items.Add<ActionNew>(LocalizedTextService, opensDialog: true, useLegacyIcon: false);
+            menu.Items.Add<ActionSort>(LocalizedTextService, hasSeparator: true, opensDialog: true, useLegacyIcon: false);
 
             //filter the standard items
             FilterUserAllowedMenuItems(menu, nodeActions);
@@ -301,23 +300,24 @@ public class ContentTreeController : ContentTreeControllerBase, ISearchableTree
     protected MenuItemCollection GetAllNodeMenuItems(IUmbracoEntity item)
     {
         MenuItemCollection menu = _menuItemCollectionFactory.Create();
-        AddActionNode<ActionNew>(item, menu, opensDialog: true);
-        AddActionNode<ActionDelete>(item, menu, opensDialog: true);
-        AddActionNode<ActionCreateBlueprintFromContent>(item, menu, opensDialog: true);
-        AddActionNode<ActionMove>(item, menu, true, true);
-        AddActionNode<ActionCopy>(item, menu, opensDialog: true);
-        AddActionNode<ActionSort>(item, menu, true, true);
-        AddActionNode<ActionAssignDomain>(item, menu, opensDialog: true);
-        AddActionNode<ActionRights>(item, menu, opensDialog: true);
-        AddActionNode<ActionProtect>(item, menu, true, true);
+        AddActionNode<ActionNew>(item, menu, opensDialog: true, useLegacyIcon: false);
+        AddActionNode<ActionDelete>(item, menu, opensDialog: true, useLegacyIcon: false);
+        AddActionNode<ActionCreateBlueprintFromContent>(item, menu, opensDialog: true, useLegacyIcon: false);
+        AddActionNode<ActionMove>(item, menu, hasSeparator: true, opensDialog: true, useLegacyIcon: false);
+        AddActionNode<ActionCopy>(item, menu, opensDialog: true, useLegacyIcon: false);
+        AddActionNode<ActionSort>(item, menu, hasSeparator: true, opensDialog: true, useLegacyIcon: false);
+        AddActionNode<ActionAssignDomain>(item, menu, opensDialog: true, useLegacyIcon: false);
+        AddActionNode<ActionRights>(item, menu, opensDialog: true, useLegacyIcon: false);
+        AddActionNode<ActionProtect>(item, menu, hasSeparator: true, opensDialog: true, useLegacyIcon: false);
 
         if (_emailSender.CanSendRequiredEmail())
         {
             menu.Items.Add(new MenuItem("notify", LocalizedTextService)
             {
-                Icon = "megaphone",
+                Icon = "icon-megaphone",
                 SeparatorBefore = true,
-                OpensDialog = true
+                OpensDialog = true,
+                UseLegacyIcon = false
             });
         }
 
@@ -337,9 +337,9 @@ public class ContentTreeController : ContentTreeControllerBase, ISearchableTree
     protected MenuItemCollection GetNodeMenuItemsForDeletedContent(IUmbracoEntity item)
     {
         MenuItemCollection menu = _menuItemCollectionFactory.Create();
-        menu.Items.Add<ActionRestore>(LocalizedTextService, opensDialog: true);
-        menu.Items.Add<ActionMove>(LocalizedTextService, opensDialog: true);
-        menu.Items.Add<ActionDelete>(LocalizedTextService, opensDialog: true);
+        menu.Items.Add<ActionRestore>(LocalizedTextService, opensDialog: true, useLegacyIcon: false);
+        menu.Items.Add<ActionMove>(LocalizedTextService, opensDialog: true, useLegacyIcon: false);
+        menu.Items.Add<ActionDelete>(LocalizedTextService, opensDialog: true, useLegacyIcon: false);
 
         menu.Items.Add(new RefreshNode(LocalizedTextService, true));
 
@@ -392,9 +392,15 @@ public class ContentTreeController : ContentTreeControllerBase, ISearchableTree
         }
     }
 
-    private void AddActionNode<TAction>(IUmbracoEntity item, MenuItemCollection menu, bool hasSeparator = false, bool opensDialog = false)
+    private void AddActionNode<TAction>(IUmbracoEntity item, MenuItemCollection menu, bool hasSeparator = false, bool opensDialog = false, bool useLegacyIcon = true)
         where TAction : IAction
     {
-        MenuItem? menuItem = menu.Items.Add<TAction>(LocalizedTextService, hasSeparator, opensDialog);
+        MenuItem? menuItem = menu.Items.Add<TAction>(LocalizedTextService, hasSeparator, opensDialog, useLegacyIcon);
+    }
+
+    public async Task<EntitySearchResults> SearchAsync(string query, int pageSize, long pageIndex, string? searchFrom = null, string? culture = null)
+    {
+        var results = _treeSearcher.ExamineSearch(query, UmbracoEntityTypes.Document, pageSize, pageIndex, out long totalFound, culture: culture, searchFrom: searchFrom);
+        return new EntitySearchResults(results, totalFound);
     }
 }
diff --git a/src/Umbraco.Web.BackOffice/Trees/ContentTreeControllerBase.cs b/src/Umbraco.Web.BackOffice/Trees/ContentTreeControllerBase.cs
index 1e0d4725a4..930c51c713 100644
--- a/src/Umbraco.Web.BackOffice/Trees/ContentTreeControllerBase.cs
+++ b/src/Umbraco.Web.BackOffice/Trees/ContentTreeControllerBase.cs
@@ -518,14 +518,17 @@ public abstract class ContentTreeControllerBase : TreeController, ITreeNodeContr
             }
 
             MenuItemCollection menu = MenuItemCollectionFactory.Create();
+
             // only add empty recycle bin if the current user is allowed to delete by default
             if (deleteAllowed)
             {
                 menu.Items.Add(new MenuItem("emptyrecyclebin", LocalizedTextService)
                 {
-                    Icon = "trash",
-                    OpensDialog = true
+                    Icon = "icon-trash",
+                    OpensDialog = true,
+                    UseLegacyIcon = false,
                 });
+
                 menu.Items.Add(new RefreshNode(LocalizedTextService, true));
             }
 
diff --git a/src/Umbraco.Web.BackOffice/Trees/ContentTypeTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/ContentTypeTreeController.cs
index 6e2bba141e..27c26004b9 100644
--- a/src/Umbraco.Web.BackOffice/Trees/ContentTypeTreeController.cs
+++ b/src/Umbraco.Web.BackOffice/Trees/ContentTypeTreeController.cs
@@ -133,18 +133,20 @@ public class ContentTypeTreeController : TreeController, ISearchableTree
 
         if (id == Constants.System.RootString)
         {
-            //set the default to create
+                //set the default to create
             menu.DefaultMenuAlias = ActionNew.ActionAlias;
 
             // root actions
-            menu.Items.Add<ActionNew>(LocalizedTextService, opensDialog: true);
+            menu.Items.Add<ActionNew>(LocalizedTextService, opensDialog: true, useLegacyIcon: false);
             menu.Items.Add(new MenuItem("importdocumenttype", LocalizedTextService)
             {
-                Icon = "page-up",
+                Icon = "icon-page-up",
                 SeparatorBefore = true,
-                OpensDialog = true
+                OpensDialog = true,
+                UseLegacyIcon = false,
             });
-            menu.Items.Add(new RefreshNode(LocalizedTextService, true));
+
+            menu.Items.Add(new RefreshNode(LocalizedTextService, separatorBefore: true));
 
             return menu;
         }
@@ -155,39 +157,46 @@ public class ContentTypeTreeController : TreeController, ISearchableTree
             //set the default to create
             menu.DefaultMenuAlias = ActionNew.ActionAlias;
 
-            menu.Items.Add<ActionNew>(LocalizedTextService, opensDialog: true);
+            menu.Items.Add<ActionNew>(LocalizedTextService, opensDialog: true, useLegacyIcon: false);
 
-            menu.Items.Add(new MenuItem("rename", LocalizedTextService) { Icon = "icon icon-edit" });
+            menu.Items.Add(new MenuItem("rename", LocalizedTextService)
+            {
+                Icon = "icon-edit",
+                UseLegacyIcon = false
+            });
 
             if (container.HasChildren == false)
             {
                 //can delete doc type
-                menu.Items.Add<ActionDelete>(LocalizedTextService, true, true);
+                menu.Items.Add<ActionDelete>(LocalizedTextService, hasSeparator: true, opensDialog: true, useLegacyIcon: false);
             }
 
-            menu.Items.Add(new RefreshNode(LocalizedTextService, true));
+            menu.Items.Add(new RefreshNode(LocalizedTextService, separatorBefore: true));
         }
         else
         {
             IContentType? ct = _contentTypeService.Get(int.Parse(id, CultureInfo.InvariantCulture));
             IContentType? parent = ct == null ? null : _contentTypeService.Get(ct.ParentId);
 
-            menu.Items.Add<ActionNew>(LocalizedTextService, opensDialog: true);
-            //no move action if this is a child doc type
+            menu.Items.Add<ActionNew>(LocalizedTextService, opensDialog: true, useLegacyIcon: false);
+
+            // No move action if this is a child doc type
             if (parent == null)
             {
-                menu.Items.Add<ActionMove>(LocalizedTextService, true, true);
+                menu.Items.Add<ActionMove>(LocalizedTextService, hasSeparator: true, opensDialog: true, useLegacyIcon: false);
             }
 
-            menu.Items.Add<ActionCopy>(LocalizedTextService, opensDialog: true);
+            menu.Items.Add<ActionCopy>(LocalizedTextService, opensDialog: true, useLegacyIcon: false);
             menu.Items.Add(new MenuItem("export", LocalizedTextService)
             {
-                Icon = "download-alt",
+                Icon = "icon-download-alt",
                 SeparatorBefore = true,
-                OpensDialog = true
+                OpensDialog = true,
+                UseLegacyIcon = false,
             });
-            menu.Items.Add<ActionDelete>(LocalizedTextService, true, true);
-            menu.Items.Add(new RefreshNode(LocalizedTextService, true));
+
+            menu.Items.Add<ActionDelete>(LocalizedTextService, hasSeparator: true, opensDialog: true, useLegacyIcon: false);
+            menu.Items.Add(new RefreshNode(LocalizedTextService, separatorBefore: true));
         }
 
         return menu;
diff --git a/src/Umbraco.Web.BackOffice/Trees/DataTypeTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/DataTypeTreeController.cs
index 6121273a22..84a32d3659 100644
--- a/src/Umbraco.Web.BackOffice/Trees/DataTypeTreeController.cs
+++ b/src/Umbraco.Web.BackOffice/Trees/DataTypeTreeController.cs
@@ -144,7 +144,7 @@ public class DataTypeTreeController : TreeController, ISearchableTree
             menu.DefaultMenuAlias = ActionNew.ActionAlias;
 
             // root actions
-            menu.Items.Add<ActionNew>(LocalizedTextService, opensDialog: true);
+            menu.Items.Add<ActionNew>(LocalizedTextService, opensDialog: true, useLegacyIcon: false);
             menu.Items.Add(new RefreshNode(LocalizedTextService, true));
             return menu;
         }
@@ -153,20 +153,21 @@ public class DataTypeTreeController : TreeController, ISearchableTree
             UmbracoObjectTypes.DataTypeContainer);
         if (container != null)
         {
-            //set the default to create
+                //set the default to create
             menu.DefaultMenuAlias = ActionNew.ActionAlias;
 
-            menu.Items.Add<ActionNew>(LocalizedTextService, opensDialog: true);
+            menu.Items.Add<ActionNew>(LocalizedTextService, opensDialog: true, useLegacyIcon: false);
 
             menu.Items.Add(new MenuItem("rename", LocalizedTextService.Localize("actions", "rename"))
             {
-                Icon = "icon icon-edit"
+                Icon = "icon-edit",
+                UseLegacyIcon = false,
             });
 
             if (container.HasChildren == false)
             {
                 //can delete data type
-                menu.Items.Add<ActionDelete>(LocalizedTextService, opensDialog: true);
+                menu.Items.Add<ActionDelete>(LocalizedTextService, opensDialog: true, useLegacyIcon: false);
             }
 
             menu.Items.Add(new RefreshNode(LocalizedTextService, true));
@@ -177,10 +178,10 @@ public class DataTypeTreeController : TreeController, ISearchableTree
 
             if (nonDeletableSystemDataTypeIds.Contains(int.Parse(id, CultureInfo.InvariantCulture)) == false)
             {
-                menu.Items.Add<ActionDelete>(LocalizedTextService, opensDialog: true);
+                menu.Items.Add<ActionDelete>(LocalizedTextService, opensDialog: true, useLegacyIcon: false);
             }
 
-            menu.Items.Add<ActionMove>(LocalizedTextService, true, true);
+            menu.Items.Add<ActionMove>(LocalizedTextService, hasSeparator: true, opensDialog: true, useLegacyIcon: false);
         }
 
         return menu;
diff --git a/src/Umbraco.Web.BackOffice/Trees/DictionaryTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/DictionaryTreeController.cs
index 274e26c4bf..c23adf76d9 100644
--- a/src/Umbraco.Web.BackOffice/Trees/DictionaryTreeController.cs
+++ b/src/Umbraco.Web.BackOffice/Trees/DictionaryTreeController.cs
@@ -140,16 +140,32 @@ public class DictionaryTreeController : TreeController
     {
         MenuItemCollection menu = _menuItemCollectionFactory.Create();
 
-        menu.Items.Add<ActionNew>(LocalizedTextService, opensDialog: true);
+        menu.Items.Add<ActionNew>(LocalizedTextService, opensDialog: true, useLegacyIcon: false);
 
         if (id != Constants.System.RootString)
         {
-            menu.Items.Add<ActionDelete>(LocalizedTextService, true, true);
-            menu.Items.Add<ActionMove>(LocalizedTextService, true, true);
+            menu.Items.Add<ActionMove>(LocalizedTextService, hasSeparator: true, opensDialog: true, useLegacyIcon: false);
+            menu.Items.Add(new MenuItem("export", LocalizedTextService)
+            {
+                Icon = "icon-download-alt",
+                SeparatorBefore = true,
+                OpensDialog = true,
+                UseLegacyIcon = false,
+            });
+            menu.Items.Add<ActionDelete>(LocalizedTextService, hasSeparator: true, opensDialog: true, useLegacyIcon: false);
+        }
+        else
+        {
+            menu.Items.Add(new MenuItem("import", LocalizedTextService)
+            {
+                Icon = "icon-page-up",
+                SeparatorBefore = true,
+                OpensDialog = true,
+                UseLegacyIcon = false,
+            });
         }
 
-
-        menu.Items.Add(new RefreshNode(LocalizedTextService, true));
+        menu.Items.Add(new RefreshNode(LocalizedTextService, separatorBefore: true));
 
         return menu;
     }
diff --git a/src/Umbraco.Web.BackOffice/Trees/FileSystemTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/FileSystemTreeController.cs
index ea44d5c381..210955bccb 100644
--- a/src/Umbraco.Web.BackOffice/Trees/FileSystemTreeController.cs
+++ b/src/Umbraco.Web.BackOffice/Trees/FileSystemTreeController.cs
@@ -60,8 +60,10 @@ public abstract class FileSystemTreeController : TreeController
 
                 var name = Path.GetFileName(directory);
                 TreeNode? node = CreateTreeNode(WebUtility.UrlEncode(directory), path, queryStrings, name,
-                    "icon-folder", hasChildren);
+                    Constants.Icons.Folder, hasChildren);
+
                 OnRenderFolderNode(ref node);
+
                 if (node != null)
                 {
                     nodes.Add(node);
@@ -97,7 +99,9 @@ public abstract class FileSystemTreeController : TreeController
 
                 var name = Path.GetFileName(file);
                 TreeNode? node = CreateTreeNode(WebUtility.UrlEncode(file), path, queryStrings, name, FileIcon, false);
+
                 OnRenderFileNode(ref node);
+
                 if (node != null)
                 {
                     nodes.Add(node);
@@ -118,8 +122,9 @@ public abstract class FileSystemTreeController : TreeController
 
         TreeNode? root = rootResult.Value;
 
-        //check if there are any children
+            //check if there are any children
         ActionResult<TreeNodeCollection> treeNodesResult = GetTreeNodes(Constants.System.RootString, queryStrings);
+
         if (!(treeNodesResult.Result is null))
         {
             return treeNodesResult.Result;
@@ -137,12 +142,14 @@ public abstract class FileSystemTreeController : TreeController
     {
         MenuItemCollection menu = MenuItemCollectionFactory.Create();
 
-        //set the default to create
+            //set the default to create
         menu.DefaultMenuAlias = ActionNew.ActionAlias;
-        //create action
-        menu.Items.Add<ActionNew>(LocalizedTextService, opensDialog: true);
-        //refresh action
-        menu.Items.Add(new RefreshNode(LocalizedTextService, true));
+
+            //create action
+        menu.Items.Add<ActionNew>(LocalizedTextService, opensDialog: true, useLegacyIcon: false);
+
+            //refresh action
+        menu.Items.Add(new RefreshNode(LocalizedTextService, separatorBefore: true));
 
         return menu;
     }
@@ -151,23 +158,24 @@ public abstract class FileSystemTreeController : TreeController
     {
         MenuItemCollection menu = MenuItemCollectionFactory.Create();
 
-        //set the default to create
+            //set the default to create
         menu.DefaultMenuAlias = ActionNew.ActionAlias;
+
         //create action
-        menu.Items.Add<ActionNew>(LocalizedTextService, opensDialog: true);
+        menu.Items.Add<ActionNew>(LocalizedTextService, opensDialog: true, useLegacyIcon: false);
 
         var hasChildren = FileSystem is not null &&
                           (FileSystem.GetFiles(path).Any() || FileSystem.GetDirectories(path).Any());
 
-        //We can only delete folders if it doesn't have any children (folders or files)
+            //We can only delete folders if it doesn't have any children (folders or files)
         if (hasChildren == false)
         {
             //delete action
-            menu.Items.Add<ActionDelete>(LocalizedTextService, true, true);
+            menu.Items.Add<ActionDelete>(LocalizedTextService, hasSeparator: true, opensDialog: true, useLegacyIcon: false);
         }
 
-        //refresh action
-        menu.Items.Add(new RefreshNode(LocalizedTextService, true));
+            //refresh action
+        menu.Items.Add(new RefreshNode(LocalizedTextService, separatorBefore: true));
 
         return menu;
     }
@@ -177,7 +185,7 @@ public abstract class FileSystemTreeController : TreeController
         MenuItemCollection menu = MenuItemCollectionFactory.Create();
 
         //if it's not a directory then we only allow to delete the item
-        menu.Items.Add<ActionDelete>(LocalizedTextService, opensDialog: true);
+        menu.Items.Add<ActionDelete>(LocalizedTextService, opensDialog: true, useLegacyIcon: false);
 
         return menu;
     }
diff --git a/src/Umbraco.Web.BackOffice/Trees/MacrosTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/MacrosTreeController.cs
index 6cb716a93f..c0b45444e9 100644
--- a/src/Umbraco.Web.BackOffice/Trees/MacrosTreeController.cs
+++ b/src/Umbraco.Web.BackOffice/Trees/MacrosTreeController.cs
@@ -81,7 +81,7 @@ public class MacrosTreeController : TreeController
         if (id == Constants.System.RootString)
         {
             //Create the normal create action
-            menu.Items.Add<ActionNew>(LocalizedTextService);
+            menu.Items.Add<ActionNew>(LocalizedTextService, useLegacyIcon: false);
 
             //refresh action
             menu.Items.Add(new RefreshNode(LocalizedTextService, true));
@@ -96,7 +96,7 @@ public class MacrosTreeController : TreeController
         }
 
         //add delete option for all macros
-        menu.Items.Add<ActionDelete>(LocalizedTextService, opensDialog: true);
+        menu.Items.Add<ActionDelete>(LocalizedTextService, opensDialog: true, useLegacyIcon: false);
 
         return menu;
     }
diff --git a/src/Umbraco.Web.BackOffice/Trees/MediaTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/MediaTreeController.cs
index 9e5221aa97..00b62a17f7 100644
--- a/src/Umbraco.Web.BackOffice/Trees/MediaTreeController.cs
+++ b/src/Umbraco.Web.BackOffice/Trees/MediaTreeController.cs
@@ -124,14 +124,15 @@ public class MediaTreeController : ContentTreeControllerBase, ISearchableTree, I
             // if the user's start node is not the root then the only menu item to display is refresh
             if (UserStartNodes.Contains(Constants.System.Root) == false)
             {
-                menu.Items.Add(new RefreshNode(LocalizedTextService, true));
+                menu.Items.Add(new RefreshNode(LocalizedTextService, separatorBefore: true));
                 return menu;
             }
 
             // root actions
-            menu.Items.Add<ActionNew>(LocalizedTextService, opensDialog: true);
-            menu.Items.Add<ActionSort>(LocalizedTextService, true, true);
-            menu.Items.Add(new RefreshNode(LocalizedTextService, true));
+            menu.Items.Add<ActionNew>(LocalizedTextService, opensDialog: true, useLegacyIcon: false);
+            menu.Items.Add<ActionSort>(LocalizedTextService, hasSeparator: true, opensDialog: true, useLegacyIcon: false);
+            menu.Items.Add(new RefreshNode(LocalizedTextService, separatorBefore: true));
+
             return menu;
         }
 
@@ -146,7 +147,7 @@ public class MediaTreeController : ContentTreeControllerBase, ISearchableTree, I
             return NotFound();
         }
 
-        //if the user has no path access for this node, all they can do is refresh
+            //if the user has no path access for this node, all they can do is refresh
         if (!_backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.HasMediaPathAccess(item, _entityService,
                 _appCaches) ?? false)
         {
@@ -155,25 +156,25 @@ public class MediaTreeController : ContentTreeControllerBase, ISearchableTree, I
         }
 
 
-        //if the media item is in the recycle bin, we don't have a default menu and we need to show a limited menu
+            //if the media item is in the recycle bin, we don't have a default menu and we need to show a limited menu
         if (item.Path.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries)
             .Contains(RecycleBinId.ToInvariantString()))
         {
-            menu.Items.Add<ActionRestore>(LocalizedTextService, opensDialog: true);
-            menu.Items.Add<ActionMove>(LocalizedTextService, opensDialog: true);
-            menu.Items.Add<ActionDelete>(LocalizedTextService, opensDialog: true);
-            menu.Items.Add(new RefreshNode(LocalizedTextService, true));
+            menu.Items.Add<ActionRestore>(LocalizedTextService, opensDialog: true, useLegacyIcon: false);
+            menu.Items.Add<ActionMove>(LocalizedTextService, opensDialog: true, useLegacyIcon: false);
+            menu.Items.Add<ActionDelete>(LocalizedTextService, opensDialog: true, useLegacyIcon: false);
+            menu.Items.Add(new RefreshNode(LocalizedTextService, separatorBefore: true));
 
             menu.DefaultMenuAlias = null;
         }
         else
         {
             //return a normal node menu:
-            menu.Items.Add<ActionNew>(LocalizedTextService, opensDialog: true);
-            menu.Items.Add<ActionMove>(LocalizedTextService, opensDialog: true);
-            menu.Items.Add<ActionDelete>(LocalizedTextService, opensDialog: true);
-            menu.Items.Add<ActionSort>(LocalizedTextService);
-            menu.Items.Add(new RefreshNode(LocalizedTextService, true));
+            menu.Items.Add<ActionNew>(LocalizedTextService, opensDialog: true, useLegacyIcon: false);
+            menu.Items.Add<ActionMove>(LocalizedTextService, opensDialog: true, useLegacyIcon: false);
+            menu.Items.Add<ActionDelete>(LocalizedTextService, opensDialog: true, useLegacyIcon: false);
+            menu.Items.Add<ActionSort>(LocalizedTextService, useLegacyIcon: false);
+            menu.Items.Add(new RefreshNode(LocalizedTextService, separatorBefore: true));
 
             //set the default to create
             menu.DefaultMenuAlias = ActionNew.ActionAlias;
@@ -183,7 +184,7 @@ public class MediaTreeController : ContentTreeControllerBase, ISearchableTree, I
     }
 
     /// <summary>
-    ///     Returns true or false if the current user has access to the node based on the user's allowed start node (path)
+        /// Returns true or false if the current user has access to the node based on the user's allowed start node (path) access
     ///     access
     /// </summary>
     /// <param name="id"></param>
diff --git a/src/Umbraco.Web.BackOffice/Trees/MediaTypeTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/MediaTypeTreeController.cs
index 1513a04a89..2f8cfafb02 100644
--- a/src/Umbraco.Web.BackOffice/Trees/MediaTypeTreeController.cs
+++ b/src/Umbraco.Web.BackOffice/Trees/MediaTypeTreeController.cs
@@ -111,7 +111,7 @@ public class MediaTypeTreeController : TreeController, ISearchableTree
             menu.DefaultMenuAlias = ActionNew.ActionAlias;
 
             // root actions
-            menu.Items.Add<ActionNew>(LocalizedTextService, opensDialog: true);
+            menu.Items.Add<ActionNew>(LocalizedTextService, opensDialog: true, useLegacyIcon: false);
             menu.Items.Add(new RefreshNode(LocalizedTextService));
             return menu;
         }
@@ -123,41 +123,42 @@ public class MediaTypeTreeController : TreeController, ISearchableTree
             // set the default to create
             menu.DefaultMenuAlias = ActionNew.ActionAlias;
 
-            menu.Items.Add<ActionNew>(LocalizedTextService, opensDialog: true);
+            menu.Items.Add<ActionNew>(LocalizedTextService, opensDialog: true, useLegacyIcon: false);
 
             menu.Items.Add(new MenuItem("rename", LocalizedTextService.Localize("actions", "rename"))
             {
-                Icon = "icon icon-edit"
+                Icon = "icon-edit",
+                UseLegacyIcon = false,
             });
 
             if (container.HasChildren == false)
             {
                 // can delete doc type
-                menu.Items.Add<ActionDelete>(LocalizedTextService, opensDialog: true);
+                    menu.Items.Add<ActionDelete>(LocalizedTextService, opensDialog: true, useLegacyIcon: false);
             }
 
-            menu.Items.Add(new RefreshNode(LocalizedTextService, true));
+            menu.Items.Add(new RefreshNode(LocalizedTextService, separatorBefore: true));
         }
         else
         {
             IMediaType? ct = _mediaTypeService.Get(int.Parse(id, CultureInfo.InvariantCulture));
             IMediaType? parent = ct == null ? null : _mediaTypeService.Get(ct.ParentId);
 
-            menu.Items.Add<ActionNew>(LocalizedTextService, opensDialog: true);
+            menu.Items.Add<ActionNew>(LocalizedTextService, opensDialog: true, useLegacyIcon: false);
 
             // no move action if this is a child doc type
             if (parent == null)
             {
-                menu.Items.Add<ActionMove>(LocalizedTextService, true, true);
+                menu.Items.Add<ActionMove>(LocalizedTextService, hasSeparator: true, opensDialog: true, useLegacyIcon: false);
             }
 
-            menu.Items.Add<ActionCopy>(LocalizedTextService, opensDialog: true);
+            menu.Items.Add<ActionCopy>(LocalizedTextService, opensDialog: true, useLegacyIcon: false);
             if (ct?.IsSystemMediaType() == false)
             {
-                menu.Items.Add<ActionDelete>(LocalizedTextService, opensDialog: true);
+                menu.Items.Add<ActionDelete>(LocalizedTextService, opensDialog: true, useLegacyIcon: false);
             }
 
-            menu.Items.Add(new RefreshNode(LocalizedTextService, true));
+            menu.Items.Add(new RefreshNode(LocalizedTextService, separatorBefore: true));
         }
 
         return menu;
diff --git a/src/Umbraco.Web.BackOffice/Trees/MemberGroupTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/MemberGroupTreeController.cs
index f57675b888..af49c43e90 100644
--- a/src/Umbraco.Web.BackOffice/Trees/MemberGroupTreeController.cs
+++ b/src/Umbraco.Web.BackOffice/Trees/MemberGroupTreeController.cs
@@ -49,7 +49,6 @@ public class MemberGroupTreeController : MemberTypeAndGroupTreeControllerBase
     {
     }
 
-
     protected override IEnumerable<TreeNode> GetTreeNodesFromService(string id, FormCollection queryStrings)
         => _memberGroupService.GetAll()
             .OrderBy(x => x.Name)
diff --git a/src/Umbraco.Web.BackOffice/Trees/MemberTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/MemberTreeController.cs
index 088ef8c33e..22cef9c085 100644
--- a/src/Umbraco.Web.BackOffice/Trees/MemberTreeController.cs
+++ b/src/Umbraco.Web.BackOffice/Trees/MemberTreeController.cs
@@ -137,9 +137,10 @@ public class MemberTreeController : TreeController, ISearchableTree, ITreeNodeCo
                         "/list/" + memberType.Alias)));
         }
 
-        //There is no menu for any of these nodes
+            //There is no menu for any of these nodes
         nodes.ForEach(x => x.MenuUrl = null);
-        //All nodes are containers
+
+            //All nodes are containers
         nodes.ForEach(x => x.AdditionalData.Add("isContainer", true));
 
         return nodes;
@@ -156,14 +157,15 @@ public class MemberTreeController : TreeController, ISearchableTree, ITreeNodeCo
             menu.DefaultMenuAlias = ActionNew.ActionAlias;
 
             //Create the normal create action
-            menu.Items.Add<ActionNew>(LocalizedTextService, opensDialog: true);
+            menu.Items.Add<ActionNew>(LocalizedTextService, opensDialog: true, useLegacyIcon: false);
 
             menu.Items.Add(new RefreshNode(LocalizedTextService, true));
+
             return menu;
         }
 
         //add delete option for all members
-        menu.Items.Add<ActionDelete>(LocalizedTextService, opensDialog: true);
+        menu.Items.Add<ActionDelete>(LocalizedTextService, opensDialog: true, useLegacyIcon: false);
 
         if (_backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.HasAccessToSensitiveData() ?? false)
         {
diff --git a/src/Umbraco.Web.BackOffice/Trees/MemberTypeAndGroupTreeControllerBase.cs b/src/Umbraco.Web.BackOffice/Trees/MemberTypeAndGroupTreeControllerBase.cs
index d935fe6d31..f9efabd83f 100644
--- a/src/Umbraco.Web.BackOffice/Trees/MemberTypeAndGroupTreeControllerBase.cs
+++ b/src/Umbraco.Web.BackOffice/Trees/MemberTypeAndGroupTreeControllerBase.cs
@@ -80,11 +80,11 @@ public abstract class MemberTypeAndGroupTreeControllerBase : TreeController
         IMemberType? memberType = _memberTypeService.Get(int.Parse(id));
         if (memberType != null)
         {
-            menu.Items.Add<ActionCopy>(LocalizedTextService, opensDialog: true);
+            menu.Items.Add<ActionCopy>(LocalizedTextService, opensDialog: true, useLegacyIcon: false);
         }
 
         // delete member type/group
-        menu.Items.Add<ActionDelete>(LocalizedTextService, opensDialog: true);
+        menu.Items.Add<ActionDelete>(LocalizedTextService, opensDialog: true, useLegacyIcon: false);
 
         return menu;
     }
diff --git a/src/Umbraco.Web.BackOffice/Trees/RelationTypeTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/RelationTypeTreeController.cs
index 495d7fef07..40512caae7 100644
--- a/src/Umbraco.Web.BackOffice/Trees/RelationTypeTreeController.cs
+++ b/src/Umbraco.Web.BackOffice/Trees/RelationTypeTreeController.cs
@@ -44,10 +44,10 @@ public class RelationTypeTreeController : TreeController
         if (id == Constants.System.RootString)
         {
             //Create the normal create action
-            menu.Items.Add<ActionNew>(LocalizedTextService);
+            menu.Items.Add<ActionNew>(LocalizedTextService, useLegacyIcon: false);
 
             //refresh action
-            menu.Items.Add(new RefreshNode(LocalizedTextService, true));
+            menu.Items.Add(new RefreshNode(LocalizedTextService, separatorBefore: true));
 
             return menu;
         }
@@ -60,7 +60,7 @@ public class RelationTypeTreeController : TreeController
 
         if (relationType.IsSystemRelationType() == false)
         {
-            menu.Items.Add<ActionDelete>(LocalizedTextService);
+            menu.Items.Add<ActionDelete>(LocalizedTextService, useLegacyIcon: false);
         }
 
         return menu;
@@ -73,8 +73,7 @@ public class RelationTypeTreeController : TreeController
         if (id == Constants.System.RootString)
         {
             nodes.AddRange(_relationService.GetAllRelationTypes()
-                .Select(rt => CreateTreeNode(rt.Id.ToString(), id, queryStrings, rt.Name,
-                    "icon-trafic", false)));
+                .Select(rt => CreateTreeNode(rt.Id.ToString(), id, queryStrings, rt.Name, "icon-trafic", hasChildren: false)));
         }
 
         return nodes;
diff --git a/src/Umbraco.Web.BackOffice/Trees/TemplatesTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/TemplatesTreeController.cs
index f0cd207f26..985bad3f63 100644
--- a/src/Umbraco.Web.BackOffice/Trees/TemplatesTreeController.cs
+++ b/src/Umbraco.Web.BackOffice/Trees/TemplatesTreeController.cs
@@ -119,14 +119,14 @@ public class TemplatesTreeController : TreeController, ISearchableTree
         MenuItemCollection menu = _menuItemCollectionFactory.Create();
 
         //Create the normal create action
-        MenuItem? item = menu.Items.Add<ActionNew>(LocalizedTextService, opensDialog: true);
+        MenuItem? item = menu.Items.Add<ActionNew>(LocalizedTextService, opensDialog: true, useLegacyIcon: false);
         item?.NavigateToRoute(
             $"{queryStrings.GetRequiredValue<string>("application")}/templates/edit/{id}?create=true");
 
         if (id == Constants.System.RootString)
         {
-            //refresh action
-            menu.Items.Add(new RefreshNode(LocalizedTextService, true));
+                //refresh action
+            menu.Items.Add(new RefreshNode(LocalizedTextService, separatorBefore: true));
 
             return menu;
         }
@@ -139,16 +139,15 @@ public class TemplatesTreeController : TreeController, ISearchableTree
 
         EntitySlim entity = FromTemplate(template);
 
-        //don't allow delete if it has child layouts
+            //don't allow delete if it has child layouts
         if (template.IsMasterTemplate == false)
         {
             //add delete option if it doesn't have children
-            menu.Items.Add<ActionDelete>(LocalizedTextService, true, true);
+            menu.Items.Add<ActionDelete>(LocalizedTextService, hasSeparator: true, opensDialog: true);
         }
 
-        //add refresh
-        menu.Items.Add(new RefreshNode(LocalizedTextService, true));
-
+            //add refresh
+        menu.Items.Add(new RefreshNode(LocalizedTextService, separatorBefore: true));
 
         return menu;
     }
diff --git a/src/Umbraco.Web.Common/Middleware/BootFailedMiddleware.cs b/src/Umbraco.Web.Common/Middleware/BootFailedMiddleware.cs
index ea2c0e02e4..11abf725c2 100644
--- a/src/Umbraco.Web.Common/Middleware/BootFailedMiddleware.cs
+++ b/src/Umbraco.Web.Common/Middleware/BootFailedMiddleware.cs
@@ -2,31 +2,25 @@ using System.Text;
 using Microsoft.AspNetCore.Hosting;
 using Microsoft.AspNetCore.Http;
 using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.FileProviders;
 using Umbraco.Cms.Core;
 using Umbraco.Cms.Core.Exceptions;
 using Umbraco.Cms.Core.Services;
 using Umbraco.Cms.Web.Common.DependencyInjection;
-using Umbraco.Extensions;
 using IHostingEnvironment = Umbraco.Cms.Core.Hosting.IHostingEnvironment;
 
 namespace Umbraco.Cms.Web.Common.Middleware;
 
 /// <summary>
-///     Executes when Umbraco booting fails in order to show the problem
+/// Executes when Umbraco booting fails in order to show the problem.
 /// </summary>
+/// <seealso cref="Microsoft.AspNetCore.Http.IMiddleware" />
 public class BootFailedMiddleware : IMiddleware
 {
     private readonly IHostingEnvironment _hostingEnvironment;
     private readonly IWebHostEnvironment _webHostEnvironment;
     private readonly IRuntimeState _runtimeState;
 
-    public BootFailedMiddleware(IRuntimeState runtimeState, IHostingEnvironment hostingEnvironment)
-    : this(runtimeState, hostingEnvironment, StaticServiceProvider.Instance.GetRequiredService<IWebHostEnvironment>())
-    {
-        _runtimeState = runtimeState;
-        _hostingEnvironment = hostingEnvironment;
-    }
-
     public BootFailedMiddleware(IRuntimeState runtimeState, IHostingEnvironment hostingEnvironment, IWebHostEnvironment webHostEnvironment)
     {
         _runtimeState = runtimeState;
@@ -34,6 +28,11 @@ public class BootFailedMiddleware : IMiddleware
         _webHostEnvironment = webHostEnvironment;
     }
 
+    [Obsolete("Use ctor with all params. This will be removed in Umbraco 12")]
+    public BootFailedMiddleware(IRuntimeState runtimeState, IHostingEnvironment hostingEnvironment)
+        : this(runtimeState, hostingEnvironment, StaticServiceProvider.Instance.GetRequiredService<IWebHostEnvironment>())
+    { }
+
     public async Task InvokeAsync(HttpContext context, RequestDelegate next)
     {
         // TODO: It would be possible to redirect to the installer here in debug mode while
@@ -53,10 +52,12 @@ public class BootFailedMiddleware : IMiddleware
                 context.Response.Clear();
                 context.Response.StatusCode = 500;
 
-                var file = GetBootErrorFileName();
-
-                var viewContent = await File.ReadAllTextAsync(file);
-                await context.Response.WriteAsync(viewContent, Encoding.UTF8);
+                IFileInfo? fileInfo = GetBootErrorFileInfo();
+                if (fileInfo is not null)
+                {
+                    using var sr = new StreamReader(fileInfo.CreateReadStream(), Encoding.UTF8);
+                    await context.Response.WriteAsync(await sr.ReadToEndAsync(), Encoding.UTF8);
+                }
             }
         }
         else
@@ -65,14 +66,20 @@ public class BootFailedMiddleware : IMiddleware
         }
     }
 
-    private string GetBootErrorFileName()
+    private IFileInfo? GetBootErrorFileInfo()
     {
-        var fileName = _webHostEnvironment.MapPathWebRoot("~/config/errors/BootFailed.html");
-        if (File.Exists(fileName))
+        IFileInfo? fileInfo = _webHostEnvironment.WebRootFileProvider.GetFileInfo("config/errors/BootFailed.html");
+        if (fileInfo.Exists)
         {
-            return fileName;
+            return fileInfo;
         }
 
-        return _webHostEnvironment.MapPathWebRoot("~/umbraco/views/errors/BootFailed.html");
+        fileInfo = _webHostEnvironment.WebRootFileProvider.GetFileInfo("umbraco/views/errors/BootFailed.html");
+        if (fileInfo.Exists)
+        {
+            return fileInfo;
+        }
+
+        return null;
     }
 }
diff --git a/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj b/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj
index e08f45e5af..c75ddfe98f 100644
--- a/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj
+++ b/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj
@@ -40,7 +40,7 @@
     <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="all" />
     <PackageReference Include="MiniProfiler.AspNetCore.Mvc" Version="4.2.22" />
     <PackageReference Include="Serilog.AspNetCore" Version="5.0.0" />
-    <PackageReference Include="SixLabors.ImageSharp.Web" Version="2.0.1" />
+    <PackageReference Include="SixLabors.ImageSharp.Web" Version="2.0.2" />
     <PackageReference Include="Smidge.InMemory" Version="4.1.0" />
     <PackageReference Include="Smidge.Nuglify" Version="4.1.0" />
     <PackageReference Include="Umbraco.Code" Version="2.0.0" PrivateAssets="all" />
diff --git a/src/Umbraco.Web.Common/Views/UmbracoViewPage.cs b/src/Umbraco.Web.Common/Views/UmbracoViewPage.cs
index 0b3132b795..c9b64e006a 100644
--- a/src/Umbraco.Web.Common/Views/UmbracoViewPage.cs
+++ b/src/Umbraco.Web.Common/Views/UmbracoViewPage.cs
@@ -39,21 +39,19 @@ public abstract class UmbracoViewPage<TModel> : RazorPage<TModel>
                 return _helper;
             }
 
+            _helper = Context.RequestServices.GetRequiredService<UmbracoHelper>();
+
             TModel model = ViewData.Model;
             var content = model as IPublishedContent;
+
             if (content is null && model is IContentModel contentModel)
             {
                 content = contentModel.Content;
             }
 
-            if (content is null)
-            {
-                content = UmbracoContext?.PublishedRequest?.PublishedContent;
-            }
+            content ??= UmbracoContext?.PublishedRequest?.PublishedContent;
 
-            _helper = Context.RequestServices.GetRequiredService<UmbracoHelper>();
-
-            if (!(content is null))
+            if (content is not null)
             {
                 _helper.AssignedContentItem = content;
             }
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbcontextmenu.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbcontextmenu.directive.js
index f666e62587..7749dacdcc 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbcontextmenu.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbcontextmenu.directive.js
@@ -7,7 +7,7 @@
  * Handles the click events on the context menu
 **/
 angular.module("umbraco.directives")
-.directive('umbContextMenu', function (navigationService, keyboardService, backdropService) {
+.directive('umbContextMenu', function (navigationService, keyboardService) {
     return {
         scope: {
             menuDialogTitle: "@",
@@ -20,22 +20,27 @@ angular.module("umbraco.directives")
         templateUrl: 'views/components/application/umb-contextmenu.html',
         link: function (scope, element, attrs, ctrl) {
 
+            // Map action icons using legacy icon font or svg icons.
+            Utilities.forEach(scope.menuActions, action => {
+              action.icon = (action.useLegacyIcon ? 'icon-' : '') + action.icon;
+            });
+            
             //adds a handler to the context menu item click, we need to handle this differently
             //depending on what the menu item is supposed to do.
-            scope.executeMenuItem = function (action) {
+            scope.executeMenuItem = action => {
                 navigationService.executeMenuAction(action, scope.currentNode, scope.currentSection);
             };
-
-            scope.outSideClick = function() {
+            
+            scope.outSideClick = () => {
                 navigationService.hideNavigation();
             };
-
-            keyboardService.bind("esc", function() {
+            
+            keyboardService.bind("esc", () =>  {
                 navigationService.hideNavigation();
             });
-
+            
             //ensure to unregister from all events!
-            scope.$on('$destroy', function () {
+            scope.$on('$destroy', () =>  {
                 keyboardService.unbind("esc");
             });
         }
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js
index 501ea9f81a..aba292523d 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js
@@ -1,4 +1,4 @@
-﻿(function () {
+(function () {
     'use strict';
 
     function ContentNodeInfoDirective($timeout, logResource, eventsService, userService, localizationService, dateHelper, editorService, redirectUrlsResource, overlayService, entityResource) {
@@ -9,6 +9,7 @@
             var isInfoTab = false;
             var auditTrailLoaded = false;
             var labels = {};
+
             scope.publishStatus = [];
             scope.currentVariant = null;
             scope.currentUrls = [];
@@ -22,19 +23,17 @@
             scope.historyLabelKey = scope.node.variants && scope.node.variants.length === 1 ? "general_history" : "auditTrails_historyIncludingVariants";
 
             function onInit() {
-                entityResource.getAll("Template").then(function (templates) {
-                    scope.allTemplates = templates;
-                });
+                entityResource.getAll("Template").then(templates => scope.allTemplates = templates);
 
                 // set currentVariant
-                scope.currentVariant = _.find(scope.node.variants, (v) => v.active);
+                scope.currentVariant = scope.node.variants.find(v => v.active);
 
                 updateCurrentUrls();
 
                 // if there are any infinite editors open we are in infinite editing
                 scope.isInfiniteMode = editorService.getNumberOfEditors() > 0 ? true : false;
 
-                userService.getCurrentUser().then(function (user) {
+                userService.getCurrentUser().then(user => {
                     // only allow change of media type if user has access to the settings sections
                     const hasAccessToSettings = user.allowedSections.indexOf("settings") !== -1 ? true : false;
                     scope.allowChangeDocumentType = hasAccessToSettings;
@@ -54,7 +53,7 @@
                 ];
 
                 localizationService.localizeMany(keys)
-                    .then(function (data) {
+                    .then(data => {
                         [labels.deleted,
                         labels.unpublished,
                         labels.published,
@@ -81,7 +80,7 @@
                     });
 
                 scope.auditTrailOptions = {
-                    "id": scope.node.id
+                    id: scope.node.id
                 };
 
                 // make sure dates are formatted to the user's locale
@@ -101,10 +100,11 @@
                     scope.previewOpenUrl = '#/settings/documentTypes/edit/' + scope.documentType.id;
                 }
 
-                var activeApp = _.find(scope.node.apps, (a) => a.active);
+                var activeApp = scope.node.apps.find(a => a.active);
                 if (activeApp.alias === "umbInfo") {
                     loadRedirectUrls();
                     loadAuditTrail();
+                    isInfoTab = true;
                 }
 
                 // never show templates for element types (if they happen to have been created in the content tree)
@@ -118,12 +118,8 @@
 
             scope.openDocumentType = function (documentType) {
 
-                const variantIsDirty = _.some(scope.node.variants, function (variant) {
-                    return variant.isDirty;
-                });
-
                 // add confirmation dialog before opening the doc type editor
-                if (variantIsDirty) {
+                if (scope.node.variants.some(variant => variant.isDirty)) {
                     const confirm = {
                         title: labels.unsavedChanges,
                         view: "default",
@@ -149,7 +145,7 @@
             function openDocTypeEditor(documentType) {
                 const editor = {
                     id: documentType.id,
-                    submit: function (model) {
+                    submit: function () {
                         editorService.close();
                     },
                     close: function () {
@@ -160,13 +156,14 @@
             }
 
             scope.openTemplate = function () {
-                var template = _.findWhere(scope.allTemplates, { alias: scope.node.template })
+                var template = scope.allTemplates.find(x => x.alias === scope.node.template);
+
                 if (!template) {
                     return;
                 }
                 var templateEditor = {
                     id: template.id,
-                    submit: function (model) {
+                    submit: function () {
                         editorService.close();
                     },
                     close: function () {
@@ -185,7 +182,7 @@
 
                 var rollback = {
                     node: scope.node,
-                    submit: function (model) {
+                    submit: function () {
                         const args = { node: scope.node };
                         eventsService.emit("editors.content.reload", args);
                         editorService.close();
@@ -211,7 +208,7 @@
 
                         // get current backoffice user and format dates
                         userService.getCurrentUser().then(currentUser => {
-                            Utilities.forEach(data.items, item => {
+                            data.items.forEach(item => {
                                 item.timestampFormatted = dateHelper.getLocalDate(item.timestamp, currentUser.locale, 'LLL');
                             });
                         });
@@ -255,7 +252,7 @@
             }
 
             function setAuditTrailLogTypeColor(auditTrail) {
-                Utilities.forEach(auditTrail, item => {
+                auditTrail.forEach(item => {
 
                     switch (item.logType) {
                         case "Save":
@@ -325,10 +322,14 @@
 
                 // find the urls for the currently selected language
                 // when there is no selected language (allow vary by culture == false), show all urls of the node.
-                scope.currentUrls = _.filter(scope.node.urls, (url) => (scope.currentVariant.language == null || scope.currentVariant.language.culture === url.culture));
+                scope.currentUrls = scope.node.urls.filter(url => scope.currentVariant.language == null || scope.currentVariant.language.culture === url.culture);
 
                 // figure out if multiple cultures apply across the content URLs
-                scope.currentUrlsHaveMultipleCultures = _.keys(_.groupBy(scope.currentUrls, url => url.culture)).length > 1;
+                // by getting an array of the url cultures, then checking that more than one culture exists in the array
+                scope.currentUrlsHaveMultipleCultures = scope.currentUrls
+                    .map(x => x.culture)
+                    .filter((v, i, arr) => arr.indexOf(v) === i)
+                    .length > 1;
             }
 
             // load audit trail and redirects when on the info tab
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditormenu.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditormenu.directive.js
index 281a52e4e5..37ce9096f6 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditormenu.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditormenu.directive.js
@@ -1,7 +1,7 @@
 (function () {
     'use strict';
 
-    function EditorMenuDirective($injector, treeService, navigationService, umbModelMapper, appState) {
+    function EditorMenuDirective(treeService, navigationService, appState) {
 
         function link(scope, el, attr, ctrl) {
 
@@ -17,7 +17,7 @@
 
             //adds a handler to the context menu item click, we need to handle this differently
             //depending on what the menu item is supposed to do.
-            scope.executeMenuItem = function (action) {
+            scope.executeMenuItem = action => {
                 //the action is called as it would be by the tree. to ensure that the action targets the correct node, 
                 //we need to set the current node in appState before calling the action. otherwise we break all actions
                 //that use the current node (and that's pretty much all of them)
@@ -34,10 +34,14 @@
                 }
 
                 if (!scope.actions) {
-                    treeService.getMenu({ treeNode: scope.currentNode })
-                        .then(function (data) {
-                            scope.actions = data.menuItems;
+                    treeService.getMenu({ treeNode: scope.currentNode }).then(data => {
+                        scope.actions = data.menuItems;
+
+                        // Map action icons using legacy icon font or svg icons.
+                        Utilities.forEach(scope.actions, action => {
+                          action.icon = (action.useLegacyIcon ? 'icon-' : '') + action.icon;
                         });
+                    });
                 }
             };
 
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbpropertyactions.component.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbpropertyactions.component.js
index 41dbd9f547..3503cde819 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbpropertyactions.component.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbpropertyactions.component.js
@@ -22,6 +22,7 @@
 
         vm.$onDestroy = onDestroy;
         vm.$onInit = onInit;
+        vm.$onChanges = onChanges;
 
         function initDropDown() {
             keyboardService.bind("esc", vm.close);
@@ -72,6 +73,25 @@
                 vm.labels.closeText = values[1];
             });
         }
+
+        function onChanges(simpleChanges) {
+            if (simpleChanges.actions) {
+
+              let actions = simpleChanges.actions.currentValue || [];
+
+              Utilities.forEach(actions, action => {
+
+                if (action.labelKey) {
+                    localizationService.localize(action.labelKey, (action.labelTokens || []), action.label).then(data => {
+                      action.label = data;
+                    });
+                    
+                    action.useLegacyIcon = action.useLegacyIcon === false ? false : true;
+                    action.icon = (action.useLegacyIcon ? 'icon-' : '') + action.icon;
+                }
+              });
+            }
+        }
     }
 
     var umbPropertyActionsComponent = {
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umblightbox.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umblightbox.directive.js
index 8d223e427e..0874c54cd2 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umblightbox.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umblightbox.directive.js
@@ -94,6 +94,8 @@
                     for (var e in eventBindings) {
                         eventBindings[e]();
                     }
+                    
+                    el.remove();
                 });
             }
 
diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/dictionary.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/dictionary.resource.js
index 38a96fbcda..fd81668d04 100644
--- a/src/Umbraco.Web.UI.Client/src/common/resources/dictionary.resource.js
+++ b/src/Umbraco.Web.UI.Client/src/common/resources/dictionary.resource.js
@@ -160,6 +160,77 @@ function dictionaryResource($q, $http, $location, umbRequestHelper, umbDataForma
             "Failed to save data for dictionary id " + dictionary.id);
     }
 
+    /**
+        * @ngdoc method
+        * @name umbraco.resources.dictionaryResource#export
+        * @methodOf umbraco.resources.dictionaryResource
+        *
+        * @description
+        * Export dictionary items of a given id.
+        *
+        * ##usage
+        * <pre>
+        * dictionaryResource.exportItem(1234){
+        *    .then(function() {
+        *       Do stuff..
+        *    });
+        * </pre>
+        *
+        * @param {Int} id the ID of the dictionary item so export
+        * @param {Bool?} includeChildren if children should also be exported
+        * @returns {Promise} resourcePromise object.
+        *
+        */
+      function exportItem(id, includeChildren) {
+          if (!id) {
+              throw "id cannot be null";
+          }
+
+          var url = umbRequestHelper.getApiUrl("dictionaryApiBaseUrl", "ExportDictionary", { id: id, includeChildren: includeChildren });
+
+          return umbRequestHelper.downloadFile(url).then(function () {
+              localizationService.localize("speechBubbles_dictionaryItemExportedSuccess").then(function(value) {
+                  notificationsService.success(value);
+              });
+          }, function (data) {
+              localizationService.localize("speechBubbles_dictionaryItemExportedError").then(function(value) {
+                  notificationsService.error(value);
+              });
+          });
+      }
+
+    /**
+        * @ngdoc method
+        * @name umbraco.resources.dictionaryResource#import
+        * @methodOf umbraco.resources.dictionaryResource
+        *
+        * @description
+        * Import a dictionary item from a file
+        *
+        * ##usage
+        * <pre>
+        * dictionaryResource.importItem("path to file"){
+        *    .then(function() {
+        *       Do stuff..
+        *    });
+        * </pre>
+        *
+        * @param {String} file path of the file to import
+        * @param {Int?} parentId the int of the parent dictionary item to move incomming dictionary items to
+        * @returns {Promise} resourcePromise object.
+        *
+        */
+    function importItem(file, parentId) {
+        if (!file) {
+            throw "file cannot be null";
+        }
+
+      return umbRequestHelper.resourcePromise(
+        $http.post(umbRequestHelper.getApiUrl("dictionaryApiBaseUrl", "ImportDictionary", { file: file, parentId: parentId })),
+            "Failed to import dictionary item " + file
+        );
+    }
+
     /**
          * @ngdoc method
          * @name umbraco.resources.dictionaryResource#getList
@@ -194,6 +265,8 @@ function dictionaryResource($q, $http, $location, umbRequestHelper, umbDataForma
     getById: getById,
     save: save,
     move: move,
+    exportItem: exportItem,
+    importItem: importItem,
     getList : getList
   };
 
diff --git a/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js
index f4c6063a9a..e7ecb5c93c 100644
--- a/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js
+++ b/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js
@@ -506,6 +506,37 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt
 
         },
 
+        /**
+         * @ngdoc method
+         * @name umbraco.services.contentEditingHelper#getPermissionsForContent
+         * @methodOf umbraco.services.contentEditingHelper
+         * @function
+         *
+         * @description
+         * Returns a object with permissions for user.
+         */
+        getPermissionsForContent: function () {
+
+            // Just ensure we do have an editorState
+            if (!editorState.current) return null;
+            
+            // Fetch current node allowed actions for the current user
+            // This is the current node & not each individual child node in the list
+            const currentUserPermissions = editorState.current.allowedActions || [];
+
+            // Create a nicer model rather than the funky & hard to remember permissions strings
+            const currentNodePermissions = {
+                canCopy: currentUserPermissions.includes('O'), //Magic Char = O
+                canCreate: currentUserPermissions.includes('C'), //Magic Char = C
+                canDelete: currentUserPermissions.includes('D'), //Magic Char = D
+                canMove: currentUserPermissions.includes('M'), //Magic Char = M
+                canPublish: currentUserPermissions.includes('U'), //Magic Char = U
+                canUnpublish: currentUserPermissions.includes('Z') //Magic Char = Z
+            };
+            
+            return currentNodePermissions;
+        },
+
         /**
          * @ngdoc method
          * @name umbraco.services.contentEditingHelper#reBindChangedProperties
diff --git a/src/Umbraco.Web.UI.Client/src/common/services/history.service.js b/src/Umbraco.Web.UI.Client/src/common/services/history.service.js
index 114d002a4d..d94ff52183 100644
--- a/src/Umbraco.Web.UI.Client/src/common/services/history.service.js
+++ b/src/Umbraco.Web.UI.Client/src/common/services/history.service.js
@@ -130,7 +130,7 @@ angular.module('umbraco.services')
         getLastAccessedItemForSection: function (sectionAlias) {
             for (var i = 0, len = nArray.length; i < len; i++) {
                 var item = nArray[i];
-                if (item.link.indexOf(sectionAlias + "/") === 0) {
+                if (item.link === sectionAlias || item.link.indexOf(sectionAlias + "/") === 0) {
                     return item;
                 }
             }
@@ -138,4 +138,4 @@ angular.module('umbraco.services')
             return null;
         }
 	};
-});
\ No newline at end of file
+});
diff --git a/src/Umbraco.Web.UI.Client/src/installer/installer.service.js b/src/Umbraco.Web.UI.Client/src/installer/installer.service.js
index 27b1381f02..c7f2645b97 100644
--- a/src/Umbraco.Web.UI.Client/src/installer/installer.service.js
+++ b/src/Umbraco.Web.UI.Client/src/installer/installer.service.js
@@ -19,7 +19,7 @@ angular.module("umbraco.install").factory('installerService', function ($rootSco
 					"Over 500 000 websites are currently powered by Umbraco",
 					"At least 2 people have named their cat 'Umbraco'",
 					"On an average day more than 1000 people download Umbraco",
-					"<a target='_blank' rel='noopener' href='https://umbraco.tv/'>umbraco.tv</a> is the premier source of Umbraco video tutorials to get you started",
+					"<a target='_blank' rel='noopener' href='https://www.youtube.com/channel/UCbGfwSAPflebnadyhEPw-wA?ref=tvFromInstaller'>Umbraco Learning Base</a> is the premier source of Umbraco video tutorials to get you started",
 					"You can find the world's friendliest CMS community at <a target='_blank' rel='noopener' href='https://our.umbraco.com/'>our.umbraco.com</a>",
 					"You can become a certified Umbraco developer by attending one of the official courses",
 					"Umbraco works really well on tablets",
diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/database.html b/src/Umbraco.Web.UI.Client/src/installer/steps/database.html
index dc3b972718..e248f3221b 100644
--- a/src/Umbraco.Web.UI.Client/src/installer/steps/database.html
+++ b/src/Umbraco.Web.UI.Client/src/installer/steps/database.html
@@ -1,144 +1,188 @@
 <div ng-controller="Umbraco.Installer.DataBaseController">
+  <h1>Configure your database</h1>
+  <p>
+    Enter connection and authentication details for the database you want to
+    install Umbraco on
+  </p>
 
-    <h1>Configure your database</h1>
-    <p>
-        Enter connection and authentication details for the database you want to install Umbraco on
-    </p>
+  <form
+    name="myForm"
+    class="form-horizontal -no-margin-bottom"
+    novalidate
+    ng-submit="validateAndForward();"
+  >
+    <div class="control-group">
+      <legend>What type of database do you use?</legend>
+      <label class="control-label" for="dbType">Database type</label>
+      <div class="controls">
+        <select
+          id="dbType"
+          ng-options="db as db.displayName for db in dbs"
+          required
+          ng-model="selectedDbMeta"
+        ></select>
+      </div>
+    </div>
 
-    <form name="myForm" class="form-horizontal" novalidate ng-submit="validateAndForward();">
-        <div class="control-group">
-            <legend>What type of database do you use?</legend>
-            <label class="control-label" for="dbType">Database type</label>
+    <div ng-if="isCustom()">
+      <legend>What is the exact connection string we should use?</legend>
+      <div class="control-group">
+        <label class="control-label" for="Custom_connectionString"
+          >Connection string</label
+        >
+        <div class="controls">
+          <textarea
+            id="Custom_connectionString"
+            class="input-block-level"
+            required
+            ng-model="installer.current.model.connectionString"
+            rows="5"
+          ></textarea>
+          <small class="inline-help"
+            >Enter a valid database connection string.</small
+          >
+        </div>
+      </div>
+
+      <div class="control-group">
+        <label class="control-label" for="Custom_providerName"
+          >Provider Name</label
+        >
+        <div class="controls">
+          <select
+            id="dbType"
+            ng-options="providerName for providerName in providerNames"
+            required
+            ng-model="installer.current.model.providerName"
+          ></select>
+        </div>
+      </div>
+    </div>
+
+    <div ng-if="!isCustom()">
+      <div class="row">
+        <legend>Where do we find your database?</legend>
+
+        <div ng-if="selectedDbMeta.requiresServer">
+          <div class="span6">
+            <div class="control-group">
+              <label class="control-label" for="Sql_Server">Server</label>
+              <div class="controls">
+                <input
+                  type="text"
+                  id="Sql_Server"
+                  placeholder="{{selectedDbMeta.serverPlaceholder}}"
+                  required
+                  ng-model="installer.current.model.server"
+                />
+                <small class="inline-help">Enter server domain or IP</small>
+              </div>
+            </div>
+          </div>
+        </div>
+
+        <div class="span6">
+          <div class="control-group">
+            <label class="control-label" for="Sql_databaseName"
+              >Database name</label
+            >
             <div class="controls">
-                <select id="dbType"
-                        ng-options="db as db.displayName for db in dbs"
-                        required
-                        ng-model="selectedDbMeta">
-                </select>
+              <input
+                type="text"
+                id="Sql_databaseName"
+                placeholder="umbraco-cms"
+                required
+                ng-model="installer.current.model.databaseName"
+              />
+              <small class="inline-help">Enter the name of the database</small>
             </div>
+          </div>
         </div>
+      </div>
 
-        <div ng-if="isCustom()">
-            <legend>What is the exact connection string we should use?</legend>
-            <div class="control-group">
-                <label class="control-label" for="Custom_connectionString">Connection string</label>
-                  <div class="controls">
-                    <textarea id="Custom_connectionString"
-                              class="input-block-level"
-                              required
-                              ng-model="installer.current.model.connectionString"
-                              rows="5"></textarea>
-                    <small class="inline-help">Enter a valid database connection string.</small>
-                </div>
-            </div>
-
-           <div class="control-group">
-                <label class="control-label" for="Custom_providerName">Provider Name</label>
-                  <div class="controls">
-                       <select id="dbType"
-                               ng-options="providerName for providerName in providerNames"
-                               required
-                               ng-model="installer.current.model.providerName">
-                        </select>
-                </div>
-            </div>
-        </div>
-
-
-
-        <div ng-if="!isCustom()">
-            <div class="row">
-                <legend>Where do we find your database?</legend>
-
-                <div ng-if="selectedDbMeta.requiresServer">
-                    <div class="span6">
-                        <div class="control-group">
-                            <label class="control-label" for="Sql_Server">Server</label>
-                            <div class="controls">
-                                <input type="text" id="Sql_Server"
-                                       placeholder="{{selectedDbMeta.serverPlaceholder}}"
-                                       required
-                                       ng-model="installer.current.model.server" />
-                                <small class="inline-help">Enter server domain or IP</small>
-                            </div>
-                        </div>
-                    </div>
-                </div>
-
-                <div class="span6">
-                    <div class="control-group">
-                        <label class="control-label" for="Sql_databaseName">Database name</label>
-                        <div class="controls">
-                            <input type="text" id="Sql_databaseName"
-                                   placeholder="umbraco-cms"
-                                   required
-                                   ng-model="installer.current.model.databaseName" />
-                            <small class="inline-help">Enter the name of the database</small>
-                        </div>
-                    </div>
-                </div>
-            </div>
-
-            <div ng-if="selectedDbMeta.requiresCredentials">
-                <div class="row">
-                    <legend>What credentials are used to access the database?</legend>
-                    <div class="span6">
-                        <div class="control-group" ng-if="!installer.current.model.integratedAuth">
-                            <label class="control-label" for="Sql_login">Login</label>
-                            <div class="controls">
-                                <input type="text" id="Sql_login"
-                                       placeholder="umbraco-db-user"
-                                       required
-                                       ng-model="installer.current.model.login" />
-                                <small class="inline-help">Enter the database user name</small>
-                            </div>
-                        </div>
-                    </div>
-
-                    <div class="span6">
-                        <div class="control-group" ng-if="!installer.current.model.integratedAuth">
-                        <label class="control-label" for="Sql_password">Password</label>
-                            <div class="controls">
-                                <input type="password" id="Sql_password"
-                                       placeholder="umbraco-db-password"
-                                       required
-                                       ng-model="installer.current.model.password" />
-                                <small class="inline-help">Enter the database password</small>
-                            </div>
-                        </div>
-                    </div>
-
-                    <div class="span12 control-group" ng-if="selectedDbMeta.supportsIntegratedAuthentication">
-                        <div class="controls">
-                            <label class="checkbox">
-                                <input type="checkbox" ng-model="installer.current.model.integratedAuth" />
-                                Use integrated authentication
-                            </label>
-                        </div>
-                    </div>
-                </div>
-            </div>
-       </div>
-
-
+      <div ng-if="selectedDbMeta.requiresCredentials">
         <div class="row">
-            <div class="control-group">
-                <div class="controls">
-                    <input type="submit" value="Install" class="btn btn-success"
-                           ng-disabled="myForm.$invalid || checking"
-                           ng-class="{disabled:myForm.$invalid}" />
-
-                    <button class="btn btn-info" ng-click="restart()">Go back</button>
-
-                    <span class="inline-help" ng-if="checking" ng-animate="'fade'">
-                        Validating your database connection...
-                    </span>
-
-                    <span class="inline-help error" ng-if="invalidDbDns" ng-animate="'fade'">
-                        Could not connect to database
-                    </span>
-                </div>
+          <legend>What credentials are used to access the database?</legend>
+          <div class="span6">
+            <div
+              class="control-group"
+              ng-if="!installer.current.model.integratedAuth"
+            >
+              <label class="control-label" for="Sql_login">Login</label>
+              <div class="controls">
+                <input
+                  type="text"
+                  id="Sql_login"
+                  placeholder="umbraco-db-user"
+                  required
+                  ng-model="installer.current.model.login"
+                />
+                <small class="inline-help">Enter the database user name</small>
+              </div>
             </div>
+          </div>
+
+          <div class="span6">
+            <div
+              class="control-group"
+              ng-if="!installer.current.model.integratedAuth"
+            >
+              <label class="control-label" for="Sql_password">Password</label>
+              <div class="controls">
+                <input
+                  type="password"
+                  id="Sql_password"
+                  placeholder="umbraco-db-password"
+                  required
+                  ng-model="installer.current.model.password"
+                />
+                <small class="inline-help">Enter the database password</small>
+              </div>
+            </div>
+          </div>
+
+          <div
+            class="span12 control-group"
+            ng-if="selectedDbMeta.supportsIntegratedAuthentication"
+          >
+            <div class="controls">
+              <label class="checkbox">
+                <input
+                  type="checkbox"
+                  ng-model="installer.current.model.integratedAuth"
+                />
+                Use integrated authentication
+              </label>
+            </div>
+          </div>
         </div>
-    </form>
+      </div>
+    </div>
+
+    <div class="row">
+      <div class="control-actions controls-space-between">
+        <button class="btn btn-info" ng-click="restart()">Go back</button>
+        <button
+          class="btn btn-success"
+          ng-disabled="myForm.$invalid || checking"
+          ng-class="{disabled:myForm.$invalid}"
+        >
+          Install
+        </button>
+      </div>
+      <div>
+        <span ng-if="checking" class="inline-help" ng-animate="'fade'">
+          Validating your database connection...
+        </span>
+
+        <span
+          ng-if="invalidDbDns"
+          class="inline-help error"
+          ng-animate="'fade'"
+        >
+          Could not connect to database
+        </span>
+      </div>
+    </div>
+  </form>
 </div>
diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/user.controller.js b/src/Umbraco.Web.UI.Client/src/installer/steps/user.controller.js
index b4aa8eedf5..dab3f1beaf 100644
--- a/src/Umbraco.Web.UI.Client/src/installer/steps/user.controller.js
+++ b/src/Umbraco.Web.UI.Client/src/installer/steps/user.controller.js
@@ -3,7 +3,7 @@ angular.module("umbraco.install").controller("Umbraco.Install.UserController", f
   $scope.majorVersion = Umbraco.Sys.ServerVariables.application.version;
   $scope.passwordPattern = /.*/;
   $scope.installer.current.model.subscribeToNewsLetter = $scope.installer.current.model.subscribeToNewsLetter || false;
-  $scope.installer.current.model.telemetryLevel = $scope.installer.current.model.telemetryLevel || $scope.installer.current.model.consentLevels[1].level;
+  setTelemetryLevelAndDescription($scope.installer.current.model.telemetryIndex ?? 1);
 
   if ($scope.installer.current.model.minNonAlphaNumericLength > 0) {
     var exp = "";
@@ -15,11 +15,6 @@ angular.module("umbraco.install").controller("Umbraco.Install.UserController", f
     $scope.passwordPattern = new RegExp(exp);
   }
 
-  $scope.consentTooltip = {
-    show: false,
-    event: null
-  };
-
   if ('noUiSlider' in window) {
     let consentSliderStartLevel = 2;
     const initialConsentLevel = $scope.installer.current.model.consentLevels.findIndex(x => x.level === $scope.installer.current.model.telemetryLevel);
@@ -36,6 +31,7 @@ angular.module("umbraco.install").controller("Umbraco.Install.UserController", f
         "min": 1,
         "max": 3
       },
+      behaviour: 'smooth-steps-tap',
       pips: {
         mode: 'values',
         density: 50,
@@ -63,22 +59,6 @@ angular.module("umbraco.install").controller("Umbraco.Install.UserController", f
       });
 
       pips.forEach(function (pip) {
-        pip.addEventListener('mouseenter', function (e) {
-          $scope.$apply(function () {
-            const value = pip.getAttribute('data-value');
-            $scope.consentTooltip.show = true;
-            $scope.consentTooltip.event = e;
-            $scope.consentTooltip.description = $sce.trustAsHtml($scope.installer.current.model.consentLevels[value - 1].description);
-          });
-        });
-
-        pip.addEventListener('mouseleave', function () {
-          $scope.$apply(function () {
-            $scope.consentTooltip.show = false;
-            $scope.consentTooltip.event = null;
-            $scope.consentTooltip.description = '';
-          });
-        });
 
         pip.addEventListener('click', function () {
           const value = pip.getAttribute('data-value');
@@ -99,8 +79,16 @@ angular.module("umbraco.install").controller("Umbraco.Install.UserController", f
   };
 
   function onChangeConsent(values) {
-    const result = Number(values[0]);
-    $scope.installer.current.model.telemetryLevel = $scope.installer.current.model.consentLevels[result - 1].level;
+    const result = Number(values[0]) - 1;
+    $scope.$apply(() => {
+      setTelemetryLevelAndDescription(result);
+    });
   };
 
+  function setTelemetryLevelAndDescription(idx) {
+    $scope.telemetryDescription = $sce.trustAsHtml($scope.installer.current.model.consentLevels[idx].description);
+    $scope.installer.current.model.telemetryIndex = idx;
+    $scope.installer.current.model.telemetryLevel = $scope.installer.current.model.consentLevels[idx].level;
+  }
+
 });
diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/user.html b/src/Umbraco.Web.UI.Client/src/installer/steps/user.html
index 9049512c8c..225d1feda5 100644
--- a/src/Umbraco.Web.UI.Client/src/installer/steps/user.html
+++ b/src/Umbraco.Web.UI.Client/src/installer/steps/user.html
@@ -1,88 +1,168 @@
 <div ng-controller="Umbraco.Install.UserController">
   <h1>Install Umbraco</h1>
 
-  <p>Enter your name, email and password to install Umbraco with its default settings, alternatively you can customize
-    your installation</p>
+  <p>
+    Enter credentials for the default administrator user and choose the level of
+    consent for telemetry data of your Umbraco installation.
+  </p>
 
-  <form name="myForm" class="form-horizontal" novalidate ng-submit="validateAndInstall();">
-
-    <div class="row">
-      <div class="span8">
-        <div class="pull-right">
-          <div class="control-group">
-            <label class="control-label" for="name">Name</label>
-            <div class="controls">
-              <input type="text" id="name" name="name" placeholder="Full name" required
-                ng-model="installer.current.model.name" />
-            </div>
+  <form
+    class="-no-margin-bottom"
+    name="myForm"
+    novalidate
+    ng-submit="validateAndInstall();"
+  >
+    <div class="installer-cols">
+      <div class="installer-col user-col">
+        <div class="control-group">
+          <label class="control-label" for="name">Name</label>
+          <div class="controls">
+            <input
+              type="text"
+              id="name"
+              name="name"
+              placeholder="Full name"
+              required
+              ng-model="installer.current.model.name"
+            />
           </div>
+        </div>
 
-          <div class="control-group">
-            <label class="control-label" for="email">Email</label>
-            <div class="controls">
-              <input type="email" id="email" name="email" placeholder="you@example.com" val-email required
-                ng-model="installer.current.model.email" />
-              <small class="inline-help">Your email will be used as your login</small>
-            </div>
+        <div class="control-group">
+          <label class="control-label" for="email">Email</label>
+          <div class="controls">
+            <input
+              type="email"
+              id="email"
+              name="email"
+              placeholder="you@example.com"
+              val-email
+              required
+              ng-model="installer.current.model.email"
+            />
+            <small class="inline-help"
+              >Your email will be used as your login</small
+            >
           </div>
+        </div>
 
-          <div class="control-group">
-            <label class="control-label" for="password">Password</label>
-            <div class="controls">
-              <!-- why isn't this masked: https://www.nngroup.com/articles/stop-password-masking/ -->
-              <input type="text" name="installer.current.model.password"
-                ng-minlength="{{installer.current.model.minCharLength}}" ng-pattern="passwordPattern" autocorrect="off"
-                autocapitalize="off" autocomplete="off" required ng-model="installer.current.model.password"
-                id="password" />
-              <small class="inline-help">At least {{installer.current.model.minCharLength}} characters long</small>
+        <div class="control-group">
+          <label class="control-label" for="password">Password</label>
+          <div class="controls">
+            <!-- why isn't this masked: https://www.nngroup.com/articles/stop-password-masking/ -->
+            <input
+              type="text"
+              name="installer.current.model.password"
+              ng-minlength="{{installer.current.model.minCharLength}}"
+              ng-pattern="passwordPattern"
+              autocorrect="off"
+              autocapitalize="off"
+              autocomplete="off"
+              required
+              ng-model="installer.current.model.password"
+              id="password"
+            />
+            <small class="inline-help"
+              >At least {{installer.current.model.minCharLength}} characters
+              long</small
+            >
 
-              <small ng-if="installer.current.model.minNonAlphaNumericLength > 0" class="inline-help">
-                At least {{installer.current.model.minNonAlphaNumericLength}}
-                symbol{{installer.current.model.minNonAlphaNumericLength > 1 ? 's' : ''}}
-              </small>
-            </div>
+            <small
+              ng-if="installer.current.model.minNonAlphaNumericLength > 0"
+              class="inline-help"
+            >
+              At least {{installer.current.model.minNonAlphaNumericLength}}
+              symbol{{installer.current.model.minNonAlphaNumericLength > 1 ? 's'
+              : ''}}
+            </small>
           </div>
+        </div>
 
+        <div class="control-group">
+          <div class="controls">
+            <label>
+              <input
+                type="checkbox"
+                id="subscribeToNewsLetter"
+                name="subscribeToNewsLetter"
+                ng-model="installer.current.model.subscribeToNewsLetter"
+              />
+              Keep me updated on Umbraco Versions, Security Bulletins and
+              Community News
+            </label>
+          </div>
+        </div>
+
+        <div
+          ng-if="installer.current.model.quickInstallSettings"
+          style="margin-top: auto"
+        >
           <div class="control-group">
-            <label class="control-label">Telemetry</label>
-            <div class="controls">
-              <div id="consentSliderWrapper">
-                <div id="consentSlider"></div>
-
-                <umb-tooltip ng-if="consentTooltip.show" event="consentTooltip.event">
-                  <div ng-bind-html="consentTooltip.description"></div>
-                </umb-tooltip>
+            <label class="control-label" for="dbType">Database</label>
+            <div class="controls -with-border">
+              <div class="input-readonly-text">
+                <strong>Provider:</strong>
+                {{installer.current.model.quickInstallSettings.displayName}}
+                <br /><strong>Name:</strong>
+                {{installer.current.model.quickInstallSettings.defaultDatabaseName}}
               </div>
             </div>
           </div>
+        </div>
+      </div>
 
-          <div class="control-group">
-            <div class="controls">
-              <label>
-                <input type="checkbox" id="subscribeToNewsLetter" name="subscribeToNewsLetter"
-                  ng-model="installer.current.model.subscribeToNewsLetter" />
-                Keep me updated on Umbraco Versions, Security Bulletins and Community News
-              </label>
+      <div class="installer-col telemetry-col">
+        <div class="control-group">
+          <label class="control-label">
+            Consent for telemetry data
+            <br /><small>
+              This can be changed later in the Settings panel.
+            </small>
+          </label>
+          <div class="controls">
+            <div id="consentSliderWrapper">
+              <div id="consentSlider"></div>
             </div>
           </div>
-
-          <div class="control-group" ng-class="{disabled:myForm.$invalid}">
-            <div class="controls">
-              <input ng-if="installer.current.model.quickInstallAvailable" type="submit" ng-disabled="myForm.$invalid"
-                value="Install" class="btn btn-success" />
-              <button
-                ng-if="installer.current.model.quickInstallAvailable && installer.current.model.customInstallAvailable"
-                class="btn btn-info control-customize" ng-disabled="myForm.$invalid"
-                ng-click="validateAndForward()">Customize</button>
-
-              <button ng-if="!installer.current.model.quickInstallAvailable" class="btn btn-primary control-customize"
-                ng-disabled="myForm.$invalid" ng-click="validateAndForward()">Next</button>
-            </div>
-          </div>
-
+          <p>
+            <small>
+              In order to improve Umbraco and add new functionality based on as
+              relevant information as possible, we would like to collect system-
+              and usage information from your installation.
+            </small>
+          </p>
+          <p><small ng-bind-html="telemetryDescription"></small></p>
         </div>
       </div>
     </div>
-  </form>
 
+    <div class="control-actions" ng-class="{disabled:myForm.$invalid}">
+      <div
+        class="controls-space-between"
+        ng-if="installer.current.model.quickInstallSettings"
+      >
+        <button
+          ng-if="installer.current.model.quickInstallSettings && installer.current.model.customInstallAvailable"
+          class="btn btn-info control-customize"
+          ng-disabled="myForm.$invalid"
+          ng-click="validateAndForward()"
+        >
+          Change Database
+        </button>
+
+        <button ng-disabled="myForm.$invalid" class="btn btn-success">
+          Install
+        </button>
+      </div>
+
+      <button
+        ng-if="!installer.current.model.quickInstallSettings"
+        class="btn btn-primary control-customize"
+        ng-disabled="myForm.$invalid"
+        ng-click="validateAndForward()"
+      >
+        Next
+      </button>
+    </div>
+  </form>
 </div>
diff --git a/src/Umbraco.Web.UI.Client/src/less/application/grid.less b/src/Umbraco.Web.UI.Client/src/less/application/grid.less
index 68160923c8..95511a232f 100644
--- a/src/Umbraco.Web.UI.Client/src/less/application/grid.less
+++ b/src/Umbraco.Web.UI.Client/src/less/application/grid.less
@@ -133,7 +133,7 @@ body.umb-drawer-is-visible #mainwrapper{
   position: absolute;
   top: 0px;
   left: 100%;
-  min-width: 250px;
+  min-width: 260px;
 }
 
 #speechbubble {
diff --git a/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-actions.less b/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-actions.less
index 0c231830de..887f5d1838 100644
--- a/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-actions.less
+++ b/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-actions.less
@@ -28,7 +28,6 @@
 
 .umb-action-link {
     position: relative;
-    white-space: nowrap;
     font-size: 15px;
     color: @black;
     padding: 9px 25px 9px 20px;
@@ -37,6 +36,7 @@
     display: flex;
     width: 100%;
     align-items: center;
+    text-align: left;
 
     body.touch & {
         padding: 7px 25px 7px 20px;
diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-avatar.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-avatar.less
index c6b9dc7261..3f32386846 100644
--- a/src/Umbraco.Web.UI.Client/src/less/components/umb-avatar.less
+++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-avatar.less
@@ -11,6 +11,8 @@
     font-weight: bold;
     font-size: 16px;
     box-sizing: border-box;
+    object-fit: cover;
+    object-position: center;
 }
 
 /* Sizes */
diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-date-time-picker.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-date-time-picker.less
index b8084bc435..bee757a90d 100644
--- a/src/Umbraco.Web.UI.Client/src/less/components/umb-date-time-picker.less
+++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-date-time-picker.less
@@ -1,3 +1,11 @@
+umb-date-time-picker {
+  &.--disabled {
+    cursor: not-allowed;
+    pointer-events: none;
+    user-select: none;
+  }
+}
+
 .flatpickr-calendar {
     border-radius: @baseBorderRadius;
     box-shadow: 0 5px 10px 0 rgba(0,0,0,0.16);
diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-logviewer.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-logviewer.less
index 32dcccb8da..77bd216317 100644
--- a/src/Umbraco.Web.UI.Client/src/less/components/umb-logviewer.less
+++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-logviewer.less
@@ -114,6 +114,16 @@
                 max-height: 250px;
                 overflow-y: scroll;
                 margin-top: -10px;
+
+                > li {
+
+                    > button {
+                        white-space: normal;
+                        flex-wrap: wrap;
+                        column-gap: 5px;
+                    }
+
+                }
             }
         }
     }
diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-node-preview.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-node-preview.less
index bac1ebc4f3..350483be97 100644
--- a/src/Umbraco.Web.UI.Client/src/less/components/umb-node-preview.less
+++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-node-preview.less
@@ -38,7 +38,6 @@
 
 .umb-node-preview__content {
     flex: 1 1 auto;
-    margin-right: 25px;
     overflow: hidden;
 }
 
diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-readonlyvalue.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-readonlyvalue.less
index 0790bdd07a..b2eca6613d 100644
--- a/src/Umbraco.Web.UI.Client/src/less/components/umb-readonlyvalue.less
+++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-readonlyvalue.less
@@ -1,3 +1,4 @@
-.umb-readonlyvalue  {
-    position:relative;
+.umb-readonlyvalue {
+  position: relative;
+  .umb-property-editor--limit-width();
 }
diff --git a/src/Umbraco.Web.UI.Client/src/less/installer.less b/src/Umbraco.Web.UI.Client/src/less/installer.less
index 61895ff793..29ea8f4276 100644
--- a/src/Umbraco.Web.UI.Client/src/less/installer.less
+++ b/src/Umbraco.Web.UI.Client/src/less/installer.less
@@ -29,27 +29,25 @@
   display: none !important;
 }
 
-
-html {
+body {
   background: url('../img/installer.svg') no-repeat center center fixed;
   background-size: cover;
-}
-
-body {
   margin: 0;
   padding: 0;
-  height: 100%;
+  height: 100vh;
   width: 100%;
   font-family: @baseFontFamily;
   font-size: @baseFontSize;
   line-height: @baseLineHeight;
   color: @textColor;
-  vertical-align: middle;
   text-align: center;
   // better font rendering
   -webkit-font-smoothing: antialiased;
   font-smoothing: antialiased;
   -moz-osx-font-smoothing: grayscale;
+  // center items
+  display: grid;
+  place-items: center center;
 }
 
 #logo {
@@ -61,16 +59,14 @@ body {
 }
 
 #installer {
-  margin: auto;
   background: @white;
-  width: 80%;
-  max-width: 750px;
-  height: 640px;
+  width: min-content;
+  min-width: 500px;
   text-align: left;
-  padding: 30px;
+  padding: 3rem;
   z-index: 667;
   border-radius: 6px;
-  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.16);
+  box-shadow: 0 0 2px 4px rgba(0, 0, 0, 0.16);
 }
 
 #overlay {
@@ -144,6 +140,14 @@ legend {
   font-weight: bold
 }
 
+#installer input {
+  width: 100%;
+
+  &[type=checkbox] {
+    width: auto;
+  }
+}
+
 input.ng-dirty.ng-invalid {
   border-color: @pink;
   color: @pink;
@@ -153,6 +157,21 @@ input.ng-dirty.ng-invalid {
   opacity: 0.6;
 }
 
+.installer-cols {
+  display: grid;
+  grid-template-columns: repeat(2, minmax(300px, 1fr));
+  gap: 120px;
+}
+
+.user-col {
+  display: flex;
+  flex-direction: column;
+}
+
+.telemetry-col {
+  min-height: 500px
+}
+
 
 #installer label.control-label,
 #installer .constrol-label {
@@ -166,6 +185,19 @@ input.ng-dirty.ng-invalid {
     display: block;
     color: @gray-3;
   }
+
+  &.-with-border {
+    border: 1px solid @inputBorder;
+  }
+}
+
+.control-actions {
+  margin-top: 2rem;
+}
+
+.controls-space-between {
+  display: flex;
+  justify-content: space-between;;
 }
 
 #installer .input-readonly-text {
@@ -176,15 +208,6 @@ input.ng-dirty.ng-invalid {
   clear: both;
 }
 
-.absolute-center {
-  margin: auto;
-  position: absolute;
-  top: 0;
-  left: 0;
-  bottom: 0;
-  right: 0;
-}
-
 .fade-hide,
 .fade-show {
   transition: all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
@@ -282,10 +305,12 @@ select {
 }
 
 #consentSliderWrapper {
-  margin-bottom: 40px;
+  margin-bottom: 60px;
 }
 
 #consentSlider {
+  width: 300px;
+
   .noUi-target {
     background: linear-gradient(to bottom, @grayLighter 0%, @grayLighter 100%);
     box-shadow: none;
diff --git a/src/Umbraco.Web.UI.Client/src/less/mixins.less b/src/Umbraco.Web.UI.Client/src/less/mixins.less
index c0ddcd6cdb..78ccbe0ace 100644
--- a/src/Umbraco.Web.UI.Client/src/less/mixins.less
+++ b/src/Umbraco.Web.UI.Client/src/less/mixins.less
@@ -421,6 +421,7 @@
 // Limit width of specific property editors
 .umb-property-editor--limit-width {
     max-width: @propertyEditorLimitedWidth;
+    word-break: break-all;
 }
 
 // Horizontal dividers
diff --git a/src/Umbraco.Web.UI.Client/src/less/navs.less b/src/Umbraco.Web.UI.Client/src/less/navs.less
index db18d3f9d2..6b4a6abb30 100644
--- a/src/Umbraco.Web.UI.Client/src/less/navs.less
+++ b/src/Umbraco.Web.UI.Client/src/less/navs.less
@@ -272,6 +272,7 @@
     color: @ui-option-type;
     display: flex;
     justify-content: flex-start;
+    align-items: center;
     clear: both;
     font-weight: normal;
     line-height: 20px;
diff --git a/src/Umbraco.Web.UI.Client/src/less/properties.less b/src/Umbraco.Web.UI.Client/src/less/properties.less
index 7960aae2c9..101faab724 100644
--- a/src/Umbraco.Web.UI.Client/src/less/properties.less
+++ b/src/Umbraco.Web.UI.Client/src/less/properties.less
@@ -47,16 +47,17 @@
     flex-direction: row;
 }
 
-.date-wrapper-mini--checkbox{
+.date-wrapper-mini--checkbox {
     margin: 0 0 0 28px;
 }
 
 .date-wrapper-mini__date {
     display: flex;
-    
+    flex-direction: column;
     margin-left: 5px;
     margin-top: 5px;
     margin-bottom: 10px;
+    flex: 1 1 50%;
     
     &:first-of-type {
         margin-left: 0;
@@ -67,8 +68,8 @@
     }
 }
 
-.date-wrapper-mini__date .flatpickr-input > a {
-    
+.date-wrapper-mini__date .flatpickr-input > a,
+.date-wrapper-mini__date .flatpickr-input > button {
     display: flex;
     align-items: center;
     justify-content: center;
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-contextmenu.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-contextmenu.html
index 107103b1b4..cee9da9ebc 100644
--- a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-contextmenu.html
+++ b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-contextmenu.html
@@ -14,7 +14,7 @@
                     ng-class="{sep:action.separator, '-opens-dialog': action.opensDialog}" ng-repeat="action in menuActions">
                     <button type="button" ng-click="executeMenuItem(action)"
                         class="umb-action-link btn-reset umb-outline">
-                        <umb-icon icon="icon-{{action.cssclass}}" class="icon"></umb-icon>
+                        <umb-icon icon="{{action.icon}}" class="icon"></umb-icon>
                         <span class="menu-label">{{action.name}}</span>
                     </button>
                 </li>
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/contenttype/umb-content-type-tab.html b/src/Umbraco.Web.UI.Client/src/views/components/contenttype/umb-content-type-tab.html
index b62e3f17d9..a145955334 100644
--- a/src/Umbraco.Web.UI.Client/src/views/components/contenttype/umb-content-type-tab.html
+++ b/src/Umbraco.Web.UI.Client/src/views/components/contenttype/umb-content-type-tab.html
@@ -28,9 +28,9 @@
                       ng-if="vm.tab.inherited">
             </umb-icon>
             <ng-form name="tabNameForm" data-element="tab-name">
-                <div class="umb-group-builder__tab-name" ng-if="vm.tab.inherited" title="{{vm.tab.alias}}">{{ vm.tab.name }}</div>
+                <div class="umb-group-builder__tab-name" ng-if="vm.tab.inherited || !vm.isOpen" title="{{vm.tab.alias}}">{{ vm.tab.name }}</div>
                 <input
-                    ng-if="!vm.tab.inherited"
+                    ng-if="!vm.tab.inherited && vm.isOpen"
                     class="umb-group-builder__group-title-input"
                     data-element="tab-name-field"
                     type="text"
@@ -40,7 +40,7 @@
                     title="{{vm.tab.alias}}"
                     ng-model="vm.tab.name"
                     ng-class="{'-placeholder': vm.tab.name == ''}"
-                    ng-disabled="!vm.isOpen || vm.allowChangeName === false"
+                    ng-disabled="vm.allowChangeName === false"
                     ng-change="vm.changeName()"
                     umb-auto-focus
                     umb-auto-resize
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-menu.html b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-menu.html
index f5ed572fa6..aa6ec85bc3 100644
--- a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-menu.html
+++ b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-menu.html
@@ -8,14 +8,13 @@
         show-caret="true"
         has-popup="true"
         is-expanded="dropdown.isOpen"
-        disabled="!actions || !actions.length || isDisabled"
-        >
+        disabled="!actions || !actions.length || isDisabled">
     </umb-button>
 
     <umb-dropdown ng-if="dropdown.isOpen" class="umb-actions" on-close="dropdown.isOpen = false" deep-blur="dropdown.isOpen = false">
-        <umb-dropdown-item class="umb-action" ng-class="{'sep':action.separatorm, '-opens-dialog': action.opensDialog}" ng-repeat="action in actions">
+        <umb-dropdown-item class="umb-action" ng-class="{'sep': action.separator, '-opens-dialog': action.opensDialog}" ng-repeat="action in actions">
             <button type="button" ng-click="executeMenuItem(action)">
-                <umb-icon icon="icon-{{action.cssclass}}" class="icon"></umb-icon>
+                <umb-icon icon="{{action.icon}}" class="icon"></umb-icon>
                 <!-- Render the text that will be visually displayed -->
                 <span class="menu-label" aria-hidden="true">{{action.name}}</span>
                 <!-- Render the textDescription from the language files if it's attached to the action object-->
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property-actions.html b/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property-actions.html
index fea619f437..d6c798e5c3 100644
--- a/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property-actions.html
+++ b/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property-actions.html
@@ -11,8 +11,8 @@
         <ul class="umb-contextmenu">
             <li ng-repeat="action in vm.actions" role="menuitem" class="umb-contextmenu-item" ng-class="{'-opens-dialog': action.opensDialog}">
                 <button type="button" class="btn-reset umb-outline" ng-click="vm.executeAction(action)" ng-disabled="action.isDisabled === true">
-                    <umb-icon icon="icon-{{action.icon}}" class="icon"></umb-icon>
-                    <span class="menu-label"><localize key="{{::action.labelKey}}" tokens="action.labelTokens"></localize></span>
+                    <umb-icon icon="{{action.icon}}" class="icon"></umb-icon>
+                    <span class="menu-label">{{::action.label}}</span>
                 </button>
             </li>
         </ul>
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/users/umb-user-group-preview.html b/src/Umbraco.Web.UI.Client/src/views/components/users/umb-user-group-preview.html
index db0c6c86c3..f96cdfa0ee 100644
--- a/src/Umbraco.Web.UI.Client/src/views/components/users/umb-user-group-preview.html
+++ b/src/Umbraco.Web.UI.Client/src/views/components/users/umb-user-group-preview.html
@@ -9,7 +9,7 @@
             <span>
                 <span class="bold"><localize key="main_sections">Sections</localize>:</span>
                 <span ng-repeat="section in sections" class="umb-user-group-preview__permission">{{ section.name }}</span>
-                <span ng-if="sections.length === 0">All sections</span>
+                <span ng-if="sections.length === 0">No sections</span>
             </span>
         </div>
 
diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.rights.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.rights.controller.js
index 96c2ef0a80..727872c903 100644
--- a/src/Umbraco.Web.UI.Client/src/views/content/content.rights.controller.js
+++ b/src/Umbraco.Web.UI.Client/src/views/content/content.rights.controller.js
@@ -23,12 +23,11 @@
 
         function onInit() {
             vm.loading = true;
-            contentResource.getDetailedPermissions($scope.currentNode.id).then(function (userGroups) {
+            contentResource.getDetailedPermissions($scope.currentNode.id).then(userGroups => {
                 initData(userGroups);
                 vm.loading = false;
                 currentForm = angularHelper.getCurrentForm($scope);
             });
-
         }
 
         /**
@@ -72,7 +71,7 @@
             //if no permissions are explicitly set this means we need to show the defaults
             vm.selectedUserGroup.permissions = vm.selectedUserGroup.defaultPermissions;
           }
-          localizationService.localize("defaultdialogs_permissionsSetForGroup", [$scope.currentNode.name, vm.selectedUserGroup.name]).then(function (value) {
+          localizationService.localize("defaultdialogs_permissionsSetForGroup", [$scope.currentNode.name, vm.selectedUserGroup.name]).then(value => {
             vm.labels.permissionsSetForGroup = value;
           });
           setViewSate("managePermissions");
@@ -160,7 +159,7 @@
                 permissions: permissionsSave
             };
 
-            contentResource.savePermissions(saveModel).then(function (userGroups) {
+            contentResource.savePermissions(saveModel).then(userGroups => {
 
                 //re-assign model from server since it could have changed
                 initData(userGroups);
@@ -168,10 +167,11 @@
                 // clear dirty state on the form so we don't see the discard changes notification
                 // we use a timeout here because in some cases the initData reformats the userGroups model and triggers a change after the form state was changed
                 $timeout(function() {
-                  if(currentForm) {
-                    currentForm.$dirty = false;
+                  if (currentForm) {
+                     currentForm.$dirty = false;
                   }
                 });
+                
                 $scope.dialog.confirmDiscardChanges = false;
                 vm.saveState = "success";
                 vm.saveSuccces = true;
@@ -185,28 +185,33 @@
         function closeDialog() {
           // check if form has been changed. If it has show discard changes notification
           if (currentForm && currentForm.$dirty) {
-              localizationService.localizeMany(["prompt_unsavedChanges", "prompt_unsavedChangesWarning", "prompt_discardChanges", "prompt_stay"]).then(
-                  function(values) {
-                      var overlay = {
-                          "view": "default",
-                          "title": values[0],
-                          "content": values[1],
-                          "disableBackdropClick": true,
-                          "disableEscKey": true,
-                          "submitButtonLabel": values[2],
-                          "closeButtonLabel": values[3],
-                          submit: function () {
-                              overlayService.close();
-                              navigationService.hideDialog();
-                          },
-                          close: function () {
-                              overlayService.close();
-                          }
-                      };
 
-                      overlayService.open(overlay);
-                  }
-              );
+            const labelKeys = [
+              "prompt_unsavedChanges",
+              "prompt_unsavedChangesWarning",
+              "prompt_discardChanges",
+              "prompt_stay"
+            ];
+
+            localizationService.localizeMany(labelKeys).then(values => {
+                
+                const overlay = {
+                    view: "default",
+                    title: values[0],
+                    content: values[1],
+                    disableBackdropClick: true,
+                    disableEscKey: true,
+                    submitButtonLabel: values[2],
+                    closeButtonLabel: values[3],
+                    submit: () => {
+                        overlayService.close();
+                        navigationService.hideDialog();
+                    },
+                    close: () => overlayService.close()
+                };
+
+              overlayService.open(overlay);
+            });
           } else {
             navigationService.hideDialog();
           }
diff --git a/src/Umbraco.Web.UI.Client/src/views/content/overlays/schedule.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/overlays/schedule.controller.js
index 53f18fbd00..d9fa1604a7 100644
--- a/src/Umbraco.Web.UI.Client/src/views/content/overlays/schedule.controller.js
+++ b/src/Umbraco.Web.UI.Client/src/views/content/overlays/schedule.controller.js
@@ -1,7 +1,7 @@
 (function () {
     "use strict";
     
-    function ScheduleContentController($scope, $timeout, localizationService, dateHelper, userService, contentEditingHelper) {
+  function ScheduleContentController($scope, $timeout, editorState, localizationService, dateHelper, userService, contentEditingHelper) {
 
         var vm = this;
 
@@ -21,11 +21,15 @@
         var origDates = [];
 
         function onInit() {
+            
+            //when this is null, we don't check permissions
+            vm.currentNodePermissions = contentEditingHelper.getPermissionsForContent();
+            vm.canUnpublish = vm.currentNodePermissions ? vm.currentNodePermissions.canUnpublish : true;
 
             vm.variants = $scope.model.variants;
             vm.displayVariants = vm.variants.slice(0);// shallow copy, we dont want to share the array-object(because we will be performing a sort method) but each entry should be shared (because we need validation and notifications).
 
-            if(!$scope.model.title) {
+            if (!$scope.model.title) {
                 localizationService.localize("general_scheduledPublishing").then(value => {
                     $scope.model.title = value;
                 });
@@ -66,18 +70,25 @@
                     var now = new Date();
                     var nowFormatted = moment(now).format("YYYY-MM-DD HH:mm");
 
-                    var datePickerConfig = {
+                    const datePickerPublishConfig  = {
                         enableTime: true,
                         dateFormat: "Y-m-d H:i",
                         time_24hr: true,
                         minDate: nowFormatted,
-                        defaultDate: nowFormatted
+                        defaultDate: nowFormatted,
+                        clickOpens: true
                     };
+                    
+                    const datePickerUnpublishConfig = Utilities.extend({}, datePickerPublishConfig, {
+                        clickOpens: vm.canUnpublish
+                    });
 
-                    variant.datePickerConfig = datePickerConfig;
+
+                    variant.datePickerPublishConfig = datePickerPublishConfig;
+                    variant.datePickerUnpublishConfig = datePickerUnpublishConfig;
                     
                     // format all dates to local
-                    if(variant.releaseDate || variant.expireDate) {
+                    if (variant.releaseDate || variant.expireDate) {
                         formatDatesToLocal(variant);
                     }
                 });
@@ -353,7 +364,8 @@
                 // remove properties only needed for this dialog
                 delete variant.releaseDateFormatted;
                 delete variant.expireDateFormatted;
-                delete variant.datePickerConfig;
+                delete variant.datePickerPublishConfig;
+                delete variant.datePickerUnpublishConfig;
                 delete variant.releaseDatePickerInstance;
                 delete variant.expireDatePickerInstance;
                 delete variant.releaseDatePickerOpen;
diff --git a/src/Umbraco.Web.UI.Client/src/views/content/overlays/schedule.html b/src/Umbraco.Web.UI.Client/src/views/content/overlays/schedule.html
index f383fb200a..8fca0973e4 100644
--- a/src/Umbraco.Web.UI.Client/src/views/content/overlays/schedule.html
+++ b/src/Umbraco.Web.UI.Client/src/views/content/overlays/schedule.html
@@ -15,15 +15,19 @@
 
                 <div class="btn-group flex" style="font-size: 15px;">
 
-                    <umb-date-time-picker ng-model="vm.variants[0].releaseDate"
-                                   options="vm.variants[0].datePickerConfig"
-                                   on-setup="vm.datePickerSetup(vm.variants[0], 'publish', fpItem)"
-                                   on-change="vm.datePickerChange(vm.variants[0], dateStr, 'publish')"
-                                   on-open="vm.datePickerShow(vm.variants[0], 'publish')"
-                                   on-close="vm.datePickerClose(vm.variants[0], 'publish')">
+                    <umb-date-time-picker
+                        ng-model="vm.variants[0].releaseDate"
+                        options="vm.variants[0].datePickerPublishConfig"
+                        on-setup="vm.datePickerSetup(vm.variants[0], 'publish', fpItem)"
+                        on-change="vm.datePickerChange(vm.variants[0], dateStr, 'publish')"
+                        on-open="vm.datePickerShow(vm.variants[0], 'publish')"
+                        on-close="vm.datePickerClose(vm.variants[0], 'publish')">
 
                         <div>
-                            <button type="button" ng-show="vm.variants[0].releaseDate" class="btn btn-reset umb-button--xs" style="outline: none;">
+                            <button type="button"
+                                    ng-show="vm.variants[0].releaseDate"
+                                    class="btn btn-reset umb-button--xs"
+                                    style="outline: none;">
                                 {{vm.variants[0].releaseDateFormatted}}
                             </button>
 
@@ -34,7 +38,11 @@
 
                     </umb-date-time-picker>
 
-                    <button type="button" ng-show="vm.variants[0].releaseDate" ng-click="vm.clearPublishDate(vm.variants[0])" class="btn btn-reset umb-button--xs dropdown-toggle umb-button-group__toggle" style="margin-left: -2px;">
+                    <button type="button"
+                            ng-show="vm.variants[0].releaseDate"
+                            ng-click="vm.clearPublishDate(vm.variants[0])"
+                            class="btn btn-reset umb-button--xs dropdown-toggle umb-button-group__toggle"
+                            style="margin-left: -2px;">
                         <umb-icon icon="icon-wrong" class="icon"></umb-icon>
                     </button>
 
@@ -47,27 +55,41 @@
                     <localize key="content_unpublishDate">Unpublish at</localize>
                 </label>
 
-                <div class="btn-group flex" style="font-size: 15px;">
-                    <umb-date-time-picker ng-model="vm.variants[0].expireDate"
-                                   options="vm.variants[0].datePickerConfig"
-                                   on-setup="vm.datePickerSetup(vm.variants[0], 'unpublish', fpItem)"
-                                   on-change="vm.datePickerChange(vm.variants[0], dateStr, 'unpublish')"
-                                   on-open="vm.datePickerShow(vm.variants[0], 'unpublish')"
-                                   on-close="vm.datePickerClose(vm.variants[0], 'unpublish')">
+                <div class="btn-group flex" style="font-size: 15px;" ng-class="{'umb-dimmed cursor-not-allowed': !vm.canUnpublish}">
+                    <umb-date-time-picker
+                        ng-model="vm.variants[0].expireDate"
+                        ng-class="{ '--disabled': !vm.canUnpublish }"
+                        options="vm.variants[0].datePickerUnpublishConfig"
+                        on-setup="vm.datePickerSetup(vm.variants[0], 'unpublish', fpItem)"
+                        on-change="vm.datePickerChange(vm.variants[0], dateStr, 'unpublish')"
+                        on-open="vm.datePickerShow(vm.variants[0], 'unpublish')"
+                        on-close="vm.datePickerClose(vm.variants[0], 'unpublish')">
 
                         <div>
-                            <button type="button" ng-show="vm.variants[0].expireDate" class="btn btn-reset umb-button--xs" style="outline: none;">
+                            <button type="button"
+                                    ng-show="vm.variants[0].expireDate"
+                                    ng-disabled="!vm.canUnpublish"
+                                    class="btn btn-reset umb-button--xs"
+                                    style="outline: none;">
                                 {{vm.variants[0].expireDateFormatted}}
                             </button>
 
-                            <button type="button" class="btn-reset" ng-hide="vm.variants[0].expireDate">
+                            <button type="button"
+                                    class="btn-reset"
+                                    ng-hide="vm.variants[0].expireDate"
+                                    ng-disabled="!vm.canUnpublish">
                                 <localize key="content_setDate">Set date</localize>
                             </button>
                         </div>
 
                     </umb-date-time-picker>
 
-                    <button type="button" ng-show="vm.variants[0].expireDate" ng-click="vm.clearUnpublishDate(vm.variants[0])" class="btn btn-reset umb-button--xs dropdown-toggle umb-button-group__toggle" style="margin-left: -2px;">
+                    <button type="button"
+                            ng-show="vm.variants[0].expireDate"
+                            ng-disabled="!vm.canUnpublish"
+                            ng-click="vm.clearUnpublishDate(vm.variants[0])"
+                            class="btn btn-reset umb-button--xs dropdown-toggle umb-button-group__toggle"
+                            style="margin-left: -2px;">
                         <umb-icon icon="icon-wrong" class="icon"></umb-icon>
                     </button>
 
@@ -119,14 +141,18 @@
                                     <div style="font-size: 13px; margin-right: 5px;">Publish:<em ng-show="!variant.save">&nbsp;&nbsp;{{variant.releaseDateFormatted}}</em></div>
 
                                     <div class="btn-group flex" style="font-size: 14px; margin-right: 10px;" ng-if="variant.save">
-                                        <umb-date-time-picker ng-model="variant.releaseDate"
-                                                       options="variant.datePickerConfig"
-                                                       on-setup="vm.datePickerSetup(variant, 'publish', fpItem)"
-                                                       on-change="vm.datePickerChange(variant, dateStr, 'publish')"
-                                                       on-open="vm.datePickerShow(variant, 'publish')"
-                                                       on-close="vm.datePickerClose(variant, 'publish')">
+                                        <umb-date-time-picker
+                                            ng-model="variant.releaseDate"
+                                            options="variant.datePickerPublishConfig"
+                                            on-setup="vm.datePickerSetup(variant, 'publish', fpItem)"
+                                            on-change="vm.datePickerChange(variant, dateStr, 'publish')"
+                                            on-open="vm.datePickerShow(variant, 'publish')"
+                                            on-close="vm.datePickerClose(variant, 'publish')">
                                             <div>
-                                                <button type="button" ng-show="variant.releaseDate" class="btn btn-reset umb-button--xxs btn-outline umb-outline">
+                                                <button type="button"
+                                                        ng-show="variant.releaseDate"
+                                                        class="btn btn-reset umb-button--xxs"
+                                                        style="outline: none;">
                                                     {{variant.releaseDateFormatted}}
                                                 </button>
 
@@ -135,7 +161,11 @@
                                                 </button>
                                             </div>
                                         </umb-date-time-picker>
-                                        <button type="button" ng-show="variant.releaseDate" ng-click="vm.clearPublishDate(variant)" class="btn umb-button--xxs dropdown-toggle umb-button-group__toggle btn-outline umb-outline" style="margin-left: -2px;">
+                                        <button type="button"
+                                                ng-show="variant.releaseDate"
+                                                ng-click="vm.clearPublishDate(variant)"
+                                                class="btn btn-reset umb-button--xxs dropdown-toggle umb-button-group__toggle umb-outline"
+                                                style="margin-left: -2px;">
                                             <umb-icon icon="icon-wrong" class="icon"></umb-icon>
                                         </button>
                                     </div>
@@ -144,15 +174,20 @@
                                 <div class="date-wrapper-mini__date" ng-if="variant.expireDate || variant.save">
                                     <div style="font-size: 13px; margin-right: 5px;">Unpublish:<em ng-show="!variant.save">&nbsp;&nbsp;{{variant.expireDateFormatted}}</em></div>
 
-                                    <div class="btn-group flex" style="font-size: 14px;" ng-if="variant.save">
-                                        <umb-date-time-picker ng-model="variant.expireDate"
-                                                       options="variant.datePickerConfig"
-                                                       on-setup="vm.datePickerSetup(variant, 'unpublish', fpItem)"
-                                                       on-change="vm.datePickerChange(variant, dateStr, 'unpublish')"
-                                                       on-open="vm.datePickerShow(variant, 'unpublish')"
-                                                       on-close="vm.datePickerClose(variant, 'unpublish')">
+                                    <div class="btn-group flex" style="font-size: 14px;" ng-class="{'umb-dimmed cursor-not-allowed': !vm.canUnpublish}" ng-if="variant.save">
+                                        <umb-date-time-picker
+                                            ng-model="variant.expireDate"
+                                            ng-class="{ '--disabled': !vm.canUnpublish }"
+                                            options="variant.datePickerUnpublishConfig"
+                                            on-setup="vm.datePickerSetup(variant, 'unpublish', fpItem)"
+                                            on-change="vm.datePickerChange(variant, dateStr, 'unpublish')"
+                                            on-open="vm.datePickerShow(variant, 'unpublish')"
+                                            on-close="vm.datePickerClose(variant, 'unpublish')">
                                             <div>
-                                                <button type="button" ng-show="variant.expireDate" class="btn btm-reset umb-button--xxs btn-outline umb-outline">
+                                                <button type="button"
+                                                        ng-show="variant.expireDate"
+                                                        class="btn btm-reset umb-button--xxs btn-outline"
+                                                        style="outline: none;">
                                                     {{variant.expireDateFormatted}}
                                                 </button>
 
@@ -161,7 +196,11 @@
                                                 </button>
                                             </div>
                                         </umb-date-time-picker>
-                                        <button type="button" ng-show="variant.expireDate" ng-click="vm.clearUnpublishDate(variant)" class="btn btn-reset umb-button--xxs dropdown-toggle umb-button-group__toggle btn-outline umb-outline" style="margin-left: -2px;">
+                                        <button type="button"
+                                                ng-show="variant.expireDate"
+                                                ng-click="vm.clearUnpublishDate(variant)"
+                                                class="btn btn-reset umb-button--xxs dropdown-toggle umb-button-group__toggle umb-outline"
+                                                style="margin-left: -2px;">
                                             <umb-icon icon="icon-wrong" class="icon"></umb-icon>
                                         </button>
                                     </div>
diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/analytics.controller.js b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/analytics.controller.js
index 094941b63a..19fcfc3c2f 100644
--- a/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/analytics.controller.js
+++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/analytics.controller.js
@@ -24,6 +24,7 @@
           "min": 1,
           "max": 3
         },
+        behaviour: 'smooth-steps-tap',
         pips: {
           mode: 'values',
           density: 50,
diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/analytics.html b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/analytics.html
index 10438a4565..d7fed23d8c 100644
--- a/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/analytics.html
+++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/analytics.html
@@ -2,7 +2,7 @@
   <umb-box>
     <umb-box-content>
       <h3 class="bold">
-        <localize key="analytics_consentForAnalytics">Consent for analytics</localize>
+        <localize key="analytics_consentForAnalytics">Consent for telemetry data</localize>
       </h3>
       <div class="umb-healthcheck-help-text">
         <localize key="analytics_analyticsDescription">
@@ -23,29 +23,24 @@
         </div>
 
         <div ng-if="vm.sliderVal === 'Minimal'">
-          <b>{{vm.sliderVal}}</b>
-          <br>
           <localize key="analytics_minimalLevelDescription">We'll only send an anonymous site ID to let us know that the
             site exists.
           </localize>
         </div>
         <div ng-if="vm.sliderVal === 'Basic'">
-          <b>{{vm.sliderVal}}</b>
-          <br>
           <localize key="analytics_basicLevelDescription">We'll send site ID, umbraco version and packages installed
           </localize>
         </div>
         <div ng-if="vm.sliderVal === 'Detailed'">
-          <b>{{vm.sliderVal}}</b>
-          <br>
           <localize key="analytics_detailedLevelDescription">
-                  We will send:
-            <br>- Anonymized site ID, umbraco version, and packages installed.
-            <br>- Number of: Root nodes, Content nodes, Macros, Media, Document Types, Templates, Languages, Domains, User Group, Users, Members, and Property Editors in use.
-            <br>- System information: Webserver, server OS, server framework, server OS language, and database provider.
-            <br>- Configuration settings: Modelsbuilder mode, if custom Umbraco path exists, ASP environment, and if you are in debug mode.
-            <br>
-            <br><i>We might change what we send on the Detailed level in the future. If so, it will be listed above.
+            We will send:
+            <ul>
+              <li>Anonymized site ID, umbraco version, and packages installed.</li>
+              <li>Number of: Root nodes, Content nodes, Macros, Media, Document Types, Templates, Languages, Domains, User Group, Users, Members, and Property Editors in use.</li>
+              <li>System information: Webserver, server OS, server framework, server OS language, and database provider.</li>
+              <li>Configuration settings: Modelsbuilder mode, if custom Umbraco path exists, ASP environment, and if you are in debug mode.</li>
+            </ul>
+            <i>We might change what we send on the Detailed level in the future. If so, it will be listed above.
             <br>By choosing "Detailed" you agree to current and future anonymized information being collected.</i>
           </localize>
         </div>
diff --git a/src/Umbraco.Web.UI.Client/src/views/dictionary/export.controller.js b/src/Umbraco.Web.UI.Client/src/views/dictionary/export.controller.js
new file mode 100644
index 0000000000..df922e6d96
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/views/dictionary/export.controller.js
@@ -0,0 +1,18 @@
+angular.module("umbraco")
+    .controller("Umbraco.Editors.Dictionary.ExportController",
+        function ($scope, dictionaryResource, navigationService) {
+            $scope.includeChildren = false;
+
+            $scope.toggleHandler = function () {
+              $scope.includeChildren = !$scope.includeChildren
+            };
+
+            $scope.export = function () {
+                dictionaryResource.exportItem($scope.currentNode.id, $scope.includeChildren);
+                navigationService.hideMenu();
+            };
+
+            $scope.cancel = function () {
+                navigationService.hideDialog();
+            };
+        });
diff --git a/src/Umbraco.Web.UI.Client/src/views/dictionary/export.html b/src/Umbraco.Web.UI.Client/src/views/dictionary/export.html
new file mode 100644
index 0000000000..c1f5c6b2da
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/views/dictionary/export.html
@@ -0,0 +1,17 @@
+<div class="umb-dialog umb-pane" ng-controller="Umbraco.Editors.Dictionary.ExportController">
+  <div class="umb-dialog-body form-horizontal" ng-cloak>
+      <umb-pane>
+        <umb-control-group localize="label" label="@defaultdialogs_includeDescendants">
+          <umb-toggle checked="includeChildren" on-click="toggleHandler()"></umb-toggle>
+        </umb-control-group>
+      </umb-pane>
+  </div>
+  <div class="umb-dialog-footer btn-toolbar umb-btn-toolbar">
+    <button type="button" class="btn btn-link" ng-click="cancel()">
+      <localize key="general_cancel">Cancel</localize>
+    </button>
+    <button class="btn btn-primary" ng-click="export()">
+      <localize key="actions_export">Export</localize>
+    </button>
+  </div>
+</div>
diff --git a/src/Umbraco.Web.UI.Client/src/views/dictionary/import.controller.js b/src/Umbraco.Web.UI.Client/src/views/dictionary/import.controller.js
new file mode 100644
index 0000000000..3936217343
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/views/dictionary/import.controller.js
@@ -0,0 +1,67 @@
+angular.module("umbraco")
+  .controller("Umbraco.Editors.Dictionary.ImportController",
+    function ($scope, dictionaryResource, notificationsService, navigationService, Upload, umbRequestHelper) {
+      var vm = this;
+
+      vm.state = "upload";
+      vm.model = {};
+      vm.uploadStatus = "";
+
+      vm.cancelButtonLabel = "cancel";
+
+      $scope.dialogTreeApi = {};
+
+      function nodeSelectHandler(args) {
+        args.event.preventDefault();
+        args.event.stopPropagation();
+
+        if ($scope.target) {
+          //un-select if there's a current one selected
+          $scope.target.selected = false;
+        }
+        $scope.target = args.node;
+        $scope.target.selected = true;
+      }
+
+      $scope.handleFiles = function (files, event, invalidFiles) {
+        if (files && files.length > 0) {
+          $scope.upload(files[0]);
+        }
+      };
+
+      $scope.upload = function (file) {
+        Upload.upload({
+          url: umbRequestHelper.getApiUrl("dictionaryApiBaseUrl", "Upload"),
+          fields: {},
+          file: file
+        }).then(function (response) {
+
+          vm.model = response.data;
+          vm.state = "confirm";
+          vm.uploadStatus = "done";
+
+        }, function (err) {
+          notificationsService.error(err.data.notifications[0].header, err.data.notifications[0].message);
+        });
+      };
+
+      $scope.import = function () {
+        var parentId = $scope.target !== undefined ? $scope.target.id : 0;
+
+        dictionaryResource.importItem(vm.model.tempFileName, parentId).then(function (path) {
+          navigationService.syncTree({ tree: "dictionary", path: path, forceReload: true, activate: false });
+
+          vm.state = "done";
+          vm.cancelButtonLabel = "general_close";
+        });
+      };
+
+      $scope.onTreeInit = function () {
+        $scope.dialogTreeApi.callbacks.treeNodeSelect(nodeSelectHandler);
+      };
+
+      $scope.close = function () {
+        navigationService.hideDialog();
+      };
+
+    });
diff --git a/src/Umbraco.Web.UI.Client/src/views/dictionary/import.html b/src/Umbraco.Web.UI.Client/src/views/dictionary/import.html
new file mode 100644
index 0000000000..64f810f4dc
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/views/dictionary/import.html
@@ -0,0 +1,81 @@
+<div class="umb-dialog" ng-controller="Umbraco.Editors.Dictionary.ImportController as vm">
+
+  <div class="umb-dialog-body with-footer">
+    <div class="umb-pane">
+      <div ng-if="vm.state === 'upload'">
+        <p>
+          <localize key="dictionary_importDictionaryItemHelp">
+            To import a Dictionary item, find the '.udt' file on your computer by clicking the 'Import' button (you'll be
+            asked for confirmation on the next screen)
+          </localize>
+        </p>
+
+        <form name="importDictionaryItem">
+
+          <!-- Select files -->
+          <button accept=".udt"
+                  class="btn btn-action"
+                  name="file"
+                  ng-model="filesHolder"
+                  ngf-change="handleFiles($files, $event, $invalidFiles)"
+                  ngf-multiple="true"
+                  ngf-pattern="*.udt"
+                  ngf-select>
+            <localize key="general_import">Import</localize>
+          </button>
+        </form>
+      </div>
+      <div ng-if="vm.state === 'confirm'">
+        <strong>
+          <localize key="dictionaryListCaption">Dictionary items</localize>
+          :
+        </strong>
+        <div ng-repeat="dictionaryItem in vm.model.dictionaryItems" style="padding-left:{{dictionaryItem.level * 20}}px;">{{dictionaryItem.name}}</div>
+        <br />
+        <br />
+
+        <div>
+
+          <hr />
+
+          <p>
+            <strong>
+              <localize key="actions_chooseWhereToImport">Chose where to import</localize>
+              <localize key="dictionaryListCaption">dictionary items</localize>.
+            </strong>
+            (optinal)
+          </p>
+
+          <umb-tree section="translation"
+                    treealias="dictionary"
+                    customtreeparams="foldersonly=1"
+                    hideheader="false"
+                    hideoptions="true"
+                    isdialog="true"
+                    api="dialogTreeApi"
+                    on-init="onTreeInit()"
+                    enablecheckboxes="true">
+          </umb-tree>
+        </div>
+
+        <button class="btn btn-primary" ng-click="import()">
+          <localize key="general_import">Import</localize>
+        </button>
+      </div>
+      <div ng-if="vm.state === 'done'">
+        <strong>
+          <localize key="speechBubbles_dictionaryItemImported">The following dictionary item(s) has been imported!</localize>        
+        </strong>
+        <div ng-repeat="dictionaryItem in vm.model.dictionaryItems" style="padding-left:{{dictionaryItem.level * 20}}px;">{{dictionaryItem.name}}</div>
+      </div>
+    </div>
+  </div>
+
+  <div class="umb-dialog-footer btn-toolbar umb-btn-toolbar">
+    <umb-button action="close()"
+                button-style="link"
+                label-key="{{vm.cancelButtonLabel}}"
+                type="button">
+    </umb-button>
+  </div>
+</div>
diff --git a/src/Umbraco.Web.UI.Client/src/views/partialViewMacros/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/partialViewMacros/edit.controller.js
index 2f7d879607..d72cdbe69a 100644
--- a/src/Umbraco.Web.UI.Client/src/views/partialViewMacros/edit.controller.js
+++ b/src/Umbraco.Web.UI.Client/src/views/partialViewMacros/edit.controller.js
@@ -1,362 +1,367 @@
 (function () {
-    "use strict";
+  "use strict";
 
-    function partialViewMacrosEditController($scope, $routeParams, codefileResource, assetsService, notificationsService, editorState, navigationService, appState, macroService, angularHelper, $timeout, contentEditingHelper, localizationService, templateHelper, macroResource, editorService) {
+  function partialViewMacrosEditController($scope, $routeParams, codefileResource, assetsService, notificationsService, editorState, navigationService, appState, macroService, angularHelper, $timeout, contentEditingHelper, localizationService, templateHelper, macroResource, editorService) {
 
-        var vm = this;
+    var vm = this;
 
-        vm.header = {};
-        vm.header.editorfor = "visuallyHiddenTexts_newPartialViewMacro";
-        vm.header.setPageTitle = true;
-        vm.page = {};
-        vm.page.loading = true;
-        vm.partialViewMacroFile = {};
+    vm.runtimeModeProduction = Umbraco.Sys.ServerVariables.application.runtimeMode == 'Production';
 
-        //menu
-        vm.page.menu = {};
-        vm.page.menu.currentSection = appState.getSectionState("currentSection");
-        vm.page.menu.currentNode = null;
+    vm.header = {};
+    vm.header.editorfor = "visuallyHiddenTexts_newPartialViewMacro";
+    vm.header.setPageTitle = true;
+    vm.page = {};
+    vm.page.loading = true;
+    vm.partialViewMacroFile = {};
 
-        // insert buttons
-        vm.page.insertDefaultButton = {
-            labelKey: "general_insert",
-            addEllipsis: "true",
-            handler: function() {
-                vm.openInsertOverlay();
-            }
-        };
-        vm.page.insertSubButtons = [
-            {
-                labelKey: "template_insertPageField",
-                addEllipsis: "true",
-                handler: function () {
-                    vm.openPageFieldOverlay();
-                }
-            },
-            {
-                labelKey: "template_insertMacro",
-                addEllipsis: "true",
-                handler: function () {
-                    vm.openMacroOverlay()
-                }
-            },
-            {
-                labelKey: "template_insertDictionaryItem",
-                addEllipsis: "true",
-                handler: function () {
-                    vm.openDictionaryItemOverlay();
-                }
-            }
-        ];
+    //menu
+    vm.page.menu = {};
+    vm.page.menu.currentSection = appState.getSectionState("currentSection");
+    vm.page.menu.currentNode = null;
 
-        // bind functions to view model
-        vm.save = save;
-        vm.openPageFieldOverlay = openPageFieldOverlay;
-        vm.openDictionaryItemOverlay = openDictionaryItemOverlay;
-        vm.openQueryBuilderOverlay = openQueryBuilderOverlay;
-        vm.openMacroOverlay = openMacroOverlay;
-        vm.openInsertOverlay = openInsertOverlay;
+    // insert buttons
+    vm.page.insertDefaultButton = {
+      labelKey: "general_insert",
+      addEllipsis: "true",
+      handler: function () {
+        vm.openInsertOverlay();
+      }
+    };
+    vm.page.insertSubButtons = [
+      {
+        labelKey: "template_insertPageField",
+        addEllipsis: "true",
+        handler: function () {
+          vm.openPageFieldOverlay();
+        }
+      },
+      {
+        labelKey: "template_insertMacro",
+        addEllipsis: "true",
+        handler: function () {
+          vm.openMacroOverlay()
+        }
+      },
+      {
+        labelKey: "template_insertDictionaryItem",
+        addEllipsis: "true",
+        handler: function () {
+          vm.openDictionaryItemOverlay();
+        }
+      }
+    ];
 
-        /* Functions bound to view model */
+    // bind functions to view model
+    vm.save = save;
+    vm.openPageFieldOverlay = openPageFieldOverlay;
+    vm.openDictionaryItemOverlay = openDictionaryItemOverlay;
+    vm.openQueryBuilderOverlay = openQueryBuilderOverlay;
+    vm.openMacroOverlay = openMacroOverlay;
+    vm.openInsertOverlay = openInsertOverlay;
 
-        function save() {
+    /* Functions bound to view model */
 
-            vm.page.saveButtonState = "busy";
-            vm.partialViewMacro.content = vm.editor.getValue();
+    function save() {
 
-            contentEditingHelper.contentEditorPerformSave({
-                saveMethod: codefileResource.save,
-                scope: $scope,
-                content: vm.partialViewMacro,
-                rebindCallback: function (orignal, saved) {}
-            }).then(function (saved) {
-                // create macro if needed
-                if($routeParams.create && $routeParams.nomacro !== "true") {
-                    macroResource.createPartialViewMacroWithFile(saved.virtualPath, saved.name).then(function (created) {
-                        navigationService.syncTree({
-                            tree: "macros",
-                            path: '-1,new',
-                            forceReload: true,
-                            activate: false
-                        });
-                        completeSave(saved);
-                    }, Utilities.noop);
-
-                    
-                } else {
-                    completeSave(saved);
-                }
-
-            }, function (err) {
-
-                vm.page.saveButtonState = "error";
-
-                localizationService.localize("speechBubbles_validationFailedHeader").then(function (headerValue) {
-                    localizationService.localize("speechBubbles_validationFailedMessage").then(function(msgValue) {
-                        notificationsService.error(headerValue, msgValue);
-                    });
-                });
+      vm.page.saveButtonState = "busy";
+      vm.partialViewMacro.content = vm.editor.getValue();
 
+      contentEditingHelper.contentEditorPerformSave({
+        saveMethod: codefileResource.save,
+        scope: $scope,
+        content: vm.partialViewMacro,
+        rebindCallback: function (orignal, saved) { }
+      }).then(function (saved) {
+        // create macro if needed
+        if ($routeParams.create && $routeParams.nomacro !== "true") {
+          macroResource.createPartialViewMacroWithFile(saved.virtualPath, saved.name).then(function (created) {
+            navigationService.syncTree({
+              tree: "macros",
+              path: '-1,new',
+              forceReload: true,
+              activate: false
             });
+            completeSave(saved);
+          }, Utilities.noop);
 
+
+        } else {
+          completeSave(saved);
         }
 
-        function completeSave(saved) {
+      }, function (err) {
 
-            localizationService.localize("speechBubbles_partialViewSavedHeader").then(function (headerValue) {
-                localizationService.localize("speechBubbles_partialViewSavedText").then(function (msgValue) {
-                    notificationsService.success(headerValue, msgValue);
-                });
-            });
+        vm.page.saveButtonState = "error";
 
-            //check if the name changed, if so we need to redirect
-            if (vm.partialViewMacro.id !== saved.id) {
-                contentEditingHelper.redirectToRenamedContent(saved.id);
-            }
-            else {
-                vm.page.saveButtonState = "success";
-                vm.partialViewMacro = saved;
+        localizationService.localize("speechBubbles_validationFailedHeader").then(function (headerValue) {
+          localizationService.localize("speechBubbles_validationFailedMessage").then(function (msgValue) {
+            notificationsService.error(headerValue, msgValue);
+          });
+        });
 
-                //sync state
-                editorState.set(vm.partialViewMacro);
-
-                // normal tree sync
-                navigationService.syncTree({ tree: "partialViewMacros", path: vm.partialViewMacro.path, forceReload: true }).then(function (syncArgs) {
-                    vm.page.menu.currentNode = syncArgs.node;
-                });
-
-                // clear $dirty state on form
-                setFormState("pristine");
-            }
-
-        }
-
-        function openInsertOverlay() {
-            var insertOverlay = {
-                allowedTypes: {
-                    macro: true,
-                    dictionary: true,
-                    umbracoField: true
-                },
-                submit: function(model) {
-                    switch(model.insert.type) {
-                        case "macro":
-                            var macroObject = macroService.collectValueData(model.insert.selectedMacro, model.insert.macroParams, "Mvc");
-                            insert(macroObject.syntax);
-                            break;
-                        case "dictionary":
-                        	var code = templateHelper.getInsertDictionarySnippet(model.insert.node.name);
-                        	insert(code);
-                            break;
-                        case "umbracoField":
-                            insert(model.insert.umbracoField);
-                            break;
-                    }
-                    editorService.close();
-                },
-                close: function(oldModel) {
-                    // close the dialog
-                    editorService.close();
-                    // focus editor
-                    vm.editor.focus();
-                }
-            };
-            editorService.insertCodeSnippet(insertOverlay);
-        }
-
-        function openMacroOverlay() {
-            var macroPicker = {
-                dialogData: {},
-                submit: function (model) {
-                    var macroObject = macroService.collectValueData(model.selectedMacro, model.macroParams, "Mvc");
-                    insert(macroObject.syntax);
-                    editorService.close();
-                },
-                close: function() {
-                    editorService.close();
-                    vm.editor.focus();
-                }
-            };
-            editorService.macroPicker(macroPicker);
-        }
-
-
-        function openPageFieldOverlay() {
-            var insertFieldEditor = {
-                submit: function (model) {
-                    insert(model.umbracoField);
-                    editorService.close();
-                },
-                close: function () {
-                    editorService.close();
-                    vm.editor.focus();                    
-                }
-            };
-            editorService.insertField(insertFieldEditor);
-        }
-
-
-        function openDictionaryItemOverlay() {
-
-            var labelKeys = [
-                "template_insertDictionaryItem",
-                "emptyStates_emptyDictionaryTree"
-            ];
-
-            localizationService.localizeMany(labelKeys).then(function(values){
-                var title = values[0];
-                var emptyStateMessage = values[1];
-
-                var dictionaryPicker = {
-                    section: "translation",
-                    treeAlias: "dictionary",
-                    entityType: "dictionary",
-                    multiPicker: false,
-                    title: title,
-                    emptyStateMessage: emptyStateMessage,
-                    select: function(node){
-                        var code = templateHelper.getInsertDictionarySnippet(node.name);
-                        insert(code);
-                        editorService.close();
-                    },
-                    close: function (model) {
-                        // close dialog
-                        editorService.close();
-                        // focus editor
-                        vm.editor.focus();
-                    }
-                };
-
-                editorService.treePicker(dictionaryPicker);
-
-            });
-        }
-
-        function openQueryBuilderOverlay() {
-            var queryBuilder = {
-                submit: function (model) {
-                    var code = templateHelper.getQuerySnippet(model.result.queryExpression);
-                    insert(code);
-                    editorService.close();
-                },
-                close: function (model) {
-                    // close dialog
-                    editorService.close();
-                    // focus editor
-                    vm.editor.focus();
-                }
-            };
-            editorService.queryBuilder(queryBuilder);
-        }
-
-        /* Local functions */
-
-        function init() {
-            //we need to load this somewhere, for now its here.
-            assetsService.loadCss("lib/ace-razor-mode/theme/razor_chrome.css", $scope);
-
-            if ($routeParams.create) {
-
-                var snippet = "Empty";
-
-                if($routeParams.snippet) {
-                    snippet = $routeParams.snippet;
-                }
-
-                codefileResource.getScaffold("partialViewMacros", $routeParams.id, snippet).then(function (partialViewMacro) {
-                    if ($routeParams.name) {
-                        partialViewMacro.name = $routeParams.name;
-                    }
-                    ready(partialViewMacro, false);
-                });
-
-            } else {
-                codefileResource.getByPath('partialViewMacros', $routeParams.id).then(function (partialViewMacro) {
-                    ready(partialViewMacro, true);
-                });
-            }
-        }
-
-        function ready(partialViewMacro, syncTree) {
-
-        	vm.page.loading = false;
-            vm.partialViewMacro = partialViewMacro;
-
-            //sync state
-            editorState.set(vm.partialViewMacro);
-
-            if (syncTree) {
-                navigationService.syncTree({ tree: "partialViewMacros", path: vm.partialViewMacro.path, forceReload: true }).then(function (syncArgs) {
-                    vm.page.menu.currentNode = syncArgs.node;
-                });
-            }
-
-            // ace configuration
-            vm.aceOption = {
-                mode: "razor",
-                theme: "chrome",
-                showPrintMargin: false,
-                advanced: {
-                    fontSize: '14px'
-                },
-                onLoad: function(_editor) {
-                    vm.editor = _editor;
-
-                    // initial cursor placement
-                    // Keep cursor in name field if we are create a new template
-                    // else set the cursor at the bottom of the code editor
-                    if(!$routeParams.create) {
-                        $timeout(function(){
-                            vm.editor.navigateFileEnd();
-                            vm.editor.focus();
-                            persistCurrentLocation();
-                        });
-                    }
-
-                    //change on blur, focus
-                    vm.editor.on("blur", persistCurrentLocation);
-                    vm.editor.on("focus", persistCurrentLocation);
-                    vm.editor.on("change", changeAceEditor);
-
-            	}
-            }
-
-        }
-
-        function insert(str) {
-            vm.editor.focus();
-            vm.editor.moveCursorToPosition(vm.currentPosition);
-            vm.editor.insert(str);
-
-            // set form state to $dirty
-            setFormState("dirty");
-        }
-
-        function persistCurrentLocation() {
-            vm.currentPosition = vm.editor.getCursorPosition();
-        }
-
-        function changeAceEditor() {
-            setFormState("dirty");
-        }
-
-        function setFormState(state) {
-
-            // get the current form
-            var currentForm = angularHelper.getCurrentForm($scope);
-
-            // set state
-            if(state === "dirty") {
-                currentForm.$setDirty();
-            } else if(state === "pristine") {
-                currentForm.$setPristine();
-            }
-        }
-
-
-        init();
+      });
 
     }
 
-    angular.module("umbraco").controller("Umbraco.Editors.PartialViewMacros.EditController", partialViewMacrosEditController);
+    function completeSave(saved) {
+
+      localizationService.localize("speechBubbles_partialViewSavedHeader").then(function (headerValue) {
+        localizationService.localize("speechBubbles_partialViewSavedText").then(function (msgValue) {
+          notificationsService.success(headerValue, msgValue);
+        });
+      });
+
+      //check if the name changed, if so we need to redirect
+      if (vm.partialViewMacro.id !== saved.id) {
+        contentEditingHelper.redirectToRenamedContent(saved.id);
+      }
+      else {
+        vm.page.saveButtonState = "success";
+        vm.partialViewMacro = saved;
+
+        //sync state
+        editorState.set(vm.partialViewMacro);
+
+        // normal tree sync
+        navigationService.syncTree({ tree: "partialViewMacros", path: vm.partialViewMacro.path, forceReload: true }).then(function (syncArgs) {
+          vm.page.menu.currentNode = syncArgs.node;
+        });
+
+        // clear $dirty state on form
+        setFormState("pristine");
+      }
+
+    }
+
+    function openInsertOverlay() {
+      var insertOverlay = {
+        allowedTypes: {
+          macro: true,
+          dictionary: true,
+          umbracoField: true
+        },
+        submit: function (model) {
+          switch (model.insert.type) {
+            case "macro":
+              var macroObject = macroService.collectValueData(model.insert.selectedMacro, model.insert.macroParams, "Mvc");
+              insert(macroObject.syntax);
+              break;
+            case "dictionary":
+              var code = templateHelper.getInsertDictionarySnippet(model.insert.node.name);
+              insert(code);
+              break;
+            case "umbracoField":
+              insert(model.insert.umbracoField);
+              break;
+          }
+          editorService.close();
+        },
+        close: function (oldModel) {
+          // close the dialog
+          editorService.close();
+          // focus editor
+          vm.editor.focus();
+        }
+      };
+      editorService.insertCodeSnippet(insertOverlay);
+    }
+
+    function openMacroOverlay() {
+      var macroPicker = {
+        dialogData: {},
+        submit: function (model) {
+          var macroObject = macroService.collectValueData(model.selectedMacro, model.macroParams, "Mvc");
+          insert(macroObject.syntax);
+          editorService.close();
+        },
+        close: function () {
+          editorService.close();
+          vm.editor.focus();
+        }
+      };
+      editorService.macroPicker(macroPicker);
+    }
+
+
+    function openPageFieldOverlay() {
+      var insertFieldEditor = {
+        submit: function (model) {
+          insert(model.umbracoField);
+          editorService.close();
+        },
+        close: function () {
+          editorService.close();
+          vm.editor.focus();
+        }
+      };
+      editorService.insertField(insertFieldEditor);
+    }
+
+
+    function openDictionaryItemOverlay() {
+
+      var labelKeys = [
+        "template_insertDictionaryItem",
+        "emptyStates_emptyDictionaryTree"
+      ];
+
+      localizationService.localizeMany(labelKeys).then(function (values) {
+        var title = values[0];
+        var emptyStateMessage = values[1];
+
+        var dictionaryPicker = {
+          section: "translation",
+          treeAlias: "dictionary",
+          entityType: "dictionary",
+          multiPicker: false,
+          title: title,
+          emptyStateMessage: emptyStateMessage,
+          select: function (node) {
+            var code = templateHelper.getInsertDictionarySnippet(node.name);
+            insert(code);
+            editorService.close();
+          },
+          close: function (model) {
+            // close dialog
+            editorService.close();
+            // focus editor
+            vm.editor.focus();
+          }
+        };
+
+        editorService.treePicker(dictionaryPicker);
+
+      });
+    }
+
+    function openQueryBuilderOverlay() {
+      var queryBuilder = {
+        submit: function (model) {
+          var code = templateHelper.getQuerySnippet(model.result.queryExpression);
+          insert(code);
+          editorService.close();
+        },
+        close: function (model) {
+          // close dialog
+          editorService.close();
+          // focus editor
+          vm.editor.focus();
+        }
+      };
+      editorService.queryBuilder(queryBuilder);
+    }
+
+    /* Local functions */
+
+    function init() {
+      //we need to load this somewhere, for now its here.
+      assetsService.loadCss("lib/ace-razor-mode/theme/razor_chrome.css", $scope);
+
+      if ($routeParams.create) {
+
+        var snippet = "Empty";
+
+        if ($routeParams.snippet) {
+          snippet = $routeParams.snippet;
+        }
+
+        codefileResource.getScaffold("partialViewMacros", $routeParams.id, snippet).then(function (partialViewMacro) {
+          if ($routeParams.name) {
+            partialViewMacro.name = $routeParams.name;
+          }
+          ready(partialViewMacro, false);
+        });
+
+      } else {
+        codefileResource.getByPath('partialViewMacros', $routeParams.id).then(function (partialViewMacro) {
+          ready(partialViewMacro, true);
+        });
+      }
+    }
+
+    function ready(partialViewMacro, syncTree) {
+
+      vm.page.loading = false;
+      vm.partialViewMacro = partialViewMacro;
+
+      //sync state
+      editorState.set(vm.partialViewMacro);
+
+      if (syncTree) {
+        navigationService.syncTree({ tree: "partialViewMacros", path: vm.partialViewMacro.path, forceReload: true }).then(function (syncArgs) {
+          vm.page.menu.currentNode = syncArgs.node;
+        });
+      }
+
+      // ace configuration
+      vm.aceOption = {
+        mode: "razor",
+        theme: "chrome",
+        showPrintMargin: false,
+        advanced: {
+          fontSize: '14px'
+        },
+        onLoad: function (_editor) {
+          vm.editor = _editor;
+
+          // Set read-only when using runtime mode Production
+          _editor.setReadOnly(vm.runtimeModeProduction);
+
+          // initial cursor placement
+          // Keep cursor in name field if we are create a new template
+          // else set the cursor at the bottom of the code editor
+          if (!$routeParams.create) {
+            $timeout(function () {
+              vm.editor.navigateFileEnd();
+              vm.editor.focus();
+              persistCurrentLocation();
+            });
+          }
+
+          //change on blur, focus
+          vm.editor.on("blur", persistCurrentLocation);
+          vm.editor.on("focus", persistCurrentLocation);
+          vm.editor.on("change", changeAceEditor);
+
+        }
+      }
+
+    }
+
+    function insert(str) {
+      vm.editor.focus();
+      vm.editor.moveCursorToPosition(vm.currentPosition);
+      vm.editor.insert(str);
+
+      // set form state to $dirty
+      setFormState("dirty");
+    }
+
+    function persistCurrentLocation() {
+      vm.currentPosition = vm.editor.getCursorPosition();
+    }
+
+    function changeAceEditor() {
+      setFormState("dirty");
+    }
+
+    function setFormState(state) {
+
+      // get the current form
+      var currentForm = angularHelper.getCurrentForm($scope);
+
+      // set state
+      if (state === "dirty") {
+        currentForm.$setDirty();
+      } else if (state === "pristine") {
+        currentForm.$setPristine();
+      }
+    }
+
+
+    init();
+
+  }
+
+  angular.module("umbraco").controller("Umbraco.Editors.PartialViewMacros.EditController", partialViewMacrosEditController);
 })();
diff --git a/src/Umbraco.Web.UI.Client/src/views/partialViewMacros/edit.html b/src/Umbraco.Web.UI.Client/src/views/partialViewMacros/edit.html
index 36fff3a044..7cffa70aa1 100644
--- a/src/Umbraco.Web.UI.Client/src/views/partialViewMacros/edit.html
+++ b/src/Umbraco.Web.UI.Client/src/views/partialViewMacros/edit.html
@@ -1,81 +1,81 @@
 <div data-element="editor-partial-view-macros" ng-controller="Umbraco.Editors.PartialViewMacros.EditController as vm">
 
-    <umb-load-indicator ng-if="vm.page.loading"></umb-load-indicator>
+  <umb-load-indicator ng-if="vm.page.loading"></umb-load-indicator>
 
-   <form name="contentForm"
-      ng-submit="vm.save()"
-      novalidate
-      val-form-manager>
+  <form name="contentForm"
+        ng-submit="vm.save()"
+        novalidate
+        val-form-manager>
 
-        <umb-editor-view ng-if="!vm.page.loading">
+    <umb-editor-view ng-if="!vm.page.loading">
 
-            <umb-editor-header
-                name="vm.partialViewMacro.name"
-                hide-alias="true"
-                description="vm.partialViewMacro.virtualPath"
-                description-locked="true"
-                hide-icon="true"
-                editorfor="vm.header.editorfor"
-                setpagetitle="vm.header.setPageTitle">
-            </umb-editor-header>
+      <umb-editor-header name="vm.partialViewMacro.name"
+                         name-locked="vm.runtimeModeProduction"
+                         hide-alias="true"
+                         description="vm.partialViewMacro.virtualPath"
+                         description-locked="true"
+                         hide-icon="true"
+                         editorfor="vm.header.editorfor"
+                         setpagetitle="vm.header.setPageTitle">
+      </umb-editor-header>
 
-            <umb-editor-container>
-                <umb-box>
-                    <umb-box-content>
+      <umb-editor-container>
+        <umb-box>
+          <umb-box-content>
+            <div class="umb-alert umb-alert--info mb3" ng-if="vm.runtimeModeProduction">
+              <localize key="template_runtimeModeProduction">Content is not editable when using runtime mode <code>Production</code>.</localize>
+            </div>
 
-                        <div class="flex" style="margin-bottom: 30px;">
+            <div class="flex mb3" ng-if="!vm.runtimeModeProduction">
 
-                            <div class="flex" style="margin-left: auto;">
+              <div class="flex" style="margin-left: auto;">
 
-                                <umb-button-group
-                                    style="margin-right: 5px;"
-                                    default-button="vm.page.insertDefaultButton"
-                                    sub-buttons="vm.page.insertSubButtons"
-                                    size="xs"
-                                    icon="icon-add">
-                                </umb-button-group>
+                <umb-button-group style="margin-right: 5px;"
+                                  default-button="vm.page.insertDefaultButton"
+                                  sub-buttons="vm.page.insertSubButtons"
+                                  size="xs"
+                                  icon="icon-add">
+                </umb-button-group>
 
-                                <umb-button
-                                    type="button"
-                                    size="xs"
-                                    action="vm.openQueryBuilderOverlay()"
-                                    icon="icon-wand"
-                                    label-key="template_queryBuilder"
-                                    add-ellipsis="true">
-                                </umb-button>
+                <umb-button type="button"
+                            size="xs"
+                            action="vm.openQueryBuilderOverlay()"
+                            icon="icon-wand"
+                            label-key="template_queryBuilder"
+                            add-ellipsis="true">
+                </umb-button>
 
-                            </div>
+              </div>
 
-                        </div>
+            </div>
 
-                        <div
-                            auto-scale="85"
-                            umb-ace-editor="vm.aceOption"
-                            model="vm.partialViewMacro.content">
-                        </div>
+            <div auto-scale="90"
+                 umb-ace-editor="vm.aceOption"
+                 model="vm.partialViewMacro.content">
+            </div>
 
-                    </umb-box-content>
-                </umb-box>
-            </umb-editor-container>
+          </umb-box-content>
+        </umb-box>
+      </umb-editor-container>
 
-            <umb-editor-footer>
+      <umb-editor-footer>
 
-            <umb-editor-footer-content-right>
+        <umb-editor-footer-content-right>
 
-               <umb-button
-                  type="submit"
-                  button-style="success"
-                  state="vm.page.saveButtonState"
-                  shortcut="ctrl+s"
-                  label="Save"
-                  label-key="buttons_save">
-               </umb-button>
+          <umb-button type="submit"
+                      button-style="success"
+                      state="vm.page.saveButtonState"
+                      disabled="vm.runtimeModeProduction"
+                      shortcut="ctrl+s"
+                      label="Save"
+                      label-key="buttons_save">
+          </umb-button>
 
-            </umb-editor-footer-content-right>
+        </umb-editor-footer-content-right>
 
-         </umb-editor-footer>
+      </umb-editor-footer>
 
-        </umb-editor-view>
-    </form>
+    </umb-editor-view>
+  </form>
 
 </div>
diff --git a/src/Umbraco.Web.UI.Client/src/views/partialViews/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/partialViews/edit.controller.js
index 1e21a63755..512c1176c1 100644
--- a/src/Umbraco.Web.UI.Client/src/views/partialViews/edit.controller.js
+++ b/src/Umbraco.Web.UI.Client/src/views/partialViews/edit.controller.js
@@ -1,433 +1,438 @@
 (function () {
-    "use strict";
+  "use strict";
 
-    function PartialViewsEditController($scope, $routeParams, codefileResource, assetsService, notificationsService, editorState, navigationService, appState, macroService, angularHelper, $timeout, contentEditingHelper, localizationService, templateHelper, editorService) {
+  function PartialViewsEditController($scope, $routeParams, codefileResource, assetsService, notificationsService, editorState, navigationService, appState, macroService, angularHelper, $timeout, contentEditingHelper, localizationService, templateHelper, editorService) {
 
-        var vm = this;
-        var infiniteMode = $scope.model && $scope.model.infiniteMode;
-        var id = infiniteMode ? $scope.model.id : $routeParams.id;
-        var create = infiniteMode ? $scope.model.create : $routeParams.create;
-        var snippet = infiniteMode ? $scope.model.snippet : $routeParams.snippet;
+    var vm = this;
+    var infiniteMode = $scope.model && $scope.model.infiniteMode;
+    var id = infiniteMode ? $scope.model.id : $routeParams.id;
+    var create = infiniteMode ? $scope.model.create : $routeParams.create;
+    var snippet = infiniteMode ? $scope.model.snippet : $routeParams.snippet;
 
-        function close() {
-            if ($scope.model.close) {
-                $scope.model.close($scope.model);
-            }
+    function close() {
+      if ($scope.model.close) {
+        $scope.model.close($scope.model);
+      }
+    }
+
+    vm.close = close;
+
+    vm.runtimeModeProduction = Umbraco.Sys.ServerVariables.application.runtimeMode == 'Production';
+
+    vm.header = {};
+    vm.header.editorfor = "visuallyHiddenTexts_newPartialView";
+    vm.header.setPageTitle = true;
+
+    vm.page = {};
+    vm.page.loading = true;
+    vm.partialView = {};
+
+    //menu
+    vm.page.menu = {};
+    vm.page.menu.currentSection = appState.getSectionState("currentSection");
+    vm.page.menu.currentNode = null;
+
+    // insert buttons
+    vm.page.insertDefaultButton = {
+      labelKey: "general_insert",
+      addEllipsis: "true",
+      handler: function () {
+        vm.openInsertOverlay();
+      }
+    };
+    vm.page.insertSubButtons = [
+      {
+        labelKey: "template_insertPageField",
+        addEllipsis: "true",
+        handler: function () {
+          vm.openPageFieldOverlay();
         }
+      },
+      {
+        labelKey: "template_insertMacro",
+        addEllipsis: "true",
+        handler: function () {
+          vm.openMacroOverlay()
+        }
+      },
+      {
+        labelKey: "template_insertDictionaryItem",
+        addEllipsis: "true",
+        handler: function () {
+          vm.openDictionaryItemOverlay();
+        }
+      }
+    ];
 
-        vm.close = close;
+    //Used to toggle the keyboard shortcut modal
+    //From a custom keybinding in ace editor - that conflicts with our own to show the dialog
+    vm.showKeyboardShortcut = false;
 
-        vm.header = {};
-        vm.header.editorfor = "visuallyHiddenTexts_newPartialView";
-        vm.header.setPageTitle = true;
+    //Keyboard shortcuts for help dialog
+    vm.page.keyboardShortcutsOverview = [];
 
-        vm.page = {};
-        vm.page.loading = true;
-        vm.partialView = {};
+    templateHelper.getGeneralShortcuts().then(function (data) {
+      vm.page.keyboardShortcutsOverview.push(data);
+    });
+    templateHelper.getEditorShortcuts().then(function (data) {
+      vm.page.keyboardShortcutsOverview.push(data);
+    });
+    templateHelper.getPartialViewEditorShortcuts().then(function (data) {
+      vm.page.keyboardShortcutsOverview.push(data);
+    });
 
-        //menu
-        vm.page.menu = {};
-        vm.page.menu.currentSection = appState.getSectionState("currentSection");
-        vm.page.menu.currentNode = null;
 
-        // insert buttons
-        vm.page.insertDefaultButton = {
-            labelKey: "general_insert",
-            addEllipsis: "true",
-            handler: function() {
-                vm.openInsertOverlay();
-            }
-        };
-        vm.page.insertSubButtons = [
-            {
-                labelKey: "template_insertPageField",
-                addEllipsis: "true",
-                handler: function () {
-                    vm.openPageFieldOverlay();
-                }
-            },
-            {
-                labelKey: "template_insertMacro",
-                addEllipsis: "true",
-                handler: function () {
-                    vm.openMacroOverlay()
-                }
-            },
-            {
-                labelKey: "template_insertDictionaryItem",
-                addEllipsis: "true",
-                handler: function () {
-                    vm.openDictionaryItemOverlay();
-                }
-            }
-        ];
+    // bind functions to view model
+    vm.save = save;
+    vm.openPageFieldOverlay = openPageFieldOverlay;
+    vm.openDictionaryItemOverlay = openDictionaryItemOverlay;
+    vm.openQueryBuilderOverlay = openQueryBuilderOverlay;
+    vm.openMacroOverlay = openMacroOverlay;
+    vm.openInsertOverlay = openInsertOverlay;
 
-        //Used to toggle the keyboard shortcut modal
-        //From a custom keybinding in ace editor - that conflicts with our own to show the dialog
-        vm.showKeyboardShortcut = false;
+    /* Functions bound to view model */
 
-        //Keyboard shortcuts for help dialog
-        vm.page.keyboardShortcutsOverview = [];
+    function save() {
 
-        templateHelper.getGeneralShortcuts().then(function(data){
-            vm.page.keyboardShortcutsOverview.push(data);
-        });
-        templateHelper.getEditorShortcuts().then(function(data){
-            vm.page.keyboardShortcutsOverview.push(data);
-        });
-        templateHelper.getPartialViewEditorShortcuts().then(function(data){
-            vm.page.keyboardShortcutsOverview.push(data);
+      vm.page.saveButtonState = "busy";
+      vm.partialView.content = vm.editor.getValue();
+
+      contentEditingHelper.contentEditorPerformSave({
+        saveMethod: codefileResource.save,
+        scope: $scope,
+        content: vm.partialView,
+        rebindCallback: function (orignal, saved) { }
+      }).then(function (saved) {
+
+        localizationService.localize("speechBubbles_partialViewSavedHeader").then(function (headerValue) {
+          localizationService.localize("speechBubbles_partialViewSavedText").then(function (msgValue) {
+            notificationsService.success(headerValue, msgValue);
+          });
         });
 
-
-        // bind functions to view model
-        vm.save = save;
-        vm.openPageFieldOverlay = openPageFieldOverlay;
-        vm.openDictionaryItemOverlay = openDictionaryItemOverlay;
-        vm.openQueryBuilderOverlay = openQueryBuilderOverlay;
-        vm.openMacroOverlay = openMacroOverlay;
-        vm.openInsertOverlay = openInsertOverlay;
-
-        /* Functions bound to view model */
-
-        function save() {
-            
-            vm.page.saveButtonState = "busy";
-            vm.partialView.content = vm.editor.getValue();
-
-            contentEditingHelper.contentEditorPerformSave({
-                saveMethod: codefileResource.save,
-                scope: $scope,
-                content: vm.partialView,
-                rebindCallback: function (orignal, saved) {}
-            }).then(function (saved) {
-
-                localizationService.localize("speechBubbles_partialViewSavedHeader").then(function (headerValue) {
-                    localizationService.localize("speechBubbles_partialViewSavedText").then(function(msgValue) {
-                        notificationsService.success(headerValue, msgValue);
-                    });
-                });
-
-                //check if the name changed, if so we need to redirect
-                if (vm.partialView.id !== saved.id) {
-                    contentEditingHelper.redirectToRenamedContent(saved.id);
-                }
-                else {
-                    vm.page.saveButtonState = "success";
-                    vm.partialView = saved;
-
-                    //sync state
-                    editorState.set(vm.partialView);
-
-                    // normal tree sync
-                    navigationService.syncTree({ tree: "partialViews", path: vm.partialView.path, forceReload: true }).then(function (syncArgs) {
-                        vm.page.menu.currentNode = syncArgs.node;
-                    });
-
-                    // clear $dirty state on form
-                    setFormState("pristine");
-                }
-            }, function (err) {
-
-                vm.page.saveButtonState = "error";
-                
-                localizationService.localize("speechBubbles_validationFailedHeader").then(function (headerValue) {
-                    localizationService.localize("speechBubbles_validationFailedMessage").then(function(msgValue) {
-                        notificationsService.error(headerValue, msgValue);
-                    });
-                });
-
-            });
-
+        //check if the name changed, if so we need to redirect
+        if (vm.partialView.id !== saved.id) {
+          contentEditingHelper.redirectToRenamedContent(saved.id);
         }
+        else {
+          vm.page.saveButtonState = "success";
+          vm.partialView = saved;
 
-        function openInsertOverlay() {
-            var insertOverlay = {
-                allowedTypes: {
-                    macro: true,
-                    dictionary: true,
-                    umbracoField: true
-                },
-                submit: function(model) {
+          //sync state
+          editorState.set(vm.partialView);
 
-                    switch(model.insert.type) {
-                        case "macro":
-                            var macroObject = macroService.collectValueData(model.insert.selectedMacro, model.insert.macroParams, "Mvc");
-                            insert(macroObject.syntax);
-                            break;
-                        case "dictionary":
-                        	var code = templateHelper.getInsertDictionarySnippet(model.insert.node.name);
-                        	insert(code);
-                            break;
-                        case "umbracoField":
-                            insert(model.insert.umbracoField);
-                            break;
-                    }
-                    editorService.close();
-                },
-                close: function() {
-                    // close the dialog
-                    editorService.close();
-                    // focus editor
-                    vm.editor.focus();
-                }
-            };
-            editorService.insertCodeSnippet(insertOverlay);
+          // normal tree sync
+          navigationService.syncTree({ tree: "partialViews", path: vm.partialView.path, forceReload: true }).then(function (syncArgs) {
+            vm.page.menu.currentNode = syncArgs.node;
+          });
+
+          // clear $dirty state on form
+          setFormState("pristine");
         }
+      }, function (err) {
 
+        vm.page.saveButtonState = "error";
 
-        function openMacroOverlay() {
-            var macroPicker = {
-                dialogData: {},
-                submit: function (model) {
-                    var macroObject = macroService.collectValueData(model.selectedMacro, model.macroParams, "Mvc");
-                    insert(macroObject.syntax);
-                    editorService.close();
-                },
-                close: function() {
-                    editorService.close();
-                    vm.editor.focus();
-                }
-            };
-            editorService.macroPicker(macroPicker);
-        }
+        localizationService.localize("speechBubbles_validationFailedHeader").then(function (headerValue) {
+          localizationService.localize("speechBubbles_validationFailedMessage").then(function (msgValue) {
+            notificationsService.error(headerValue, msgValue);
+          });
+        });
 
-        function openPageFieldOverlay() {
-            var insertFieldEditor = {
-                submit: function (model) {
-                    insert(model.umbracoField);
-                    editorService.close();
-                },
-                close: function () {
-                    editorService.close();
-                    vm.editor.focus();                    
-                }
-            };
-            editorService.insertField(insertFieldEditor);
-        }
-
-
-        function openDictionaryItemOverlay() {
-
-            var labelKeys = [
-                "template_insertDictionaryItem",
-                "emptyStates_emptyDictionaryTree"
-            ];
-
-            localizationService.localizeMany(labelKeys).then(function(values){
-                var title = values[0];
-                var emptyStateMessage = values[1];
-
-                var dictionaryItem = {
-                    section: "translation",
-                    treeAlias: "dictionary",
-                    entityType: "dictionary",
-                    multiPicker: false,
-                    title: title,
-                    emptyStateMessage: emptyStateMessage,
-                    select: function(node){
-                        var code = templateHelper.getInsertDictionarySnippet(node.name);
-                        insert(code);
-                        editorService.close();
-                    },
-                    close: function (model) {
-                        // close dialog
-                        editorService.close();
-                        // focus editor
-                        vm.editor.focus();
-                    }
-                };
-                editorService.treePicker(dictionaryItem);
-            });
-        }
-
-        function openQueryBuilderOverlay() {
-            var queryBuilder = {
-                title: "Query for content",
-                submit: function (model) {
-                    var code = templateHelper.getQuerySnippet(model.result.queryExpression);
-                    insert(code);
-                    editorService.close();
-                },
-                close: function () {
-                    // close dialog
-                    editorService.close();
-                    // focus editor
-                    vm.editor.focus();   
-                }
-            };
-            editorService.queryBuilder(queryBuilder);
-        }
-
-        /* Local functions */
-
-        function init() {
-            //we need to load this somewhere, for now its here.
-            assetsService.loadCss("lib/ace-razor-mode/theme/razor_chrome.css", $scope);
-
-            if (create) {
-
-                if (!snippet) {
-                    snippet = "Empty";
-                }
-
-                codefileResource.getScaffold("partialViews", id, snippet).then(function (partialView) {
-                    ready(partialView, false);
-                });
-                
-            } else {
-                codefileResource.getByPath('partialViews', id).then(function (partialView) {
-                    ready(partialView, true);
-                });
-            }
-
-        }
-
-        function ready(partialView, syncTree) {
-
-        	vm.page.loading = false;
-            vm.partialView = partialView;
-
-            //sync state
-            editorState.set(vm.partialView);
-
-            if (!infiniteMode && syncTree) {
-                navigationService.syncTree({ tree: "partialViews", path: vm.partialView.path, forceReload: true }).then(function (syncArgs) {
-                    vm.page.menu.currentNode = syncArgs.node;
-                });
-            }
-
-            // ace configuration
-            vm.aceOption = {
-                mode: "razor",
-                theme: "chrome",
-                showPrintMargin: false,
-                advanced: {
-                    fontSize: '14px'
-                },
-                onLoad: function(_editor) {
-                    vm.editor = _editor;
-
-                    //Update the auto-complete method to use ctrl+alt+space
-                    _editor.commands.bindKey("ctrl-alt-space", "startAutocomplete");
-                    
-                    //Unassigns the keybinding (That was previously auto-complete)
-                    //As conflicts with our own tree search shortcut
-                    _editor.commands.bindKey("ctrl-space", null);
-
-                    // Assign new keybinding
-                    _editor.commands.addCommands([
-                        //Disable (alt+shift+K)
-                        //Conflicts with our own show shortcuts dialog - this overrides it
-                        {
-                            name: 'unSelectOrFindPrevious',
-                            bindKey: 'Alt-Shift-K',
-                            exec: function () {
-                                //Toggle the show keyboard shortcuts overlay
-                                $scope.$apply(function () {
-                                    vm.showKeyboardShortcut = !vm.showKeyboardShortcut;
-                                });
-                            },
-                            readOnly: true
-                        },
-                        {
-                            name: 'insertUmbracoValue',
-                            bindKey: 'Alt-Shift-V',
-                            exec: function () {
-                                $scope.$apply(function () {
-                                    openPageFieldOverlay();
-                                });
-                            },
-                            readOnly: true
-                        },
-                        {
-                            name: 'insertDictionary',
-                            bindKey: 'Alt-Shift-D',
-                            exec: function () {
-                                $scope.$apply(function () {
-                                    openDictionaryItemOverlay();
-                                });
-                            },
-                            readOnly: true
-                        },
-                        {
-                            name: 'insertUmbracoMacro',
-                            bindKey: 'Alt-Shift-M',
-                            exec: function () {
-                                $scope.$apply(function () {
-                                    openMacroOverlay();
-                                });
-                            },
-                            readOnly: true
-                        },
-                        {
-                            name: 'insertQuery',
-                            bindKey: 'Alt-Shift-Q',
-                            exec: function () {
-                                $scope.$apply(function () {
-                                    openQueryBuilderOverlay();
-                                });
-                            },
-                            readOnly: true
-                        }
-
-                    ]);
-                    
-                    // initial cursor placement
-                    // Keep cursor in name field if we are create a new template
-                    // else set the cursor at the bottom of the code editor
-                    if(!create) {
-                        $timeout(function(){
-                            vm.editor.navigateFileEnd();
-                            vm.editor.focus();
-                            persistCurrentLocation();
-                        });
-                    }
-
-                    //change on blur, focus
-                    vm.editor.on("blur", persistCurrentLocation);
-                    vm.editor.on("focus", persistCurrentLocation);
-                    vm.editor.on("change", changeAceEditor);
-
-            	}
-            }
-
-        }
-
-        function insert(str) {
-            vm.editor.focus();
-            vm.editor.moveCursorToPosition(vm.currentPosition);
-            vm.editor.insert(str);
-
-            // set form state to $dirty
-            setFormState("dirty");
-        }
-
-        function persistCurrentLocation() {
-            vm.currentPosition = vm.editor.getCursorPosition();
-        }
-
-        function changeAceEditor() {
-            setFormState("dirty");
-        }
-
-        function setFormState(state) {
-            
-            // get the current form
-            var currentForm = angularHelper.getCurrentForm($scope);
-
-            // set state
-            if(state === "dirty") {
-                currentForm.$setDirty();
-            } else if(state === "pristine") {
-                currentForm.$setPristine();
-            }
-        }
-
-    
-        init();
+      });
 
     }
 
-    angular.module("umbraco").controller("Umbraco.Editors.PartialViews.EditController", PartialViewsEditController);
+    function openInsertOverlay() {
+      var insertOverlay = {
+        allowedTypes: {
+          macro: true,
+          dictionary: true,
+          umbracoField: true
+        },
+        submit: function (model) {
+
+          switch (model.insert.type) {
+            case "macro":
+              var macroObject = macroService.collectValueData(model.insert.selectedMacro, model.insert.macroParams, "Mvc");
+              insert(macroObject.syntax);
+              break;
+            case "dictionary":
+              var code = templateHelper.getInsertDictionarySnippet(model.insert.node.name);
+              insert(code);
+              break;
+            case "umbracoField":
+              insert(model.insert.umbracoField);
+              break;
+          }
+          editorService.close();
+        },
+        close: function () {
+          // close the dialog
+          editorService.close();
+          // focus editor
+          vm.editor.focus();
+        }
+      };
+      editorService.insertCodeSnippet(insertOverlay);
+    }
+
+
+    function openMacroOverlay() {
+      var macroPicker = {
+        dialogData: {},
+        submit: function (model) {
+          var macroObject = macroService.collectValueData(model.selectedMacro, model.macroParams, "Mvc");
+          insert(macroObject.syntax);
+          editorService.close();
+        },
+        close: function () {
+          editorService.close();
+          vm.editor.focus();
+        }
+      };
+      editorService.macroPicker(macroPicker);
+    }
+
+    function openPageFieldOverlay() {
+      var insertFieldEditor = {
+        submit: function (model) {
+          insert(model.umbracoField);
+          editorService.close();
+        },
+        close: function () {
+          editorService.close();
+          vm.editor.focus();
+        }
+      };
+      editorService.insertField(insertFieldEditor);
+    }
+
+
+    function openDictionaryItemOverlay() {
+
+      var labelKeys = [
+        "template_insertDictionaryItem",
+        "emptyStates_emptyDictionaryTree"
+      ];
+
+      localizationService.localizeMany(labelKeys).then(function (values) {
+        var title = values[0];
+        var emptyStateMessage = values[1];
+
+        var dictionaryItem = {
+          section: "translation",
+          treeAlias: "dictionary",
+          entityType: "dictionary",
+          multiPicker: false,
+          title: title,
+          emptyStateMessage: emptyStateMessage,
+          select: function (node) {
+            var code = templateHelper.getInsertDictionarySnippet(node.name);
+            insert(code);
+            editorService.close();
+          },
+          close: function (model) {
+            // close dialog
+            editorService.close();
+            // focus editor
+            vm.editor.focus();
+          }
+        };
+        editorService.treePicker(dictionaryItem);
+      });
+    }
+
+    function openQueryBuilderOverlay() {
+      var queryBuilder = {
+        title: "Query for content",
+        submit: function (model) {
+          var code = templateHelper.getQuerySnippet(model.result.queryExpression);
+          insert(code);
+          editorService.close();
+        },
+        close: function () {
+          // close dialog
+          editorService.close();
+          // focus editor
+          vm.editor.focus();
+        }
+      };
+      editorService.queryBuilder(queryBuilder);
+    }
+
+    /* Local functions */
+
+    function init() {
+      //we need to load this somewhere, for now its here.
+      assetsService.loadCss("lib/ace-razor-mode/theme/razor_chrome.css", $scope);
+
+      if (create) {
+
+        if (!snippet) {
+          snippet = "Empty";
+        }
+
+        codefileResource.getScaffold("partialViews", id, snippet).then(function (partialView) {
+          ready(partialView, false);
+        });
+
+      } else {
+        codefileResource.getByPath('partialViews', id).then(function (partialView) {
+          ready(partialView, true);
+        });
+      }
+
+    }
+
+    function ready(partialView, syncTree) {
+
+      vm.page.loading = false;
+      vm.partialView = partialView;
+
+      //sync state
+      editorState.set(vm.partialView);
+
+      if (!infiniteMode && syncTree) {
+        navigationService.syncTree({ tree: "partialViews", path: vm.partialView.path, forceReload: true }).then(function (syncArgs) {
+          vm.page.menu.currentNode = syncArgs.node;
+        });
+      }
+
+      // ace configuration
+      vm.aceOption = {
+        mode: "razor",
+        theme: "chrome",
+        showPrintMargin: false,
+        advanced: {
+          fontSize: '14px'
+        },
+        onLoad: function (_editor) {
+          vm.editor = _editor;
+
+          // Set read-only when using runtime mode Production
+          _editor.setReadOnly(vm.runtimeModeProduction);
+
+          //Update the auto-complete method to use ctrl+alt+space
+          _editor.commands.bindKey("ctrl-alt-space", "startAutocomplete");
+
+          //Unassigns the keybinding (That was previously auto-complete)
+          //As conflicts with our own tree search shortcut
+          _editor.commands.bindKey("ctrl-space", null);
+
+          // Assign new keybinding
+          _editor.commands.addCommands([
+            //Disable (alt+shift+K)
+            //Conflicts with our own show shortcuts dialog - this overrides it
+            {
+              name: 'unSelectOrFindPrevious',
+              bindKey: 'Alt-Shift-K',
+              exec: function () {
+                //Toggle the show keyboard shortcuts overlay
+                $scope.$apply(function () {
+                  vm.showKeyboardShortcut = !vm.showKeyboardShortcut;
+                });
+              },
+              readOnly: true
+            },
+            {
+              name: 'insertUmbracoValue',
+              bindKey: 'Alt-Shift-V',
+              exec: function () {
+                $scope.$apply(function () {
+                  openPageFieldOverlay();
+                });
+              },
+              readOnly: true
+            },
+            {
+              name: 'insertDictionary',
+              bindKey: 'Alt-Shift-D',
+              exec: function () {
+                $scope.$apply(function () {
+                  openDictionaryItemOverlay();
+                });
+              },
+              readOnly: true
+            },
+            {
+              name: 'insertUmbracoMacro',
+              bindKey: 'Alt-Shift-M',
+              exec: function () {
+                $scope.$apply(function () {
+                  openMacroOverlay();
+                });
+              },
+              readOnly: true
+            },
+            {
+              name: 'insertQuery',
+              bindKey: 'Alt-Shift-Q',
+              exec: function () {
+                $scope.$apply(function () {
+                  openQueryBuilderOverlay();
+                });
+              },
+              readOnly: true
+            }
+
+          ]);
+
+          // initial cursor placement
+          // Keep cursor in name field if we are create a new template
+          // else set the cursor at the bottom of the code editor
+          if (!create) {
+            $timeout(function () {
+              vm.editor.navigateFileEnd();
+              vm.editor.focus();
+              persistCurrentLocation();
+            });
+          }
+
+          //change on blur, focus
+          vm.editor.on("blur", persistCurrentLocation);
+          vm.editor.on("focus", persistCurrentLocation);
+          vm.editor.on("change", changeAceEditor);
+
+        }
+      }
+
+    }
+
+    function insert(str) {
+      vm.editor.focus();
+      vm.editor.moveCursorToPosition(vm.currentPosition);
+      vm.editor.insert(str);
+
+      // set form state to $dirty
+      setFormState("dirty");
+    }
+
+    function persistCurrentLocation() {
+      vm.currentPosition = vm.editor.getCursorPosition();
+    }
+
+    function changeAceEditor() {
+      setFormState("dirty");
+    }
+
+    function setFormState(state) {
+
+      // get the current form
+      var currentForm = angularHelper.getCurrentForm($scope);
+
+      // set state
+      if (state === "dirty") {
+        currentForm.$setDirty();
+      } else if (state === "pristine") {
+        currentForm.$setPristine();
+      }
+    }
+
+
+    init();
+
+  }
+
+  angular.module("umbraco").controller("Umbraco.Editors.PartialViews.EditController", PartialViewsEditController);
 })();
diff --git a/src/Umbraco.Web.UI.Client/src/views/partialViews/edit.html b/src/Umbraco.Web.UI.Client/src/views/partialViews/edit.html
index 4a4d01385e..e1eb195545 100644
--- a/src/Umbraco.Web.UI.Client/src/views/partialViews/edit.html
+++ b/src/Umbraco.Web.UI.Client/src/views/partialViews/edit.html
@@ -1,97 +1,98 @@
 <div data-element="editor-partial-views" ng-controller="Umbraco.Editors.PartialViews.EditController as vm">
 
-    <umb-load-indicator ng-if="vm.page.loading"></umb-load-indicator>
+  <umb-load-indicator ng-if="vm.page.loading"></umb-load-indicator>
 
-   <form name="contentForm"
-      ng-submit="vm.save()"
-      novalidate
-      val-form-manager>
+  <form name="contentForm"
+        ng-submit="vm.save()"
+        novalidate
+        val-form-manager>
 
-        <umb-editor-view ng-if="!vm.page.loading">
+    <umb-editor-view ng-if="!vm.page.loading">
 
-            <umb-editor-header
-                name="vm.partialView.name"
-                hide-alias="true"
-                description="vm.partialView.virtualPath"
-                description-locked="true"
-                hide-icon="true"
-                editorfor="vm.header.editorfor"
-                setpagetitle="vm.header.setPageTitle">
-            </umb-editor-header>
+      <umb-editor-header name="vm.partialView.name"
+                         name-locked="vm.runtimeModeProduction"
+                         hide-alias="true"
+                         description="vm.partialView.virtualPath"
+                         description-locked="true"
+                         hide-icon="true"
+                         editorfor="vm.header.editorfor"
+                         setpagetitle="vm.header.setPageTitle">
+      </umb-editor-header>
 
-            <umb-editor-container>
+      <umb-editor-container>
 
-                <umb-box>
-                    <umb-box-content>
+        <umb-box>
+          <umb-box-content>
+            <div class="umb-alert umb-alert--info mb3" ng-if="vm.runtimeModeProduction">
+              <localize key="template_runtimeModeProduction">Content is not editable when using runtime mode <code>Production</code>.</localize>
+            </div>
 
-                        <div class="flex" style="margin-bottom: 30px;">
+            <div class="flex mb3" ng-if="!vm.runtimeModeProduction">
 
-                            <div class="flex" style="margin-left: auto;">
+              <div class="flex" style="margin-left: auto;">
 
-                                <umb-button-group
-                                    style="margin-right: 5px;"
-                                    default-button="vm.page.insertDefaultButton"
-                                    sub-buttons="vm.page.insertSubButtons"
-                                    size="xs"
-                                    icon="icon-add">
-                                </umb-button-group>
+                <umb-button-group style="margin-right: 5px;"
+                                  default-button="vm.page.insertDefaultButton"
+                                  sub-buttons="vm.page.insertSubButtons"
+                                  size="xs"
+                                  icon="icon-add">
+                </umb-button-group>
 
-                                <umb-button
-                                    type="button"
-                                    size="xs"
-                                    action="vm.openQueryBuilderOverlay()"
-                                    icon="icon-wand"
-                                    label-key="template_queryBuilder"
-                                    add-ellipsis="true">
-                                </umb-button>
+                <umb-button type="button"
+                            size="xs"
+                            action="vm.openQueryBuilderOverlay()"
+                            icon="icon-wand"
+                            label-key="template_queryBuilder"
+                            add-ellipsis="true">
+                </umb-button>
 
-                            </div>
+              </div>
 
-                        </div>
+            </div>
 
-                        <div
-                            auto-scale="85"
-                            umb-ace-editor="vm.aceOption"
-                            model="vm.partialView.content">
-                        </div>
+            <div auto-scale="90"
+                 umb-ace-editor="vm.aceOption"
+                 model="vm.partialView.content">
+            </div>
 
-                    </umb-box-content>
-                </umb-box>
-                
-            </umb-editor-container>
+          </umb-box-content>
+        </umb-box>
 
-            <umb-editor-footer>
+      </umb-editor-container>
 
-                <umb-editor-footer-content-left>
-                    <umb-keyboard-shortcuts-overview
-                        model="vm.page.keyboardShortcutsOverview"
-                        show-overlay="vm.showKeyboardShortcut"
-                        on-close="vm.showKeyboardShortcut = false;">
-                    </umb-keyboard-shortcuts-overview>
-                </umb-editor-footer-content-left>
+      <umb-editor-footer>
 
-                <umb-editor-footer-content-right>
+        <umb-editor-footer-content-left>
+          <umb-keyboard-shortcuts-overview ng-if="!vm.runtimeModeProduction"
+                                           model="vm.page.keyboardShortcutsOverview"
+                                           show-overlay="vm.showKeyboardShortcut"
+                                           on-close="vm.showKeyboardShortcut = false;">
+          </umb-keyboard-shortcuts-overview>
+        </umb-editor-footer-content-left>
 
-                    <umb-button ng-if="model.infiniteMode"
-                                type="button"
-                                button-style="link"
-                                label-key="general_close"
-                                shortcut="esc"
-                                action="vm.close()">
-                    </umb-button>
-                        
-                    <umb-button type="submit"
-                                button-style="success"
-                                state="vm.page.saveButtonState"
-                                shortcut="ctrl+s"
-                                label="Save"
-                                label-key="buttons_save">
-                    </umb-button>
-                </umb-editor-footer-content-right>
+        <umb-editor-footer-content-right>
 
-         </umb-editor-footer>
+          <umb-button ng-if="model.infiniteMode"
+                      type="button"
+                      button-style="link"
+                      label-key="general_close"
+                      shortcut="esc"
+                      action="vm.close()">
+          </umb-button>
 
-        </umb-editor-view>
-    </form>
+          <umb-button type="submit"
+                      button-style="success"
+                      state="vm.page.saveButtonState"
+                      disabled="vm.runtimeModeProduction"
+                      shortcut="ctrl+s"
+                      label="Save"
+                      label-key="buttons_save">
+          </umb-button>
+        </umb-editor-footer-content-right>
+
+      </umb-editor-footer>
+
+    </umb-editor-view>
+  </form>
 
 </div>
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/umbBlockListPropertyEditor.component.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/umbBlockListPropertyEditor.component.js
index b74d4c0060..35c478b297 100644
--- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/umbBlockListPropertyEditor.component.js
+++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/umbBlockListPropertyEditor.component.js
@@ -32,10 +32,10 @@
 
         var unsubscribe = [];
         var modelObject;
-
+        
         // Property actions:
-        var copyAllBlocksAction = null;
-        var deleteAllBlocksAction = null;
+        let copyAllBlocksAction = null;
+        let deleteAllBlocksAction = null;
 
         var inlineEditing = false;
         var liveEditing = true;
@@ -125,21 +125,23 @@
             } else if(vm.umbElementEditorContent && vm.umbElementEditorContent.getScope) {
                 scopeOfExistence = vm.umbElementEditorContent.getScope();
             }
-
+            
             copyAllBlocksAction = {
                 labelKey: "clipboard_labelForCopyAllEntries",
                 labelTokens: [vm.model.label],
-                icon: "documents",
+                icon: "icon-documents",
                 method: requestCopyAllBlocks,
-                isDisabled: true
+                isDisabled: true,
+                useLegacyIcon: false
             };
 
             deleteAllBlocksAction = {
-                labelKey: 'clipboard_labelForRemoveAllEntries',
+                labelKey: "clipboard_labelForRemoveAllEntries",
                 labelTokens: [],
-                icon: 'trash',
+                icon: "icon-trash",
                 method: requestDeleteAllBlocks,
-                isDisabled: true
+                isDisabled: true,
+                useLegacyIcon: false
             };
 
             var propertyActions = [
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js
index 54f3380ee5..992c01c750 100644
--- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js
+++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js
@@ -117,12 +117,13 @@ function contentPickerController($scope, $q, $routeParams, $location, entityReso
         }
     };
 
-    var removeAllEntriesAction = {
-        labelKey: 'clipboard_labelForRemoveAllEntries',
+    let removeAllEntriesAction = {
+        labelKey: "clipboard_labelForRemoveAllEntries",
         labelTokens: [],
-        icon: 'trash',
+        icon: "icon-trash",
         method: removeAllEntries,
-        isDisabled: true
+        isDisabled: true,
+        useLegacyIcon: false
     };
 
     if ($scope.model.config) {
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js
index 1eb0b0f68c..c7ea562d08 100644
--- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js
+++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js
@@ -1,4 +1,4 @@
-function listViewController($scope, $interpolate, $routeParams, $injector, $timeout, currentUserResource, notificationsService, iconHelper, editorState, localizationService, appState, $location, listViewHelper, navigationService, editorService, overlayService, languageResource, mediaHelper, eventsService) {
+function listViewController($scope, $interpolate, $routeParams, $injector, $timeout, currentUserResource, notificationsService, iconHelper, editorState, localizationService, appState, $location, contentEditingHelper, listViewHelper, navigationService, editorService, overlayService, languageResource, mediaHelper, eventsService) {
 
     //this is a quick check to see if we're in create mode, if so just exit - we cannot show children for content
     // that isn't created yet, if we continue this will use the parent id in the route params which isn't what
@@ -67,28 +67,8 @@ function listViewController($scope, $interpolate, $routeParams, $injector, $time
     $scope.createAllowedButtonSingleWithBlueprints = false;
     $scope.createAllowedButtonMultiWithBlueprints = false;
 
-
     //when this is null, we don't check permissions
-    $scope.currentNodePermissions = null;
-
-    if ($scope.entityType === "content") {
-        //Just ensure we do have an editorState
-        if (editorState.current) {
-            //Fetch current node allowed actions for the current user
-            //This is the current node & not each individual child node in the list
-            var currentUserPermissions = editorState.current.allowedActions;
-
-            //Create a nicer model rather than the funky & hard to remember permissions strings
-            $scope.currentNodePermissions = {
-                "canCopy": _.contains(currentUserPermissions, 'O'), //Magic Char = O
-                "canCreate": _.contains(currentUserPermissions, 'C'), //Magic Char = C
-                "canDelete": _.contains(currentUserPermissions, 'D'), //Magic Char = D
-                "canMove": _.contains(currentUserPermissions, 'M'), //Magic Char = M
-                "canPublish": _.contains(currentUserPermissions, 'U'), //Magic Char = U
-                "canUnpublish": _.contains(currentUserPermissions, 'U') //Magic Char = Z (however UI says it can't be set, so if we can publish 'U' we can unpublish)
-            };
-        }
-    }
+    $scope.currentNodePermissions = $scope.entityType === "content" ? contentEditingHelper.getPermissionsForContent() : null;
 
     //when this is null, we don't check permissions
     $scope.buttonPermissions = null;
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html
index 80b4d3d565..83ec4e9279 100644
--- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html
+++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html
@@ -50,16 +50,15 @@
                            <span class="caret" aria-hidden="true"></span>
                        </button>
 
-                       <umb-dropdown ng-if="page.createDropdownOpen" on-close="page.createDropdownOpen = false">
+                       <umb-dropdown class="umb-actions" ng-if="page.createDropdownOpen" on-close="page.createDropdownOpen = false">
                            <umb-dropdown-item ng-repeat="contentType in listViewAllowedTypes track by contentType.key | orderBy:'name':false">
                                <button type="button" ng-click="createBlank(entityType,contentType.alias)">
                                    <umb-icon icon="{{::contentType.icon}}"></umb-icon>
-                                   {{::contentType.name}} <span ng-show="contentType.blueprints" style="text-transform: lowercase;">(<localize key="blueprints_blankBlueprint">blank</localize>)</span>
+                                   <span class="menu-label">{{::contentType.name}}</span> <span ng-show="contentType.blueprints" style="text-transform: lowercase;">(<localize key="blueprints_blankBlueprint">blank</localize>)</span>
                                </button>
-
                                <button type="button" ng-repeat="blueprint in contentType.blueprints track by blueprint.id | orderBy:'name':false" ng-click="createFromBlueprint(entityType, contentType.alias, blueprint.id)">
-                                   &nbsp;&nbsp;<umb-icon icon="{{::contentType.icon}}"></umb-icon>
-                                   {{::blueprint.name}}
+                                   <umb-icon icon="{{::contentType.icon}}"></umb-icon>
+                                   <span class="menu-label">{{::blueprint.name}}</span>
                                </button>
                            </umb-dropdown-item>
                        </umb-dropdown>
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js
index 219d47dff7..4389d1d560 100644
--- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js
+++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js
@@ -3,7 +3,7 @@
 angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerController",
     function ($scope, entityResource, mediaHelper, $timeout, userService, localizationService, editorService, overlayService, clipboardService) {
 
-        var vm = this;
+        const vm = this;
 
         vm.labels = {};
         vm.labels.deletedItem = "";
@@ -351,20 +351,22 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl
             });
         }
 
-        var copyAllEntriesAction = {
-            labelKey: 'clipboard_labelForCopyAllEntries',
+        let copyAllEntriesAction = {
+            labelKey: "clipboard_labelForCopyAllEntries",
             labelTokens: ['Media'],
-            icon: "documents",
+            icon: "icon-documents",
             method: copyAllEntries,
-            isDisabled: true
+            isDisabled: true,
+            useLegacyIcon: false
         }
 
-        var removeAllEntriesAction = {
-            labelKey: 'clipboard_labelForRemoveAllEntries',
+        let removeAllEntriesAction = {
+            labelKey: "clipboard_labelForRemoveAllEntries",
             labelTokens: [],
-            icon: 'trash',
+            icon: "icon-trash",
             method: removeAllEntries,
-            isDisabled: true
+            isDisabled: true,
+            useLegacyIcon: false
         };
 
         if (multiPicker === true) {
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker3/umbMediaPicker3PropertyEditor.component.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker3/umbMediaPicker3PropertyEditor.component.js
index 0446dac804..bcfbda4759 100644
--- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker3/umbMediaPicker3PropertyEditor.component.js
+++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker3/umbMediaPicker3PropertyEditor.component.js
@@ -33,8 +33,8 @@
         var unsubscribe = [];
 
         // Property actions:
-        var copyAllMediasAction = null;
-        var removeAllMediasAction = null;
+        let copyAllMediasAction = null;
+        let removeAllMediasAction = null;
 
         var vm = this;
 
@@ -83,17 +83,19 @@
             copyAllMediasAction = {
                 labelKey: "clipboard_labelForCopyAllEntries",
                 labelTokens: [vm.model.label],
-                icon: "documents",
+                icon: "icon-documents",
                 method: requestCopyAllMedias,
-                isDisabled: true
+                isDisabled: true,
+                useLegacyIcon: false
             };
 
             removeAllMediasAction = {
-                labelKey: 'clipboard_labelForRemoveAllEntries',
+                labelKey: "clipboard_labelForRemoveAllEntries",
                 labelTokens: [],
-                icon: 'trash',
+                icon: "icon-trash",
                 method: requestRemoveAllMedia,
-                isDisabled: true
+                isDisabled: true,
+                useLegacyIcon: false
             };
 
             var propertyActions = [];
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/membergrouppicker/membergrouppicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/membergrouppicker/membergrouppicker.controller.js
index 4a867e7dca..90ad86121e 100644
--- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/membergrouppicker/membergrouppicker.controller.js
+++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/membergrouppicker/membergrouppicker.controller.js
@@ -2,7 +2,7 @@
 //with a specified callback, this callback will receive an object with a selection on it
 function memberGroupPicker($scope, editorService, memberGroupResource, localizationService, overlayService){
 
-    var vm = this;
+    const vm = this;
 
     vm.openMemberGroupPicker = openMemberGroupPicker;
     vm.remove = remove;
@@ -18,12 +18,13 @@ function memberGroupPicker($scope, editorService, memberGroupResource, localizat
     $scope.allowAdd = !$scope.readonly;
     $scope.groupIds = [];
 
-    var removeAllEntriesAction = {
-        labelKey: 'clipboard_labelForRemoveAllEntries',
+    let removeAllEntriesAction = {
+        labelKey: "clipboard_labelForRemoveAllEntries",
         labelTokens: [],
-        icon: 'trash',
+        icon: "icon-trash",
         method: removeAllEntries,
-        isDisabled: !$scope.allowRemove
+        isDisabled: !$scope.allowRemove,
+        useLegacyIcon: false
     };
 
     if ($scope.model.config && $scope.umbProperty) {
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js
index d404cf1c86..2914e666b5 100644
--- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js
+++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js
@@ -1,4 +1,4 @@
-function multiUrlPickerController($scope, localizationService, entityResource, iconHelper, editorService) {
+function multiUrlPickerController($scope, localizationService, entityResource, iconHelper, editorService, overlayService) {
 
     var vm = {
         labels: {
@@ -10,12 +10,29 @@ function multiUrlPickerController($scope, localizationService, entityResource, i
     $scope.allowEdit = !$scope.readonly;
     $scope.allowRemove = !$scope.readonly;
 
+    let removeAllEntriesAction = {
+        labelKey: "clipboard_labelForRemoveAllEntries",
+        labelTokens: [],
+        icon: "icon-trash",
+        method: removeAllEntries,
+        isDisabled: true,
+        useLegacyIcon: false
+    };
+
     $scope.renderModel = [];
 
     if ($scope.preview) {
         return;
     }
 
+    if ($scope.model.config && parseInt($scope.model.config.maxNumber) !== 1 && $scope.umbProperty) {
+        var propertyActions = [
+          removeAllEntriesAction
+        ];
+
+        $scope.umbProperty.setPropertyActions(propertyActions);
+    }
+
     if (!Array.isArray($scope.model.value)) {
         $scope.model.value = [];
     }
@@ -62,7 +79,11 @@ function multiUrlPickerController($scope, localizationService, entityResource, i
             else {
                 $scope.multiUrlPickerForm.maxCount.$setValidity("maxCount", true);
             }
+            
             $scope.sortableOptions.disabled = $scope.renderModel.length === 1 || $scope.readonly;
+
+            removeAllEntriesAction.isDisabled = $scope.renderModel.length === 0;
+            
             //Update value
             $scope.model.value = $scope.renderModel;
         }
@@ -72,10 +93,16 @@ function multiUrlPickerController($scope, localizationService, entityResource, i
         if (!$scope.allowRemove) return;
 
         $scope.renderModel.splice($index, 1);
-
+        
         setDirty();
     };
 
+    $scope.clear = function ($index) {
+      $scope.renderModel = [];
+
+      setDirty();
+    };
+
     $scope.openLinkPicker = function (link, $index) {
         if (!$scope.allowAdd || !$scope.allowEdit) return;
 
@@ -152,6 +179,22 @@ function multiUrlPickerController($scope, localizationService, entityResource, i
         }
     }
 
+    function removeAllEntries() {
+        localizationService.localizeMany(["content_nestedContentDeleteAllItems", "general_delete"]).then(function (data) {
+          overlayService.confirmDelete({
+            title: data[1],
+            content: data[0],
+            close: function () {
+              overlayService.close();
+            },
+            submit: function () {
+              $scope.clear();
+              overlayService.close();
+            }
+          });
+        });
+    }
+
     function init() {
 
         localizationService.localizeMany(["general_recycleBin"])
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js
index 788ce20664..7fcf5cc365 100644
--- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js
+++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js
@@ -1,4 +1,4 @@
-﻿(function () {
+(function () {
     'use strict';
 
     /**
@@ -67,7 +67,8 @@
 
     function NestedContentController($scope, $interpolate, $filter, serverValidationManager, contentResource, localizationService, iconHelper, clipboardService, eventsService, overlayService, $attrs) {
 
-        var vm = this;
+        const vm = this;
+        
         var model = $scope.$parent.$parent.model;
 
         vm.readonly = false;
@@ -106,7 +107,8 @@
         if (vm.maxItems === 0)
             vm.maxItems = 1000;
 
-        vm.singleMode = vm.minItems === 1 && vm.maxItems === 1 && model.config.contentTypes.length === 1;;
+        vm.singleMode = vm.minItems === 1 && vm.maxItems === 1 && model.config.contentTypes.length === 1;
+        vm.expandsOnLoad = Object.toBoolean(model.config.expandsOnLoad)
         vm.showIcons = Object.toBoolean(model.config.showIcons);
         vm.wideMode = Object.toBoolean(model.config.hideLabel);
         vm.hasContentTypes = model.config.contentTypes.length > 0;
@@ -148,17 +150,27 @@
             });
         }
 
-        var copyAllEntriesAction = {
-            labelKey: 'clipboard_labelForCopyAllEntries',
+        let copyAllEntriesAction = {
+            labelKey: "clipboard_labelForCopyAllEntries",
             labelTokens: [model.label],
-            icon: 'documents',
+            icon: "icon-documents",
             method: copyAllEntries,
-            isDisabled: true
-        }
+            isDisabled: true,
+            useLegacyIcon: false
+        };
 
+        let removeAllEntriesAction = {
+            labelKey: "clipboard_labelForRemoveAllEntries",
+            labelTokens: [],
+            icon: "icon-trash",
+            method: removeAllEntries,
+            isDisabled: true,
+            useLegacyIcon: false
+        };
+        
+        function removeAllEntries() {
 
-        var removeAllEntries = function () {
-            localizationService.localizeMany(["content_nestedContentDeleteAllItems", "general_delete"]).then(function (data) {
+            localizationService.localizeMany(["content_nestedContentDeleteAllItems", "general_delete"]).then(data => {
                 overlayService.confirmDelete({
                     title: data[1],
                     content: data[0],
@@ -174,22 +186,12 @@
                 });
             });
         }
-
-        var removeAllEntriesAction = {
-            labelKey: 'clipboard_labelForRemoveAllEntries',
-            labelTokens: [],
-            icon: 'trash',
-            method: removeAllEntries,
-            isDisabled: true
-        };
-
+        
         // helper to force the current form into the dirty state
         function setDirty() {
-
             if (vm.umbProperty) {
                 vm.umbProperty.setDirty();
             }
-
         };
 
         function addNode(alias) {
@@ -630,8 +632,8 @@
                 modelWasChanged = true;
             }
 
-            // If there is only one item, set it as current node
-            if (vm.singleMode || (vm.nodes.length === 1 && vm.maxItems === 1)) {
+            // If there is only one item and expandsOnLoad property is true, set it as current node
+            if (vm.singleMode || (vm.expandsOnLoad && vm.nodes.length === 1)) {
                 setCurrentNode(vm.nodes[0], false);
             }
 
@@ -743,8 +745,6 @@
             removeAllEntriesAction.isDisabled = copyAllEntriesAction.isDisabled || vm.readonly;
         }
 
-
-
         var propertyActions = [
             copyAllEntriesAction,
             removeAllEntriesAction
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textarea/textarea.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textarea/textarea.html
index 8801909d6f..3955e29594 100644
--- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textarea/textarea.html
+++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textarea/textarea.html
@@ -10,7 +10,8 @@
             ng-trim="false"
             ng-required="model.validation.mandatory"
             aria-required="{{model.validation.mandatory}}"
-            ng-readonly="readonly">
+            ng-readonly="readonly"
+            aria-invalid="{{validLength ? false : true}}">
         </textarea>
 
         <span ng-messages="textareaFieldForm.textarea.$error" show-validation-on-submit >
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.html
index 58f2710cfd..1a324b598a 100644
--- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.html
+++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.html
@@ -8,7 +8,7 @@
                val-server="value"
                ng-required="model.validation.mandatory"
                aria-required="{{model.validation.mandatory}}"
-               aria-invalid="False"
+               aria-invalid="{{validLength ? false : true}}"
                ng-trim="false"
                ng-change="change()"
                ng-readonly="readonly" />
diff --git a/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js
index 99f11f9913..1e14cf1e87 100644
--- a/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js
+++ b/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js
@@ -1,723 +1,728 @@
 (function () {
-    "use strict";
+  "use strict";
 
-    function TemplatesEditController($scope, $routeParams, $timeout, templateResource, assetsService, notificationsService, editorState, navigationService, appState, macroService, treeService, contentEditingHelper, localizationService, angularHelper, templateHelper, editorService) {
+  function TemplatesEditController($scope, $routeParams, $timeout, templateResource, assetsService, notificationsService, editorState, navigationService, appState, macroService, treeService, contentEditingHelper, localizationService, angularHelper, templateHelper, editorService) {
 
-        var vm = this;
-        var oldMasterTemplateAlias = null;
-        var infiniteMode = $scope.model && $scope.model.infiniteMode;
-        var id = infiniteMode ? $scope.model.id : $routeParams.id;
-        var create = infiniteMode ? $scope.model.create : $routeParams.create;
+    var vm = this;
+    var oldMasterTemplateAlias = null;
+    var infiniteMode = $scope.model && $scope.model.infiniteMode;
+    var id = infiniteMode ? $scope.model.id : $routeParams.id;
+    var create = infiniteMode ? $scope.model.create : $routeParams.create;
 
-        vm.header = {};
-        vm.header.editorfor = "template_template";
-        vm.header.setPageTitle = true;
+    vm.runtimeModeProduction = Umbraco.Sys.ServerVariables.application.runtimeMode == 'Production';
 
-        vm.page = {};
-        vm.page.loading = true;
-        vm.templates = [];
+    vm.header = {};
+    vm.header.editorfor = "template_template";
+    vm.header.setPageTitle = true;
 
-        //menu
-        vm.page.menu = {};
-        vm.page.menu.currentSection = appState.getSectionState("currentSection");
-        vm.page.menu.currentNode = null;
+    vm.page = {};
+    vm.page.loading = true;
+    vm.templates = [];
 
-        // insert buttons
-        vm.page.insertDefaultButton = {
-            labelKey: "general_insert",
-            addEllipsis: "true",
-            handler: function () {
-                vm.openInsertOverlay();
-            }
-        };
-        vm.page.insertSubButtons = [
-            {
-                labelKey: "template_insertPageField",
-                addEllipsis: "true",
-                handler: function () {
-                    vm.openPageFieldOverlay();
-                }
-            },
-            {
-                labelKey: "template_insertPartialView",
-                addEllipsis: "true",
-                handler: function () {
-                    vm.openPartialOverlay();
-                }
-            },
-            {
-                labelKey: "template_insertDictionaryItem",
-                addEllipsis: "true",
-                handler: function () {
-                    vm.openDictionaryItemOverlay();
-                }
-            },
-            {
-                labelKey: "template_insertMacro",
-                addEllipsis: "true",
-                handler: function () {
-                    vm.openMacroOverlay()
-                }
-            }
-        ];
+    //menu
+    vm.page.menu = {};
+    vm.page.menu.currentSection = appState.getSectionState("currentSection");
+    vm.page.menu.currentNode = null;
 
-        //Used to toggle the keyboard shortcut modal
-        //From a custom keybinding in ace editor - that conflicts with our own to show the dialog
-        vm.showKeyboardShortcut = false;
+    // insert buttons
+    vm.page.insertDefaultButton = {
+      labelKey: "general_insert",
+      addEllipsis: "true",
+      handler: function () {
+        vm.openInsertOverlay();
+      }
+    };
+    vm.page.insertSubButtons = [
+      {
+        labelKey: "template_insertPageField",
+        addEllipsis: "true",
+        handler: function () {
+          vm.openPageFieldOverlay();
+        }
+      },
+      {
+        labelKey: "template_insertPartialView",
+        addEllipsis: "true",
+        handler: function () {
+          vm.openPartialOverlay();
+        }
+      },
+      {
+        labelKey: "template_insertDictionaryItem",
+        addEllipsis: "true",
+        handler: function () {
+          vm.openDictionaryItemOverlay();
+        }
+      },
+      {
+        labelKey: "template_insertMacro",
+        addEllipsis: "true",
+        handler: function () {
+          vm.openMacroOverlay()
+        }
+      }
+    ];
 
-        //Keyboard shortcuts for help dialog
-        vm.page.keyboardShortcutsOverview = [];
+    //Used to toggle the keyboard shortcut modal
+    //From a custom keybinding in ace editor - that conflicts with our own to show the dialog
+    vm.showKeyboardShortcut = false;
 
-        templateHelper.getGeneralShortcuts().then(function (data) {
-            vm.page.keyboardShortcutsOverview.push(data);
-        });
-        templateHelper.getEditorShortcuts().then(function (data) {
-            vm.page.keyboardShortcutsOverview.push(data);
-        });
-        templateHelper.getTemplateEditorShortcuts().then(function (data) {
-            vm.page.keyboardShortcutsOverview.push(data);
+    //Keyboard shortcuts for help dialog
+    vm.page.keyboardShortcutsOverview = [];
+
+    templateHelper.getGeneralShortcuts().then(function (data) {
+      vm.page.keyboardShortcutsOverview.push(data);
+    });
+    templateHelper.getEditorShortcuts().then(function (data) {
+      vm.page.keyboardShortcutsOverview.push(data);
+    });
+    templateHelper.getTemplateEditorShortcuts().then(function (data) {
+      vm.page.keyboardShortcutsOverview.push(data);
+    });
+
+    vm.save = function (suppressNotification) {
+      vm.page.saveButtonState = "busy";
+
+      vm.template.content = vm.editor.getValue();
+
+      contentEditingHelper.contentEditorPerformSave({
+        saveMethod: templateResource.save,
+        scope: $scope,
+        content: vm.template,
+        rebindCallback: function (orignal, saved) { }
+      }).then(function (saved) {
+
+        if (!suppressNotification) {
+          localizationService.localizeMany(["speechBubbles_templateSavedHeader", "speechBubbles_templateSavedText"]).then(function (data) {
+            var header = data[0];
+            var message = data[1];
+            notificationsService.success(header, message);
+          });
+        }
+
+        vm.page.saveButtonState = "success";
+        vm.template = saved;
+
+        //sync state
+        if (!infiniteMode) {
+          editorState.set(vm.template);
+        }
+
+        // sync tree
+        // if master template alias has changed move the node to it's new location
+        if (!infiniteMode && oldMasterTemplateAlias !== vm.template.masterTemplateAlias) {
+
+          // When creating a new template the id is -1. Make sure We don't remove the root node.
+          if (vm.page.menu.currentNode.id !== "-1") {
+            // move node to new location in tree
+            //first we need to remove the node that we're working on
+            treeService.removeNode(vm.page.menu.currentNode);
+          }
+
+          // update stored alias to the new one so the node won't move again unless the alias is changed again
+          oldMasterTemplateAlias = vm.template.masterTemplateAlias;
+
+          navigationService.syncTree({ tree: "templates", path: vm.template.path, forceReload: true, activate: true }).then(function (args) {
+            vm.page.menu.currentNode = args.node;
+          });
+
+        } else {
+
+          // normal tree sync
+          if (!infiniteMode) {
+            navigationService.syncTree({ tree: "templates", path: vm.template.path, forceReload: true }).then(function (syncArgs) {
+              vm.page.menu.currentNode = syncArgs.node;
+            });
+          }
+
+        }
+
+        // clear $dirty state on form
+        setFormState("pristine");
+
+        if (infiniteMode) {
+          submit();
+        }
+
+
+      }, function (err) {
+        if (suppressNotification) {
+          vm.page.saveButtonState = "error";
+
+          localizationService.localizeMany(["speechBubbles_validationFailedHeader", "speechBubbles_validationFailedMessage"]).then(function (data) {
+            var header = data[0];
+            var message = data[1];
+            notificationsService.error(header, message);
+          });
+        }
+      });
+
+    };
+
+    vm.init = function () {
+
+      // we need to load this somewhere, for now its here.
+      assetsService.loadCss("lib/ace-razor-mode/theme/razor_chrome.css", $scope);
+
+      // load templates - used in the master template picker
+      templateResource.getAll()
+        .then(function (templates) {
+          vm.templates = templates;
         });
 
-        vm.save = function (suppressNotification) {
-            vm.page.saveButtonState = "busy";
+      if (create) {
+        templateResource.getScaffold((id)).then(function (template) {
+          vm.ready(template);
+        });
+      } else {
+        templateResource.getById(id).then(function (template) {
+          vm.ready(template);
+        });
+      }
 
-            vm.template.content = vm.editor.getValue();
-
-            contentEditingHelper.contentEditorPerformSave({
-                saveMethod: templateResource.save,
-                scope: $scope,
-                content: vm.template,
-                rebindCallback: function (orignal, saved) { }
-            }).then(function (saved) {
-
-                if (!suppressNotification) {
-                    localizationService.localizeMany(["speechBubbles_templateSavedHeader", "speechBubbles_templateSavedText"]).then(function (data) {
-                        var header = data[0];
-                        var message = data[1];
-                        notificationsService.success(header, message);
-                    });
-                }
-
-                vm.page.saveButtonState = "success";
-                vm.template = saved;
-
-                //sync state
-                if (!infiniteMode) {
-                    editorState.set(vm.template);
-                }
-
-                // sync tree
-                // if master template alias has changed move the node to it's new location
-                if (!infiniteMode && oldMasterTemplateAlias !== vm.template.masterTemplateAlias) {
-
-                    // When creating a new template the id is -1. Make sure We don't remove the root node.
-                    if (vm.page.menu.currentNode.id !== "-1") {
-                        // move node to new location in tree
-                        //first we need to remove the node that we're working on
-                        treeService.removeNode(vm.page.menu.currentNode);
-                    }
-
-                    // update stored alias to the new one so the node won't move again unless the alias is changed again
-                    oldMasterTemplateAlias = vm.template.masterTemplateAlias;
-
-                    navigationService.syncTree({ tree: "templates", path: vm.template.path, forceReload: true, activate: true }).then(function (args) {
-                        vm.page.menu.currentNode = args.node;
-                    });
-
-                } else {
-
-                    // normal tree sync
-                    if (!infiniteMode) {
-                        navigationService.syncTree({ tree: "templates", path: vm.template.path, forceReload: true }).then(function (syncArgs) {
-                            vm.page.menu.currentNode = syncArgs.node;
-                        });
-                    }
-
-                }
-
-                // clear $dirty state on form
-                setFormState("pristine");
-
-                if (infiniteMode) {
-                    submit();
-                }
+    };
 
 
-            }, function (err) {
-                if (suppressNotification) {
-                    vm.page.saveButtonState = "error";
+    vm.ready = function (template) {
+      vm.page.loading = false;
+      vm.template = template;
 
-                    localizationService.localizeMany(["speechBubbles_validationFailedHeader", "speechBubbles_validationFailedMessage"]).then(function (data) {
-                        var header = data[0];
-                        var message = data[1];
-                        notificationsService.error(header, message);
-                    });
-                }
+      // if this is a new template, bind to the blur event on the name
+      if (create) {
+        $timeout(function () {
+          var nameField = $('[data-element="editor-name-field"]');
+          if (nameField) {
+            nameField.on('blur', function (event) {
+              if (event.target.value) {
+                vm.save(true);
+              }
             });
+          }
+        });
+      }
 
+      // sync state
+      if (!infiniteMode) {
+        editorState.set(vm.template);
+        navigationService.syncTree({ tree: "templates", path: vm.template.path, forceReload: true }).then(function (syncArgs) {
+          vm.page.menu.currentNode = syncArgs.node;
+        });
+      }
+
+      // save state of master template to use for comparison when syncing the tree on save
+      oldMasterTemplateAlias = Utilities.copy(template.masterTemplateAlias);
+
+      // ace configuration
+      vm.aceOption = {
+        mode: "razor",
+        theme: "chrome",
+        showPrintMargin: false,
+        advanced: {
+          fontSize: '14px',
+          enableSnippets: false, //The Razor mode snippets are awful (Need a way to override these)
+          enableBasicAutocompletion: true,
+          enableLiveAutocompletion: false
+        },
+        onLoad: function (_editor) {
+          vm.editor = _editor;
+
+          // Set read-only when using runtime mode Production
+          _editor.setReadOnly(vm.runtimeModeProduction);
+
+          // Update the auto-complete method to use ctrl+alt+space
+          _editor.commands.bindKey("ctrl-alt-space", "startAutocomplete");
+
+          // Unassigns the keybinding (That was previously auto-complete)
+          // As conflicts with our own tree search shortcut
+          _editor.commands.bindKey("ctrl-space", null);
+
+          // Assign new keybinding
+          _editor.commands.addCommands([
+            // Disable (alt+shift+K)
+            // Conflicts with our own show shortcuts dialog - this overrides it
+            {
+              name: 'unSelectOrFindPrevious',
+              bindKey: 'Alt-Shift-K',
+              exec: function () {
+                // Toggle the show keyboard shortcuts overlay
+                $scope.$apply(function () {
+                  vm.showKeyboardShortcut = !vm.showKeyboardShortcut;
+                });
+
+              },
+              readOnly: true
+            },
+            {
+              name: 'insertUmbracoValue',
+              bindKey: 'Alt-Shift-V',
+              exec: function () {
+                $scope.$apply(function () {
+                  openPageFieldOverlay();
+                });
+              },
+              readOnly: true
+            },
+            {
+              name: 'insertPartialView',
+              bindKey: 'Alt-Shift-P',
+              exec: function () {
+                $scope.$apply(function () {
+                  openPartialOverlay();
+                });
+              },
+              readOnly: true
+            },
+            {
+              name: 'insertDictionary',
+              bindKey: 'Alt-Shift-D',
+              exec: function () {
+                $scope.$apply(function () {
+                  openDictionaryItemOverlay();
+                });
+              },
+              readOnly: true
+            },
+            {
+              name: 'insertUmbracoMacro',
+              bindKey: 'Alt-Shift-M',
+              exec: function () {
+                $scope.$apply(function () {
+                  openMacroOverlay();
+                });
+              },
+              readOnly: true
+            },
+            {
+              name: 'insertQuery',
+              bindKey: 'Alt-Shift-Q',
+              exec: function () {
+                $scope.$apply(function () {
+                  openQueryBuilderOverlay();
+                });
+              },
+              readOnly: true
+            },
+            {
+              name: 'insertSection',
+              bindKey: 'Alt-Shift-S',
+              exec: function () {
+                $scope.$apply(function () {
+                  openSectionsOverlay();
+                });
+              },
+              readOnly: true
+            },
+            {
+              name: 'chooseMasterTemplate',
+              bindKey: 'Alt-Shift-T',
+              exec: function () {
+                $scope.$apply(function () {
+                  openMasterTemplateOverlay();
+                });
+              },
+              readOnly: true
+            }
+
+          ]);
+
+          // initial cursor placement
+          // Keep cursor in name field if we are create a new template
+          // else set the cursor at the bottom of the code editor
+          if (!create) {
+            $timeout(function () {
+              vm.editor.navigateFileEnd();
+              vm.editor.focus();
+              persistCurrentLocation();
+            });
+          }
+
+          // change on blur, focus
+          vm.editor.on("blur", persistCurrentLocation);
+          vm.editor.on("focus", persistCurrentLocation);
+          vm.editor.on("change", changeAceEditor);
+        }
+      }
+
+    };
+
+    vm.openPageFieldOverlay = openPageFieldOverlay;
+    vm.openDictionaryItemOverlay = openDictionaryItemOverlay;
+    vm.openQueryBuilderOverlay = openQueryBuilderOverlay;
+    vm.openMacroOverlay = openMacroOverlay;
+    vm.openInsertOverlay = openInsertOverlay;
+    vm.openSectionsOverlay = openSectionsOverlay;
+    vm.openPartialOverlay = openPartialOverlay;
+    vm.openMasterTemplateOverlay = openMasterTemplateOverlay;
+    vm.selectMasterTemplate = selectMasterTemplate;
+    vm.getMasterTemplateName = getMasterTemplateName;
+    vm.removeMasterTemplate = removeMasterTemplate;
+    vm.closeShortcuts = closeShortcuts;
+    vm.submit = submit;
+    vm.close = close;
+
+    function openInsertOverlay() {
+      var insertOverlay = {
+        allowedTypes: {
+          macro: true,
+          dictionary: true,
+          partial: true,
+          umbracoField: true
+        },
+        submit: function (model) {
+          switch (model.insert.type) {
+            case "macro":
+              var macroObject = macroService.collectValueData(model.insert.selectedMacro, model.insert.macroParams, "Mvc");
+              insert(macroObject.syntax);
+              break;
+            case "dictionary":
+              var code = templateHelper.getInsertDictionarySnippet(model.insert.node.name);
+              insert(code);
+              break;
+            case "partial":
+              var code = templateHelper.getInsertPartialSnippet(model.insert.node.parentId, model.insert.node.name);
+              insert(code);
+              break;
+            case "umbracoField":
+              insert(model.insert.umbracoField);
+              break;
+          }
+          editorService.close();
+        },
+        close: function (oldModel) {
+          // close the dialog
+          editorService.close();
+          // focus editor
+          vm.editor.focus();
+        }
+      };
+      editorService.insertCodeSnippet(insertOverlay);
+    }
+
+    function openMacroOverlay() {
+      var macroPicker = {
+        dialogData: {},
+        submit: function (model) {
+          var macroObject = macroService.collectValueData(model.selectedMacro, model.macroParams, "Mvc");
+          insert(macroObject.syntax);
+          editorService.close();
+        },
+        close: function () {
+          editorService.close();
+          vm.editor.focus();
+        }
+      };
+      editorService.macroPicker(macroPicker);
+    }
+
+    function openPageFieldOverlay() {
+      var insertFieldEditor = {
+        submit: function (model) {
+          insert(model.umbracoField);
+          editorService.close();
+        },
+        close: function () {
+          editorService.close();
+          vm.editor.focus();
+        }
+      };
+      editorService.insertField(insertFieldEditor);
+    }
+
+
+    function openDictionaryItemOverlay() {
+
+      var labelKeys = [
+        "template_insertDictionaryItem",
+        "emptyStates_emptyDictionaryTree"
+      ];
+
+      localizationService.localizeMany(labelKeys).then(function (values) {
+        var title = values[0];
+        var emptyStateMessage = values[1];
+
+        var dictionaryItem = {
+          section: "translation",
+          treeAlias: "dictionary",
+          entityType: "dictionary",
+          multiPicker: false,
+          title: title,
+          emptyStateMessage: emptyStateMessage,
+          select: function (node) {
+            var code = templateHelper.getInsertDictionarySnippet(node.name);
+            insert(code);
+            editorService.close();
+          },
+          close: function (model) {
+            // close dialog
+            editorService.close();
+            // focus editor
+            vm.editor.focus();
+          }
         };
 
-        vm.init = function () {
+        editorService.treePicker(dictionaryItem);
 
-            // we need to load this somewhere, for now its here.
-            assetsService.loadCss("lib/ace-razor-mode/theme/razor_chrome.css", $scope);
-
-            // load templates - used in the master template picker
-            templateResource.getAll()
-                .then(function (templates) {
-                    vm.templates = templates;
-                });
-
-            if (create) {
-                templateResource.getScaffold((id)).then(function (template) {
-                    vm.ready(template);
-                });
-            } else {
-                templateResource.getById(id).then(function (template) {
-                    vm.ready(template);
-                });
-            }
-
-        };
-
-
-        vm.ready = function (template) {
-            vm.page.loading = false;
-            vm.template = template;
-
-            // if this is a new template, bind to the blur event on the name
-            if (create) {
-                $timeout(function () {
-                    var nameField = $('[data-element="editor-name-field"]');
-                    if (nameField) {
-                        nameField.on('blur', function (event) {
-                            if (event.target.value) {
-                                vm.save(true);
-                            }
-                        });
-                    }
-                });
-            }
-
-            // sync state
-            if (!infiniteMode) {
-                editorState.set(vm.template);
-                navigationService.syncTree({ tree: "templates", path: vm.template.path, forceReload: true }).then(function (syncArgs) {
-                    vm.page.menu.currentNode = syncArgs.node;
-                });
-            }
-
-            // save state of master template to use for comparison when syncing the tree on save
-            oldMasterTemplateAlias = Utilities.copy(template.masterTemplateAlias);
-
-            // ace configuration
-            vm.aceOption = {
-                mode: "razor",
-                theme: "chrome",
-                showPrintMargin: false,
-                advanced: {
-                    fontSize: '14px',
-                    enableSnippets: false, //The Razor mode snippets are awful (Need a way to override these)
-                    enableBasicAutocompletion: true,
-                    enableLiveAutocompletion: false
-                },
-                onLoad: function (_editor) {
-                    vm.editor = _editor;
-
-                    //Update the auto-complete method to use ctrl+alt+space
-                    _editor.commands.bindKey("ctrl-alt-space", "startAutocomplete");
-
-                    // Unassigns the keybinding (That was previously auto-complete)
-                    // As conflicts with our own tree search shortcut
-                    _editor.commands.bindKey("ctrl-space", null);
-
-                    // Assign new keybinding
-                    _editor.commands.addCommands([
-                        // Disable (alt+shift+K)
-                        // Conflicts with our own show shortcuts dialog - this overrides it
-                        {
-                            name: 'unSelectOrFindPrevious',
-                            bindKey: 'Alt-Shift-K',
-                            exec: function () {
-                                // Toggle the show keyboard shortcuts overlay
-                                $scope.$apply(function () {
-                                    vm.showKeyboardShortcut = !vm.showKeyboardShortcut;
-                                });
-
-                            },
-                            readOnly: true
-                        },
-                        {
-                            name: 'insertUmbracoValue',
-                            bindKey: 'Alt-Shift-V',
-                            exec: function () {
-                                $scope.$apply(function () {
-                                    openPageFieldOverlay();
-                                });
-                            },
-                            readOnly: true
-                        },
-                        {
-                            name: 'insertPartialView',
-                            bindKey: 'Alt-Shift-P',
-                            exec: function () {
-                                $scope.$apply(function () {
-                                    openPartialOverlay();
-                                });
-                            },
-                            readOnly: true
-                        },
-                        {
-                            name: 'insertDictionary',
-                            bindKey: 'Alt-Shift-D',
-                            exec: function () {
-                                $scope.$apply(function () {
-                                    openDictionaryItemOverlay();
-                                });
-                            },
-                            readOnly: true
-                        },
-                        {
-                            name: 'insertUmbracoMacro',
-                            bindKey: 'Alt-Shift-M',
-                            exec: function () {
-                                $scope.$apply(function () {
-                                    openMacroOverlay();
-                                });
-                            },
-                            readOnly: true
-                        },
-                        {
-                            name: 'insertQuery',
-                            bindKey: 'Alt-Shift-Q',
-                            exec: function () {
-                                $scope.$apply(function () {
-                                    openQueryBuilderOverlay();
-                                });
-                            },
-                            readOnly: true
-                        },
-                        {
-                            name: 'insertSection',
-                            bindKey: 'Alt-Shift-S',
-                            exec: function () {
-                                $scope.$apply(function () {
-                                    openSectionsOverlay();
-                                });
-                            },
-                            readOnly: true
-                        },
-                        {
-                            name: 'chooseMasterTemplate',
-                            bindKey: 'Alt-Shift-T',
-                            exec: function () {
-                                $scope.$apply(function () {
-                                    openMasterTemplateOverlay();
-                                });
-                            },
-                            readOnly: true
-                        }
-
-                    ]);
-
-                    // initial cursor placement
-                    // Keep cursor in name field if we are create a new template
-                    // else set the cursor at the bottom of the code editor
-                    if (!create) {
-                        $timeout(function () {
-                            vm.editor.navigateFileEnd();
-                            vm.editor.focus();
-                            persistCurrentLocation();
-                        });
-                    }
-
-                    // change on blur, focus
-                    vm.editor.on("blur", persistCurrentLocation);
-                    vm.editor.on("focus", persistCurrentLocation);
-                    vm.editor.on("change", changeAceEditor);
-                }
-            }
-
-        };
-
-        vm.openPageFieldOverlay = openPageFieldOverlay;
-        vm.openDictionaryItemOverlay = openDictionaryItemOverlay;
-        vm.openQueryBuilderOverlay = openQueryBuilderOverlay;
-        vm.openMacroOverlay = openMacroOverlay;
-        vm.openInsertOverlay = openInsertOverlay;
-        vm.openSectionsOverlay = openSectionsOverlay;
-        vm.openPartialOverlay = openPartialOverlay;
-        vm.openMasterTemplateOverlay = openMasterTemplateOverlay;
-        vm.selectMasterTemplate = selectMasterTemplate;
-        vm.getMasterTemplateName = getMasterTemplateName;
-        vm.removeMasterTemplate = removeMasterTemplate;
-        vm.closeShortcuts = closeShortcuts;
-        vm.submit = submit;
-        vm.close = close;
-
-        function openInsertOverlay() {
-            var insertOverlay = {
-                allowedTypes: {
-                    macro: true,
-                    dictionary: true,
-                    partial: true,
-                    umbracoField: true
-                },
-                submit: function (model) {
-                    switch (model.insert.type) {
-                        case "macro":
-                            var macroObject = macroService.collectValueData(model.insert.selectedMacro, model.insert.macroParams, "Mvc");
-                            insert(macroObject.syntax);
-                            break;
-                        case "dictionary":
-                            var code = templateHelper.getInsertDictionarySnippet(model.insert.node.name);
-                            insert(code);
-                            break;
-                        case "partial":
-                            var code = templateHelper.getInsertPartialSnippet(model.insert.node.parentId, model.insert.node.name);
-                            insert(code);
-                            break;
-                        case "umbracoField":
-                            insert(model.insert.umbracoField);
-                            break;
-                    }
-                    editorService.close();
-                },
-                close: function (oldModel) {
-                    // close the dialog
-                    editorService.close();
-                    // focus editor
-                    vm.editor.focus();
-                }
-            };
-            editorService.insertCodeSnippet(insertOverlay);
-        }
-
-        function openMacroOverlay() {
-            var macroPicker = {
-                dialogData: {},
-                submit: function (model) {
-                    var macroObject = macroService.collectValueData(model.selectedMacro, model.macroParams, "Mvc");
-                    insert(macroObject.syntax);
-                    editorService.close();
-                },
-                close: function () {
-                    editorService.close();
-                    vm.editor.focus();
-                }
-            };
-            editorService.macroPicker(macroPicker);
-        }
-
-        function openPageFieldOverlay() {
-            var insertFieldEditor = {
-                submit: function (model) {
-                    insert(model.umbracoField);
-                    editorService.close();
-                },
-                close: function () {
-                    editorService.close();
-                    vm.editor.focus();
-                }
-            };
-            editorService.insertField(insertFieldEditor);
-        }
-
-
-        function openDictionaryItemOverlay() {
-
-            var labelKeys = [
-                "template_insertDictionaryItem",
-                "emptyStates_emptyDictionaryTree"
-            ];
-
-            localizationService.localizeMany(labelKeys).then(function (values) {
-                var title = values[0];
-                var emptyStateMessage = values[1];
-
-                var dictionaryItem = {
-                    section: "translation",
-                    treeAlias: "dictionary",
-                    entityType: "dictionary",
-                    multiPicker: false,
-                    title: title,
-                    emptyStateMessage: emptyStateMessage,
-                    select: function (node) {
-                        var code = templateHelper.getInsertDictionarySnippet(node.name);
-                        insert(code);
-                        editorService.close();
-                    },
-                    close: function (model) {
-                        // close dialog
-                        editorService.close();
-                        // focus editor
-                        vm.editor.focus();
-                    }
-                };
-
-                editorService.treePicker(dictionaryItem);
-
-            });
-
-        }
-
-        function openPartialOverlay() {
-
-            localizationService.localize("template_insertPartialView").then(function (value) {
-                var title = value;
-
-                var partialItem = {
-                    section: "settings",
-                    treeAlias: "partialViews",
-                    entityType: "partialView",
-                    multiPicker: false,
-                    title: title,
-                    filter: function (i) {
-                        if (i.name.indexOf(".cshtml") === -1 && i.name.indexOf(".vbhtml") === -1) {
-                            return true;
-                        }
-                    },
-                    filterCssClass: "not-allowed",
-                    select: function (node) {
-                        var code = templateHelper.getInsertPartialSnippet(node.parentId, node.name);
-                        insert(code);
-                        editorService.close();
-                    },
-                    close: function (model) {
-                        // close dialog
-                        editorService.close();
-                        // focus editor
-                        vm.editor.focus();
-                    }
-                };
-
-                editorService.treePicker(partialItem);
-            });
-        }
-
-        function openQueryBuilderOverlay() {
-            var queryBuilder = {
-                submit: function (model) {
-                    var code = templateHelper.getQuerySnippet(model.result.queryExpression);
-                    insert(code);
-                    editorService.close();
-                },
-                close: function () {
-                    editorService.close();
-                    // focus editor
-                    vm.editor.focus();
-                }
-            };
-            editorService.queryBuilder(queryBuilder);
-        }
-
-
-        function openSectionsOverlay() {
-            var templateSections = {
-                isMaster: vm.template.isMasterTemplate,
-                submit: function (model) {
-
-                    if (model.insertType === 'renderBody') {
-                        var code = templateHelper.getRenderBodySnippet();
-                        insert(code);
-                    }
-
-                    if (model.insertType === 'renderSection') {
-                        var code = templateHelper.getRenderSectionSnippet(model.renderSectionName, model.mandatoryRenderSection);
-                        insert(code);
-                    }
-
-                    if (model.insertType === 'addSection') {
-                        var code = templateHelper.getAddSectionSnippet(model.sectionName);
-                        wrap(code);
-                    }
-
-                    editorService.close();
-
-                },
-                close: function (model) {
-                    editorService.close();
-                    vm.editor.focus();
-                }
-            }
-            editorService.templateSections(templateSections);
-        }
-
-        function openMasterTemplateOverlay() {
-
-            // make collection of available master templates
-            var availableMasterTemplates = [];
-
-            // filter out the current template and the selected master template
-            vm.templates.forEach(function (template) {
-                if (template.alias !== vm.template.alias && template.alias !== vm.template.masterTemplateAlias) {
-                    var templatePathArray = template.path.split(',');
-                    // filter descendant templates of current template
-                    if (templatePathArray.indexOf(String(vm.template.id)) === -1) {
-                        availableMasterTemplates.push(template);
-                    }
-                }
-            });
-          
-            const editor = {
-                filterCssClass: 'not-allowed',
-                filter: item => !availableMasterTemplates.some(template => template.id == item.id),
-                submit: model => {
-                  const template = model.selection[0];
-                  if (template && template.alias) {
-                    vm.template.masterTemplateAlias = template.alias;
-                    setLayout(template.alias + ".cshtml");
-                  } else {
-                    vm.template.masterTemplateAlias = null;
-                    setLayout(null);
-                  }
-                  editorService.close();
-                },
-                close: () => editorService.close()
-            }
-
-            localizationService.localize("template_mastertemplate").then(title => {
-                editor.title = title;
-                
-                const currentTemplate = vm.templates.find(template => template.alias == vm.template.masterTemplateAlias);
-                if (currentTemplate) {
-                    editor.currentNode = {
-                        path: currentTemplate.path
-                    };
-                }
-
-                editorService.templatePicker(editor);
-            });
-
-        }
-
-        function selectMasterTemplate(template) {
-
-            if (template && template.alias) {
-                vm.template.masterTemplateAlias = template.alias;
-                setLayout(template.alias + ".cshtml");
-            } else {
-                vm.template.masterTemplateAlias = null;
-                setLayout(null);
-            }
-
-        }
-
-        function getMasterTemplateName(masterTemplateAlias, templates) {
-            if (masterTemplateAlias) {
-                var templateName = "";
-                templates.forEach(function (template) {
-                    if (template.alias === masterTemplateAlias) {
-                        templateName = template.name;
-                    }
-                });
-                return templateName;
-            }
-        }
-
-        function removeMasterTemplate() {
-
-            vm.template.masterTemplateAlias = null;
-
-            // call set layout with no paramters to set layout to null
-            setLayout();
-
-        }
-
-        function setLayout(templatePath) {
-
-            var templateCode = vm.editor.getValue();
-            var newValue = templatePath;
-            var layoutDefRegex = new RegExp("(@{[\\s\\S]*?Layout\\s*?=\\s*?)(\"[^\"]*?\"|null)(;[\\s\\S]*?})", "gi");
-
-            if (newValue !== undefined && newValue !== "") {
-                if (layoutDefRegex.test(templateCode)) {
-                    // Declaration exists, so just update it
-                    templateCode = templateCode.replace(layoutDefRegex, "$1\"" + newValue + "\"$3");
-                } else {
-                    // Declaration doesn't exist, so prepend to start of doc
-                    // TODO: Maybe insert at the cursor position, rather than just at the top of the doc?
-                    templateCode = "@{\n\tLayout = \"" + newValue + "\";\n}\n" + templateCode;
-                }
-            } else {
-                if (layoutDefRegex.test(templateCode)) {
-                    // Declaration exists, so just update it
-                    templateCode = templateCode.replace(layoutDefRegex, "$1null$3");
-                }
-            }
-
-            vm.editor.setValue(templateCode);
-            vm.editor.clearSelection();
-            vm.editor.navigateFileStart();
-
-            vm.editor.focus();
-            // set form state to $dirty
-            setFormState("dirty");
-
-        }
-
-
-        function insert(str) {
-            vm.editor.focus();
-            vm.editor.moveCursorToPosition(vm.currentPosition);
-            vm.editor.insert(str);
-
-            // set form state to $dirty
-            setFormState("dirty");
-        }
-
-        function wrap(str) {
-
-            var selectedContent = vm.editor.session.getTextRange(vm.editor.getSelectionRange());
-            str = str.replace("{0}", selectedContent);
-            vm.editor.insert(str);
-            vm.editor.focus();
-
-            // set form state to $dirty
-            setFormState("dirty");
-        }
-
-        function persistCurrentLocation() {
-            vm.currentPosition = vm.editor.getCursorPosition();
-        }
-
-        function changeAceEditor() {
-            setFormState("dirty");
-        }
-
-        function setFormState(state) {
-
-            // get the current form
-            var currentForm = angularHelper.getCurrentForm($scope);
-
-            // set state
-            if (state === "dirty") {
-                currentForm.$setDirty();
-            } else if (state === "pristine") {
-                currentForm.$setPristine();
-            }
-        }
-
-        function closeShortcuts() {
-            vm.showKeyboardShortcut = false;
-        }
-
-        function submit() {
-            if ($scope.model.submit) {
-                $scope.model.template = vm.template;
-                $scope.model.submit($scope.model);
-            }
-        }
-
-        function close() {
-            if ($scope.model.close) {
-                $scope.model.close();
-            }
-        }
-
-        vm.init();
+      });
 
     }
 
-    angular.module("umbraco").controller("Umbraco.Editors.Templates.EditController", TemplatesEditController);
+    function openPartialOverlay() {
+
+      localizationService.localize("template_insertPartialView").then(function (value) {
+        var title = value;
+
+        var partialItem = {
+          section: "settings",
+          treeAlias: "partialViews",
+          entityType: "partialView",
+          multiPicker: false,
+          title: title,
+          filter: function (i) {
+            if (i.name.indexOf(".cshtml") === -1 && i.name.indexOf(".vbhtml") === -1) {
+              return true;
+            }
+          },
+          filterCssClass: "not-allowed",
+          select: function (node) {
+            var code = templateHelper.getInsertPartialSnippet(node.parentId, node.name);
+            insert(code);
+            editorService.close();
+          },
+          close: function (model) {
+            // close dialog
+            editorService.close();
+            // focus editor
+            vm.editor.focus();
+          }
+        };
+
+        editorService.treePicker(partialItem);
+      });
+    }
+
+    function openQueryBuilderOverlay() {
+      var queryBuilder = {
+        submit: function (model) {
+          var code = templateHelper.getQuerySnippet(model.result.queryExpression);
+          insert(code);
+          editorService.close();
+        },
+        close: function () {
+          editorService.close();
+          // focus editor
+          vm.editor.focus();
+        }
+      };
+      editorService.queryBuilder(queryBuilder);
+    }
+
+
+    function openSectionsOverlay() {
+      var templateSections = {
+        isMaster: vm.template.isMasterTemplate,
+        submit: function (model) {
+
+          if (model.insertType === 'renderBody') {
+            var code = templateHelper.getRenderBodySnippet();
+            insert(code);
+          }
+
+          if (model.insertType === 'renderSection') {
+            var code = templateHelper.getRenderSectionSnippet(model.renderSectionName, model.mandatoryRenderSection);
+            insert(code);
+          }
+
+          if (model.insertType === 'addSection') {
+            var code = templateHelper.getAddSectionSnippet(model.sectionName);
+            wrap(code);
+          }
+
+          editorService.close();
+
+        },
+        close: function (model) {
+          editorService.close();
+          vm.editor.focus();
+        }
+      }
+      editorService.templateSections(templateSections);
+    }
+
+    function openMasterTemplateOverlay() {
+
+      // make collection of available master templates
+      var availableMasterTemplates = [];
+
+      // filter out the current template and the selected master template
+      vm.templates.forEach(function (template) {
+        if (template.alias !== vm.template.alias && template.alias !== vm.template.masterTemplateAlias) {
+          var templatePathArray = template.path.split(',');
+          // filter descendant templates of current template
+          if (templatePathArray.indexOf(String(vm.template.id)) === -1) {
+            availableMasterTemplates.push(template);
+          }
+        }
+      });
+
+      const editor = {
+        filterCssClass: 'not-allowed',
+        filter: item => !availableMasterTemplates.some(template => template.id == item.id),
+        submit: model => {
+          const template = model.selection[0];
+          if (template && template.alias) {
+            vm.template.masterTemplateAlias = template.alias;
+            setLayout(template.alias + ".cshtml");
+          } else {
+            vm.template.masterTemplateAlias = null;
+            setLayout(null);
+          }
+          editorService.close();
+        },
+        close: () => editorService.close()
+      }
+
+      localizationService.localize("template_mastertemplate").then(title => {
+        editor.title = title;
+
+        const currentTemplate = vm.templates.find(template => template.alias == vm.template.masterTemplateAlias);
+        if (currentTemplate) {
+          editor.currentNode = {
+            path: currentTemplate.path
+          };
+        }
+
+        editorService.templatePicker(editor);
+      });
+
+    }
+
+    function selectMasterTemplate(template) {
+
+      if (template && template.alias) {
+        vm.template.masterTemplateAlias = template.alias;
+        setLayout(template.alias + ".cshtml");
+      } else {
+        vm.template.masterTemplateAlias = null;
+        setLayout(null);
+      }
+
+    }
+
+    function getMasterTemplateName(masterTemplateAlias, templates) {
+      if (masterTemplateAlias) {
+        var templateName = "";
+        templates.forEach(function (template) {
+          if (template.alias === masterTemplateAlias) {
+            templateName = template.name;
+          }
+        });
+        return templateName;
+      }
+    }
+
+    function removeMasterTemplate() {
+
+      vm.template.masterTemplateAlias = null;
+
+      // call set layout with no paramters to set layout to null
+      setLayout();
+
+    }
+
+    function setLayout(templatePath) {
+
+      var templateCode = vm.editor.getValue();
+      var newValue = templatePath;
+      var layoutDefRegex = new RegExp("(@{[\\s\\S]*?Layout\\s*?=\\s*?)(\"[^\"]*?\"|null)(;[\\s\\S]*?})", "gi");
+
+      if (newValue !== undefined && newValue !== "") {
+        if (layoutDefRegex.test(templateCode)) {
+          // Declaration exists, so just update it
+          templateCode = templateCode.replace(layoutDefRegex, "$1\"" + newValue + "\"$3");
+        } else {
+          // Declaration doesn't exist, so prepend to start of doc
+          // TODO: Maybe insert at the cursor position, rather than just at the top of the doc?
+          templateCode = "@{\n\tLayout = \"" + newValue + "\";\n}\n" + templateCode;
+        }
+      } else {
+        if (layoutDefRegex.test(templateCode)) {
+          // Declaration exists, so just update it
+          templateCode = templateCode.replace(layoutDefRegex, "$1null$3");
+        }
+      }
+
+      vm.editor.setValue(templateCode);
+      vm.editor.clearSelection();
+      vm.editor.navigateFileStart();
+
+      vm.editor.focus();
+      // set form state to $dirty
+      setFormState("dirty");
+
+    }
+
+
+    function insert(str) {
+      vm.editor.focus();
+      vm.editor.moveCursorToPosition(vm.currentPosition);
+      vm.editor.insert(str);
+
+      // set form state to $dirty
+      setFormState("dirty");
+    }
+
+    function wrap(str) {
+
+      var selectedContent = vm.editor.session.getTextRange(vm.editor.getSelectionRange());
+      str = str.replace("{0}", selectedContent);
+      vm.editor.insert(str);
+      vm.editor.focus();
+
+      // set form state to $dirty
+      setFormState("dirty");
+    }
+
+    function persistCurrentLocation() {
+      vm.currentPosition = vm.editor.getCursorPosition();
+    }
+
+    function changeAceEditor() {
+      setFormState("dirty");
+    }
+
+    function setFormState(state) {
+
+      // get the current form
+      var currentForm = angularHelper.getCurrentForm($scope);
+
+      // set state
+      if (state === "dirty") {
+        currentForm.$setDirty();
+      } else if (state === "pristine") {
+        currentForm.$setPristine();
+      }
+    }
+
+    function closeShortcuts() {
+      vm.showKeyboardShortcut = false;
+    }
+
+    function submit() {
+      if ($scope.model.submit) {
+        $scope.model.template = vm.template;
+        $scope.model.submit($scope.model);
+      }
+    }
+
+    function close() {
+      if ($scope.model.close) {
+        $scope.model.close();
+      }
+    }
+
+    vm.init();
+
+  }
+
+  angular.module("umbraco").controller("Umbraco.Editors.Templates.EditController", TemplatesEditController);
 })();
diff --git a/src/Umbraco.Web.UI.Client/src/views/templates/edit.html b/src/Umbraco.Web.UI.Client/src/views/templates/edit.html
index f909bc197f..a5527095d7 100644
--- a/src/Umbraco.Web.UI.Client/src/views/templates/edit.html
+++ b/src/Umbraco.Web.UI.Client/src/views/templates/edit.html
@@ -1,144 +1,142 @@
 <div data-element="editor-templates" ng-controller="Umbraco.Editors.Templates.EditController as vm">
 
-    <umb-load-indicator ng-if="vm.page.loading"></umb-load-indicator>
+  <umb-load-indicator ng-if="vm.page.loading"></umb-load-indicator>
 
-   <form name="contentForm"
-      ng-submit="vm.save()"
-      novalidate
-      val-form-manager>
+  <form name="contentForm"
+        ng-submit="vm.save()"
+        novalidate
+        val-form-manager>
 
-        <umb-editor-view ng-if="!vm.page.loading">
+    <umb-editor-view ng-if="!vm.page.loading">
 
-            <umb-editor-header
-                name="vm.template.name"
-                alias="vm.template.alias"
-                key="vm.template.key"
-                description="vm.template.virtualPath"
-                description-locked="true"
-                hide-icon="false"
-                editorfor="vm.header.editorfor"
-                setpagetitle="vm.header.setPageTitle">
-            </umb-editor-header>
+      <umb-editor-header name="vm.template.name"
+                         alias="vm.template.alias"
+                         alias-locked="vm.runtimeModeProduction"
+                         key="vm.template.key"
+                         description="vm.template.virtualPath"
+                         description-locked="true"
+                         hide-icon="false"
+                         editorfor="vm.header.editorfor"
+                         setpagetitle="vm.header.setPageTitle">
+      </umb-editor-header>
 
-            <umb-editor-container>
-                <umb-box>
-                    <umb-box-content>
+      <umb-editor-container>
+        <umb-box>
 
-                        <div class="flex" style="margin-bottom: 30px;">
+          <umb-box-content>
+            <div class="umb-alert umb-alert--info mb3" ng-if="vm.runtimeModeProduction">
+              <localize key="template_runtimeModeProduction">Content is not editable when using runtime mode <code>Production</code>.</localize>
+            </div>
 
-                            <div class="flex">
+            <div class="flex mb3" ng-if="!vm.runtimeModeProduction">
 
-                                <div ng-class="{'btn-group': vm.template.masterTemplateAlias}" style="margin-right: 10px;">
+              <div class="flex">
 
-                                    <button type="button"
-                                            data-element="button-masterTemplate"
-                                            class="btn umb-button__button umb-button--xs umb-outline"
-                                            style="font-size: 14px;"
-                                            ng-click="vm.openMasterTemplateOverlay()">
-                                        <span class="bold"><localize key="template_mastertemplate">Master template</localize>:</span>
-                                        <span style="margin-left: 5px;">
-                                            <span ng-if="vm.template.masterTemplateAlias">{{ vm.getMasterTemplateName(vm.template.masterTemplateAlias, vm.templates) }}</span>
-                                            <span ng-if="!vm.template.masterTemplateAlias"><localize key="template_noMaster">No master</localize></span>
-                                        </span>
-                                    </button>
+                <div ng-class="{'btn-group': vm.template.masterTemplateAlias}" style="margin-right: 10px;">
 
-                                    <button type="button"
-                                            class="btn umb-button__button umb-button--xs dropdown-toggle umb-button-group__toggle flex-inline items-center"
-                                            style="font-size: 14px; height: 100%;"
-                                            localize="title"
-                                            title="@general_remove"
-                                            ng-if="vm.template.masterTemplateAlias"
-                                            ng-click="vm.removeMasterTemplate()">
-                                        <umb-icon icon="icon-wrong" class="icon"></umb-icon>
-                                    </button>
+                  <button type="button"
+                          data-element="button-masterTemplate"
+                          class="btn umb-button__button umb-button--xs umb-outline"
+                          style="font-size: 14px;"
+                          ng-click="vm.openMasterTemplateOverlay()">
+                    <span class="bold"><localize key="template_mastertemplate">Master template</localize>:</span>
+                    <span style="margin-left: 5px;">
+                      <span ng-if="vm.template.masterTemplateAlias">{{ vm.getMasterTemplateName(vm.template.masterTemplateAlias, vm.templates) }}</span>
+                      <span ng-if="!vm.template.masterTemplateAlias"><localize key="template_noMaster">No master</localize></span>
+                    </span>
+                  </button>
 
-                                </div>
+                  <button type="button"
+                          class="btn umb-button__button umb-button--xs dropdown-toggle umb-button-group__toggle flex-inline items-center"
+                          style="font-size: 14px; height: 100%;"
+                          localize="title"
+                          title="@general_remove"
+                          ng-if="vm.template.masterTemplateAlias"
+                          ng-click="vm.removeMasterTemplate()">
+                    <umb-icon icon="icon-wrong" class="icon"></umb-icon>
+                  </button>
 
-                            </div>
+                </div>
 
-                            <div class="flex" style="margin-left: auto;">
+              </div>
 
-                                <umb-button-group
-                                    style="margin-right: 5px;"
-                                    default-button="vm.page.insertDefaultButton"
-                                    sub-buttons="vm.page.insertSubButtons"
-                                    size="xs">
-                                </umb-button-group>
+              <div class="flex" style="margin-left: auto;">
 
-                                <umb-button
-                                    style="margin-right: 5px;"
-                                    alias="queryBuilder"
-                                    type="button"
-                                    size="xs"
-                                    action="vm.openQueryBuilderOverlay()"
-                                    icon="icon-wand"
-                                    label-key="template_queryBuilder"
-                                    add-ellipsis="true">
-                                </umb-button>
+                <umb-button-group style="margin-right: 5px;"
+                                  default-button="vm.page.insertDefaultButton"
+                                  sub-buttons="vm.page.insertSubButtons"
+                                  size="xs">
+                </umb-button-group>
 
-                                <umb-button
-                                    alias="sections"
-                                    type="button"
-                                    size="xs"
-                                    action="vm.openSectionsOverlay()"
-                                    icon="icon-indent"
-                                    label-key="template_insertSections"
-                                    add-ellipsis="true">
-                                </umb-button>
-
-                            </div>
-
-                        </div>
-
-                        <div
-                            data-element="code-editor"
-                            auto-scale="85"
-                            umb-ace-editor="vm.aceOption"
-                            model="vm.template.content">
-                        </div>
-                    </umb-box-content>
-                </umb-box>
-
-            </umb-editor-container>
-
-            <umb-editor-footer>
-
-                <umb-editor-footer-content-left>
-
-                    <umb-keyboard-shortcuts-overview
-                        model="vm.page.keyboardShortcutsOverview"
-                        show-overlay="vm.showKeyboardShortcut"
-                        on-close="vm.closeShortcuts()">
-                    </umb-keyboard-shortcuts-overview>
-
-                </umb-editor-footer-content-left>
-
-            <umb-editor-footer-content-right>
-
-                <umb-button
-                    ng-if="model.infiniteMode"
-                    type="button"
-                    button-style="link"
-                    label-key="general_close"
-                    shortcut="esc"
-                    action="vm.close()">
+                <umb-button style="margin-right: 5px;"
+                            alias="queryBuilder"
+                            type="button"
+                            size="xs"
+                            action="vm.openQueryBuilderOverlay()"
+                            icon="icon-wand"
+                            label-key="template_queryBuilder"
+                            add-ellipsis="true">
                 </umb-button>
 
-                <umb-button
-                    alias="save"
-                    type="submit"
-                    button-style="success"
-                    state="vm.page.saveButtonState"
-                    shortcut="ctrl+s"
-                    label="Save"
-                    label-key="buttons_save">
+                <umb-button alias="sections"
+                            type="button"
+                            size="xs"
+                            action="vm.openSectionsOverlay()"
+                            icon="icon-indent"
+                            label-key="template_insertSections"
+                            add-ellipsis="true">
                 </umb-button>
 
-            </umb-editor-footer-content-right>
+              </div>
 
-         </umb-editor-footer>
+            </div>
 
-        </umb-editor-view>
-    </form>
+            <div data-element="code-editor"
+                 auto-scale="90"
+                 umb-ace-editor="vm.aceOption"
+                 model="vm.template.content">
+            </div>
+          </umb-box-content>
+        </umb-box>
+
+      </umb-editor-container>
+
+      <umb-editor-footer>
+
+        <umb-editor-footer-content-left>
+
+          <umb-keyboard-shortcuts-overview ng-if="!vm.runtimeModeProduction"
+                                           model="vm.page.keyboardShortcutsOverview"
+                                           show-overlay="vm.showKeyboardShortcut"
+                                           on-close="vm.closeShortcuts()">
+          </umb-keyboard-shortcuts-overview>
+
+        </umb-editor-footer-content-left>
+
+        <umb-editor-footer-content-right>
+
+          <umb-button ng-if="model.infiniteMode"
+                      type="button"
+                      button-style="link"
+                      label-key="general_close"
+                      shortcut="esc"
+                      action="vm.close()">
+          </umb-button>
+
+          <umb-button alias="save"
+                      type="submit"
+                      button-style="success"
+                      state="vm.page.saveButtonState"
+                      shortcut="ctrl+s"
+                      label="Save"
+                      label-key="buttons_save">
+          </umb-button>
+
+        </umb-editor-footer-content-right>
+
+      </umb-editor-footer>
+
+    </umb-editor-view>
+  </form>
 
 </div>
diff --git a/src/Umbraco.Web.UI.Client/src/views/users/views/groups/groups.html b/src/Umbraco.Web.UI.Client/src/views/users/views/groups/groups.html
index bc33b3b577..0d77e3e97e 100644
--- a/src/Umbraco.Web.UI.Client/src/views/users/views/groups/groups.html
+++ b/src/Umbraco.Web.UI.Client/src/views/users/views/groups/groups.html
@@ -96,7 +96,7 @@
                 <div class="umb-table-cell">
                     <div>
                         <span ng-repeat="section in group.sections">{{ section.name }}<span ng-if="!$last">, </span></span>
-                        <span ng-if="group.sections.length === 0">All sections</span>
+                        <span ng-if="group.sections.length === 0">No sections</span>
                     </div>
                 </div>
                 <div class="umb-table-cell umb-table-cell--small">
diff --git a/src/Umbraco.Web.UI.Client/test/unit/app/templates/template-editor-controller.spec.js b/src/Umbraco.Web.UI.Client/test/unit/app/templates/template-editor-controller.spec.js
index b4615eaad8..9b872f137f 100644
--- a/src/Umbraco.Web.UI.Client/test/unit/app/templates/template-editor-controller.spec.js
+++ b/src/Umbraco.Web.UI.Client/test/unit/app/templates/template-editor-controller.spec.js
@@ -1,4 +1,4 @@
-﻿(function() {
+(function() {
     "use strict";
 
     describe("templates editor controller",
@@ -26,6 +26,7 @@
                     getCursorPosition: function() {},
                     getValue: function() {},
                     setValue: function() {},
+                    setReadOnly: function () { },
                     focus: function() {},
                     clearSelection: function() {},
                     navigateFileStart: function() {},
diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj
index 5f067d8a9d..2ec54bc2d8 100644
--- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj
+++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj
@@ -2,6 +2,7 @@
   <PropertyGroup>
     <TargetFramework>net6.0</TargetFramework>
     <RootNamespace>Umbraco.Cms.Web.UI</RootNamespace>
+    <EnablePackageValidation>false</EnablePackageValidation>
   </PropertyGroup>
 
   <Import Project="..\Umbraco.Cms\buildTransitive\Umbraco.Cms.props" />
@@ -15,17 +16,21 @@
     <PackageReference Include="Umbraco.Code" Version="2.0.0" PrivateAssets="all" />
   </ItemGroup>
 
-  <!-- Force Windows to use ICU. Otherwise Windows 10 2019H1+ will do it, but older Windows 10 and most, if not all, Windows Server editions will run NLS -->
   <ItemGroup>
+    <!-- Opt-in to app-local ICU to ensure consistent globalization APIs across different platforms -->
     <PackageReference Include="Microsoft.ICU.ICU4C.Runtime" Version="68.2.0.9" />
-    <RuntimeHostConfigurationOption Condition="$(RuntimeIdentifier.StartsWith('linux')) or $(RuntimeIdentifier.StartsWith('win')) or ('$(RuntimeIdentifier)' == '' and !$([MSBuild]::IsOSPlatform('osx')))" Include="System.Globalization.AppLocalIcu" Value="68.2.0.9" />
+    <RuntimeHostConfigurationOption Include="System.Globalization.AppLocalIcu" Value="68.2.0.9" Condition="$(RuntimeIdentifier.StartsWith('linux')) or $(RuntimeIdentifier.StartsWith('win')) or ('$(RuntimeIdentifier)' == '' and !$([MSBuild]::IsOSPlatform('osx')))" />
   </ItemGroup>
 
-  <!-- Remove RazorCompileOnBuild, RazorCompileOnPublish and CopyRazorGenerateFilesToPublishDirectory when not using ModelsMode InMemoryAuto -->
   <PropertyGroup>
+    <!-- Razor files are needed for the backoffice to work correctly -->
+    <CopyRazorGenerateFilesToPublishDirectory>true</CopyRazorGenerateFilesToPublishDirectory>
+  </PropertyGroup>
+
+  <PropertyGroup>
+    <!-- Remove RazorCompileOnBuild and RazorCompileOnPublish when not using ModelsMode InMemoryAuto -->
     <RazorCompileOnBuild>false</RazorCompileOnBuild>
     <RazorCompileOnPublish>false</RazorCompileOnPublish>
-    <CopyRazorGenerateFilesToPublishDirectory>true</CopyRazorGenerateFilesToPublishDirectory>
   </PropertyGroup>
 
   <PropertyGroup>
diff --git a/src/Umbraco.Web.UI/appsettings.template.json b/src/Umbraco.Web.UI/appsettings.template.json
index ed03407c0b..1ac02f8904 100644
--- a/src/Umbraco.Web.UI/appsettings.template.json
+++ b/src/Umbraco.Web.UI/appsettings.template.json
@@ -68,8 +68,7 @@
         "EnableTours": true
       },
       "ModelsBuilder": {
-        "ModelsMode": "InMemoryAuto",
-        "Enable": true
+        "ModelsMode": "InMemoryAuto"
       }
     }
   }
diff --git a/templates/UmbracoProject/UmbracoProject.csproj b/templates/UmbracoProject/UmbracoProject.csproj
index 574ff04452..2880b754e6 100644
--- a/templates/UmbracoProject/UmbracoProject.csproj
+++ b/templates/UmbracoProject/UmbracoProject.csproj
@@ -10,25 +10,25 @@
     <PackageReference Include="Umbraco.Cms" Version="UMBRACO_VERSION_FROM_TEMPLATE" />
   </ItemGroup>
 
-  <!-- Force Windows to use ICU. Otherwise Windows 10 2019H1+ will do it, but older Windows 10 and most, if not all, Windows Server editions will run NLS -->
   <ItemGroup>
+    <!-- Opt-in to app-local ICU to ensure consistent globalization APIs across different platforms -->
     <PackageReference Include="Microsoft.ICU.ICU4C.Runtime" Version="68.2.0.9" />
     <RuntimeHostConfigurationOption Include="System.Globalization.AppLocalIcu" Value="68.2.0.9" Condition="$(RuntimeIdentifier.StartsWith('linux')) or $(RuntimeIdentifier.StartsWith('win')) or ('$(RuntimeIdentifier)' == '' and !$([MSBuild]::IsOSPlatform('osx')))" />
   </ItemGroup>
 
+  <PropertyGroup>
+    <!-- Razor files are needed for the backoffice to work correctly -->
+    <CopyRazorGenerateFilesToPublishDirectory>true</CopyRazorGenerateFilesToPublishDirectory>
+  </PropertyGroup>
+
+  <PropertyGroup>
+    <!-- Remove RazorCompileOnBuild and RazorCompileOnPublish when not using ModelsMode InMemoryAuto -->
+    <RazorCompileOnBuild>false</RazorCompileOnBuild>
+    <RazorCompileOnPublish>false</RazorCompileOnPublish>
+  </PropertyGroup>
+
   <Import Project="..\PACKAGE_PROJECT_NAME_FROM_TEMPLATE\build\PACKAGE_PROJECT_NAME_FROM_TEMPLATE.targets" Condition="'$(PackageProjectName)' != ''" />
   <ItemGroup Condition="'$(PackageProjectName)' != ''">
     <ProjectReference Include="..\PACKAGE_PROJECT_NAME_FROM_TEMPLATE\PACKAGE_PROJECT_NAME_FROM_TEMPLATE.csproj" />
   </ItemGroup>
-
-  <ItemGroup>
-    <Folder Include="wwwroot" />
-  </ItemGroup>
-
-  <!-- Remove RazorCompileOnBuild, RazorCompileOnPublish and CopyRazorGenerateFilesToPublishDirectory when not using ModelsMode InMemoryAuto -->
-  <PropertyGroup>
-    <RazorCompileOnBuild>false</RazorCompileOnBuild>
-    <RazorCompileOnPublish>false</RazorCompileOnPublish>
-    <CopyRazorGenerateFilesToPublishDirectory>true</CopyRazorGenerateFilesToPublishDirectory>
-  </PropertyGroup>
 </Project>
diff --git a/tests/Umbraco.Tests.AcceptanceTest/cypress/integration/Content/content.ts b/tests/Umbraco.Tests.AcceptanceTest/cypress/integration/Content/content.ts
index 0e12541338..9288dd6f72 100644
--- a/tests/Umbraco.Tests.AcceptanceTest/cypress/integration/Content/content.ts
+++ b/tests/Umbraco.Tests.AcceptanceTest/cypress/integration/Content/content.ts
@@ -792,4 +792,45 @@ context('Content', () => {
     cy.umbracoEnsureMacroNameNotExists(macroName);
     cy.umbracoEnsurePartialViewMacroFileNameNotExists(macroFileName);
   });
+
+  it('Create content with the same name', () => {
+    const documentTypeName = "TestType";
+    const nodeName = "Home";
+    const expectedNodeName = nodeName + " (1)";
+
+    cy.umbracoEnsureDocumentTypeNameNotExists(documentTypeName);
+    cy.deleteAllContent();
+
+    const documentType = new DocumentTypeBuilder()
+        .withName(documentTypeName)
+        .withAllowAsRoot(true)
+        .build();
+
+    cy.saveDocumentType(documentType).then((generatedDocumentType) => {
+      const rootContentNode = new ContentBuilder()
+          .withAction("saveNew")
+          .withContentTypeAlias(generatedDocumentType["alias"])
+          .addVariant()
+          .withName(nodeName)
+          .withSave(true)
+          .done()
+          .build();
+
+      cy.saveContent(rootContentNode)
+    });
+
+    refreshContentTree();
+
+    cy.get('li .umb-tree-root:contains("Content")').should("be.visible").rightclick();
+    cy.umbracoContextMenuAction("action-create").click();
+    cy.get('.umb-action-link').contains(documentTypeName).click();
+    cy.umbracoEditorHeaderName(nodeName);
+    cy.umbracoButtonByLabelKey("buttons_saveAndPublish").click();
+
+    cy.umbracoSuccessNotification().should('be.visible');
+    cy.umbracoTreeItem("content", [expectedNodeName]).should('be.visible');
+
+    cy.umbracoEnsureDocumentTypeNameNotExists(documentTypeName);
+    cy.deleteAllContent();
+  });
 });
diff --git a/tests/Umbraco.Tests.AcceptanceTest/cypress/integration/HelpPanel/systemInformation.ts b/tests/Umbraco.Tests.AcceptanceTest/cypress/integration/HelpPanel/systemInformation.ts
index dbc9d19427..8bed0bf7ce 100644
--- a/tests/Umbraco.Tests.AcceptanceTest/cypress/integration/HelpPanel/systemInformation.ts
+++ b/tests/Umbraco.Tests.AcceptanceTest/cypress/integration/HelpPanel/systemInformation.ts
@@ -20,7 +20,22 @@ context('System Information', () => {
 
     it('Check System Info Displays', () => {
         openSystemInformation();
-        cy.get('.table').find('tr').should('have.length', 13);
+        
+        // Assert
+        cy.get('.table').find('tr').should('contain', 'Server OS');
+        cy.get('.table').find('tr').should('contain', 'Server Framework');
+        cy.get('.table').find('tr').should('contain', 'Default Language');
+        cy.get('.table').find('tr').should('contain', 'Umbraco Version');
+        cy.get('.table').find('tr').should('contain', 'Current Culture');
+        cy.get('.table').find('tr').should('contain', 'Current UI Culture');
+        cy.get('.table').find('tr').should('contain', 'Current Webserver');
+        cy.get('.table').find('tr').should('contain', 'Models Builder Mode');
+        cy.get('.table').find('tr').should('contain', 'Debug Mode');
+        cy.get('.table').find('tr').should('contain', 'Database Provider');
+        cy.get('.table').find('tr').should('contain', 'Current Server Role');
+        cy.get('.table').find('tr').should('contain', 'Browser');
+        cy.get('.table').find('tr').should('contain', 'Browser OS');
+        cy.contains('Default Language').parent().should('contain', 'en-US');
         cy.contains('Current Culture').parent().should('contain', 'en-US');
         cy.contains('Current UI Culture').parent().should('contain', 'en-US');
     });
diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/Telemetry/TelemetryServiceTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/Telemetry/TelemetryServiceTests.cs
index 058b6d83c2..be64b7593e 100644
--- a/tests/Umbraco.Tests.Integration/Umbraco.Core/Telemetry/TelemetryServiceTests.cs
+++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/Telemetry/TelemetryServiceTests.cs
@@ -1,4 +1,4 @@
-using System;
+﻿using System;
 using System.Linq;
 using Microsoft.Extensions.DependencyInjection;
 using NUnit.Framework;
@@ -50,6 +50,7 @@ public class TelemetryServiceTests : UmbracoIntegrationTest
             Constants.Telemetry.AspEnvironment,
             Constants.Telemetry.IsDebug,
             Constants.Telemetry.DatabaseProvider,
+            Constants.Telemetry.CurrentServerRole
         };
 
         MetricsConsentService.SetConsentLevel(TelemetryLevel.Detailed);
diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/ContentTypeRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/ContentTypeRepositoryTest.cs
index 3547182538..aae69e2f61 100644
--- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/ContentTypeRepositoryTest.cs
+++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/ContentTypeRepositoryTest.cs
@@ -5,10 +5,12 @@ using System;
 using System.Collections.Generic;
 using System.Linq;
 using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
 using Moq;
 using NUnit.Framework;
 using Umbraco.Cms.Core;
 using Umbraco.Cms.Core.Cache;
+using Umbraco.Cms.Core.Configuration.Models;
 using Umbraco.Cms.Core.IO;
 using Umbraco.Cms.Core.Mapping;
 using Umbraco.Cms.Core.Models;
@@ -77,6 +79,9 @@ public class ContentTypeRepositoryTest : UmbracoIntegrationTest
         var provider = ScopeProvider;
         using (var scope = provider.CreateScope())
         {
+            var runtimeSettingsMock = new Mock<IOptionsMonitor<RuntimeSettings>>();
+            runtimeSettingsMock.Setup(x => x.CurrentValue).Returns(new RuntimeSettings());
+
             var templateRepo = new TemplateRepository(
                 (IScopeAccessor)provider,
                 AppCaches.Disabled,
@@ -84,7 +89,8 @@ public class ContentTypeRepositoryTest : UmbracoIntegrationTest
                 FileSystems,
                 IOHelper,
                 ShortStringHelper,
-                Mock.Of<IViewHelper>());
+                Mock.Of<IViewHelper>(),
+                runtimeSettingsMock.Object);
             var repository = ContentTypeRepository;
             Template[] templates =
             {
diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DocumentRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DocumentRepositoryTest.cs
index bcdcf7666a..9870926544 100644
--- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DocumentRepositoryTest.cs
+++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DocumentRepositoryTest.cs
@@ -111,7 +111,10 @@ public class DocumentRepositoryTest : UmbracoIntegrationTest
     {
         appCaches ??= AppCaches;
 
-        templateRepository = new TemplateRepository(scopeAccessor, appCaches, LoggerFactory.CreateLogger<TemplateRepository>(), FileSystems, IOHelper, ShortStringHelper, Mock.Of<IViewHelper>());
+        var runtimeSettingsMock = new Mock<IOptionsMonitor<RuntimeSettings>>();
+        runtimeSettingsMock.Setup(x => x.CurrentValue).Returns(new RuntimeSettings());
+
+        templateRepository = new TemplateRepository(scopeAccessor, appCaches, LoggerFactory.CreateLogger<TemplateRepository>(), FileSystems, IOHelper, ShortStringHelper, Mock.Of<IViewHelper>(), runtimeSettingsMock.Object);
         var tagRepository = new TagRepository(scopeAccessor, appCaches, LoggerFactory.CreateLogger<TagRepository>());
         var commonRepository =
             new ContentTypeCommonRepository(scopeAccessor, templateRepository, appCaches, ShortStringHelper);
diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/TemplateRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/TemplateRepositoryTest.cs
index bcf0f185d1..e0665aaf6d 100644
--- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/TemplateRepositoryTest.cs
+++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/TemplateRepositoryTest.cs
@@ -7,6 +7,7 @@ using System.IO;
 using System.Linq;
 using System.Text;
 using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
 using Moq;
 using NUnit.Framework;
 using Umbraco.Cms.Core;
@@ -56,10 +57,13 @@ public class TemplateRepositoryTest : UmbracoIntegrationTest
     private IHostingEnvironment HostingEnvironment => GetRequiredService<IHostingEnvironment>();
 
     private FileSystems FileSystems => GetRequiredService<FileSystems>();
+
     private IViewHelper ViewHelper => GetRequiredService<IViewHelper>();
 
+    private IOptionsMonitor<RuntimeSettings> RuntimeSettings => GetRequiredService<IOptionsMonitor<RuntimeSettings>>();
+
     private ITemplateRepository CreateRepository(IScopeProvider provider) =>
-        new TemplateRepository((IScopeAccessor)provider, AppCaches.Disabled, LoggerFactory.CreateLogger<TemplateRepository>(), FileSystems, IOHelper, ShortStringHelper, ViewHelper);
+        new TemplateRepository((IScopeAccessor)provider, AppCaches.Disabled, LoggerFactory.CreateLogger<TemplateRepository>(), FileSystems, IOHelper, ShortStringHelper, ViewHelper, RuntimeSettings);
 
     [Test]
     public void Can_Instantiate_Repository()
diff --git a/tests/Umbraco.Tests.UnitTests/AutoFixture/Customizations/UmbracoCustomizations.cs b/tests/Umbraco.Tests.UnitTests/AutoFixture/Customizations/UmbracoCustomizations.cs
index 45ff7320c2..76643279fa 100644
--- a/tests/Umbraco.Tests.UnitTests/AutoFixture/Customizations/UmbracoCustomizations.cs
+++ b/tests/Umbraco.Tests.UnitTests/AutoFixture/Customizations/UmbracoCustomizations.cs
@@ -12,6 +12,7 @@ using Umbraco.Cms.Core.Configuration.Models;
 using Umbraco.Cms.Core.Hosting;
 using Umbraco.Cms.Core.Security;
 using Umbraco.Cms.Core.Services;
+using Umbraco.Cms.Infrastructure.Install;
 using Umbraco.Cms.Infrastructure.Migrations.Install;
 using Umbraco.Cms.Web.BackOffice.Controllers;
 using Umbraco.Cms.Web.BackOffice.Install;
@@ -37,7 +38,8 @@ internal class UmbracoCustomizations : ICustomization
             .Customize(new ConstructorCustomization(typeof(BackOfficeUserManager), new GreedyConstructorQuery()))
             .Customize(new ConstructorCustomization(typeof(MemberManager), new GreedyConstructorQuery()))
             .Customize(new ConstructorCustomization(typeof(DatabaseSchemaCreatorFactory), new GreedyConstructorQuery()))
-            .Customize(new ConstructorCustomization(typeof(BackOfficeServerVariables), new GreedyConstructorQuery()));
+            .Customize(new ConstructorCustomization(typeof(BackOfficeServerVariables), new GreedyConstructorQuery()))
+            .Customize(new ConstructorCustomization(typeof(InstallHelper), new GreedyConstructorQuery()));
 
         // When requesting an IUserStore ensure we actually uses a IUserLockoutStore
         fixture.Customize<IUserStore<BackOfficeIdentityUser>>(cc =>
diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Services/UserDataServiceTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Services/UserDataServiceTests.cs
index f50bb8ae09..4104f3c0aa 100644
--- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Services/UserDataServiceTests.cs
+++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Services/UserDataServiceTests.cs
@@ -1,4 +1,4 @@
-using System.Collections.Generic;
+﻿using System.Collections.Generic;
 using System.Globalization;
 using System.Linq;
 using System.Threading;
@@ -12,6 +12,7 @@ using Umbraco.Cms.Core.Configuration.Models;
 using Umbraco.Cms.Core.Models;
 using Umbraco.Cms.Core.Semver;
 using Umbraco.Cms.Core.Services;
+using Umbraco.Cms.Core.Sync;
 using Umbraco.Cms.Infrastructure.Persistence;
 using Umbraco.Cms.Infrastructure.Telemetry.Providers;
 
@@ -133,13 +134,12 @@ public class UserDataServiceTests
         return new SystemInformationTelemetryProvider(
             _umbracoVersion,
             localizationService,
-            Mock.Of<IOptionsMonitor<ModelsBuilderSettings>>(x =>
-                x.CurrentValue == new ModelsBuilderSettings { ModelsMode = modelsMode }),
+            Mock.Of<IOptionsMonitor<ModelsBuilderSettings>>(x => x.CurrentValue == new ModelsBuilderSettings { ModelsMode = modelsMode }),
             Mock.Of<IOptionsMonitor<HostingSettings>>(x => x.CurrentValue == new HostingSettings { Debug = isDebug }),
             Mock.Of<IOptionsMonitor<GlobalSettings>>(x => x.CurrentValue == new GlobalSettings()),
             Mock.Of<IHostEnvironment>(),
-            Mock.Of<IUmbracoDatabaseFactory>(x =>
-                x.CreateDatabase() == Mock.Of<IUmbracoDatabase>(y => y.DatabaseType == DatabaseType.SQLite)));
+            Mock.Of<IUmbracoDatabaseFactory>(x => x.CreateDatabase() == Mock.Of<IUmbracoDatabase>(y => y.DatabaseType == DatabaseType.SQLite)),
+            Mock.Of<IServerRoleAccessor>());
     }
 
     private ILocalizationService CreateILocalizationService(string culture)
diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Telemetry/SystemInformationTelemetryProviderTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Telemetry/SystemInformationTelemetryProviderTests.cs
index 28fc224b8a..596670b0b0 100644
--- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Telemetry/SystemInformationTelemetryProviderTests.cs
+++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Telemetry/SystemInformationTelemetryProviderTests.cs
@@ -1,4 +1,4 @@
-using System.Globalization;
+﻿using System.Globalization;
 using System.Linq;
 using System.Threading;
 using Microsoft.Extensions.Hosting;
@@ -10,6 +10,7 @@ using Umbraco.Cms.Core;
 using Umbraco.Cms.Core.Configuration;
 using Umbraco.Cms.Core.Configuration.Models;
 using Umbraco.Cms.Core.Services;
+using Umbraco.Cms.Core.Sync;
 using Umbraco.Cms.Infrastructure.Persistence;
 using Umbraco.Cms.Infrastructure.Telemetry.Providers;
 
@@ -110,13 +111,11 @@ public class SystemInformationTelemetryProviderTests
         return new SystemInformationTelemetryProvider(
             Mock.Of<IUmbracoVersion>(),
             Mock.Of<ILocalizationService>(),
-            Mock.Of<IOptionsMonitor<ModelsBuilderSettings>>(x =>
-                x.CurrentValue == new ModelsBuilderSettings { ModelsMode = modelsMode }),
+            Mock.Of<IOptionsMonitor<ModelsBuilderSettings>>(x => x.CurrentValue == new ModelsBuilderSettings{ ModelsMode = modelsMode }),
             Mock.Of<IOptionsMonitor<HostingSettings>>(x => x.CurrentValue == new HostingSettings { Debug = isDebug }),
-            Mock.Of<IOptionsMonitor<GlobalSettings>>(x =>
-                x.CurrentValue == new GlobalSettings { UmbracoPath = umbracoPath }),
+            Mock.Of<IOptionsMonitor<GlobalSettings>>(x => x.CurrentValue == new GlobalSettings { UmbracoPath = umbracoPath }),
             hostEnvironment.Object,
-            Mock.Of<IUmbracoDatabaseFactory>(x =>
-                x.CreateDatabase() == Mock.Of<IUmbracoDatabase>(y => y.DatabaseType == DatabaseType.SQLite)));
+            Mock.Of<IUmbracoDatabaseFactory>(x => x.CreateDatabase() == Mock.Of<IUmbracoDatabase>(y => y.DatabaseType == DatabaseType.SQLite)),
+            Mock.Of<IServerRoleAccessor>());
     }
 }
diff --git a/version.json b/version.json
index c5916db03d..0f9ad08c15 100644
--- a/version.json
+++ b/version.json
@@ -1,6 +1,6 @@
 {
   "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json",
-  "version": "10.1.0-alpha.1",
+  "version": "10.2.0",
   "assemblyVersion": {
     "precision": "Build" // optional. Use when you want a more precise assembly version than the default major.minor.
   },
