diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index bb1c41d866..598a320045 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -1568,6 +1568,8 @@ _notification:
   youReceivedFollowRequest: "フォローリクエストが来ました"
   yourFollowRequestAccepted: "フォローリクエストが承認されました"
   youWereInvitedToGroup: "{userName}があなたをグループに招待しました"
+  readAllNotifications: "通知はすべて既読です"
+  readAllMessagingMessages: "チャットはすべて既読です"
 
   _types:
     all: "すべて"
diff --git a/src/client/init.ts b/src/client/init.ts
index e3441976e3..27cca0eb42 100644
--- a/src/client/init.ts
+++ b/src/client/init.ts
@@ -56,7 +56,7 @@ import { router } from '@/router';
 import { applyTheme } from '@/scripts/theme';
 import { isDeviceDarkmode } from '@/scripts/is-device-darkmode';
 import { i18n } from '@/i18n';
-import { api, stream, isMobile, dialog, post } from '@/os';
+import { stream, isMobile, dialog, post } from '@/os';
 import * as sound from '@/scripts/sound';
 import { $i, refreshAccount, login, updateAccount, signout } from '@/account';
 import { defaultStore, ColdDeviceStorage } from '@/store';
@@ -68,7 +68,6 @@ import { initializeSw } from '@/scripts/initialize-sw';
 import { reloadChannel } from '@/scripts/unison-reload';
 import { deleteLoginId } from '@/scripts/login-id';
 import { getAccountFromId } from '@/scripts/get-account-from-id';
-import { SwMessage } from '@/sw/types';
 
 console.info(`Misskey v${version}`);
 
@@ -240,33 +239,6 @@ components(app);
 
 await router.isReady();
 
-//#region Listen message from SW
-navigator.serviceWorker.addEventListener('message', ev => {
-	if (_DEV_) {
-		console.log('sw msg', ev.data);
-	}
-
-	const data = ev.data as SwMessage;
-	if (data.type !== 'order') return;
-
-	if (data.loginId !== $i?.id) {
-		return getAccountFromId(data.loginId).then(account => {
-			if (!account) return;
-			return login(account.token, data.url);
-		});
-	}
-
-	switch (data.order) {
-		case 'post':
-			return post(data.options);
-		case 'push':
-			return router.push(data.url);
-		default:
-			return;
-	}
-});
-//#endregion
-
 //document.body.innerHTML = '<div id="app"></div>';
 
 app.mount('body');
@@ -415,22 +387,3 @@ if ($i) {
 		signout();
 	});
 }
-
-/**
- * Convert the URL safe base64 string to a Uint8Array
- * @param base64String base64 string
- */
-function urlBase64ToUint8Array(base64String: string): Uint8Array {
-	const padding = '='.repeat((4 - base64String.length % 4) % 4);
-	const base64 = (base64String + padding)
-		.replace(/-/g, '+')
-		.replace(/_/g, '/');
-
-	const rawData = window.atob(base64);
-	const outputArray = new Uint8Array(rawData.length);
-
-	for (let i = 0; i < rawData.length; ++i) {
-		outputArray[i] = rawData.charCodeAt(i);
-	}
-	return outputArray;
-}
diff --git a/src/client/sw/create-notification.ts b/src/client/sw/create-notification.ts
index 1519ce48f3..921e92e0cc 100644
--- a/src/client/sw/create-notification.ts
+++ b/src/client/sw/create-notification.ts
@@ -169,14 +169,42 @@ async function composeNotification(data: pushNotificationData): Promise<[string,
 					icon: body.user.avatarUrl,
 					tag: `messaging:user:${body.userId}`,
 					data,
+					renotify: true,
 				}];
 			}
 			return [t('_notification.youGotMessagingMessageFromGroup', { name: body.group.name }), {
 				icon: body.user.avatarUrl,
 				tag: `messaging:group:${body.groupId}`,
 				data,
+				renotify: true,
 			}];
 		default:
 			return null;
 	}
 }
