diff --git a/README.md b/README.md
index f32570c..4f78155 100644
--- a/README.md
+++ b/README.md
@@ -28,6 +28,7 @@ works.
| `0013-Explain-what-went-wrong-...` | The Logs tab says what a crash was and offers a fix where there is one, without a connection. |
| `0014-Browse-skins-from-...` | A Browse tab on the skin page: Ely.by's catalogue in the app, and NameMC, laby.net and crafty.gg in a window. |
| `0015-Use-the-desktop-s-file-picker-...` | File pickers on Linux are the desktop's own, such as KDE's, through the XDG desktop portal. |
+| `0016-Show-the-account-in-the-title-bar-...` | With the right sidebar folded away, the Minecraft account is shown in the title bar and managed from there. |
### Offline accounts
@@ -49,11 +50,11 @@ Microsoft sign-in opens your own browser rather than a webview inside the launch
password manager, autofill and passkeys work, and you can see in the address bar that the page is
really Microsoft's.
-Microsoft cannot hand the result back: the client id the launcher uses is Minecraft's own, whose
-only registered redirect is a fixed page on `login.live.com`, with no loopback address for the
-launcher to listen on. So the browser lands on that page with the code in the address and you copy
-the address into the launcher. The webview is still one click away for anyone the browser does not
-work out for.
+The client id the launcher uses is Minecraft's own, with no redirect the launcher could listen on.
+So the browser signs in on Microsoft's device code page, with the code already filled in, while the
+launcher asks Microsoft every few seconds whether that has happened. Once it has, the account is
+added and the launcher comes back to the front, without anything to paste. The window inside the
+launcher is still one click away in the dialog, for when the browser does not work out.
### Ely.by accounts
@@ -80,8 +81,10 @@ Settings > Features > Sidebar.
The news section in the right sidebar folds away by clicking its heading. The right sidebar itself
folds away with the arrow button in the title bar, which upstream only shows once "Hide right
sidebar" is turned on in settings. Both remember what they were set to across restarts, and the
-title bar button briefly shows a check once its state is saved. On pages that need the sidebar, such
-as the mod browser, the button stays in place, greyed out.
+title bar button briefly shows a check once its state is saved; the first time, a short note
+explains this. On pages that need the sidebar, such as the mod browser, the button stays in place,
+greyed out. While the sidebar is folded away, the
+Minecraft account sits in the title bar next to the window buttons, to switch, add or remove accounts.
None of the three reach Modrinth. Preference syncing maps a fixed list of named fields in both
directions and these are not in it, so they are neither sent to your Modrinth account nor
diff --git a/patches/0005-Make-the-sidebars-foldable.patch b/patches/0005-Make-the-sidebars-foldable.patch
index a9793da..25e21cb 100644
--- a/patches/0005-Make-the-sidebars-foldable.patch
+++ b/patches/0005-Make-the-sidebars-foldable.patch
@@ -30,14 +30,14 @@ page headed "Sync features across devices" and would otherwise look like
it syncs. The other two are not settings entries - a heading and a title
bar button - so nothing there promises anything.
---
- apps/app-frontend/src/App.vue | 153 ++++++++++++++++--
- .../ui/settings/display/FeaturesSettings.vue | 28 ++++
- .../src/composables/use-app-settings.ts | 3 +
- packages/app-lib/src/state/settings.rs | 3 +
- 4 files changed, 172 insertions(+), 15 deletions(-)
+ apps/app-frontend/src/App.vue | 184 ++++++++++++++++--
+ .../ui/settings/display/FeaturesSettings.vue | 28 +++
+ .../src/composables/use-app-settings.ts | 4 +
+ packages/app-lib/src/state/settings.rs | 4 +
+ 4 files changed, 205 insertions(+), 15 deletions(-)
diff --git a/apps/app-frontend/src/App.vue b/apps/app-frontend/src/App.vue
-index d750a5f..ac8308c 100644
+index d750a5f..c47484d 100644
--- a/apps/app-frontend/src/App.vue
+++ b/apps/app-frontend/src/App.vue
@@ -10,6 +10,8 @@ import {
@@ -49,7 +49,23 @@ index d750a5f..ac8308c 100644
ChevronLeftIcon,
ChevronRightIcon,
CompassIcon,
-@@ -254,12 +256,41 @@ const credentials = ref()
+@@ -34,6 +36,7 @@ import {
+ AccountSwitchOverlay,
+ Admonition,
+ Avatar,
++ Button,
+ ButtonLink,
+ commonMessages,
+ commonSettingsMessages,
+@@ -44,6 +47,7 @@ import {
+ I18nDebugPanel,
+ IconButton,
+ LoadingBar,
++ NewModal,
+ NewsArticleCard,
+ NotificationPanel,
+ PopupNotificationPanel,
+@@ -254,12 +258,46 @@ const credentials = ref()
const storedModrinthAccounts = ref([])
let credentialsRefreshId = 0
const sidebarToggled = ref(true)
@@ -72,29 +88,34 @@ index d750a5f..ac8308c 100644
+ (hide) => setSidebarCollapsed(hide),
)
+
-+// Briefly shows a check on the fold button once the state is saved, so it is
-+// clear the choice is remembered.
++// Briefly shows a check on the fold button once the state is saved, and the
++// first time also explains that the choice is remembered.
+const sidebarSaved = ref(false)
+let sidebarSavedTimeout
++const sidebarRememberedModal = ref(null)
+
+function setSidebarCollapsed(collapsed) {
++ const explain = !appSettings.featureFlags.sidebar_fold_explained
+ appSettings.featureFlags.right_sidebar_collapsed = collapsed
++ appSettings.featureFlags.sidebar_fold_explained = true
+ getSettings()
+ .then((settings) => {
+ settings.feature_flags.right_sidebar_collapsed = collapsed
++ settings.feature_flags.sidebar_fold_explained = true
+ return setSettings(settings)
+ })
+ .then(() => {
+ sidebarSaved.value = true
+ clearTimeout(sidebarSavedTimeout)
+ sidebarSavedTimeout = setTimeout(() => (sidebarSaved.value = false), 1600)
++ if (explain) sidebarRememberedModal.value?.show()
+ })
+ .catch(handleError)
+}
const forceSidebar = computed(
() =>
route.path.startsWith('/browse') ||
-@@ -358,6 +389,21 @@ useQuery({
+@@ -358,6 +396,21 @@ useQuery({
// ad and the gradient being drawn above it.
const showAd = computed(() => false)
const adConsentAvailable = computed(() => false)
@@ -116,15 +137,24 @@ index d750a5f..ac8308c 100644
providePageContext({
hierarchicalSidebarAvailable: ref(true),
showAds: showAd,
-@@ -588,6 +634,16 @@ const messages = defineMessages({
+@@ -588,6 +641,25 @@ const messages = defineMessages({
goBack: { id: 'app.navigation.go-back', defaultMessage: 'Go back' },
goForward: { id: 'app.navigation.go-forward', defaultMessage: 'Go forward' },
nextImage: { id: 'app.navigation.next-image', defaultMessage: 'Next image' },
+ hideSidebar: { id: 'app.navigation.hide-sidebar', defaultMessage: 'Hide sidebar' },
+ showSidebar: { id: 'app.navigation.show-sidebar', defaultMessage: 'Show sidebar' },
-+ sidebarRemembered: {
-+ id: 'app.navigation.sidebar-remembered',
-+ defaultMessage: 'Remembered after restart',
++ sidebarRememberedTitle: {
++ id: 'app.navigation.sidebar-remembered.title',
++ defaultMessage: 'The sidebar stays how you leave it',
++ },
++ sidebarRememberedBody: {
++ id: 'app.navigation.sidebar-remembered.body',
++ defaultMessage:
++ 'Folding the sidebar away or bringing it back is saved right away, so the app opens the same way next time, even after a restart. The check on the button shows when it has been saved.',
++ },
++ sidebarRememberedDone: {
++ id: 'app.navigation.sidebar-remembered.done',
++ defaultMessage: 'Done',
+ },
+ sidebarRequired: {
+ id: 'app.navigation.sidebar-required',
@@ -133,7 +163,7 @@ index d750a5f..ac8308c 100644
updateDownloadMissingVersion: {
id: 'app.update.download-error.missing-version',
defaultMessage: 'Failed to download update: no version available',
-@@ -2252,6 +2308,7 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
+@@ -2252,6 +2324,7 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
@@ -162,11 +192,9 @@ index d750a5f..ac8308c 100644
+ formatMessage(
+ forceSidebar
+ ? messages.sidebarRequired
-+ : sidebarSaved
-+ ? messages.sidebarRemembered
-+ : sidebarToggled
-+ ? messages.hideSidebar
-+ : messages.showSidebar,
++ : sidebarToggled
++ ? messages.hideSidebar
++ : messages.showSidebar,
+ )
+ "
+ class="relative mr-3 flex"
@@ -195,7 +223,7 @@ index d750a5f..ac8308c 100644
++
++
+
+
+
+@@ -2743,6 +2865,38 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
display: contents;
}
@@ -238,9 +288,11 @@ index d750a5f..ac8308c 100644
+ opacity: 0;
+ }
+
++ /* The path runs from the long stroke's tip back to the short one, so it is
++ revealed from its end: the check is drawn left to right. */
+ .sidebar-saved-badge :deep(path) {
-+ stroke-dasharray: 20;
-+ stroke-dashoffset: 20;
++ stroke-dasharray: 24;
++ stroke-dashoffset: -24;
+ animation: sidebar-saved-draw 0.3s 0.15s ease-out forwards;
+ }
+}
@@ -336,29 +388,31 @@ index 1d3e379..ae88550 100644
{{ formatMessage(messages.quickInstancesTitle) }}
diff --git a/apps/app-frontend/src/composables/use-app-settings.ts b/apps/app-frontend/src/composables/use-app-settings.ts
-index 0d58903..59da62b 100644
+index 0d58903..28c78c3 100644
--- a/apps/app-frontend/src/composables/use-app-settings.ts
+++ b/apps/app-frontend/src/composables/use-app-settings.ts
-@@ -24,6 +24,9 @@ export const DEFAULT_FEATURE_FLAGS = {
+@@ -24,6 +24,10 @@ export const DEFAULT_FEATURE_FLAGS = {
friends_pending_collapsed: true,
dismissed_photosensitivity_filter_warning: false,
localhost_sign_in: false,
+ show_hosting_in_sidebar: true,
+ news_collapsed: false,
++ sidebar_fold_explained: false,
+ right_sidebar_collapsed: false,
}
export type FeatureFlag = keyof typeof DEFAULT_FEATURE_FLAGS
diff --git a/packages/app-lib/src/state/settings.rs b/packages/app-lib/src/state/settings.rs
-index 20601b8..2fd0d87 100644
+index 20601b8..b5f2ec6 100644
--- a/packages/app-lib/src/state/settings.rs
+++ b/packages/app-lib/src/state/settings.rs
-@@ -88,6 +88,9 @@ pub enum FeatureFlag {
+@@ -88,6 +88,10 @@ pub enum FeatureFlag {
FriendsPendingCollapsed,
DismissedPhotosensitivityFilterWarning,
LocalhostSignIn,
+ ShowHostingInSidebar,
+ NewsCollapsed,
++ SidebarFoldExplained,
+ RightSidebarCollapsed,
}
diff --git a/patches/0007-Sign-in-to-Microsoft-in-the-player-s-own-browser.patch b/patches/0007-Sign-in-to-Microsoft-in-the-player-s-own-browser.patch
index dad0a69..fb39349 100644
--- a/patches/0007-Sign-in-to-Microsoft-in-the-player-s-own-browser.patch
+++ b/patches/0007-Sign-in-to-Microsoft-in-the-player-s-own-browser.patch
@@ -8,30 +8,30 @@ which is the one place a player cannot use the tools they use everywhere
else: no password manager, no autofill, no passkeys, and no way to tell
by looking that the page is really Microsoft's.
-The browser gets all of it. What it cannot do is hand the code back:
-this client id is Minecraft's own, its only registered redirect is a
-fixed page on login.live.com, and no loopback address is registered for
-it, so there is nothing for the launcher to listen on. The browser
-therefore lands on that page with the code in the address, and the
-player copies the address over - which is what the new modal asks for,
-and what `login_browser_finish` reads the code out of.
+The browser gets all of it. What it cannot do is hand a code back: this
+client id is Minecraft's own, and no loopback address is registered for
+it. So the browser signs in on Microsoft's device code page, with the
+code already filled in, while the launcher polls for the result and
+comes back to the front once it is there.
Every entry point goes through it, since they all end up at
`AccountsCard.login()`. The webview is still one click away in that
modal for anyone the browser does not work out for.
---
- .../src/components/ui/AccountsCard.vue | 17 +-
- .../src/components/ui/MicrosoftLoginModal.vue | 213 ++++++++++++++++++
+ .../src/components/ui/AccountsCard.vue | 21 +-
+ .../src/components/ui/MicrosoftLoginModal.vue | 219 ++++++++++++++++++
.../MinecraftRequiredModal.vue | 29 +--
- apps/app-frontend/src/helpers/auth.js | 25 ++
+ apps/app-frontend/src/helpers/auth.js | 23 ++
apps/app/build.rs | 2 +
- apps/app/src/api/auth.rs | 77 +++++++
+ apps/app/src/api/auth.rs | 64 +++++
+ packages/app-lib/src/api/minecraft_auth.rs | 30 +++
packages/app-lib/src/api/mod.rs | 12 +-
- 7 files changed, 333 insertions(+), 42 deletions(-)
+ packages/app-lib/src/state/minecraft_auth.rs | 145 +++++++++++-
+ 9 files changed, 499 insertions(+), 46 deletions(-)
create mode 100644 apps/app-frontend/src/components/ui/MicrosoftLoginModal.vue
diff --git a/apps/app-frontend/src/components/ui/AccountsCard.vue b/apps/app-frontend/src/components/ui/AccountsCard.vue
-index 35c21fa..c81603c 100644
+index 35c21fa..224d776 100644
--- a/apps/app-frontend/src/components/ui/AccountsCard.vue
+++ b/apps/app-frontend/src/components/ui/AccountsCard.vue
@@ -105,6 +105,7 @@
@@ -84,46 +84,64 @@ index 35c21fa..c81603c 100644
}
async function accountAdded() {
+@@ -316,8 +309,8 @@ const messages = defineMessages({
+ defaultMessage: 'Not signed in',
+ },
+ addAccount: {
+- id: 'minecraft-account.add-account',
+- defaultMessage: 'Add account',
++ id: 'minecraft-account.add-microsoft-account',
++ defaultMessage: 'Add Microsoft account',
+ },
+ addOfflineAccount: {
+ id: 'minecraft-account.add-offline-account',
diff --git a/apps/app-frontend/src/components/ui/MicrosoftLoginModal.vue b/apps/app-frontend/src/components/ui/MicrosoftLoginModal.vue
new file mode 100644
-index 0000000..283b89c
+index 0000000..d587736
--- /dev/null
+++ b/apps/app-frontend/src/components/ui/MicrosoftLoginModal.vue
-@@ -0,0 +1,213 @@
+@@ -0,0 +1,219 @@
+
-+
++
+
diff --git a/apps/app/build.rs b/apps/app/build.rs
-index 4553d48..90e22ef 100644
+index 0fb53a5..f01ba91 100644
--- a/apps/app/build.rs
+++ b/apps/app/build.rs
@@ -44,6 +44,19 @@ fn main() {
@@ -1165,7 +1165,7 @@ index 8429e4c..04eedbd 100644
.plugin(api::onboarding_checklist::init())
.plugin(api::import::init())
diff --git a/packages/app-lib/src/api/mod.rs b/packages/app-lib/src/api/mod.rs
-index 566810b..a6f770d 100644
+index 4daf723..e64a606 100644
--- a/packages/app-lib/src/api/mod.rs
+++ b/packages/app-lib/src/api/mod.rs
@@ -17,6 +17,7 @@ pub mod process;
diff --git a/patches/0016-Show-the-account-in-the-title-bar-when-the-sidebar-i.patch b/patches/0016-Show-the-account-in-the-title-bar-when-the-sidebar-i.patch
new file mode 100644
index 0000000..e1494a6
--- /dev/null
+++ b/patches/0016-Show-the-account-in-the-title-bar-when-the-sidebar-i.patch
@@ -0,0 +1,268 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Modrinth Enhanced
+Date: Tue, 15 Sep 2026 15:14:28 +0200
+Subject: [PATCH] Show the account in the title bar when the sidebar is folded
+
+The Minecraft account lives in the right sidebar, so folding the sidebar
+away took with it any way to see which account is selected or to switch
+to another. While the sidebar is folded, the account now sits in the
+title bar next to the window buttons: its head and name, opening a menu
+to switch accounts, add one or remove one.
+
+The menu stands in for the sidebar's account card rather than being a
+second one. That card stays mounted while the sidebar is hidden, so the
+selection, the avatar and the sign-in dialogs are the card's own and
+stay in step with it, and a dialog opened from the menu outlives the
+menu closing.
+---
+ apps/app-frontend/src/App.vue | 8 +
+ .../src/components/ui/AccountsCard.vue | 32 ++--
+ .../components/ui/TitleBarAccountSwitcher.vue | 163 ++++++++++++++++++
+ 3 files changed, 191 insertions(+), 12 deletions(-)
+ create mode 100644 apps/app-frontend/src/components/ui/TitleBarAccountSwitcher.vue
+
+diff --git a/apps/app-frontend/src/App.vue b/apps/app-frontend/src/App.vue
+index 0ffddff..5cbe7d8 100644
+--- a/apps/app-frontend/src/App.vue
++++ b/apps/app-frontend/src/App.vue
+@@ -79,6 +79,7 @@ import { computed, nextTick, onMounted, onUnmounted, provide, ref, watch } from
+ import { RouterView, useRoute, useRouter } from 'vue-router'
+
+ import AccountsCard from '@/components/ui/AccountsCard.vue'
++import TitleBarAccountSwitcher from '@/components/ui/TitleBarAccountSwitcher.vue'
+ import AppActionBar from '@/components/ui/AppActionBar.vue'
+ import Breadcrumbs from '@/components/ui/Breadcrumbs.vue'
+ import ErrorModal from '@/components/ui/ErrorModal.vue'
+@@ -2509,6 +2510,13 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
+
+
+