+
+export async function createAllReadNotification(type: 'notifications' | 'messagingMessages') {
+	const n = await composeAllReadNotification(type);
+	if (n) return self.registration.showNotification(...n);
+}
+
+async function composeAllReadNotification(type: string): Promise<[string, NotificationOptions] | null | undefined> {
+	if (!swLang.i18n) swLang.fetchLocale();
+	const i18n = await swLang.i18n as I18n<any>;
+	const { t } = i18n;
+
+	if (type === 'notifications') {
+		return [t('readAllNotifications'), {
+			silent: true,
+			tag: 'user_visible_auto_notification',
+		}];
+	}
+	if (type === 'messagingMessages') {
+		return [t('readAllMessagingMessages'), {
+			silent: true,
+			tag: 'user_visible_auto_notification',
+		}];
+	}
+
+	return;
+}
diff --git a/src/client/sw/operations.ts b/src/client/sw/operations.ts
index bf6434d5ea..a6b5afe765 100644
--- a/src/client/sw/operations.ts
+++ b/src/client/sw/operations.ts
@@ -61,7 +61,12 @@ export async function openPost(options: any, loginId: string) {
 export async function openClient(order: swMessageOrderType, url: string, loginId: string, query: any = {}) {
 	const client = await self.clients.matchAll({
 		type: 'window'
-	}).then(clients => clients.length > 0 ? clients[0] as WindowClient : null);
+	}).then(clients => {
+		for (const c of clients) {
+			if (c.url.indexOf('?zen') < 0) return c;
+		}
+		return null;
+	});
 
 	if (client) {
 		client.postMessage({ type: 'order', ...query, order, loginId, url } as SwMessage);
diff --git a/src/client/sw/sw.ts b/src/client/sw/sw.ts
index a2fbcf3c22..eb78571998 100644
--- a/src/client/sw/sw.ts
+++ b/src/client/sw/sw.ts
@@ -38,6 +38,14 @@ self.addEventListener('fetch', ev => {
 
 //#region When: Caught Notification
 self.addEventListener('push', ev => {
+	setTimeout(async () => {
+		console.log('to');
+		for (const n of await self.registration.getNotifications({ tag: 'user_visible_auto_notification' })) {
+			console.log(close)
+			n.close();
+		}
+	}, 5000);
+
 	// クライアント取得
 	ev.waitUntil(self.clients.matchAll({
 		includeUncontrolled: true,
diff --git a/src/client/ui/_common_/common.vue b/src/client/ui/_common_/common.vue
index e5cdaca235..8709f5a686 100644
--- a/src/client/ui/_common_/common.vue
+++ b/src/client/ui/_common_/common.vue
@@ -14,10 +14,15 @@
 </template>
 
 <script lang="ts">
-import { defineAsyncComponent, defineComponent } from 'vue';
-import { stream, popup, popups, uploads, pendingApiRequestsCount } from '@/os';
+import { defineAsyncComponent, defineComponent, inject } from 'vue';
+import { stream, popup, popups, uploads, pendingApiRequestsCount, pageWindow, post } from '@/os';
 import * as sound from '@/scripts/sound';
-import { $i } from '@/account';
+import { $i, login } from '@/account';
+import { SwMessage } from '@/sw/types';
+import { popout } from '@/scripts/popout';
+import { defaultStore, ColdDeviceStorage } from '@/store';
+import { getAccountFromId } from '@/scripts/get-account-from-id';
+import { router } from '@/router';
 
 export default defineComponent({
 	components: {
@@ -45,6 +50,49 @@ export default defineComponent({
 		if ($i) {
 			const connection = stream.useSharedConnection('main', 'UI');
 			connection.on('notification', onNotification);
+
+			const navHook = inject('navHook', null);
+			const sideViewHook = inject('sideViewHook', null);
+
+			//#region Listen message from SW
+			navigator.serviceWorker.addEventListener('message', ev => {
+				if (_DEV_) {
+					console.log('sw msg', ev.data);
+				}
+
+				const data = ev.data as SwMessage;
+				if (data.type !== 'order') return;
+
+				if (data.loginId !== $i?.id) {
+					return getAccountFromId(data.loginId).then(account => {
+						if (!account) return;
+						return login(account.token, data.url);
+					});
+				}
+
+				switch (data.order) {
+					case 'post':
+						return post(data.options);
+					case 'push':
+						if (data.url.startsWith('/my/messaging')) {
+							if (router.currentRoute.value.path === data.url) return;
+							if (ColdDeviceStorage.get('chatOpenBehavior') === 'window') return pageWindow(data.url);
+							if (ColdDeviceStorage.get('chatOpenBehavior') === 'popout') return popout(data.url);
+						}
+						if (router.currentRoute.value.path === data.url) {
+							return window.scroll({ top: 0, behavior: 'smooth' });
+						}
+						if (navHook) {
+							return navHook(data.url);
+						}
+						if (sideViewHook && defaultStore.state.defaultSideView && data.url !== '/') {
+							return sideViewHook(data.url);
+						}
+						return router.push(data.url);
+					default:
+						return;
+				}
+			});
 		}
 
 		return {
@@ -52,7 +100,7 @@ export default defineComponent({
 			popups,
 			pendingApiRequestsCount,
 		};
-	},
+	}
 });
 </script>