From 2c4ba4723f7ce936a1eadd056416f0d676d1860e Mon Sep 17 00:00:00 2001
From: atsuchan <83960488+atsu1125@users.noreply.github.com>
Date: Sun, 4 Feb 2024 20:44:35 +0900
Subject: [PATCH 01/40] =?UTF-8?q?fix(backend):=20=E3=83=A1=E3=83=BC?=
 =?UTF-8?q?=E3=83=AB=E9=85=8D=E4=BF=A1=E6=A9=9F=E8=83=BD=E3=81=8C=E7=84=A1?=
 =?UTF-8?q?=E5=8A=B9=E3=81=AA=E3=82=89=E3=81=B0=E3=83=A1=E3=83=BC=E3=83=AB?=
 =?UTF-8?q?=E3=82=92=E9=80=81=E3=82=8B=E3=81=93=E3=81=A8=E3=81=AE=E3=81=AA?=
 =?UTF-8?q?=E3=81=84=E3=82=88=E3=81=86=E3=81=AB=20(#13152)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Do not send email if email delivery is disabled
---
 packages/backend/src/core/EmailService.ts | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/packages/backend/src/core/EmailService.ts b/packages/backend/src/core/EmailService.ts
index 8daee148eb..89722965c1 100644
--- a/packages/backend/src/core/EmailService.ts
+++ b/packages/backend/src/core/EmailService.ts
@@ -40,6 +40,8 @@ export class EmailService {
 	public async sendEmail(to: string, subject: string, html: string, text: string) {
 		const meta = await this.metaService.fetch(true);
 
+		if (!meta.enableEmail) return;
+
 		const iconUrl = `${this.config.url}/static-assets/mi-white.png`;
 		const emailSettingUrl = `${this.config.url}/settings/email`;
 
-- 
GitLab


From bafef1f8b45b5117c4418f68160ea288135571c3 Mon Sep 17 00:00:00 2001
From: Gianni Ceccarelli <dakkar@thenautilus.net>
Date: Sun, 4 Feb 2024 11:46:28 +0000
Subject: [PATCH 02/40] ignore `instance.actor` when checking if there are
 local users (#13146)

* ignore `instance.actor` when checking if there are local users

We've seen this happen a few times:

* there was some AP software at $some_domain
* it gets replaced by Misskey
* before the first user can be created, an AP activity comes in
* Misskey resolves the activity
* to do this, it creates the `instance.actor` to sign its request
* now there *is* a local user, so the `meta` endpoint returns
  `requireSetup:false`
* the admin is very confused

This commit factors out the check, and doesn't count the
`instance.actor` as a real user.

* autogen bits
---
 packages/backend/src/core/InstanceActorService.ts     | 10 +++++++++-
 packages/backend/src/core/SignupService.ts            |  4 +++-
 .../src/server/api/endpoints/admin/accounts/create.ts |  8 ++++----
 packages/backend/src/server/api/endpoints/meta.ts     | 11 ++++-------
 packages/misskey-js/src/autogen/apiClientJSDoc.ts     |  2 +-
 packages/misskey-js/src/autogen/endpoint.ts           |  2 +-
 packages/misskey-js/src/autogen/entities.ts           |  2 +-
 packages/misskey-js/src/autogen/models.ts             |  2 +-
 packages/misskey-js/src/autogen/types.ts              |  2 +-
 9 files changed, 25 insertions(+), 18 deletions(-)

diff --git a/packages/backend/src/core/InstanceActorService.ts b/packages/backend/src/core/InstanceActorService.ts
index b40fd46291..7ce8dc96a1 100644
--- a/packages/backend/src/core/InstanceActorService.ts
+++ b/packages/backend/src/core/InstanceActorService.ts
@@ -4,7 +4,7 @@
  */
 
 import { Inject, Injectable } from '@nestjs/common';
-import { IsNull } from 'typeorm';
+import { IsNull, Not } from 'typeorm';
 import type { MiLocalUser } from '@/models/User.js';
 import type { UsersRepository } from '@/models/_.js';
 import { MemorySingleCache } from '@/misc/cache.js';
@@ -27,6 +27,14 @@ export class InstanceActorService {
 		this.cache = new MemorySingleCache<MiLocalUser>(Infinity);
 	}
 
+	@bindThis
+	public async realLocalUsersPresent(): Promise<boolean> {
+		return await this.usersRepository.existsBy({
+			host: IsNull(),
+			username: Not(ACTOR_USERNAME),
+		});
+	}
+
 	@bindThis
 	public async getInstanceActor(): Promise<MiLocalUser> {
 		const cached = this.cache.get();
diff --git a/packages/backend/src/core/SignupService.ts b/packages/backend/src/core/SignupService.ts
index b9e3ded46f..81c2b241eb 100644
--- a/packages/backend/src/core/SignupService.ts
+++ b/packages/backend/src/core/SignupService.ts
@@ -16,6 +16,7 @@ import { MiUserKeypair } from '@/models/UserKeypair.js';
 import { MiUsedUsername } from '@/models/UsedUsername.js';
 import generateUserToken from '@/misc/generate-native-user-token.js';
 import { UserEntityService } from '@/core/entities/UserEntityService.js';
+import { InstanceActorService } from '@/core/InstanceActorService.js';
 import { bindThis } from '@/decorators.js';
 import UsersChart from '@/core/chart/charts/users.js';
 import { UtilityService } from '@/core/UtilityService.js';
@@ -37,6 +38,7 @@ export class SignupService {
 		private userEntityService: UserEntityService,
 		private idService: IdService,
 		private metaService: MetaService,
+		private instanceActorService: InstanceActorService,
 		private usersChart: UsersChart,
 	) {
 	}
@@ -81,7 +83,7 @@ export class SignupService {
 			throw new Error('USED_USERNAME');
 		}
 
-		const isTheFirstUser = (await this.usersRepository.countBy({ host: IsNull() })) === 0;
+		const isTheFirstUser = !await this.instanceActorService.realLocalUsersPresent();
 
 		if (!opts.ignorePreservedUsernames && !isTheFirstUser) {
 			const instance = await this.metaService.fetch(true);
diff --git a/packages/backend/src/server/api/endpoints/admin/accounts/create.ts b/packages/backend/src/server/api/endpoints/admin/accounts/create.ts
index b18a7e0e41..14fd69a1a2 100644
--- a/packages/backend/src/server/api/endpoints/admin/accounts/create.ts
+++ b/packages/backend/src/server/api/endpoints/admin/accounts/create.ts
@@ -9,6 +9,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js';
 import type { UsersRepository } from '@/models/_.js';
 import { SignupService } from '@/core/SignupService.js';
 import { UserEntityService } from '@/core/entities/UserEntityService.js';
+import { InstanceActorService } from '@/core/InstanceActorService.js';
 import { localUsernameSchema, passwordSchema } from '@/models/User.js';
 import { DI } from '@/di-symbols.js';
 import { Packed } from '@/misc/json-schema.js';
@@ -46,13 +47,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 
 		private userEntityService: UserEntityService,
 		private signupService: SignupService,
+		private instanceActorService: InstanceActorService,
 	) {
 		super(meta, paramDef, async (ps, _me, token) => {
 			const me = _me ? await this.usersRepository.findOneByOrFail({ id: _me.id }) : null;
-			const noUsers = (await this.usersRepository.countBy({
-				host: IsNull(),
-			})) === 0;
-			if ((!noUsers && !me?.isRoot) || token !== null) throw new Error('access denied');
+			const realUsers = await this.instanceActorService.realLocalUsersPresent();
+			if ((realUsers && !me?.isRoot) || token !== null) throw new Error('access denied');
 
 			const { account, secret } = await this.signupService.signup({
 				username: ps.username,
diff --git a/packages/backend/src/server/api/endpoints/meta.ts b/packages/backend/src/server/api/endpoints/meta.ts
index e1d3473482..c6489f67ac 100644
--- a/packages/backend/src/server/api/endpoints/meta.ts
+++ b/packages/backend/src/server/api/endpoints/meta.ts
@@ -6,11 +6,12 @@
 import { IsNull, LessThanOrEqual, MoreThan, Brackets } from 'typeorm';
 import { Inject, Injectable } from '@nestjs/common';
 import JSON5 from 'json5';
-import type { AdsRepository, UsersRepository } from '@/models/_.js';
+import type { AdsRepository } from '@/models/_.js';
 import { MAX_NOTE_TEXT_LENGTH } from '@/const.js';
 import { Endpoint } from '@/server/api/endpoint-base.js';
 import { UserEntityService } from '@/core/entities/UserEntityService.js';
 import { MetaService } from '@/core/MetaService.js';
+import { InstanceActorService } from '@/core/InstanceActorService.js';
 import type { Config } from '@/config.js';
 import { DI } from '@/di-symbols.js';
 import { DEFAULT_POLICIES } from '@/core/RoleService.js';
@@ -326,14 +327,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 		@Inject(DI.config)
 		private config: Config,
 
-		@Inject(DI.usersRepository)
-		private usersRepository: UsersRepository,
-
 		@Inject(DI.adsRepository)
 		private adsRepository: AdsRepository,
 
 		private userEntityService: UserEntityService,
 		private metaService: MetaService,
+		private instanceActorService: InstanceActorService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
 			const instance = await this.metaService.fetch(true);
@@ -412,9 +411,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				...(ps.detail ? {
 					cacheRemoteFiles: instance.cacheRemoteFiles,
 					cacheRemoteSensitiveFiles: instance.cacheRemoteSensitiveFiles,
-					requireSetup: (await this.usersRepository.countBy({
-						host: IsNull(),
-					})) === 0,
+					requireSetup: !await this.instanceActorService.realLocalUsersPresent(),
 				} : {}),
 			};
 
diff --git a/packages/misskey-js/src/autogen/apiClientJSDoc.ts b/packages/misskey-js/src/autogen/apiClientJSDoc.ts
index 0f1223d1f8..121e349b85 100644
--- a/packages/misskey-js/src/autogen/apiClientJSDoc.ts
+++ b/packages/misskey-js/src/autogen/apiClientJSDoc.ts
@@ -1,6 +1,6 @@
 /*
  * version: 2024.2.0-beta.8
- * generatedAt: 2024-01-31T01:46:47.964Z
+ * generatedAt: 2024-02-02T14:18:15.716Z
  */
 
 import type { SwitchCaseResponseType } from '../api.js';
diff --git a/packages/misskey-js/src/autogen/endpoint.ts b/packages/misskey-js/src/autogen/endpoint.ts
index d319fe7978..f7b6f52fde 100644
--- a/packages/misskey-js/src/autogen/endpoint.ts
+++ b/packages/misskey-js/src/autogen/endpoint.ts
@@ -1,6 +1,6 @@
 /*
  * version: 2024.2.0-beta.8
- * generatedAt: 2024-01-31T01:46:47.962Z
+ * generatedAt: 2024-02-02T14:18:15.712Z
  */
 
 import type {
diff --git a/packages/misskey-js/src/autogen/entities.ts b/packages/misskey-js/src/autogen/entities.ts
index ea2ca3948a..b971d8bda7 100644
--- a/packages/misskey-js/src/autogen/entities.ts
+++ b/packages/misskey-js/src/autogen/entities.ts
@@ -1,6 +1,6 @@
 /*
  * version: 2024.2.0-beta.8
- * generatedAt: 2024-01-31T01:46:47.961Z
+ * generatedAt: 2024-02-02T14:18:15.709Z
  */
 
 import { operations } from './types.js';
diff --git a/packages/misskey-js/src/autogen/models.ts b/packages/misskey-js/src/autogen/models.ts
index 8ab5eeac9f..719f7923de 100644
--- a/packages/misskey-js/src/autogen/models.ts
+++ b/packages/misskey-js/src/autogen/models.ts
@@ -1,6 +1,6 @@
 /*
  * version: 2024.2.0-beta.8
- * generatedAt: 2024-01-31T01:46:47.959Z
+ * generatedAt: 2024-02-02T14:18:15.708Z
  */
 
 import { components } from './types.js';
diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts
index 1731b57003..0a78be1dfd 100644
--- a/packages/misskey-js/src/autogen/types.ts
+++ b/packages/misskey-js/src/autogen/types.ts
@@ -3,7 +3,7 @@
 
 /*
  * version: 2024.2.0-beta.8
- * generatedAt: 2024-01-31T01:46:47.878Z
+ * generatedAt: 2024-02-02T14:18:15.529Z
  */
 
 /**
-- 
GitLab


From dabf1867fd5619d82b860482ffaf7908a4b9f0df Mon Sep 17 00:00:00 2001
From: Gianni Ceccarelli <dakkar@thenautilus.net>
Date: Sun, 4 Feb 2024 12:03:49 +0000
Subject: [PATCH 03/40] keep cached avatar&banner when refresh fails to get new
 values (#13145)

* keep cached avatar&banner when refresh fails to get new values

when the remote explicitly tells us a user image is gone, we remove
our cached value, but if we fail to get the image, we keep whatever
value we already have

this should minimise the problem of avatars randomly disappearing

* autogen bits

* pnpm run build-misskey-js-with-types

---------

Co-authored-by: tamaina <tamaina@hotmail.co.jp>
---
 .../activitypub/models/ApPersonService.ts     | 35 ++++++++++++++-----
 .../misskey-js/src/autogen/apiClientJSDoc.ts  |  2 +-
 packages/misskey-js/src/autogen/endpoint.ts   |  2 +-
 packages/misskey-js/src/autogen/entities.ts   |  2 +-
 packages/misskey-js/src/autogen/models.ts     |  2 +-
 packages/misskey-js/src/autogen/types.ts      |  2 +-
 6 files changed, 31 insertions(+), 14 deletions(-)

diff --git a/packages/backend/src/core/activitypub/models/ApPersonService.ts b/packages/backend/src/core/activitypub/models/ApPersonService.ts
index bf38d5fd60..aec34aeb54 100644
--- a/packages/backend/src/core/activitypub/models/ApPersonService.ts
+++ b/packages/backend/src/core/activitypub/models/ApPersonService.ts
@@ -225,20 +225,37 @@ export class ApPersonService implements OnModuleInit {
 		return null;
 	}
 
-	private async resolveAvatarAndBanner(user: MiRemoteUser, icon: any, image: any): Promise<Pick<MiRemoteUser, 'avatarId' | 'bannerId' | 'avatarUrl' | 'bannerUrl' | 'avatarBlurhash' | 'bannerBlurhash'>> {
+	private async resolveAvatarAndBanner(user: MiRemoteUser, icon: any, image: any): Promise<Partial<Pick<MiRemoteUser, 'avatarId' | 'bannerId' | 'avatarUrl' | 'bannerUrl' | 'avatarBlurhash' | 'bannerBlurhash'>>> {
+		if (user == null) throw new Error('failed to create user: user is null');
+
 		const [avatar, banner] = await Promise.all([icon, image].map(img => {
-			if (img == null) return null;
-			if (user == null) throw new Error('failed to create user: user is null');
+			// if we have an explicitly missing image, return an
+			// explicitly-null set of values
+			if ((img == null) || (typeof img === 'object' && img.url == null)) {
+				return { id: null, url: null, blurhash: null };
+			}
+
 			return this.apImageService.resolveImage(user, img).catch(() => null);
 		}));
 
+		/*
+			we don't want to return nulls on errors! if the database fields
+			are already null, nothing changes; if the database has old
+			values, we should keep those. The exception is if the remote has
+			actually removed the images: in that case, the block above
+			returns the special {id:null}&c value, and we return those
+		*/
 		return {
-			avatarId: avatar?.id ?? null,
-			bannerId: banner?.id ?? null,
-			avatarUrl: avatar ? this.driveFileEntityService.getPublicUrl(avatar, 'avatar') : null,
-			bannerUrl: banner ? this.driveFileEntityService.getPublicUrl(banner) : null,
-			avatarBlurhash: avatar?.blurhash ?? null,
-			bannerBlurhash: banner?.blurhash ?? null,
+			...( avatar ? {
+				avatarId: avatar.id,
+				avatarUrl: avatar.url ? this.driveFileEntityService.getPublicUrl(avatar, 'avatar') : null,
+				avatarBlurhash: avatar.blurhash,
+			} : {}),
+			...( banner ? {
+				bannerId: banner.id,
+				bannerUrl: banner.url ? this.driveFileEntityService.getPublicUrl(banner) : null,
+				bannerBlurhash: banner.blurhash,
+			} : {}),
 		};
 	}
 
diff --git a/packages/misskey-js/src/autogen/apiClientJSDoc.ts b/packages/misskey-js/src/autogen/apiClientJSDoc.ts
index 121e349b85..7c727d2878 100644
--- a/packages/misskey-js/src/autogen/apiClientJSDoc.ts
+++ b/packages/misskey-js/src/autogen/apiClientJSDoc.ts
@@ -1,6 +1,6 @@
 /*
  * version: 2024.2.0-beta.8
- * generatedAt: 2024-02-02T14:18:15.716Z
+ * generatedAt: 2024-02-04T11:51:13.598Z
  */
 
 import type { SwitchCaseResponseType } from '../api.js';
diff --git a/packages/misskey-js/src/autogen/endpoint.ts b/packages/misskey-js/src/autogen/endpoint.ts
index f7b6f52fde..cf9f96b526 100644
--- a/packages/misskey-js/src/autogen/endpoint.ts
+++ b/packages/misskey-js/src/autogen/endpoint.ts
@@ -1,6 +1,6 @@
 /*
  * version: 2024.2.0-beta.8
- * generatedAt: 2024-02-02T14:18:15.712Z
+ * generatedAt: 2024-02-04T11:51:13.595Z
  */
 
 import type {
diff --git a/packages/misskey-js/src/autogen/entities.ts b/packages/misskey-js/src/autogen/entities.ts
index b971d8bda7..2930f2cb7f 100644
--- a/packages/misskey-js/src/autogen/entities.ts
+++ b/packages/misskey-js/src/autogen/entities.ts
@@ -1,6 +1,6 @@
 /*
  * version: 2024.2.0-beta.8
- * generatedAt: 2024-02-02T14:18:15.709Z
+ * generatedAt: 2024-02-04T11:51:13.593Z
  */
 
 import { operations } from './types.js';
diff --git a/packages/misskey-js/src/autogen/models.ts b/packages/misskey-js/src/autogen/models.ts
index 719f7923de..f01beaa706 100644
--- a/packages/misskey-js/src/autogen/models.ts
+++ b/packages/misskey-js/src/autogen/models.ts
@@ -1,6 +1,6 @@
 /*
  * version: 2024.2.0-beta.8
- * generatedAt: 2024-02-02T14:18:15.708Z
+ * generatedAt: 2024-02-04T11:51:13.592Z
  */
 
 import { components } from './types.js';
diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts
index 0a78be1dfd..77a493fe2e 100644
--- a/packages/misskey-js/src/autogen/types.ts
+++ b/packages/misskey-js/src/autogen/types.ts
@@ -3,7 +3,7 @@
 
 /*
  * version: 2024.2.0-beta.8
- * generatedAt: 2024-02-02T14:18:15.529Z
+ * generatedAt: 2024-02-04T11:51:13.473Z
  */
 
 /**
-- 
GitLab


From 03351cec0ccdba39b36619a401c6af8b65ea0295 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Mon, 5 Feb 2024 11:03:12 +0900
Subject: [PATCH 04/40] update patrons

---
 packages/frontend/src/pages/about-misskey.vue | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/packages/frontend/src/pages/about-misskey.vue b/packages/frontend/src/pages/about-misskey.vue
index f8eced8d72..acaae9f1d7 100644
--- a/packages/frontend/src/pages/about-misskey.vue
+++ b/packages/frontend/src/pages/about-misskey.vue
@@ -214,6 +214,12 @@ const patronsWithIcon = [{
 }, {
 	name: 'taichan',
 	icon: 'https://assets.misskey-hub.net/patrons/f981ab0159fb4e2c998e05f7263e1cd9.png',
+}, {
+	name: '猫吉よりお',
+	icon: 'https://assets.misskey-hub.net/patrons/a11518b3b34b4536a4bdd7178ba76a7b.png',
+}, {
+	name: '有栖かずみ',
+	icon: 'https://assets.misskey-hub.net/patrons/9240e8e0ba294a8884143e99ac7ed6a0.png',
 }];
 
 const patrons = [
-- 
GitLab


From c5ac2ae163c2035a1dc92eaee478ded261a89d9c Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Mon, 5 Feb 2024 11:03:55 +0900
Subject: [PATCH 05/40] New Crowdin updates (#13156)

* New translations ja-jp.yml (Italian)

* New translations ja-jp.yml (Italian)

* New translations ja-jp.yml (Korean)

* New translations ja-jp.yml (Korean (Gyeongsang))

* New translations ja-jp.yml (Korean (Gyeongsang))

* New translations ja-jp.yml (Korean)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (Catalan)
---
 locales/ca-ES.yml | 191 ++++++++++++++++++++++++++++++++++++++++++++++
 locales/it-IT.yml |  60 ++++++++++++++-
 locales/ko-GS.yml |  72 ++++++++++++++++-
 locales/ko-KR.yml |   8 +-
 4 files changed, 324 insertions(+), 7 deletions(-)

diff --git a/locales/ca-ES.yml b/locales/ca-ES.yml
index 9b1806a2cf..6c0d212ad2 100644
--- a/locales/ca-ES.yml
+++ b/locales/ca-ES.yml
@@ -1157,6 +1157,12 @@ edited: "Editat"
 notificationRecieveConfig: "Paràmetres de notificacions"
 mutualFollow: "Seguidor mutu"
 fileAttachedOnly: "Només notes amb adjunts"
+showRepliesToOthersInTimeline: "Mostrar les respostes a altres a la línia de temps"
+hideRepliesToOthersInTimeline: "Amagar les respostes a altres a la línia de temps"
+showRepliesToOthersInTimelineAll: "Mostrar les respostes a altres a usuaris que segueixes a la línia de temps"
+hideRepliesToOthersInTimelineAll: "Ocultar les teves respostes a tots els usuaris que segueixes a la línia de temps"
+confirmShowRepliesAll: "Aquesta opció no té marxa enrere. Vols mostrar les teves respostes a tots els que segueixes a la teva línia de temps?"
+confirmHideRepliesAll: "Aquesta opció no té marxa enrere. Vols ocultar les teves respostes a tots els usuaris que segueixes a la línia de temps?"
 externalServices: "Serveis externs"
 impressum: "Impressum"
 impressumUrl: "Adreça URL impressum"
@@ -1187,7 +1193,25 @@ seasonalScreenEffect: "Efectes de pantalla segons les estacions"
 decorate: "Decorar"
 addMfmFunction: "Afegeix funcions MFM"
 enableQuickAddMfmFunction: "Activar accés ràpid per afegir funcions MFM"
+bubbleGame: "Bubble Game"
+sfx: "Efectes de so"
+soundWillBePlayed: "Es reproduiran efectes de so"
+showReplay: "Veure reproducció"
+replay: "Reproduir"
+replaying: "Reproduint"
+ranking: "Classificació"
 lastNDays: "Últims {n} dies"
+backToTitle: "Torna al títol"
+hemisphere: "Geolocalització"
+withSensitive: "Incloure notes amb fitxers sensibles"
+userSaysSomethingSensitive: "La publicació de {name} conte material sensible"
+enableHorizontalSwipe: "Lliscar per canviar de pestanya"
+_bubbleGame:
+  howToPlay: "Com es juga"
+  _howToPlay:
+    section1: "Ajusta la posició i deixa caure l'objecte dintre la caixa."
+    section2: "Quan dos objectes del mateix tipus es toquen, canviaran en un objecte diferent i guanyares punts."
+    section3: "El joc s'acabarà quan els objectes sobresurtin de la caixa. Intenta aconseguir la puntuació més gran possible fusionant objectes mentre impedeixes que sobresurtin de la caixa!"
 _announcement:
   forExistingUsers: "Anunci per usuaris registrats"
   forExistingUsersDescription: "Aquest avís només es mostrarà als usuaris existents fins al moment de la publicació. Si no també es mostrarà als usuaris que es registrin després de la publicació."
@@ -1209,8 +1233,32 @@ _initialAccountSetting:
   privacySetting: "Configuració de seguretat"
   theseSettingsCanEditLater: "Aquests ajustos es poden canviar més tard."
   youCanEditMoreSettingsInSettingsPageLater: "A més d'això, es poden fer diferents configuracions a través de la pàgina de configuració. Assegureu-vos de comprovar-ho més tard."
+  followUsers: "Prova de seguir usuaris que t'interessin per construir la teva línia de temps."
+  pushNotificationDescription: "Activant les notificacions emergents et permetrà rebre notificacions de {name} directament al teu dispositiu."
+  initialAccountSettingCompleted: "Configuració del perfil completada!"
+  haveFun: "Disfruta {name}!"
+  youCanContinueTutorial: "Pots continuar amb un tutorial per aprendre a Fer servir {name} (MissKey) o tu pots estalviar i començar a fer-lo servir ja."
+  startTutorial: "Començar el tutorial"
+  skipAreYouSure: "Et vols saltar la configuració del perfil?"
+  laterAreYouSure: "Vols continuar la configuració del perfil més tard?"
 _initialTutorial:
+  launchTutorial: "Començar tutorial"
+  title: "Tutorial"
+  wellDone: "Ben fet!"
+  skipAreYouSure: "Sortir del tutorial?"
+  _landing:
+    title: "Benvingut al tutorial"
+    description: "Aquí aprendràs el bàsic per poder fer servir Misskey i les seves característiques."
+  _note:
+    title: "Què és una Nota?"
+    description: "Les publicacions a Misskey es diuen 'Notes'. Les Notes s'ordenen cronològicament a la línia de temps i s'actualitzen de forma automàtica."
+    reply: "Fes clic en aquest botó per contestar a un missatge. També és possible contestar a una contestació, continuant la conversació en forma de fil."
+    renote: "Pots compartir una Nota a la teva pròpia línia de temps. Inclús pots citar-les amb els teus comentaris."
+    reaction: "Pots afegir reaccions a les Notes. Entrarem més en detall a la pròxima pàgina."
+    menu: "Pots veure els detalls de les Notes, copiar enllaços i fer diferents accions."
   _reaction:
+    title: "Què són les Reaccions?"
+    description: "Es poden reaccionar a les Notes amb diferents emoticones. Les reaccions et permeten expressar matisos que hi són més enllà d'un simple m'agrada."
     letsTryReacting: "Es poden afegir reaccions fent clic al botó '+'. Prova reaccionant a aquesta nota!"
     reactToContinue: "Afegeix una reacció per continuar."
     reactNotification: "Rebràs notificacions en temps real quan un usuari reaccioni a les teves notes."
@@ -1272,9 +1320,75 @@ _serverSettings:
   shortName: "Nom curt"
   shortNameDescription: "Una abreviatura del nom de la instància que es poguí mostrar en cas que el nom oficial sigui massa llarg"
   fanoutTimelineDescription: "Quan es troba activat millora bastant el rendiment quan es recuperen les línies de temps i redueix la carrega de la base de dades. Com a contrapunt, l'ús de memòria de Redis es veurà incrementada. Considera d'estabilitat aquesta opció en cas de tenir un servidor amb poca memòria o si tens problemes de inestabilitat."
+  fanoutTimelineDbFallback: "Carregar de la base de dades"
+  fanoutTimelineDbFallbackDescription: "Quan s'activa, la línia de temps fa servir la base de dades per consultes adicionals si la línia de temps no es troba a la memòria cau. Si és desactiva la càrrega del servidor és veure reduïda, però també és reduirà el nombre de línies de temps que és poden obtenir."
+_accountMigration:
+  moveFrom: "Migrar un altre compte a aquest"
+  moveFromSub: "Crear un àlies per un altre compte"
+  moveFromLabel: "Compte original #{n}"
+  moveFromDescription: "Has de crear un àlies del compte que vols migrar en aquest compte.\nFes servir aquest format per posar el compte que vols migrar: @nomusuari@servidor.exemple.com\nPer esborrar l'àlies deixa el camp en blanc (no és recomanable de fer)"
+  moveTo: "Migrar aquest compte a un altre"
+  moveToLabel: "Compte al qual es vol migrar:"
+  moveCannotBeUndone: "Les migracions dels comptes no es poden desfer."
+  moveAccountDescription: "Això migrarà la teva compte a un altre diferent.\n ・Els seguidors d'aquest compte és passaran al compte nou de forma automàtica\n ・Es deixaran de seguir a tots els usuaris que es segueixen actualment en aquest compte\n ・No es poden crear notes noves, etc. en aquest compte\n\nSi bé la migració de seguidors es automàtica, has de preparar alguns pasos manualment per migrar la llista d'usuaris que segueixes. Per fer això has d'exportar els seguidors que després importaraes al compte nou mitjançant el menú de configuració. El mateix procediment s'ha de seguir per less teves llistes i els teus usuaris silenciats i bloquejats.\n\n(Aquesta explicació s'aplica a Misskey v13.12.0 i posteriors. Altres aplicacions, com Mastodon, poden funcionar diferent.)"
+  moveAccountHowTo: "Per fer la migració, primer has de crear un àlies per aquest compte al compte al qual vols migrar.\nDesprés de crear l'àlies, introdueix el compte al qual vols migrar amb el format següent: @nomusuari@servidor.exemple.com"
+  startMigration: "Migrar"
+  migrationConfirm: "Vols migrar aquest compte a {account}? Una vegada comenci la migració no es podrà parar O fer marxa enrere i no podràs tornar a fer servir aquest compte mai més."
+  movedAndCannotBeUndone: "Aquest compte ha migrat.\nLes migracions no es poden desfer."
+  postMigrationNote: "Aquest compte deixarà de seguir tots els comptes que segueix 24 hores després de germinar la migració.\nEl nombre de seguidors i seguits passarà a ser de zero. Per evitar que els teus seguidors no puguin veure les publicacions marcades com a només seguidors continuaren seguint aquest compte."
+  movedTo: "Nou compte:"
 _achievements:
+  earnedAt: "Desbloquejat el"
   _types:
+    _notes1:
+      title: "Aquí, configurant el meu msky"
+      description: "Publica la teva primera Nota"
+      flavor: "Passa-t'ho bé fent servir Miskey!"
+    _notes10:
+      title: "Algunes notes"
+      description: "Publica 10 notes"
+    _notes100:
+      title: "Un piló de notes"
+      description: "Publica 100 notes"
+    _notes500:
+      title: "Cobert de notes"
+      description: "Publica 500 notes"
+    _notes1000:
+      title: "Un piló de notes"
+      description: "1 000 notes publicades"
+    _notes5000:
+      title: "Desbordament de notes"
+      description: "5 000 notes publicades"
+    _notes10000:
+      title: "Supernota"
+      description: "10 000 notes publicades"
+    _notes20000:
+      title: "Necessito... Més... Notes!"
+      description: "20 000 notes publicades"
+    _notes30000:
+      title: "Notes notes notes!"
+      description: "30 000 notes publicades"
+    _notes40000:
+      title: "Fàbrica de notes"
+      description: "40 000 notes publicades"
+    _notes50000:
+      title: "Planeta de notes"
+      description: "50 000 notes publicades"
+    _notes60000:
+      title: "Quàsar de notes"
+      description: "60 000 notes publicades"
+    _notes70000:
+      title: "Forat negre de notes"
+      description: "70 000 notes publicades"
+    _notes80000:
+      title: "Galàxia de notes"
+      description: "80 000 notes publicades"
+    _notes90000:
+      title: "Univers de notes"
+      description: "90 000 notes publicades"
     _notes100000:
+      title: "ALL YOUR NOTE ARE BELONG TO US"
+      description: "100 000 notes publicades"
       flavor: "Segur que tens moltes coses a dir?"
     _login3:
       title: "Principiant I"
@@ -1347,13 +1461,90 @@ _achievements:
       description: "És la primera vegada que et segueixo"
     _following10:
       title: "Segueix-me... Segueix-me..."
+      description: "Seguir 10 usuaris"
+    _following50:
+      title: "Molts amics"
+      description: "Seguir 50 comptes"
+    _following100:
+      title: "100 amics"
+      description: "Segueixes 100 comptes"
+    _following300:
+      title: "Sobrecàrrega d'amics"
+      description: "Segueixes 300 comptes"
+    _followers1:
+      title: "Primer seguidor"
+      description: "1 seguidor guanyat"
+    _followers10:
+      title: "Segueix-me!"
+      description: "10 seguidors guanyats"
+    _followers50:
+      title: "Venen en manada"
+      description: "50 seguidors guanyats"
+    _followers100:
+      title: "Popular"
+      description: "100 seguidors guanyats"
+    _followers300:
+      title: "Si us plau, d'un en un!"
+      description: "300 seguidors guanyats"
+    _followers500:
+      title: "Torre de ràdio"
+      description: "500 seguidors guanyats"
+    _followers1000:
+      title: "Influenciador"
+      description: "1 000 seguidors guanyats"
+    _collectAchievements30:
+      title: "Col·leccionista d'èxits "
+      description: "Desbloqueja 30 assoliments"
+    _viewAchievements3min:
+      title: "M'agraden els èxits "
+      description: "Mira la teva llista d'assoliments durant més de 3 minuts"
+    _iLoveMisskey:
+      title: "Estimo Misskey"
+      description: "Publica \"I ❤ #Misskey\""
+      flavor: "L'equip de desenvolupament de Misskey agraeix el vostre suport!"
+    _foundTreasure:
+      title: "A la Recerca del Tresor"
+      description: "Has trobat el tresor amagat"
+    _client30min:
+      title: "Parem una estona"
+      description: "Mantingues obert Misskey per 30 minuts"
+    _client60min:
+      title: "A totes amb Misskey"
+      description: "Mantingues Misskey obert per 60 minuts"
+    _noteDeletedWithin1min:
+      title: "No et preocupis"
+      description: "Esborra una nota al minut de publicar-la"
+    _postedAtLateNight:
+      title: "Nocturn"
+      description: "Publica una nota a altes hores de la nit "
+      flavor: "És hora d'anar a dormir."
     _open3windows:
       title: "Multi finestres"
       description: "I va obrir més de tres finestres"
     _driveFolderCircularReference:
       title: "Consulteu la secció de bucle"
 _role:
+  permission: "Permisos de rol"
+  descriptionOfPermission: "Els <b>Moderadors</b> poden fer operacions bàsiques de moderació.\nEls <b>Administradors</b> poden canviar tots els ajustos del servidor."
   assignTarget: "Assignar "
+  descriptionOfAssignTarget: "<b>Manual</b> per canviar manualment qui és part d'aquest rol i qui no.\n<b>Condicional</b> per afegir o eliminar de manera automàtica els usuaris d'aquest rol basat en una determinada condició."
+  manual: "Manual"
+  manualRoles: "Rols manuals"
+  conditional: "Condicional"
+  conditionalRoles: "Rols condicionals"
+  condition: "Condició"
+  isConditionalRole: "Aquest és un rol condicional"
+  isPublic: "Rol públic"
+  descriptionOfIsPublic: "Aquest rol es mostrarà al perfil dels usuaris al que se'ls assigni."
+  options: "Opcions"
+  policies: "Polítiques"
+  baseRole: "Plantilla de rols"
+  useBaseValue: "Fer servir els valors de la plantilla de rols"
+  chooseRoleToAssign: "Selecciona els rols a assignar"
+  iconUrl: "URL de la icona "
+  asBadge: "Mostrar com a insígnia "
+  descriptionOfAsBadge: "La icona d'aquest rol es mostrarà al costat dels noms d'usuaris que tinguin assignats aquest rol."
+  isExplorable: "Fer el rol explorable"
   priority: "Prioritat"
   _priority:
     low: "Baixa"
diff --git a/locales/it-IT.yml b/locales/it-IT.yml
index 5678ab7e34..fdddf24360 100644
--- a/locales/it-IT.yml
+++ b/locales/it-IT.yml
@@ -102,7 +102,7 @@ defaultNoteVisibility: "Privacy predefinita delle note"
 follow: "Segui"
 followRequest: "Richiesta di follow"
 followRequests: "Richieste di follow"
-unfollow: "Interrompi following"
+unfollow: "Smetti di seguire"
 followRequestPending: "Richiesta in approvazione"
 enterEmoji: "Inserisci emoji"
 renote: "Rinota"
@@ -380,9 +380,11 @@ hcaptcha: "hCaptcha"
 enableHcaptcha: "Abilita hCaptcha"
 hcaptchaSiteKey: "Chiave del sito"
 hcaptchaSecretKey: "Chiave segreta"
+mcaptcha: "mCaptcha"
 enableMcaptcha: "Abilita hCaptcha"
 mcaptchaSiteKey: "Chiave del sito"
 mcaptchaSecretKey: "Chiave segreta"
+mcaptchaInstanceUrl: "URL della istanza mCaptcha"
 recaptcha: "reCAPTCHA"
 enableRecaptcha: "Abilita reCAPTCHA"
 recaptchaSiteKey: "Chiave del sito"
@@ -630,6 +632,7 @@ medium: "Medio"
 small: "Piccolo"
 generateAccessToken: "Genera token di accesso"
 permission: "Autorizzazioni "
+adminPermission: "Privilegi amministrativi"
 enableAll: "Abilita tutto"
 disableAll: "Disabilita tutto"
 tokenRequested: "Autorizza accesso al profilo"
@@ -673,6 +676,7 @@ useGlobalSettingDesc: "Quando attiva, verranno utilizzate le impostazioni notifi
 other: "Ulteriori"
 regenerateLoginToken: "Genera di nuovo un token di connessione"
 regenerateLoginTokenDescription: "Genera un nuovo token di autenticazione. Solitamente questa operazione non è necessaria: quando si genera un nuovo token, tutti i dispositivi vanno disconnessi."
+theKeywordWhenSearchingForCustomEmoji: "Questa sarà la parola chiave durante la ricerca di emoji personalizzate"
 setMultipleBySeparatingWithSpace: "È possibile creare multiple voci separate da spazi."
 fileIdOrUrl: "ID o URL del file"
 behavior: "Comportamento"
@@ -869,7 +873,7 @@ pubSub: "Publish/Subscribe del profilo"
 lastCommunication: "La comunicazione più recente"
 resolved: "Risolto"
 unresolved: "Non risolto"
-breakFollow: "Interrompi follow"
+breakFollow: "Impedire di seguirmi"
 breakFollowConfirm: "Vuoi davvero che questo profilo smetta di seguirti?"
 itsOn: "Abilitato"
 itsOff: "Disabilitato"
@@ -885,6 +889,8 @@ makeReactionsPublicDescription: "La lista delle reazioni che avete fatto è a di
 classic: "Classico"
 muteThread: "Silenzia conversazione"
 unmuteThread: "Riattiva la conversazione"
+followingVisibility: "Visibilità dei profili seguiti"
+followersVisibility: "Visibilità dei profili che ti seguono"
 continueThread: "Altre conversazioni"
 deleteAccountConfirm: "Così verrà eliminato il profilo. Vuoi procedere?"
 incorrectPassword: "La password è errata."
@@ -1053,6 +1059,8 @@ limitWidthOfReaction: "Limita la larghezza delle reazioni e ridimensionale"
 noteIdOrUrl: "ID della Nota o URL"
 video: "Video"
 videos: "Video"
+audio: "Audio"
+audioFiles: "Audio"
 dataSaver: "Risparmia dati"
 accountMigration: "Migrazione del profilo"
 accountMoved: "Questo profilo ha migrato altrove:"
@@ -1183,7 +1191,27 @@ remainingN: "Rimangono: {n}"
 overwriteContentConfirm: "Vuoi davvero sostituire l'attuale contenuto?"
 seasonalScreenEffect: "Schermate in base alla stagione"
 decorate: "Decora"
+addMfmFunction: "Aggiungi decorazioni"
+enableQuickAddMfmFunction: "Attiva il selettore di funzioni MFM"
+bubbleGame: "Bubble Game"
+sfx: "Effetti sonori"
+soundWillBePlayed: "Verrà riprodotto il suono"
+showReplay: "Vedi i replay"
+replay: "Replay"
+replaying: "Replay in corso"
+ranking: "Classifica"
 lastNDays: "Ultimi {n} giorni"
+backToTitle: "Torna al titolo"
+hemisphere: "Geolocalizzazione"
+withSensitive: "Mostra le Note con allegati espliciti"
+userSaysSomethingSensitive: "Note da {name} con allegati espliciti"
+enableHorizontalSwipe: "Trascina per invertire i tab"
+_bubbleGame:
+  howToPlay: "Come giocare"
+  _howToPlay:
+    section1: "Regola la posizione e rilascia l'oggetto nella casella."
+    section2: "Ottieni un punteggio, quando due oggetti dello stesso tipo si toccano e si trasformano in un oggetto diverso."
+    section3: "Se gli oggetti traboccano dalla scatola, il gioco finisce. Cerca di ottenere un punteggio elevato fondendo gli oggetti, evitando che escano dalla scatola!"
 _announcement:
   forExistingUsers: "Solo ai profili attuali"
   forExistingUsersDescription: "L'annuncio sarà visibile solo ai profili esistenti in questo momento. Se disabilitato, sarà visibile anche ai profili che verranno creati dopo la pubblicazione di questo annuncio."
@@ -1554,6 +1582,13 @@ _achievements:
     _tutorialCompleted:
       title: "Attestato di partecipazione al corso per principianti di Misskey"
       description: "Ha completato il tutorial"
+    _bubbleGameExplodingHead:
+      title: "🤯"
+      description: "Estrai l'oggetto più grande dal Bubble Game"
+    _bubbleGameDoubleExplodingHead:
+      title: "Doppio 🤯"
+      description: "Due oggetti più grossi contemporaneamente nel Bubble Game"
+      flavor: "Ha le dimensioni di una bento-box 🤯 🤯"
 _role:
   new: "Nuovo ruolo"
   edit: "Modifica ruolo"
@@ -1644,6 +1679,7 @@ _emailUnavailable:
   disposable: "Indirizzo email non utilizzabile"
   mx: "Server email non corretto"
   smtp: "Il server email non risponde"
+  banned: "Non puoi registrarti con questo indirizzo email"
 _ffVisibility:
   public: "Pubblica"
   followers: "Mostra solo ai follower"
@@ -1935,6 +1971,26 @@ _permissions:
   "write:flash": "Modifica Play"
   "read:flash-likes": "Visualizza lista di Play piaciuti"
   "write:flash-likes": "Modifica lista di Play piaciuti"
+  "read:admin:abuse-user-reports": "Mostra i report dai profili utente"
+  "write:admin:delete-account": "Elimina l'account utente"
+  "write:admin:delete-all-files-of-a-user": "Elimina i file dell'account utente"
+  "read:admin:index-stats": "Visualizza informazioni sugli indici del database"
+  "read:admin:table-stats": "Visualizza informazioni sulle tabelle del database"
+  "read:admin:user-ips": "Visualizza indirizzi IP degli account"
+  "read:admin:meta": "Visualizza i metadati dell'istanza"
+  "write:admin:reset-password": "Ripristina la password dell'account utente"
+  "write:admin:resolve-abuse-user-report": "Risolvere le segnalazioni dagli account utente"
+  "write:admin:send-email": "Spedire email"
+  "read:admin:server-info": "Vedere le informazioni sul server"
+  "read:admin:show-moderation-log": "Vedere lo storico di moderazione"
+  "read:admin:show-user": "Vedere le informazioni private degli account utente"
+  "read:admin:show-users": "Vedere le informazioni private degli account utente"
+  "write:admin:suspend-user": "Sospendere i profili"
+  "write:admin:unset-user-avatar": "Rimuovere la foto profilo dai profili"
+  "write:admin:unset-user-banner": "Rimuovere l'immagine testata dai profili"
+  "write:admin:unsuspend-user": "Togliere la sospensione ai profili"
+  "write:admin:meta": "Modificare i metadati dell'istanza"
+  "write:admin:user-note": "Scrivere annotazioni di moderazione"
 _auth:
   shareAccessTitle: "Permessi dell'applicazione"
   shareAccess: "Vuoi autorizzare {name} ad accedere al tuo profilo?"
diff --git a/locales/ko-GS.yml b/locales/ko-GS.yml
index 29bfe5394d..b1702114be 100644
--- a/locales/ko-GS.yml
+++ b/locales/ko-GS.yml
@@ -40,7 +40,7 @@ favorites: "질겨찾기"
 unfavorite: "질겨찾기서 어ᇝ애기"
 favorited: "질겨찾기에 담앗십니다."
 alreadyFavorited: "벌시로 질겨찾기에 담기 잇십니다."
-cantFavorite: "질겨찾기에 몬 담았십니다."
+cantFavorite: "질겨찾기에 몬 담앗십니다."
 pin: "프로필에 붙이기"
 unpin: "프로필서 띠기"
 copyContent: "내용 복사하기"
@@ -124,6 +124,7 @@ reactions: "반엉"
 reactionSettingDescription2: "꺼시서 두고, 누질라서 뭉캐고,  ‘+’럴 누질라서 옇십니다."
 rememberNoteVisibility: "공개 범위럴 기억하기"
 attachCancel: "붙임 빼기"
+deleteFile: "파일 뭉캐기"
 markAsSensitive: "수ᇚ힘 설정"
 unmarkAsSensitive: "수ᇚ힘 무루기"
 enterFileName: "파일 이럼 서기"
@@ -463,6 +464,8 @@ onlyOneFileCanBeAttached: "메시지엔 파일 하나까제밖에 몬 넣십니
 invitations: "초대하기"
 invitationCode: "초대장"
 checking: "학인하고 잇십니다"
+tooShort: "억수로 짜립니다"
+tooLong: "억수로 집니다"
 passwordMatched: "맞십니다"
 passwordNotMatched: "안 맞십니다"
 signinFailed: "로그인 몬 했십니다. 고 이름이랑 비밀번호 제대로 썼는가 확인해 주이소."
@@ -571,7 +574,11 @@ userSilenced: "요 게정은... 수ᇚ혀 있십니다."
 relays: "릴레이"
 addRelay: "릴레이 옇기"
 addedRelays: "옇은 릴레이"
+deletedNote: "뭉캔 걸"
 enableInfiniteScroll: "알아서 더 보기"
+useCw: "내용 수ᇚ후기"
+description: "설멩"
+describeFile: "캡션 옇기"
 author: "맨던 사람"
 manage: "간리"
 emailServer: "전자우펜 서버"
@@ -600,6 +607,7 @@ renotesCount: "리노트한 수"
 renotedCount: "리노트덴 수"
 followingCount: "팔로우 수"
 followersCount: "팔로워 수"
+noteFavoritesCount: "질겨찾기한 노트 수"
 clips: "클립 맨걸기"
 clearCache: "캐시 비우기"
 unlikeConfirm: "좋네예럴 무룹니꺼?"
@@ -608,6 +616,7 @@ user: "사용자"
 administration: "간리"
 on: "í‚´"
 off: "껌"
+hide: "수ᇚ후기"
 clickToFinishEmailVerification: "[{ok}]럴 누질라서 전자우펜 정멩얼 껕내이소."
 searchByGoogle: "찾기"
 tenMinutes: "십 분"
@@ -626,9 +635,11 @@ role: "옉할"
 noRole: "옉할이 없십니다"
 thisPostMayBeAnnoyingCancel: "아이예"
 likeOnly: "좋네예마"
+myClips: "내 클립"
 icon: "아바타"
 replies: "답하기"
 renotes: "리노트"
+attach: "옇기"
 _initialAccountSetting:
   startTutorial: "길라잡이 하기"
 _initialTutorial:
@@ -641,9 +652,52 @@ _initialTutorial:
     title: "길라잡이가 껕낫십니다!🎉"
 _achievements:
   _types:
+    _notes1:
+      description: "첫 노트럴 섯어예"
+    _notes10:
+      description: "노트럴 10번 섰어예"
+    _notes100:
+      description: "노트럴 100번 섰어예"
+    _notes500:
+      description: "노트럴 500번 섰어예"
+    _notes1000:
+      description: "노트럴 1,000번 섰어예"
+    _notes5000:
+      description: "노트럴 5,000번 섰어예"
+    _notes10000:
+      description: "노트럴 10,000번 섰어예"
+    _notes20000:
+      description: "노트럴 20,000번 섰어예"
+    _notes30000:
+      description: "노트럴 30,000번 섰어예"
+    _notes40000:
+      description: "노트럴 40,000번 섰어예"
+    _notes50000:
+      description: "노트럴 50,000번 섰어예"
+    _notes60000:
+      description: "노트럴 60,000번 섰어예"
+    _notes70000:
+      description: "노트럴 70,000번 섰어예"
+    _notes80000:
+      description: "노트럴 80,000번 섰어예"
+    _notes90000:
+      description: "노트럴 90,000번 섰어예"
+    _notes100000:
+      description: "노트럴 100,000번 섰어예"
+    _noteClipped1:
+      description: "첫 노트럴 클립햇어예"
+    _noteFavorited1:
+      description: "첫 노트럴 질겨찾기에 담앗어예"
+    _myNoteFavorited1:
+      description: "다런 사람이 내 노트럴 질겨찾기에 담앗십니다"
+    _iLoveMisskey:
+      description: "“I ❤ #Misskey”럴 섰어예"
+    _postedAt0min0sec:
+      description: "0분 0초에 노트를 섰어예"
     _tutorialCompleted:
       description: "길라잡이럴 껕냇십니다"
 _gallery:
+  my: "내 걸"
   liked: "좋네예한 걸"
   like: "좋네예!"
   unlike: "좋네예 무루기"
@@ -654,7 +708,12 @@ _serverDisconnectedBehavior:
   reload: "알아서 새로곤침"
 _channel:
   removeBanner: "배너 뭉캐기"
+  usersCount: "{n}명 참여"
+  notesCount: "노트 {n}개"
+_menuDisplay:
+  hide: "수ᇚ후기"
 _theme:
+  description: "설멩"
   keys:
     mention: "멘션"
 _sfx:
@@ -663,6 +722,9 @@ _sfx:
 _2fa:
   step3Title: "학인 기호럴 서기"
   renewTOTPCancel: "뎃어예"
+_permissions:
+  "read:favorites": "질겨찾기 보기"
+  "write:favorites": "질겨찾기 곤치기"
 _widgets:
   profile: "프로필"
   instanceInfo: "서버 정보"
@@ -674,7 +736,10 @@ _widgets:
   _userList:
     chooseList: "리스트 개리기"
 _cw:
+  hide: "수ᇚ후기"
   show: "더 볼래예"
+  chars: "걸자 {count}개"
+  files: "파일 {count}개"
 _visibility:
   home: "덜머리"
   followers: "팔로워"
@@ -682,6 +747,7 @@ _profile:
   name: "이럼"
   username: "사용자 이럼"
 _exportOrImport:
+  favoritedNotes: "질겨찾기한 노트"
   clips: "클립 맨걸기"
   followingList: "팔로잉"
   muteList: "수ᇚ후기"
@@ -692,16 +758,20 @@ _charts:
 _timelines:
   home: "덜머리"
 _play:
+  my: "내 플레이"
   script: "스크립트"
+  summary: "설멩"
 _pages:
   like: "좋네예"
   unlike: "좋네예 무루기"
+  my: "내 페이지"
   blocks:
     image: "이미지"
     _note:
       id: "노트 아이디"
 _notification:
   youWereFollowed: "새 팔로워가 잇십니다"
+  newNote: "새 걸"
   _types:
     follow: "팔로잉"
     mention: "멘션"
diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml
index 718d04caae..2a59ab9a23 100644
--- a/locales/ko-KR.yml
+++ b/locales/ko-KR.yml
@@ -279,7 +279,7 @@ uploadFromUrl: "URL 업로드"
 uploadFromUrlDescription: "업로드하려는 파일의 URL"
 uploadFromUrlRequested: "업로드를 요청했습니다"
 uploadFromUrlMayTakeTime: "업로드가 완료될 때까지 시간이 소요될 수 있습니다."
-explore: "발견하기"
+explore: "둘러보기"
 messageRead: "읽음"
 noMoreHistory: "이것보다 과거의 기록이 없습니다"
 startMessaging: "대화 시작하기"
@@ -1022,7 +1022,7 @@ internalServerError: "내부 서버 오류"
 internalServerErrorDescription: "내부 서버에서 예기치 않은 오류가 발생했습니다."
 copyErrorInfo: "오류 정보 복사"
 joinThisServer: "이 서버에 가입"
-exploreOtherServers: "다른 서버 둘러보기"
+exploreOtherServers: "다른 서버 찾기"
 letsLookAtTimeline: "타임라인 구경하기"
 disableFederationConfirm: "정말로 연합을 끄시겠습니까?"
 disableFederationConfirmWarn: "연합을 끄더라도 게시물이 비공개로 전환되는 것은 아닙니다. 대부분의 경우 연합을 비활성화할 필요가 없습니다."
@@ -1797,7 +1797,7 @@ _instanceMute:
   title: "지정한 서버의 노트를 숨깁니다."
   heading: "뮤트할 서버"
 _theme:
-  explore: "테마 찾아보기"
+  explore: "테마 둘러보기"
   install: "테마 설치"
   manage: "테마 관리"
   code: "테마 코드"
@@ -2022,7 +2022,7 @@ _permissions:
   "write:report-abuse": "위반 내용 신고하기"
 _auth:
   shareAccessTitle: "어플리케이션의 접근 허가"
-  shareAccess: "\"{name}\" 이 계정에 접근하는 것을 허용하시겠습니까?"
+  shareAccess: "‘{name}’에서 계정에 접근하는 것을 허용하시겠습니까?"
   shareAccessAsk: "이 애플리케이션이 계정에 접근하는 것을 허용하시겠습니까?"
   permission: "{name}에서 다음 권한을 요청하였습니다"
   permissionAsk: "이 앱은 다음의 권한을 요청합니다"
-- 
GitLab


From ddfc3b8a6a398f005ccb186654572b37f5e232a5 Mon Sep 17 00:00:00 2001
From: taichan <40626578+tai-cha@users.noreply.github.com>
Date: Mon, 5 Feb 2024 15:01:31 +0900
Subject: [PATCH 06/40] =?UTF-8?q?Fix(frontend):=20=E3=82=AF=E3=83=AD?=
 =?UTF-8?q?=E3=83=83=E3=83=97=E5=BE=8C=E3=81=AE=E8=A7=A3=E5=83=8F=E5=BA=A6?=
 =?UTF-8?q?=E3=81=8C=E7=95=B0=E6=A7=98=E3=81=AB=E4=BD=8E=E3=81=8F=E3=81=AA?=
 =?UTF-8?q?=E3=82=8B=E5=95=8F=E9=A1=8C=E3=81=AE=E4=BF=AE=E6=AD=A3&?=
 =?UTF-8?q?=E3=82=AF=E3=83=AD=E3=83=83=E3=83=97=E3=81=AB=E5=A4=B1=E6=95=97?=
 =?UTF-8?q?=E3=81=99=E3=82=8B=E5=95=8F=E9=A1=8C&=E3=82=B3=E3=83=A1?=
 =?UTF-8?q?=E3=83=B3=E3=83=88=E3=81=ABnull=E3=81=A8=E3=81=84=E3=81=86?=
 =?UTF-8?q?=E6=96=87=E5=AD=97=E5=88=97=E3=81=8C=E5=85=A5=E3=82=8B=E5=95=8F?=
 =?UTF-8?q?=E9=A1=8C=E3=81=AE=E4=BF=AE=E6=AD=A3=20(#13162)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* Fix(frontend): Fix resolution of cropped image (misskey-dev#11489)

* CHANGELOG

* Fix(frontend): クロップの際、folderIdがnullだと文字列のnullが送られ検索できない問題

* Fix: キャプションが存在しないときにクロップすると'null'がキャプションに入ってしまう問題 (misskey-dev#11813)

* Update CHANGELOG
---
 CHANGELOG.md                                    |  3 +++
 .../frontend/src/components/MkCropperDialog.vue | 17 ++++++++++++-----
 2 files changed, 15 insertions(+), 5 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8f6cf2071b..c089aae279 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -62,6 +62,9 @@
 - Enhance: ページ遷移時にPlayerを閉じるように
 - Fix: iOSで大きな画像を変換してアップロードできない問題を修正
 - Fix: 「アニメーション画像を再生しない」もしくは「データセーバー(アイコン)」を有効にしていても、アイコンデコレーションのアニメーションが停止されない問題を修正
+- Fix: 画像をクロップするとクロップ後の解像度が異様に低くなる問題の修正
+- Fix: 画像をクロップ時、正常に完了できない問題の修正
+- Fix: キャプションが空の画像をクロップするとキャプションにnullという文字列が入ってしまう問題の修正
 
 ### Server
 - Enhance: 連合先のレートリミットに引っかかった際にリトライするようになりました
diff --git a/packages/frontend/src/components/MkCropperDialog.vue b/packages/frontend/src/components/MkCropperDialog.vue
index 0a1ddd3171..745453646c 100644
--- a/packages/frontend/src/components/MkCropperDialog.vue
+++ b/packages/frontend/src/components/MkCropperDialog.vue
@@ -63,18 +63,25 @@ const loading = ref(true);
 
 const ok = async () => {
 	const promise = new Promise<Misskey.entities.DriveFile>(async (res) => {
-		const croppedCanvas = await cropper?.getCropperSelection()?.$toCanvas();
+		const croppedImage = await cropper?.getCropperImage();
+		const croppedSection = await cropper?.getCropperSelection();
+
+		// 拡大率を計算し、(ほぼ)元の大きさに戻す
+		const zoomedRate = croppedImage.getBoundingClientRect().width / croppedImage.clientWidth;
+		const widthToRender = croppedSection.getBoundingClientRect().width / zoomedRate;
+
+		const croppedCanvas = await croppedSection?.$toCanvas({ width: widthToRender });
 		croppedCanvas?.toBlob(blob => {
 			if (!blob) return;
 			const formData = new FormData();
 			formData.append('file', blob);
 			formData.append('name', `cropped_${props.file.name}`);
 			formData.append('isSensitive', props.file.isSensitive ? 'true' : 'false');
-			formData.append('comment', props.file.comment ?? 'null');
+			if (props.file.comment) { formData.append('comment', props.file.comment);}
 			formData.append('i', $i!.token);
-			if (props.uploadFolder || props.uploadFolder === null) {
-				formData.append('folderId', props.uploadFolder ?? 'null');
-			} else if (defaultStore.state.uploadFolder) {
+			if (props.uploadFolder) {
+				formData.append('folderId', props.uploadFolder);
+			} else if (props.uploadFolder !== null && defaultStore.state.uploadFolder) {
 				formData.append('folderId', defaultStore.state.uploadFolder);
 			}
 
-- 
GitLab


From 0df069494e70eafa7217b79d133bb248865baa85 Mon Sep 17 00:00:00 2001
From: zyoshoka <107108195+zyoshoka@users.noreply.github.com>
Date: Mon, 5 Feb 2024 15:02:30 +0900
Subject: [PATCH 07/40] =?UTF-8?q?refactor(frontend):=20`os.popup()`?=
 =?UTF-8?q?=E3=81=AE`events`=E3=81=AE=E5=9E=8B=E3=83=81=E3=82=A7=E3=83=83?=
 =?UTF-8?q?=E3=82=AF=E3=82=92=E6=9C=89=E5=8A=B9=E5=8C=96=20(#13165)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 packages/frontend/src/os.ts | 18 +++++++++++++++---
 1 file changed, 15 insertions(+), 3 deletions(-)

diff --git a/packages/frontend/src/os.ts b/packages/frontend/src/os.ts
index 7cce77cdf7..010fdbb6d8 100644
--- a/packages/frontend/src/os.ts
+++ b/packages/frontend/src/os.ts
@@ -128,9 +128,10 @@ export function promiseDialog<T extends Promise<any>>(
 
 let popupIdCount = 0;
 export const popups = ref([]) as Ref<{
-	id: any;
-	component: any;
+	id: number;
+	component: Component;
 	props: Record<string, any>;
+	events: Record<string, any>;
 }[]>;
 
 const zIndexes = {
@@ -144,7 +145,18 @@ export function claimZIndex(priority: keyof typeof zIndexes = 'low'): number {
 	return zIndexes[priority];
 }
 
-export async function popup<T extends Component>(component: T, props: ComponentProps<T>, events = {}, disposeEvent?: string) {
+// InstanceType<typeof Component>['$emit'] だとインターセクション型が返ってきて
+// 使い物にならないので、代わりに ['$props'] から色々省くことで emit の型を生成する
+// FIXME: 何故か *.ts ファイルからだと型がうまく取れない?ことがあるのをなんとかしたい
+type ComponentEmit<T> = T extends new () => { $props: infer Props }
+	? EmitsExtractor<Props>
+	: never;
+
+type EmitsExtractor<T> = {
+	[K in keyof T as K extends `onVnode${string}` ? never : K extends `on${infer E}` ? Uncapitalize<E> : K extends string ? never : K]: T[K];
+};
+
+export async function popup<T extends Component>(component: T, props: ComponentProps<T>, events: ComponentEmit<T> = {} as ComponentEmit<T>, disposeEvent?: keyof ComponentEmit<T>) {
 	markRaw(component);
 
 	const id = ++popupIdCount;
-- 
GitLab


From 2f54a530623122f4fa962f7fd8b7de396f30c704 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Mon, 5 Feb 2024 21:08:05 +0900
Subject: [PATCH 08/40] 2024.2.0-beta.10

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index aff28ca7e2..bd4529f159 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "misskey",
-	"version": "2024.2.0-beta.9",
+	"version": "2024.2.0-beta.10",
 	"codename": "nasubi",
 	"repository": {
 		"type": "git",
-- 
GitLab


From 16eccad49271b81282cdbd482d29e7a8bb048f4c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?=
 <67428053+kakkokari-gtyih@users.noreply.github.com>
Date: Tue, 6 Feb 2024 15:03:07 +0900
Subject: [PATCH 09/40] =?UTF-8?q?enhance(frontend):=20=E3=82=B7=E3=83=B3?=
 =?UTF-8?q?=E3=82=BF=E3=83=83=E3=82=AF=E3=82=B9=E3=83=8F=E3=82=A4=E3=83=A9?=
 =?UTF-8?q?=E3=82=A4=E3=83=88=E3=81=AB=E3=83=86=E3=83=BC=E3=83=9E=E3=82=92?=
 =?UTF-8?q?=E9=81=A9=E7=94=A8=E3=81=A7=E3=81=8D=E3=82=8B=E3=82=88=E3=81=86?=
 =?UTF-8?q?=E3=81=AB=20(#13175)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* enhance(frontend): シンタックスハイライトにテーマを適用できるように

* Update Changelog

* こっちも

* テーマの値がディープマージされるように

* 常にテーマ設定に準じるように

* テーマ更新時に新しいshikiテーマを読み込むように
---
 CHANGELOG.md                                  |  1 +
 .../frontend/src/components/MkCode.core.vue   | 56 +++++++++++++--
 packages/frontend/src/components/MkCode.vue   |  8 +--
 .../frontend/src/components/MkCodeEditor.vue  |  5 +-
 .../frontend/src/components/MkCodeInline.vue  |  3 +-
 packages/frontend/src/components/MkSelect.vue |  7 +-
 .../frontend/src/pages/settings/theme.vue     | 15 +++-
 packages/frontend/src/pizzax.ts               | 23 +------
 packages/frontend/src/router/main.ts          |  4 ++
 packages/frontend/src/scripts/clone.ts        |  4 +-
 .../frontend/src/scripts/code-highlighter.ts  | 68 ++++++++++++++++++-
 packages/frontend/src/scripts/merge.ts        | 31 +++++++++
 packages/frontend/src/scripts/theme.ts        | 10 ++-
 packages/frontend/src/store.ts                |  1 +
 packages/frontend/src/themes/_dark.json5      |  4 ++
 packages/frontend/src/themes/_light.json5     |  4 ++
 16 files changed, 202 insertions(+), 42 deletions(-)
 create mode 100644 packages/frontend/src/scripts/merge.ts

diff --git a/CHANGELOG.md b/CHANGELOG.md
index c089aae279..c39a447c59 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -49,6 +49,7 @@
 - Enhance: MFMの属性でオートコンプリートが使用できるように #12735
 - Enhance: 絵文字編集ダイアログをモーダルではなくウィンドウで表示するように
 - Enhance: リモートのユーザーはメニューから直接リモートで表示できるように
+- Enhance: コードのシンタックスハイライトにテーマを適用できるように
 - Fix: ネイティブモードの絵文字がモノクロにならないように
 - Fix: v2023.12.0で追加された「モデレーターがユーザーのアイコンもしくはバナー画像を未設定状態にできる機能」が管理画面上で正しく表示されていない問題を修正
 - Fix: AiScriptの`readline`関数が不正な値を返すことがある問題のv2023.12.0時点での修正がPlay以外に適用されていないのを修正
diff --git a/packages/frontend/src/components/MkCode.core.vue b/packages/frontend/src/components/MkCode.core.vue
index c655ff416e..68c50c4c69 100644
--- a/packages/frontend/src/components/MkCode.core.vue
+++ b/packages/frontend/src/components/MkCode.core.vue
@@ -5,14 +5,15 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <!-- eslint-disable vue/no-v-html -->
 <template>
-<div :class="[$style.codeBlockRoot, { [$style.codeEditor]: codeEditor }]" v-html="html"></div>
+<div :class="[$style.codeBlockRoot, { [$style.codeEditor]: codeEditor }, (darkMode ? $style.dark : $style.light)]" v-html="html"></div>
 </template>
 
 <script lang="ts" setup>
 import { ref, computed, watch } from 'vue';
 import { bundledLanguagesInfo } from 'shiki';
 import type { BuiltinLanguage } from 'shiki';
-import { getHighlighter } from '@/scripts/code-highlighter.js';
+import { getHighlighter, getTheme } from '@/scripts/code-highlighter.js';
+import { defaultStore } from '@/store.js';
 
 const props = defineProps<{
 	code: string;
@@ -21,11 +22,23 @@ const props = defineProps<{
 }>();
 
 const highlighter = await getHighlighter();
-
+const darkMode = defaultStore.reactiveState.darkMode;
 const codeLang = ref<BuiltinLanguage | 'aiscript'>('js');
+
+const [lightThemeName, darkThemeName] = await Promise.all([
+	getTheme('light', true),
+	getTheme('dark', true),
+]);
+
 const html = computed(() => highlighter.codeToHtml(props.code, {
 	lang: codeLang.value,
-	theme: 'dark-plus',
+	themes: {
+		fallback: 'dark-plus',
+		light: lightThemeName,
+		dark: darkThemeName,
+	},
+	defaultColor: false,
+	cssVariablePrefix: '--shiki-',
 }));
 
 async function fetchLanguage(to: string): Promise<void> {
@@ -64,6 +77,15 @@ watch(() => props.lang, (to) => {
 	margin: .5em 0;
 	overflow: auto;
 	border-radius: 8px;
+	border: 1px solid var(--divider);
+
+	color: var(--shiki-fallback);
+	background-color: var(--shiki-fallback-bg);
+
+	& span {
+		color: var(--shiki-fallback);
+		background-color: var(--shiki-fallback-bg);
+	}
 
 	& pre,
 	& code {
@@ -71,6 +93,26 @@ watch(() => props.lang, (to) => {
 	}
 }
 
+.light.codeBlockRoot :global(.shiki) {
+	color: var(--shiki-light);
+	background-color: var(--shiki-light-bg);
+
+	& span {
+		color: var(--shiki-light);
+		background-color: var(--shiki-light-bg);
+	}
+}
+
+.dark.codeBlockRoot :global(.shiki) {
+	color: var(--shiki-dark);
+	background-color: var(--shiki-dark-bg);
+
+	& span {
+		color: var(--shiki-dark);
+		background-color: var(--shiki-dark-bg);
+	}
+}
+
 .codeBlockRoot.codeEditor {
 	min-width: 100%;
 	height: 100%;
@@ -79,6 +121,7 @@ watch(() => props.lang, (to) => {
 		padding: 12px;
 		margin: 0;
 		border-radius: 6px;
+		border: none;
 		min-height: 130px;
 		pointer-events: none;
 		min-width: calc(100% - 24px);
@@ -90,6 +133,11 @@ watch(() => props.lang, (to) => {
 		text-rendering: inherit;
     text-transform: inherit;
     white-space: pre;
+
+		& span {
+			display: inline-block;
+			min-height: 1em;
+		}
 	}
 }
 </style>
diff --git a/packages/frontend/src/components/MkCode.vue b/packages/frontend/src/components/MkCode.vue
index 251e6ade00..6c14738937 100644
--- a/packages/frontend/src/components/MkCode.vue
+++ b/packages/frontend/src/components/MkCode.vue
@@ -53,7 +53,6 @@ function copy() {
 }
 
 .codeBlockCopyButton {
-	color: #D4D4D4;
 	position: absolute;
 	top: 8px;
 	right: 8px;
@@ -67,8 +66,7 @@ function copy() {
 .codeBlockFallbackRoot {
 	display: block;
 	overflow-wrap: anywhere;
-	color: #D4D4D4;
-	background: #1E1E1E;
+	background: var(--bg);
 	padding: 1em;
 	margin: .5em 0;
 	overflow: auto;
@@ -93,8 +91,8 @@ function copy() {
 	border-radius: 8px;
 	padding: 24px;
 	margin-top: 4px;
-	color: #D4D4D4;
-	background: #1E1E1E;
+	color: var(--fg);
+	background: var(--bg);
 }
 
 .codePlaceholderContainer {
diff --git a/packages/frontend/src/components/MkCodeEditor.vue b/packages/frontend/src/components/MkCodeEditor.vue
index c8c3deb610..3cf8234e72 100644
--- a/packages/frontend/src/components/MkCodeEditor.vue
+++ b/packages/frontend/src/components/MkCodeEditor.vue
@@ -196,10 +196,11 @@ watch(v, newValue => {
 	resize: none;
 	text-align: left;
 	color: transparent;
-	caret-color: rgb(225, 228, 232);
+	caret-color: var(--fg);
 	background-color: transparent;
 	border: 0;
 	border-radius: 6px;
+	box-sizing: border-box;
 	outline: 0;
 	min-width: calc(100% - 24px);
 	height: 100%;
@@ -210,6 +211,6 @@ watch(v, newValue => {
 }
 
 .textarea::selection {
-	color: #fff;
+	color: var(--bg);
 }
 </style>
diff --git a/packages/frontend/src/components/MkCodeInline.vue b/packages/frontend/src/components/MkCodeInline.vue
index 5340c1fd5f..6a9d97ab5a 100644
--- a/packages/frontend/src/components/MkCodeInline.vue
+++ b/packages/frontend/src/components/MkCodeInline.vue
@@ -18,8 +18,7 @@ const props = defineProps<{
 	display: inline-block;
 	font-family: Consolas, Monaco, Andale Mono, Ubuntu Mono, monospace;
 	overflow-wrap: anywhere;
-	color: #D4D4D4;
-	background: #1E1E1E;
+	background: var(--bg);
 	padding: .1em;
 	border-radius: .3em;
 }
diff --git a/packages/frontend/src/components/MkSelect.vue b/packages/frontend/src/components/MkSelect.vue
index 8dd2b9129d..3e8ff99387 100644
--- a/packages/frontend/src/components/MkSelect.vue
+++ b/packages/frontend/src/components/MkSelect.vue
@@ -27,7 +27,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 	</div>
 	<div :class="$style.caption"><slot name="caption"></slot></div>
 
-	<MkButton v-if="manualSave && changed" primary @click="updated"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton>
+	<MkButton v-if="manualSave && changed" primary :class="$style.save" @click="updated"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton>
 </div>
 </template>
 
@@ -138,6 +138,7 @@ function show() {
 			active: computed(() => v.value === option.props?.value),
 			action: () => {
 				v.value = option.props?.value;
+				changed.value = true;
 				emit('changeByUser', v.value);
 			},
 		});
@@ -288,6 +289,10 @@ function show() {
 	padding-left: 6px;
 }
 
+.save {
+	margin: 8px 0 0 0;
+}
+
 .chevron {
 	transition: transform 0.1s ease-out;
 }
diff --git a/packages/frontend/src/pages/settings/theme.vue b/packages/frontend/src/pages/settings/theme.vue
index dedac10270..1d6fec5290 100644
--- a/packages/frontend/src/pages/settings/theme.vue
+++ b/packages/frontend/src/pages/settings/theme.vue
@@ -88,6 +88,18 @@ import { uniqueBy } from '@/scripts/array.js';
 import { fetchThemes, getThemes } from '@/theme-store.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import { miLocalStorage } from '@/local-storage.js';
+import { unisonReload } from '@/scripts/unison-reload.js';
+import * as os from '@/os.js';
+
+async function reloadAsk() {
+	const { canceled } = await os.confirm({
+		type: 'info',
+		text: i18n.ts.reloadToApplySetting,
+	});
+	if (canceled) return;
+
+	unisonReload();
+}
 
 const installedThemes = ref(getThemes());
 const builtinThemes = getBuiltinThemesRef();
@@ -124,6 +136,7 @@ const lightThemeId = computed({
 		}
 	},
 });
+
 const darkMode = computed(defaultStore.makeGetterSetter('darkMode'));
 const syncDeviceDarkMode = computed(ColdDeviceStorage.makeGetterSetter('syncDeviceDarkMode'));
 const wallpaper = ref(miLocalStorage.getItem('wallpaper'));
@@ -141,7 +154,7 @@ watch(wallpaper, () => {
 	} else {
 		miLocalStorage.setItem('wallpaper', wallpaper.value);
 	}
-	location.reload();
+	reloadAsk();
 });
 
 onActivated(() => {
diff --git a/packages/frontend/src/pizzax.ts b/packages/frontend/src/pizzax.ts
index 68c36ca1b4..043b6efd73 100644
--- a/packages/frontend/src/pizzax.ts
+++ b/packages/frontend/src/pizzax.ts
@@ -13,6 +13,7 @@ import { get, set } from '@/scripts/idb-proxy.js';
 import { defaultStore } from '@/store.js';
 import { useStream } from '@/stream.js';
 import { deepClone } from '@/scripts/clone.js';
+import { deepMerge } from '@/scripts/merge.js';
 
 type StateDef = Record<string, {
 	where: 'account' | 'device' | 'deviceAccount';
@@ -84,29 +85,9 @@ export class Storage<T extends StateDef> {
 		return typeof value === 'object' && value !== null && !Array.isArray(value);
 	}
 
-	/**
-	 * valueにないキーをdefからもらう(再帰的)\
-	 * nullはそのまま、undefinedはdefの値
-	 **/
-	private mergeObject<X>(value: X, def: X): X {
-		if (this.isPureObject(value) && this.isPureObject(def)) {
-			const result = structuredClone(value) as X;
-			for (const [k, v] of Object.entries(def) as [keyof X, X[keyof X]][]) {
-				if (!Object.prototype.hasOwnProperty.call(value, k) || value[k] === undefined) {
-					result[k] = v;
-				} else if (this.isPureObject(v) && this.isPureObject(result[k])) {
-					const child = structuredClone(result[k]) as X[keyof X] & Record<string | number | symbol, unknown>;
-					result[k] = this.mergeObject<typeof v>(child, v);
-				}
-			}
-			return result;
-		}
-		return value;
-	}
-
 	private mergeState<X>(value: X, def: X): X {
 		if (this.isPureObject(value) && this.isPureObject(def)) {
-			const merged = this.mergeObject(value, def);
+			const merged = deepMerge(value, def);
 
 			if (_DEV_) console.log('Merging state. Incoming: ', value, ' Default: ', def, ' Result: ', merged);
 
diff --git a/packages/frontend/src/router/main.ts b/packages/frontend/src/router/main.ts
index 5adb3f606f..c6a520e913 100644
--- a/packages/frontend/src/router/main.ts
+++ b/packages/frontend/src/router/main.ts
@@ -80,6 +80,10 @@ class MainRouterProxy implements IRouter {
 		return this.supplier().resolve(path);
 	}
 
+	init(): void {
+		this.supplier().init();
+	}
+
 	eventNames(): Array<EventEmitter.EventNames<RouterEvent>> {
 		return this.supplier().eventNames();
 	}
diff --git a/packages/frontend/src/scripts/clone.ts b/packages/frontend/src/scripts/clone.ts
index ac38faefaa..6d3a1c8c79 100644
--- a/packages/frontend/src/scripts/clone.ts
+++ b/packages/frontend/src/scripts/clone.ts
@@ -8,13 +8,13 @@
 // あと、Vue RefをIndexedDBに保存しようとしてstructredCloneを使ったらエラーになった
 // https://github.com/misskey-dev/misskey/pull/8098#issuecomment-1114144045
 
-type Cloneable = string | number | boolean | null | undefined | { [key: string]: Cloneable } | Cloneable[];
+export type Cloneable = string | number | boolean | null | undefined | { [key: string]: Cloneable } | { [key: number]: Cloneable } | { [key: symbol]: Cloneable } | Cloneable[];
 
 export function deepClone<T extends Cloneable>(x: T): T {
 	if (typeof x === 'object') {
 		if (x === null) return x;
 		if (Array.isArray(x)) return x.map(deepClone) as T;
-		const obj = {} as Record<string, Cloneable>;
+		const obj = {} as Record<string | number | symbol, Cloneable>;
 		for (const [k, v] of Object.entries(x)) {
 			obj[k] = v === undefined ? undefined : deepClone(v);
 		}
diff --git a/packages/frontend/src/scripts/code-highlighter.ts b/packages/frontend/src/scripts/code-highlighter.ts
index bc05ec94d5..b11dfed41a 100644
--- a/packages/frontend/src/scripts/code-highlighter.ts
+++ b/packages/frontend/src/scripts/code-highlighter.ts
@@ -1,9 +1,51 @@
+import { bundledThemesInfo } from 'shiki';
 import { getHighlighterCore, loadWasm } from 'shiki/core';
 import darkPlus from 'shiki/themes/dark-plus.mjs';
-import type { Highlighter, LanguageRegistration } from 'shiki';
+import { unique } from './array.js';
+import { deepClone } from './clone.js';
+import { deepMerge } from './merge.js';
+import type { Highlighter, LanguageRegistration, ThemeRegistration, ThemeRegistrationRaw } from 'shiki';
+import { ColdDeviceStorage } from '@/store.js';
+import lightTheme from '@/themes/_light.json5';
+import darkTheme from '@/themes/_dark.json5';
 
 let _highlighter: Highlighter | null = null;
 
+export async function getTheme(mode: 'light' | 'dark', getName: true): Promise<string>;
+export async function getTheme(mode: 'light' | 'dark', getName?: false): Promise<ThemeRegistration | ThemeRegistrationRaw>;
+export async function getTheme(mode: 'light' | 'dark', getName = false): Promise<ThemeRegistration | ThemeRegistrationRaw | string | null> {
+	const theme = deepClone(ColdDeviceStorage.get(mode === 'light' ? 'lightTheme' : 'darkTheme'));
+
+	if (theme.base) {
+		const base = [lightTheme, darkTheme].find(x => x.id === theme.base);
+		if (base && base.codeHighlighter) theme.codeHighlighter = Object.assign({}, base.codeHighlighter, theme.codeHighlighter);
+	}
+	
+	if (theme.codeHighlighter) {
+		let _res: ThemeRegistration = {};
+		if (theme.codeHighlighter.base === '_none_') {
+			_res = deepClone(theme.codeHighlighter.overrides);
+		} else {
+			const base = await bundledThemesInfo.find(t => t.id === theme.codeHighlighter!.base)?.import() ?? darkPlus;
+			_res = deepMerge(theme.codeHighlighter.overrides ?? {}, 'default' in base ? base.default : base);
+		}
+		if (_res.name == null) {
+			_res.name = theme.id;
+		}
+		_res.type = mode;
+
+		if (getName) {
+			return _res.name;
+		}
+		return _res;
+	}
+
+	if (getName) {
+		return 'dark-plus';
+	}
+	return darkPlus;
+}
+
 export async function getHighlighter(): Promise<Highlighter> {
 	if (!_highlighter) {
 		return await initHighlighter();
@@ -13,11 +55,17 @@ export async function getHighlighter(): Promise<Highlighter> {
 
 export async function initHighlighter() {
 	const aiScriptGrammar = await import('aiscript-vscode/aiscript/syntaxes/aiscript.tmLanguage.json');
-
+	
 	await loadWasm(import('shiki/onig.wasm?init'));
 
+	// テーマの重複を消す
+	const themes = unique([
+		darkPlus,
+		...(await Promise.all([getTheme('light'), getTheme('dark')])),
+	]);
+
 	const highlighter = await getHighlighterCore({
-		themes: [darkPlus],
+		themes,
 		langs: [
 			import('shiki/langs/javascript.mjs'),
 			{
@@ -27,6 +75,20 @@ export async function initHighlighter() {
 		],
 	});
 
+	ColdDeviceStorage.watch('lightTheme', async () => {
+		const newTheme = await getTheme('light');
+		if (newTheme.name && !highlighter.getLoadedThemes().includes(newTheme.name)) {
+			highlighter.loadTheme(newTheme);
+		}
+	});
+
+	ColdDeviceStorage.watch('darkTheme', async () => {
+		const newTheme = await getTheme('dark');
+		if (newTheme.name && !highlighter.getLoadedThemes().includes(newTheme.name)) {
+			highlighter.loadTheme(newTheme);
+		}
+	});
+
 	_highlighter = highlighter;
 
 	return highlighter;
diff --git a/packages/frontend/src/scripts/merge.ts b/packages/frontend/src/scripts/merge.ts
new file mode 100644
index 0000000000..60097051fa
--- /dev/null
+++ b/packages/frontend/src/scripts/merge.ts
@@ -0,0 +1,31 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { deepClone } from './clone.js';
+import type { Cloneable } from './clone.js';
+
+function isPureObject(value: unknown): value is Record<string | number | symbol, unknown> {
+	return typeof value === 'object' && value !== null && !Array.isArray(value);
+}
+
+/**
+ * valueにないキーをdefからもらう(再帰的)\
+ * nullはそのまま、undefinedはdefの値
+ **/
+export function deepMerge<X extends Record<string | number | symbol, unknown>>(value: X, def: X): X {
+	if (isPureObject(value) && isPureObject(def)) {
+		const result = deepClone(value as Cloneable) as X;
+		for (const [k, v] of Object.entries(def) as [keyof X, X[keyof X]][]) {
+			if (!Object.prototype.hasOwnProperty.call(value, k) || value[k] === undefined) {
+				result[k] = v;
+			} else if (isPureObject(v) && isPureObject(result[k])) {
+				const child = deepClone(result[k] as Cloneable) as X[keyof X] & Record<string | number | symbol, unknown>;
+				result[k] = deepMerge<typeof v>(child, v);
+			}
+		}
+		return result;
+	}
+	return value;
+}
diff --git a/packages/frontend/src/scripts/theme.ts b/packages/frontend/src/scripts/theme.ts
index 21ef85fe7a..d3bd9ba4bc 100644
--- a/packages/frontend/src/scripts/theme.ts
+++ b/packages/frontend/src/scripts/theme.ts
@@ -6,6 +6,7 @@
 import { ref } from 'vue';
 import tinycolor from 'tinycolor2';
 import { deepClone } from './clone.js';
+import type { BuiltinTheme } from 'shiki';
 import { globalEvents } from '@/events.js';
 import lightTheme from '@/themes/_light.json5';
 import darkTheme from '@/themes/_dark.json5';
@@ -18,6 +19,13 @@ export type Theme = {
 	desc?: string;
 	base?: 'dark' | 'light';
 	props: Record<string, string>;
+	codeHighlighter?: {
+		base: BuiltinTheme;
+		overrides?: Record<string, any>;
+	} | {
+		base: '_none_';
+		overrides: Record<string, any>;
+	};
 };
 
 export const themeProps = Object.keys(lightTheme.props).filter(key => !key.startsWith('X'));
@@ -53,7 +61,7 @@ export const getBuiltinThemesRef = () => {
 	return builtinThemes;
 };
 
-let timeout = null;
+let timeout: number | null = null;
 
 export function applyTheme(theme: Theme, persist = true) {
 	if (timeout) window.clearTimeout(timeout);
diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts
index afc35bb825..641a506679 100644
--- a/packages/frontend/src/store.ts
+++ b/packages/frontend/src/store.ts
@@ -7,6 +7,7 @@ import { markRaw, ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import { miLocalStorage } from './local-storage.js';
 import type { SoundType } from '@/scripts/sound.js';
+import type { BuiltinTheme as ShikiBuiltinTheme } from 'shiki';
 import { Storage } from '@/pizzax.js';
 import { hemisphere } from '@/scripts/intl-const.js';
 
diff --git a/packages/frontend/src/themes/_dark.json5 b/packages/frontend/src/themes/_dark.json5
index 3f5822977a..c82a956868 100644
--- a/packages/frontend/src/themes/_dark.json5
+++ b/packages/frontend/src/themes/_dark.json5
@@ -94,4 +94,8 @@
 		X16: ':alpha<0.7<@panel',
 		X17: ':alpha<0.8<@bg',
 	},
+
+	codeHighlighter: {
+		base: 'one-dark-pro',
+	},
 }
diff --git a/packages/frontend/src/themes/_light.json5 b/packages/frontend/src/themes/_light.json5
index 6ebfcaafeb..63bc030916 100644
--- a/packages/frontend/src/themes/_light.json5
+++ b/packages/frontend/src/themes/_light.json5
@@ -94,4 +94,8 @@
 		X16: ':alpha<0.7<@panel',
 		X17: ':alpha<0.8<@bg',
 	},
+
+	codeHighlighter: {
+		base: 'catppuccin-latte',
+	},
 }
-- 
GitLab


From edb39a089d6652eb5b0212069f3050823c4c07e7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?=
 <67428053+kakkokari-gtyih@users.noreply.github.com>
Date: Tue, 6 Feb 2024 16:26:03 +0900
Subject: [PATCH 10/40] =?UTF-8?q?enhance(frontend):=20KeepAlive=E3=81=AE?=
 =?UTF-8?q?=E3=83=9A=E3=83=BC=E3=82=B8=E3=82=AD=E3=83=A3=E3=83=83=E3=82=B7?=
 =?UTF-8?q?=E3=83=A5=E3=82=92=E5=89=8A=E9=99=A4=E3=81=A7=E3=81=8D=E3=82=8B?=
 =?UTF-8?q?=E3=82=88=E3=81=86=E3=81=AB=20(#13180)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* enhance(frontend): 内部のページキャッシュを削除できるように

* Update Changelog
---
 CHANGELOG.md                                  |  1 +
 .../src/components/global/RouterView.vue      | 42 ++++++++++++++++---
 packages/frontend/src/events.ts               |  8 +++-
 .../frontend/src/pages/settings/profile.vue   |  5 +++
 4 files changed, 49 insertions(+), 7 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index c39a447c59..0fc99cf5b8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -66,6 +66,7 @@
 - Fix: 画像をクロップするとクロップ後の解像度が異様に低くなる問題の修正
 - Fix: 画像をクロップ時、正常に完了できない問題の修正
 - Fix: キャプションが空の画像をクロップするとキャプションにnullという文字列が入ってしまう問題の修正
+- Fix: プロフィールを編集してもリロードするまで反映されない問題を修正
 
 ### Server
 - Enhance: 連合先のレートリミットに引っかかった際にリトライするようになりました
diff --git a/packages/frontend/src/components/global/RouterView.vue b/packages/frontend/src/components/global/RouterView.vue
index dc7474835d..aeb87e659b 100644
--- a/packages/frontend/src/components/global/RouterView.vue
+++ b/packages/frontend/src/components/global/RouterView.vue
@@ -4,7 +4,10 @@ SPDX-License-Identifier: AGPL-3.0-only
 -->
 
 <template>
-<KeepAlive :max="defaultStore.state.numberOfPageCache">
+<KeepAlive
+	:max="defaultStore.state.numberOfPageCache"
+	:exclude="pageCacheController"
+>
 	<Suspense :timeout="0">
 		<component :is="currentPageComponent" :key="key" v-bind="Object.fromEntries(currentPageProps)"/>
 
@@ -16,9 +19,11 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { inject, onBeforeUnmount, provide, ref, shallowRef } from 'vue';
-import { IRouter, Resolved } from '@/nirax.js';
+import { inject, onBeforeUnmount, provide, ref, shallowRef, computed, nextTick } from 'vue';
+import { IRouter, Resolved, RouteDef } from '@/nirax.js';
 import { defaultStore } from '@/store.js';
+import { globalEvents } from '@/events.js';
+import MkLoadingPage from '@/pages/_loading_.vue';
 
 const props = defineProps<{
 	router?: IRouter;
@@ -46,20 +51,47 @@ function resolveNested(current: Resolved, d = 0): Resolved | null {
 }
 
 const current = resolveNested(router.current)!;
-const currentPageComponent = shallowRef(current.route.component);
+const currentPageComponent = shallowRef('component' in current.route ? current.route.component : MkLoadingPage);
 const currentPageProps = ref(current.props);
 const key = ref(current.route.path + JSON.stringify(Object.fromEntries(current.props)));
 
 function onChange({ resolved, key: newKey }) {
 	const current = resolveNested(resolved);
-	if (current == null) return;
+	if (current == null || 'redirect' in current.route) return;
 	currentPageComponent.value = current.route.component;
 	currentPageProps.value = current.props;
 	key.value = current.route.path + JSON.stringify(Object.fromEntries(current.props));
+
+	nextTick(() => {
+		// ページ遷移完了後に再びキャッシュを有効化
+		if (clearCacheRequested.value) {
+			clearCacheRequested.value = false;
+		}
+	});
 }
 
 router.addListener('change', onChange);
 
+// #region キャッシュ制御
+
+/**
+ * キャッシュクリアが有効になったら、全キャッシュをクリアする
+ * 
+ * keepAlive側にwatcherがあるのですぐ消えるとはおもうけど、念のためページ遷移完了まではキャッシュを無効化しておく。
+ * キャッシュ有効時向けにexcludeを使いたい場合は、pageCacheControllerに並列に突っ込むのではなく、下に追記すること
+ */
+const pageCacheController = computed(() => clearCacheRequested.value ? /.*/ : undefined);
+const clearCacheRequested = ref(false);
+
+globalEvents.on('requestClearPageCache', () => {
+	if (_DEV_) console.log('clear page cache requested');
+	if (!clearCacheRequested.value) {
+		clearCacheRequested.value = true;
+	}
+});
+
+// #endregion
+
 onBeforeUnmount(() => {
 	router.removeListener('change', onChange);
 });
diff --git a/packages/frontend/src/events.ts b/packages/frontend/src/events.ts
index 90d5f6eede..46faec8d3e 100644
--- a/packages/frontend/src/events.ts
+++ b/packages/frontend/src/events.ts
@@ -4,6 +4,10 @@
  */
 
 import { EventEmitter } from 'eventemitter3';
+import * as Misskey from 'misskey-js';
 
-// TODO: 型付け
-export const globalEvents = new EventEmitter();
+export const globalEvents = new EventEmitter<{
+	themeChanged: () => void;
+	clientNotification: (notification: Misskey.entities.Notification) => void;
+	requestClearPageCache: () => void;
+}>();
diff --git a/packages/frontend/src/pages/settings/profile.vue b/packages/frontend/src/pages/settings/profile.vue
index 3e707041eb..aba7d0574a 100644
--- a/packages/frontend/src/pages/settings/profile.vue
+++ b/packages/frontend/src/pages/settings/profile.vue
@@ -125,6 +125,7 @@ import { langmap } from '@/scripts/langmap.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import { claimAchievement } from '@/scripts/achievements.js';
 import { defaultStore } from '@/store.js';
+import { globalEvents } from '@/events.js';
 import MkInfo from '@/components/MkInfo.vue';
 import MkTextarea from '@/components/MkTextarea.vue';
 
@@ -173,6 +174,7 @@ function saveFields() {
 	os.apiWithDialog('i/update', {
 		fields: fields.value.filter(field => field.name !== '' && field.value !== '').map(field => ({ name: field.name, value: field.value })),
 	});
+	globalEvents.emit('requestClearPageCache');
 }
 
 function save() {
@@ -191,6 +193,7 @@ function save() {
 		isBot: !!profile.isBot,
 		isCat: !!profile.isCat,
 	});
+	globalEvents.emit('requestClearPageCache');
 	claimAchievement('profileFilled');
 	if (profile.name === 'syuilo' || profile.name === 'しゅいろ') {
 		claimAchievement('setNameToSyuilo');
@@ -222,6 +225,7 @@ function changeAvatar(ev) {
 		});
 		$i.avatarId = i.avatarId;
 		$i.avatarUrl = i.avatarUrl;
+		globalEvents.emit('requestClearPageCache');
 		claimAchievement('profileFilled');
 	});
 }
@@ -248,6 +252,7 @@ function changeBanner(ev) {
 		});
 		$i.bannerId = i.bannerId;
 		$i.bannerUrl = i.bannerUrl;
+		globalEvents.emit('requestClearPageCache');
 	});
 }
 
-- 
GitLab


From 74245df3829622a3cc0c880ea710b5c1c4f5c584 Mon Sep 17 00:00:00 2001
From: 1Step621 <86859447+1STEP621@users.noreply.github.com>
Date: Tue, 6 Feb 2024 16:45:21 +0900
Subject: [PATCH 11/40] =?UTF-8?q?Enhance(frontend):=20=E3=83=95=E3=83=AD?=
 =?UTF-8?q?=E3=83=B3=E3=83=88=E5=81=B4=E3=81=A7=E3=82=82=E3=83=AA=E3=82=A2?=
 =?UTF-8?q?=E3=82=AF=E3=82=B7=E3=83=A7=E3=83=B3=E6=A8=A9=E9=99=90=E3=81=AE?=
 =?UTF-8?q?=E3=83=81=E3=82=A7=E3=83=83=E3=82=AF=E3=82=92=E3=81=99=E3=82=8B?=
 =?UTF-8?q?=E3=82=88=E3=81=86=E3=81=AB=20(#13134)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* フロント側でもリアクション権限のチェックをするように

* update CHANGELOG.md

* lint fixes

* remove unrelated diffs

* deny -> reject
denyは「(信用しないことを理由に)拒否する」という意味らしい

* allow -> accept

* EmojiSimpleにlocalOnlyを含めるように

* リアクション権限のない絵文字は打てないように(ダイアログを出すのではなく)

* regenerate type definitions

* lint fix

* remove unused locales

* remove unnecessary async
---
 CHANGELOG.md                                   |  4 ++++
 .../src/core/entities/EmojiEntityService.ts    |  1 +
 .../backend/src/models/json-schema/emoji.ts    |  4 ++++
 .../frontend/src/components/MkEmojiPicker.vue  |  4 +++-
 .../src/components/MkEmojiPickerDialog.vue     |  3 +++
 .../src/components/MkEmojiPickerWindow.vue     |  4 +++-
 packages/frontend/src/components/MkNote.vue    |  2 +-
 .../frontend/src/components/MkNoteDetailed.vue |  2 +-
 .../components/MkReactionsViewer.reaction.vue  | 18 +++++++++++++-----
 .../src/pages/settings/emoji-picker.vue        |  2 +-
 .../src/scripts/check-reaction-permissions.ts  |  8 ++++++++
 .../frontend/src/scripts/reaction-picker.ts    |  6 +++++-
 .../misskey-js/src/autogen/apiClientJSDoc.ts   |  2 +-
 packages/misskey-js/src/autogen/endpoint.ts    |  2 +-
 packages/misskey-js/src/autogen/entities.ts    |  2 +-
 packages/misskey-js/src/autogen/models.ts      |  2 +-
 packages/misskey-js/src/autogen/types.ts       |  3 ++-
 17 files changed, 53 insertions(+), 16 deletions(-)
 create mode 100644 packages/frontend/src/scripts/check-reaction-permissions.ts

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0fc99cf5b8..17c06a90e9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -50,6 +50,10 @@
 - Enhance: 絵文字編集ダイアログをモーダルではなくウィンドウで表示するように
 - Enhance: リモートのユーザーはメニューから直接リモートで表示できるように
 - Enhance: コードのシンタックスハイライトにテーマを適用できるように
+- Enhance: リアクション権限がない場合、ハートにフォールバックするのではなく、権限がないことをダイアログで表示するように
+  - リモートのユーザーにローカルのみのカスタム絵文字をリアクションしようとした場合
+  - センシティブなリアクションを認めていないユーザーにセンシティブなカスタム絵文字をリアクションしようとした場合
+  - ロールが必要な絵文字をリアクションしようとした場合
 - Fix: ネイティブモードの絵文字がモノクロにならないように
 - Fix: v2023.12.0で追加された「モデレーターがユーザーのアイコンもしくはバナー画像を未設定状態にできる機能」が管理画面上で正しく表示されていない問題を修正
 - Fix: AiScriptの`readline`関数が不正な値を返すことがある問題のv2023.12.0時点での修正がPlay以外に適用されていないのを修正
diff --git a/packages/backend/src/core/entities/EmojiEntityService.ts b/packages/backend/src/core/entities/EmojiEntityService.ts
index 5b97cfad5e..655c4c5ada 100644
--- a/packages/backend/src/core/entities/EmojiEntityService.ts
+++ b/packages/backend/src/core/entities/EmojiEntityService.ts
@@ -31,6 +31,7 @@ export class EmojiEntityService {
 			category: emoji.category,
 			// || emoji.originalUrl してるのは後方互換性のため(publicUrlはstringなので??はだめ)
 			url: emoji.publicUrl || emoji.originalUrl,
+			localOnly: emoji.localOnly ? true : undefined,
 			isSensitive: emoji.isSensitive ? true : undefined,
 			roleIdsThatCanBeUsedThisEmojiAsReaction: emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.length > 0 ? emoji.roleIdsThatCanBeUsedThisEmojiAsReaction : undefined,
 		};
diff --git a/packages/backend/src/models/json-schema/emoji.ts b/packages/backend/src/models/json-schema/emoji.ts
index 99a58f8773..954eb98d57 100644
--- a/packages/backend/src/models/json-schema/emoji.ts
+++ b/packages/backend/src/models/json-schema/emoji.ts
@@ -27,6 +27,10 @@ export const packedEmojiSimpleSchema = {
 			type: 'string',
 			optional: false, nullable: false,
 		},
+		localOnly: {
+			type: 'boolean',
+			optional: true, nullable: false,
+		},
 		isSensitive: {
 			type: 'boolean',
 			optional: true, nullable: false,
diff --git a/packages/frontend/src/components/MkEmojiPicker.vue b/packages/frontend/src/components/MkEmojiPicker.vue
index 58160cdf5b..f5ab7a2e29 100644
--- a/packages/frontend/src/components/MkEmojiPicker.vue
+++ b/packages/frontend/src/components/MkEmojiPicker.vue
@@ -118,6 +118,7 @@ import { i18n } from '@/i18n.js';
 import { defaultStore } from '@/store.js';
 import { customEmojiCategories, customEmojis, customEmojisMap } from '@/custom-emojis.js';
 import { $i } from '@/account.js';
+import { checkReactionPermissions } from '@/scripts/check-reaction-permissions.js';
 
 const props = withDefaults(defineProps<{
 	showPinned?: boolean;
@@ -126,6 +127,7 @@ const props = withDefaults(defineProps<{
 	asDrawer?: boolean;
 	asWindow?: boolean;
 	asReactionPicker?: boolean; // 今は使われてないが将来的に使いそう
+	targetNote?: Misskey.entities.Note;
 }>(), {
 	showPinned: true,
 });
@@ -340,7 +342,7 @@ watch(q, () => {
 });
 
 function filterAvailable(emoji: Misskey.entities.EmojiSimple): boolean {
-	return ((emoji.roleIdsThatCanBeUsedThisEmojiAsReaction == null || emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.length === 0) || ($i && $i.roles.some(r => emoji.roleIdsThatCanBeUsedThisEmojiAsReaction?.includes(r.id)))) ?? false;
+	return !props.targetNote || checkReactionPermissions($i!, props.targetNote, emoji);
 }
 
 function focus() {
diff --git a/packages/frontend/src/components/MkEmojiPickerDialog.vue b/packages/frontend/src/components/MkEmojiPickerDialog.vue
index 6660dcf1ed..444e8a4cec 100644
--- a/packages/frontend/src/components/MkEmojiPickerDialog.vue
+++ b/packages/frontend/src/components/MkEmojiPickerDialog.vue
@@ -24,6 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 		:showPinned="showPinned"
 		:pinnedEmojis="pinnedEmojis"
 		:asReactionPicker="asReactionPicker"
+		:targetNote="targetNote"
 		:asDrawer="type === 'drawer'"
 		:max-height="maxHeight"
 		@chosen="chosen"
@@ -32,6 +33,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
+import * as Misskey from 'misskey-js';
 import { shallowRef } from 'vue';
 import MkModal from '@/components/MkModal.vue';
 import MkEmojiPicker from '@/components/MkEmojiPicker.vue';
@@ -43,6 +45,7 @@ const props = withDefaults(defineProps<{
 	showPinned?: boolean;
   pinnedEmojis?: string[],
 	asReactionPicker?: boolean;
+	targetNote?: Misskey.entities.Note;
   choseAndClose?: boolean;
 }>(), {
 	manualShowing: null,
diff --git a/packages/frontend/src/components/MkEmojiPickerWindow.vue b/packages/frontend/src/components/MkEmojiPickerWindow.vue
index 1a2c55e785..2a6828f242 100644
--- a/packages/frontend/src/components/MkEmojiPickerWindow.vue
+++ b/packages/frontend/src/components/MkEmojiPickerWindow.vue
@@ -13,12 +13,13 @@ SPDX-License-Identifier: AGPL-3.0-only
 	:front="true"
 	@closed="emit('closed')"
 >
-	<MkEmojiPicker :showPinned="showPinned" :asReactionPicker="asReactionPicker" asWindow :class="$style.picker" @chosen="chosen"/>
+	<MkEmojiPicker :showPinned="showPinned" :asReactionPicker="asReactionPicker" :targetNote="targetNote" asWindow :class="$style.picker" @chosen="chosen"/>
 </MkWindow>
 </template>
 
 <script lang="ts" setup>
 import { } from 'vue';
+import * as Misskey from 'misskey-js';
 import MkWindow from '@/components/MkWindow.vue';
 import MkEmojiPicker from '@/components/MkEmojiPicker.vue';
 
@@ -26,6 +27,7 @@ withDefaults(defineProps<{
 	src?: HTMLElement;
 	showPinned?: boolean;
 	asReactionPicker?: boolean;
+	targetNote?: Misskey.entities.Note
 }>(), {
 	showPinned: true,
 });
diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue
index 735ec86564..27e8bc45b7 100644
--- a/packages/frontend/src/components/MkNote.vue
+++ b/packages/frontend/src/components/MkNote.vue
@@ -385,7 +385,7 @@ function react(viaKeyboard = false): void {
 		}
 	} else {
 		blur();
-		reactionPicker.show(reactButton.value ?? null, reaction => {
+		reactionPicker.show(reactButton.value ?? null, note.value, reaction => {
 			sound.playMisskeySfx('reaction');
 
 			if (props.mock) {
diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue
index dd956b21ad..a53dd90d27 100644
--- a/packages/frontend/src/components/MkNoteDetailed.vue
+++ b/packages/frontend/src/components/MkNoteDetailed.vue
@@ -385,7 +385,7 @@ function react(viaKeyboard = false): void {
 		}
 	} else {
 		blur();
-		reactionPicker.show(reactButton.value ?? null, reaction => {
+		reactionPicker.show(reactButton.value ?? null, note.value, reaction => {
 			sound.playMisskeySfx('reaction');
 
 			misskeyApi('notes/reactions/create', {
diff --git a/packages/frontend/src/components/MkReactionsViewer.reaction.vue b/packages/frontend/src/components/MkReactionsViewer.reaction.vue
index ffbf62a45c..67b448ccb7 100644
--- a/packages/frontend/src/components/MkReactionsViewer.reaction.vue
+++ b/packages/frontend/src/components/MkReactionsViewer.reaction.vue
@@ -32,6 +32,8 @@ import { claimAchievement } from '@/scripts/achievements.js';
 import { defaultStore } from '@/store.js';
 import { i18n } from '@/i18n.js';
 import * as sound from '@/scripts/sound.js';
+import { checkReactionPermissions } from '@/scripts/check-reaction-permissions.js';
+import { customEmojis } from '@/custom-emojis.js';
 
 const props = defineProps<{
 	reaction: string;
@@ -48,13 +50,19 @@ const emit = defineEmits<{
 
 const buttonEl = shallowRef<HTMLElement>();
 
-const canToggle = computed(() => !props.reaction.match(/@\w/) && $i);
+const isCustomEmoji = computed(() => props.reaction.includes(':'));
+const emoji = computed(() => isCustomEmoji.value ? customEmojis.value.find(emoji => emoji.name === props.reaction.replace(/:/g, '').replace(/@\./, '')) : null);
+
+const canToggle = computed(() => {
+	return !props.reaction.match(/@\w/) && $i
+			&& (emoji.value && checkReactionPermissions($i, props.note, emoji.value))
+			|| !isCustomEmoji.value;
+});
+const canGetInfo = computed(() => !props.reaction.match(/@\w/) && props.reaction.includes(':'));
 
 async function toggleReaction() {
 	if (!canToggle.value) return;
 
-	// TODO: その絵文字を使う権限があるかどうか確認
-
 	const oldReaction = props.note.myReaction;
 	if (oldReaction) {
 		const confirm = await os.confirm({
@@ -101,8 +109,8 @@ async function toggleReaction() {
 }
 
 async function menu(ev) {
-	if (!canToggle.value) return;
-	if (!props.reaction.includes(':')) return;
+	if (!canGetInfo.value) return;
+
 	os.popupMenu([{
 		text: i18n.ts.info,
 		icon: 'ti ti-info-circle',
diff --git a/packages/frontend/src/pages/settings/emoji-picker.vue b/packages/frontend/src/pages/settings/emoji-picker.vue
index 61f3332122..1215af35cf 100644
--- a/packages/frontend/src/pages/settings/emoji-picker.vue
+++ b/packages/frontend/src/pages/settings/emoji-picker.vue
@@ -157,7 +157,7 @@ const chooseEmoji = (ev: MouseEvent) => pickEmoji(pinnedEmojis, ev);
 const setDefaultEmoji = () => setDefault(pinnedEmojis);
 
 function previewReaction(ev: MouseEvent) {
-	reactionPicker.show(getHTMLElement(ev));
+	reactionPicker.show(getHTMLElement(ev), null);
 }
 
 function previewEmoji(ev: MouseEvent) {
diff --git a/packages/frontend/src/scripts/check-reaction-permissions.ts b/packages/frontend/src/scripts/check-reaction-permissions.ts
new file mode 100644
index 0000000000..c9d2a5bfc6
--- /dev/null
+++ b/packages/frontend/src/scripts/check-reaction-permissions.ts
@@ -0,0 +1,8 @@
+import * as Misskey from 'misskey-js';
+
+export function checkReactionPermissions(me: Misskey.entities.MeDetailed, note: Misskey.entities.Note, emoji: Misskey.entities.EmojiSimple): boolean {
+  const roleIdsThatCanBeUsedThisEmojiAsReaction = emoji.roleIdsThatCanBeUsedThisEmojiAsReaction ?? [];
+  return !(emoji.localOnly && note.user.host !== me.host)
+      && !(emoji.isSensitive && (note.reactionAcceptance === 'nonSensitiveOnly' || note.reactionAcceptance === 'nonSensitiveOnlyForLocalLikeOnlyForRemote'))
+      && (roleIdsThatCanBeUsedThisEmojiAsReaction.length === 0 || me.roles.some(role => roleIdsThatCanBeUsedThisEmojiAsReaction.includes(role.id)));
+}
diff --git a/packages/frontend/src/scripts/reaction-picker.ts b/packages/frontend/src/scripts/reaction-picker.ts
index a13351b536..193ac838a2 100644
--- a/packages/frontend/src/scripts/reaction-picker.ts
+++ b/packages/frontend/src/scripts/reaction-picker.ts
@@ -3,6 +3,7 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
+import * as Misskey from 'misskey-js';
 import { defineAsyncComponent, Ref, ref } from 'vue';
 import { popup } from '@/os.js';
 import { defaultStore } from '@/store.js';
@@ -10,6 +11,7 @@ import { defaultStore } from '@/store.js';
 class ReactionPicker {
 	private src: Ref<HTMLElement | null> = ref(null);
 	private manualShowing = ref(false);
+	private targetNote: Ref<Misskey.entities.Note | null> = ref(null);
 	private onChosen?: (reaction: string) => void;
 	private onClosed?: () => void;
 
@@ -23,6 +25,7 @@ class ReactionPicker {
 			src: this.src,
 			pinnedEmojis: reactionsRef,
 			asReactionPicker: true,
+			targetNote: this.targetNote,
 			manualShowing: this.manualShowing,
 		}, {
 			done: reaction => {
@@ -38,8 +41,9 @@ class ReactionPicker {
 		});
 	}
 
-	public show(src: HTMLElement | null, onChosen?: ReactionPicker['onChosen'], onClosed?: ReactionPicker['onClosed']) {
+	public show(src: HTMLElement | null, targetNote: Misskey.entities.Note | null, onChosen?: ReactionPicker['onChosen'], onClosed?: ReactionPicker['onClosed']) {
 		this.src.value = src;
+		this.targetNote.value = targetNote;
 		this.manualShowing.value = true;
 		this.onChosen = onChosen;
 		this.onClosed = onClosed;
diff --git a/packages/misskey-js/src/autogen/apiClientJSDoc.ts b/packages/misskey-js/src/autogen/apiClientJSDoc.ts
index 7c727d2878..cb761493a2 100644
--- a/packages/misskey-js/src/autogen/apiClientJSDoc.ts
+++ b/packages/misskey-js/src/autogen/apiClientJSDoc.ts
@@ -1,6 +1,6 @@
 /*
  * version: 2024.2.0-beta.8
- * generatedAt: 2024-02-04T11:51:13.598Z
+ * generatedAt: 2024-02-04T16:51:09.469Z
  */
 
 import type { SwitchCaseResponseType } from '../api.js';
diff --git a/packages/misskey-js/src/autogen/endpoint.ts b/packages/misskey-js/src/autogen/endpoint.ts
index cf9f96b526..10ec645a53 100644
--- a/packages/misskey-js/src/autogen/endpoint.ts
+++ b/packages/misskey-js/src/autogen/endpoint.ts
@@ -1,6 +1,6 @@
 /*
  * version: 2024.2.0-beta.8
- * generatedAt: 2024-02-04T11:51:13.595Z
+ * generatedAt: 2024-02-04T16:51:09.467Z
  */
 
 import type {
diff --git a/packages/misskey-js/src/autogen/entities.ts b/packages/misskey-js/src/autogen/entities.ts
index 2930f2cb7f..04b52e9fb6 100644
--- a/packages/misskey-js/src/autogen/entities.ts
+++ b/packages/misskey-js/src/autogen/entities.ts
@@ -1,6 +1,6 @@
 /*
  * version: 2024.2.0-beta.8
- * generatedAt: 2024-02-04T11:51:13.593Z
+ * generatedAt: 2024-02-04T16:51:09.466Z
  */
 
 import { operations } from './types.js';
diff --git a/packages/misskey-js/src/autogen/models.ts b/packages/misskey-js/src/autogen/models.ts
index f01beaa706..ee14b22f74 100644
--- a/packages/misskey-js/src/autogen/models.ts
+++ b/packages/misskey-js/src/autogen/models.ts
@@ -1,6 +1,6 @@
 /*
  * version: 2024.2.0-beta.8
- * generatedAt: 2024-02-04T11:51:13.592Z
+ * generatedAt: 2024-02-04T16:51:09.465Z
  */
 
 import { components } from './types.js';
diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts
index 77a493fe2e..cce2dadcdb 100644
--- a/packages/misskey-js/src/autogen/types.ts
+++ b/packages/misskey-js/src/autogen/types.ts
@@ -3,7 +3,7 @@
 
 /*
  * version: 2024.2.0-beta.8
- * generatedAt: 2024-02-04T11:51:13.473Z
+ * generatedAt: 2024-02-04T16:51:09.378Z
  */
 
 /**
@@ -4423,6 +4423,7 @@ export type components = {
       name: string;
       category: string | null;
       url: string;
+      localOnly?: boolean;
       isSensitive?: boolean;
       roleIdsThatCanBeUsedThisEmojiAsReaction?: string[];
     };
-- 
GitLab


From 4bf3974abdb41613d33bc2cb1a7f99a24c096b04 Mon Sep 17 00:00:00 2001
From: Soli <personal@str08.net>
Date: Tue, 6 Feb 2024 16:47:17 +0900
Subject: [PATCH 12/40] =?UTF-8?q?fix(frontend):=20=E3=82=A8=E3=83=A9?=
 =?UTF-8?q?=E3=83=BC=E7=94=BB=E5=83=8FURL=E3=82=92=E8=A8=AD=E5=AE=9A?=
 =?UTF-8?q?=E3=81=97=E3=81=9F=E5=BE=8C=E8=A7=A3=E9=99=A4=E3=81=99=E3=82=8B?=
 =?UTF-8?q?=E3=81=A8=EF=BC=8C=E3=83=87=E3=83=95=E3=82=A9=E3=83=AB=E3=83=88?=
 =?UTF-8?q?=E3=81=AE=E7=94=BB=E5=83=8F=E3=81=8C=E8=A1=A8=E7=A4=BA=E3=81=95?=
 =?UTF-8?q?=E3=82=8C=E3=81=AA=E3=81=84=E5=95=8F=E9=A1=8C=E3=81=AE=E4=BF=AE?=
 =?UTF-8?q?=E6=AD=A3=20(#13172)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Co-authored-by: かっこかり <67428053+kakkokari-gtyih@users.noreply.github.com>
---
 CHANGELOG.md                                   | 1 +
 packages/frontend/src/pages/admin/branding.vue | 6 +++---
 2 files changed, 4 insertions(+), 3 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 17c06a90e9..98c565fe90 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -71,6 +71,7 @@
 - Fix: 画像をクロップ時、正常に完了できない問題の修正
 - Fix: キャプションが空の画像をクロップするとキャプションにnullという文字列が入ってしまう問題の修正
 - Fix: プロフィールを編集してもリロードするまで反映されない問題を修正
+- Fix: エラー画像URLを設定した後解除すると,デフォルトの画像が表示されない問題の修正
 
 ### Server
 - Enhance: 連合先のレートリミットに引っかかった際にリトライするようになりました
diff --git a/packages/frontend/src/pages/admin/branding.vue b/packages/frontend/src/pages/admin/branding.vue
index dbbb3941d8..7f01b6021f 100644
--- a/packages/frontend/src/pages/admin/branding.vue
+++ b/packages/frontend/src/pages/admin/branding.vue
@@ -148,9 +148,9 @@ function save() {
 		themeColor: themeColor.value === '' ? null : themeColor.value,
 		defaultLightTheme: defaultLightTheme.value === '' ? null : defaultLightTheme.value,
 		defaultDarkTheme: defaultDarkTheme.value === '' ? null : defaultDarkTheme.value,
-		infoImageUrl: infoImageUrl.value,
-		notFoundImageUrl: notFoundImageUrl.value,
-		serverErrorImageUrl: serverErrorImageUrl.value,
+		infoImageUrl: infoImageUrl.value === '' ? null : infoImageUrl.value,
+		notFoundImageUrl: notFoundImageUrl.value === '' ? null : notFoundImageUrl.value,
+		serverErrorImageUrl: serverErrorImageUrl.value === '' ? null : serverErrorImageUrl.value,
 		manifestJsonOverride: manifestJsonOverride.value === '' ? '{}' : JSON.stringify(JSON5.parse(manifestJsonOverride.value)),
 	}).then(() => {
 		fetchInstance();
-- 
GitLab


From 6829ecb50949249ec49b68d0d2071cb90e05ea7f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?=
 <67428053+kakkokari-gtyih@users.noreply.github.com>
Date: Tue, 6 Feb 2024 16:49:57 +0900
Subject: [PATCH 13/40] =?UTF-8?q?enhance(frontend):=20=E3=83=AA=E3=83=A2?=
 =?UTF-8?q?=E3=83=BC=E3=83=88=E3=81=B8=E3=81=AE=E5=BC=95=E7=94=A8=E3=83=AA?=
 =?UTF-8?q?=E3=83=8E=E3=83=BC=E3=83=88=E3=81=A8=E5=90=8C=E4=B8=80=E3=81=AE?=
 =?UTF-8?q?=E3=83=AA=E3=83=B3=E3=82=AF=E3=81=AB=E3=81=AF=E3=83=AA=E3=83=B3?=
 =?UTF-8?q?=E3=82=AF=E3=83=97=E3=83=AC=E3=83=93=E3=83=A5=E3=83=BC=E3=82=92?=
 =?UTF-8?q?=E8=A1=A8=E7=A4=BA=E3=81=97=E3=81=AA=E3=81=84=E3=82=88=E3=81=86?=
 =?UTF-8?q?=E3=81=AB=20(#13178)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* enhance(frontend): リモートへの引用リノートと同一のリンクにはリンクプレビューを表示しないように

* Update Changelog

---------

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
---
 CHANGELOG.md                                        | 1 +
 packages/frontend/src/components/MkNote.vue         | 2 +-
 packages/frontend/src/components/MkNoteDetailed.vue | 2 +-
 3 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 98c565fe90..1ba032b3f9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -49,6 +49,7 @@
 - Enhance: MFMの属性でオートコンプリートが使用できるように #12735
 - Enhance: 絵文字編集ダイアログをモーダルではなくウィンドウで表示するように
 - Enhance: リモートのユーザーはメニューから直接リモートで表示できるように
+- Enhance: リモートへの引用リノートと同一のリンクにはリンクプレビューを表示しないように
 - Enhance: コードのシンタックスハイライトにテーマを適用できるように
 - Enhance: リアクション権限がない場合、ハートにフォールバックするのではなく、権限がないことをダイアログで表示するように
   - リモートのユーザーにローカルのみのカスタム絵文字をリアクションしようとした場合
diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue
index 27e8bc45b7..ac5abc3f66 100644
--- a/packages/frontend/src/components/MkNote.vue
+++ b/packages/frontend/src/components/MkNote.vue
@@ -253,7 +253,7 @@ const appearNote = computed(() => isRenote ? note.value.renote as Misskey.entiti
 const isMyRenote = $i && ($i.id === note.value.userId);
 const showContent = ref(false);
 const parsed = computed(() => appearNote.value.text ? mfm.parse(appearNote.value.text) : null);
-const urls = computed(() => parsed.value ? extractUrlFromMfm(parsed.value) : null);
+const urls = computed(() => parsed.value ? extractUrlFromMfm(parsed.value).filter((url) => appearNote.value.renote?.url !== url && appearNote.value.renote?.uri !== url) : null);
 const isLong = shouldCollapsed(appearNote.value, urls.value ?? []);
 const collapsed = ref(appearNote.value.cw == null && isLong);
 const isDeleted = ref(false);
diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue
index a53dd90d27..89ab88ac36 100644
--- a/packages/frontend/src/components/MkNoteDetailed.vue
+++ b/packages/frontend/src/components/MkNoteDetailed.vue
@@ -277,7 +277,7 @@ const muted = ref($i ? checkWordMute(appearNote.value, $i, $i.mutedWords) : fals
 const translation = ref<Misskey.entities.NotesTranslateResponse | null>(null);
 const translating = ref(false);
 const parsed = appearNote.value.text ? mfm.parse(appearNote.value.text) : null;
-const urls = parsed ? extractUrlFromMfm(parsed) : null;
+const urls = parsed ? extractUrlFromMfm(parsed).filter((url) => appearNote.value.renote?.url !== url && appearNote.value.renote?.uri !== url) : null;
 const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && appearNote.value.user.instance);
 const conversation = ref<Misskey.entities.Note[]>([]);
 const replies = ref<Misskey.entities.Note[]>([]);
-- 
GitLab


From d6cb68b0910cfb53e469d5cf53714f71a1f06ba6 Mon Sep 17 00:00:00 2001
From: MeiMei <30769358+mei23@users.noreply.github.com>
Date: Tue, 6 Feb 2024 16:51:14 +0900
Subject: [PATCH 14/40] =?UTF-8?q?AP=20Key=20=E3=81=AE=20JSON-LD=20?=
 =?UTF-8?q?=E8=A1=A8=E7=8F=BE=E3=82=92=E4=BF=AE=E6=AD=A3=20(#13170)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 packages/backend/src/core/activitypub/ApRendererService.ts | 1 +
 1 file changed, 1 insertion(+)

diff --git a/packages/backend/src/core/activitypub/ApRendererService.ts b/packages/backend/src/core/activitypub/ApRendererService.ts
index 1891c341e4..530e4a3c33 100644
--- a/packages/backend/src/core/activitypub/ApRendererService.ts
+++ b/packages/backend/src/core/activitypub/ApRendererService.ts
@@ -625,6 +625,7 @@ export class ApRendererService {
 				'https://www.w3.org/ns/activitystreams',
 				'https://w3id.org/security/v1',
 				{
+					Key: 'sec:Key',
 					// as non-standards
 					manuallyApprovesFollowers: 'as:manuallyApprovesFollowers',
 					sensitive: 'as:sensitive',
-- 
GitLab


From 653ca7e7089075909c0981c484aef8305a3bd79f Mon Sep 17 00:00:00 2001
From: 1Step621 <86859447+1STEP621@users.noreply.github.com>
Date: Tue, 6 Feb 2024 16:55:21 +0900
Subject: [PATCH 15/40] =?UTF-8?q?CHANGELOG=E3=82=92=E4=BF=AE=E6=AD=A3=20(#?=
 =?UTF-8?q?13181)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 CHANGELOG.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1ba032b3f9..63bf42c72f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -51,7 +51,7 @@
 - Enhance: リモートのユーザーはメニューから直接リモートで表示できるように
 - Enhance: リモートへの引用リノートと同一のリンクにはリンクプレビューを表示しないように
 - Enhance: コードのシンタックスハイライトにテーマを適用できるように
-- Enhance: リアクション権限がない場合、ハートにフォールバックするのではなく、権限がないことをダイアログで表示するように
+- Enhance: リアクション権限がない場合、ハートにフォールバックするのではなくリアクションピッカーなどから打てないように
   - リモートのユーザーにローカルのみのカスタム絵文字をリアクションしようとした場合
   - センシティブなリアクションを認めていないユーザーにセンシティブなカスタム絵文字をリアクションしようとした場合
   - ロールが必要な絵文字をリアクションしようとした場合
-- 
GitLab


From 93e711d8a9fe1dae4c1eaed7f509d643398f8cd8 Mon Sep 17 00:00:00 2001
From: Kisaragi <48310258+KisaragiEffective@users.noreply.github.com>
Date: Tue, 6 Feb 2024 17:17:52 +0900
Subject: [PATCH 16/40] chore(frontend): reword possible typo (#13182)

---
 packages/frontend/src/pizzax.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/frontend/src/pizzax.ts b/packages/frontend/src/pizzax.ts
index 043b6efd73..199addaefd 100644
--- a/packages/frontend/src/pizzax.ts
+++ b/packages/frontend/src/pizzax.ts
@@ -239,7 +239,7 @@ export class Storage<T extends StateDef> {
 
 	/**
 	 * 特定のキーの、簡易的なgetter/setterを作ります
-	 * 主にvue場で設定コントロールのmodelとして使う用
+	 * 主にvue上で設定コントロールのmodelとして使う用
 	 */
 	public makeGetterSetter<K extends keyof T>(key: K, getter?: (v: T[K]) => unknown, setter?: (v: unknown) => T[K]): {
 		get: () => T[K]['default'];
-- 
GitLab


From 500ea793b3ce6bc38ba6887d7446f91bba7dda9a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?=
 <67428053+kakkokari-gtyih@users.noreply.github.com>
Date: Tue, 6 Feb 2024 19:24:52 +0900
Subject: [PATCH 17/40] =?UTF-8?q?fix(bubble-game):=20=E5=85=B1=E6=9C=89?=
 =?UTF-8?q?=E7=94=A8=E7=94=BB=E5=83=8F=E3=81=AE=E3=82=B3=E3=83=A1=E3=83=B3?=
 =?UTF-8?q?=E3=83=88=E3=81=ABnull=E3=81=8C=E5=85=A5=E3=82=8B=E5=95=8F?=
 =?UTF-8?q?=E9=A1=8C=E3=82=92=E4=BF=AE=E6=AD=A3=20(#13183)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 packages/frontend/src/components/MkLaunchPad.vue     | 1 +
 packages/frontend/src/pages/drop-and-fusion.game.vue | 1 -
 2 files changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/frontend/src/components/MkLaunchPad.vue b/packages/frontend/src/components/MkLaunchPad.vue
index 21a382f2f1..1ec1f36770 100644
--- a/packages/frontend/src/components/MkLaunchPad.vue
+++ b/packages/frontend/src/components/MkLaunchPad.vue
@@ -119,6 +119,7 @@ function close() {
 				margin-top: 12px;
 				font-size: 0.8em;
 				line-height: 1.5em;
+				text-align: center;
 			}
 
 			> .indicatorWithValue {
diff --git a/packages/frontend/src/pages/drop-and-fusion.game.vue b/packages/frontend/src/pages/drop-and-fusion.game.vue
index 51819fafd0..2e1098a516 100644
--- a/packages/frontend/src/pages/drop-and-fusion.game.vue
+++ b/packages/frontend/src/pages/drop-and-fusion.game.vue
@@ -893,7 +893,6 @@ function getGameImageDriveFile() {
 				formData.append('file', blob);
 				formData.append('name', `bubble-game-${Date.now()}.png`);
 				formData.append('isSensitive', 'false');
-				formData.append('comment', 'null');
 				formData.append('i', $i.token);
 				if (defaultStore.state.uploadFolder) {
 					formData.append('folderId', defaultStore.state.uploadFolder);
-- 
GitLab


From c81b61eb2e4e3a16f0039ebfa9c174ee216ccafb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E3=81=8A=E3=81=95=E3=82=80=E3=81=AE=E3=81=B2=E3=81=A8?=
 <46447427+samunohito@users.noreply.github.com>
Date: Tue, 6 Feb 2024 21:03:29 +0900
Subject: [PATCH 18/40] =?UTF-8?q?fix(misskey-js):=20=E8=87=AA=E5=8B=95?=
 =?UTF-8?q?=E7=94=9F=E6=88=90=E7=89=A9=E3=81=AE=E5=86=92=E9=A0=AD=E3=81=8B?=
 =?UTF-8?q?=E3=82=89=E3=83=90=E3=83=BC=E3=82=B8=E3=83=A7=E3=83=B3=E3=81=A8?=
 =?UTF-8?q?=E6=97=A5=E4=BB=98=E3=82=92=E5=89=8A=E9=99=A4=20(#13185)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../misskey-js/generator/src/generator.ts     | 30 -------------------
 .../misskey-js/src/autogen/apiClientJSDoc.ts  |  5 ----
 packages/misskey-js/src/autogen/endpoint.ts   |  5 ----
 packages/misskey-js/src/autogen/entities.ts   |  5 ----
 packages/misskey-js/src/autogen/models.ts     |  5 ----
 packages/misskey-js/src/autogen/types.ts      |  5 ----
 6 files changed, 55 deletions(-)

diff --git a/packages/misskey-js/generator/src/generator.ts b/packages/misskey-js/generator/src/generator.ts
index 7e72359167..f091e599a9 100644
--- a/packages/misskey-js/generator/src/generator.ts
+++ b/packages/misskey-js/generator/src/generator.ts
@@ -4,22 +4,6 @@ import { toPascal } from 'ts-case-convert';
 import OpenAPIParser from '@readme/openapi-parser';
 import openapiTS from 'openapi-typescript';
 
-function generateVersionHeaderComment(openApiDocs: OpenAPIV3_1.Document): string {
-	const contents = {
-		version: openApiDocs.info.version,
-		generatedAt: new Date().toISOString(),
-	};
-
-	const lines: string[] = [];
-	lines.push('/*');
-	for (const [key, value] of Object.entries(contents)) {
-		lines.push(` * ${key}: ${value}`);
-	}
-	lines.push(' */');
-
-	return lines.join('\n');
-}
-
 async function generateBaseTypes(
 	openApiDocs: OpenAPIV3_1.Document,
 	openApiJsonPath: string,
@@ -36,9 +20,6 @@ async function generateBaseTypes(
 	}
 	lines.push('');
 
-	lines.push(generateVersionHeaderComment(openApiDocs));
-	lines.push('');
-
 	const generatedTypes = await openapiTS(openApiJsonPath, { exportType: true });
 	lines.push(generatedTypes);
 	lines.push('');
@@ -59,8 +40,6 @@ async function generateSchemaEntities(
 	const schemaNames = Object.keys(schemas);
 	const typeAliasLines: string[] = [];
 
-	typeAliasLines.push(generateVersionHeaderComment(openApiDocs));
-	typeAliasLines.push('');
 	typeAliasLines.push(`import { components } from '${toImportPath(typeFileName)}';`);
 	typeAliasLines.push(
 		...schemaNames.map(it => `export type ${it} = components['schemas']['${it}'];`),
@@ -119,9 +98,6 @@ async function generateEndpoints(
 
 	const entitiesOutputLine: string[] = [];
 
-	entitiesOutputLine.push(generateVersionHeaderComment(openApiDocs));
-	entitiesOutputLine.push('');
-
 	entitiesOutputLine.push(`import { operations } from '${toImportPath(typeFileName)}';`);
 	entitiesOutputLine.push('');
 
@@ -139,9 +115,6 @@ async function generateEndpoints(
 
 	const endpointOutputLine: string[] = [];
 
-	endpointOutputLine.push(generateVersionHeaderComment(openApiDocs));
-	endpointOutputLine.push('');
-
 	endpointOutputLine.push('import type {');
 	endpointOutputLine.push(
 		...[emptyRequest, emptyResponse, ...entities].map(it => '\t' + it.generateName() + ','),
@@ -187,9 +160,6 @@ async function generateApiClientJSDoc(
 
 	const endpointOutputLine: string[] = [];
 
-	endpointOutputLine.push(generateVersionHeaderComment(openApiDocs));
-	endpointOutputLine.push('');
-
 	endpointOutputLine.push(`import type { SwitchCaseResponseType } from '${toImportPath(apiClientFileName)}';`);
 	endpointOutputLine.push(`import type { Endpoints } from '${toImportPath(endpointsFileName)}';`);
 	endpointOutputLine.push('');
diff --git a/packages/misskey-js/src/autogen/apiClientJSDoc.ts b/packages/misskey-js/src/autogen/apiClientJSDoc.ts
index cb761493a2..d27413810c 100644
--- a/packages/misskey-js/src/autogen/apiClientJSDoc.ts
+++ b/packages/misskey-js/src/autogen/apiClientJSDoc.ts
@@ -1,8 +1,3 @@
-/*
- * version: 2024.2.0-beta.8
- * generatedAt: 2024-02-04T16:51:09.469Z
- */
-
 import type { SwitchCaseResponseType } from '../api.js';
 import type { Endpoints } from './endpoint.js';
 
diff --git a/packages/misskey-js/src/autogen/endpoint.ts b/packages/misskey-js/src/autogen/endpoint.ts
index 10ec645a53..7e5ca9b9ce 100644
--- a/packages/misskey-js/src/autogen/endpoint.ts
+++ b/packages/misskey-js/src/autogen/endpoint.ts
@@ -1,8 +1,3 @@
-/*
- * version: 2024.2.0-beta.8
- * generatedAt: 2024-02-04T16:51:09.467Z
- */
-
 import type {
 	EmptyRequest,
 	EmptyResponse,
diff --git a/packages/misskey-js/src/autogen/entities.ts b/packages/misskey-js/src/autogen/entities.ts
index 04b52e9fb6..9363ef7bcf 100644
--- a/packages/misskey-js/src/autogen/entities.ts
+++ b/packages/misskey-js/src/autogen/entities.ts
@@ -1,8 +1,3 @@
-/*
- * version: 2024.2.0-beta.8
- * generatedAt: 2024-02-04T16:51:09.466Z
- */
-
 import { operations } from './types.js';
 
 export type EmptyRequest = Record<string, unknown> | undefined;
diff --git a/packages/misskey-js/src/autogen/models.ts b/packages/misskey-js/src/autogen/models.ts
index ee14b22f74..8d594e61bb 100644
--- a/packages/misskey-js/src/autogen/models.ts
+++ b/packages/misskey-js/src/autogen/models.ts
@@ -1,8 +1,3 @@
-/*
- * version: 2024.2.0-beta.8
- * generatedAt: 2024-02-04T16:51:09.465Z
- */
-
 import { components } from './types.js';
 export type Error = components['schemas']['Error'];
 export type UserLite = components['schemas']['UserLite'];
diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts
index cce2dadcdb..b7d65406cb 100644
--- a/packages/misskey-js/src/autogen/types.ts
+++ b/packages/misskey-js/src/autogen/types.ts
@@ -1,11 +1,6 @@
 /* eslint @typescript-eslint/naming-convention: 0 */
 /* eslint @typescript-eslint/no-explicit-any: 0 */
 
-/*
- * version: 2024.2.0-beta.8
- * generatedAt: 2024-02-04T16:51:09.378Z
- */
-
 /**
  * This file was auto-generated by openapi-typescript.
  * Do not make direct changes to the file.
-- 
GitLab


From 6a94a52131840728219cec1a91357970837ee85f Mon Sep 17 00:00:00 2001
From: Marie <marie@kaifa.ch>
Date: Tue, 6 Feb 2024 21:23:37 +0100
Subject: [PATCH 19/40] merge: upstream

---
 CHANGELOG.md                                  |  11 +
 locales/ca-ES.yml                             | 191 ++++++++++++++++++
 locales/it-IT.yml                             |  60 +++++-
 locales/ko-GS.yml                             |  72 ++++++-
 locales/ko-KR.yml                             |   8 +-
 package.json                                  |   2 +-
 packages/backend/src/core/EmailService.ts     |   2 +
 .../backend/src/core/InstanceActorService.ts  |  10 +-
 packages/backend/src/core/SignupService.ts    |   4 +-
 .../src/core/activitypub/ApRendererService.ts |   1 +
 .../activitypub/models/ApPersonService.ts     |  43 ++--
 .../src/core/entities/EmojiEntityService.ts   |   1 +
 .../backend/src/models/json-schema/emoji.ts   |   4 +
 .../api/endpoints/admin/accounts/create.ts    |   8 +-
 .../backend/src/server/api/endpoints/meta.ts  |  11 +-
 .../frontend/src/components/MkCode.core.vue   |  56 ++++-
 packages/frontend/src/components/MkCode.vue   |   8 +-
 .../frontend/src/components/MkCodeEditor.vue  |   5 +-
 .../frontend/src/components/MkCodeInline.vue  |   3 +-
 .../src/components/MkCropperDialog.vue        |  17 +-
 .../frontend/src/components/MkEmojiPicker.vue |   4 +-
 .../src/components/MkEmojiPickerDialog.vue    |   3 +
 .../src/components/MkEmojiPickerWindow.vue    |   4 +-
 .../frontend/src/components/MkLaunchPad.vue   |   1 +
 packages/frontend/src/components/MkNote.vue   |   6 +-
 .../src/components/MkNoteDetailed.vue         |   6 +-
 .../frontend/src/components/MkPageWindow.vue  |   2 +-
 .../components/MkReactionsViewer.reaction.vue |  18 +-
 packages/frontend/src/components/MkSelect.vue |   7 +-
 packages/frontend/src/components/SkNote.vue   |   4 +-
 .../src/components/SkNoteDetailed.vue         |   4 +-
 .../src/components/global/RouterView.vue      |  42 +++-
 packages/frontend/src/events.ts               |   8 +-
 packages/frontend/src/os.ts                   |  18 +-
 .../src/pages/admin/bot-protection.vue        |   6 +-
 .../frontend/src/pages/admin/branding.vue     |   6 +-
 .../frontend/src/pages/admin/security.vue     |   6 +-
 .../src/pages/drop-and-fusion.game.vue        |   1 -
 packages/frontend/src/pages/instance-info.vue |   2 +-
 packages/frontend/src/pages/reversi/index.vue |  16 +-
 .../src/pages/settings/emoji-picker.vue       |   2 +-
 .../frontend/src/pages/settings/profile.vue   |  12 +-
 .../frontend/src/pages/settings/theme.vue     |  15 +-
 packages/frontend/src/pizzax.ts               |  25 +--
 packages/frontend/src/router/main.ts          |   4 +
 .../src/scripts/check-reaction-permissions.ts |   8 +
 packages/frontend/src/scripts/clone.ts        |   4 +-
 .../frontend/src/scripts/code-highlighter.ts  |  68 ++++++-
 packages/frontend/src/scripts/merge.ts        |  31 +++
 .../frontend/src/scripts/reaction-picker.ts   |   6 +-
 packages/frontend/src/scripts/theme.ts        |  10 +-
 packages/frontend/src/store.ts                |   1 +
 packages/frontend/src/themes/_dark.json5      |   4 +
 packages/frontend/src/themes/_light.json5     |   4 +
 .../misskey-js/generator/src/generator.ts     |  30 ---
 .../misskey-js/src/autogen/apiClientJSDoc.ts  |   5 -
 packages/misskey-js/src/autogen/endpoint.ts   |   5 -
 packages/misskey-js/src/autogen/entities.ts   |   5 -
 packages/misskey-js/src/autogen/models.ts     |   5 -
 packages/misskey-js/src/autogen/types.ts      |   6 +-
 60 files changed, 741 insertions(+), 190 deletions(-)
 create mode 100644 packages/frontend/src/scripts/check-reaction-permissions.ts
 create mode 100644 packages/frontend/src/scripts/merge.ts

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8f6cf2071b..63bf42c72f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -49,6 +49,12 @@
 - Enhance: MFMの属性でオートコンプリートが使用できるように #12735
 - Enhance: 絵文字編集ダイアログをモーダルではなくウィンドウで表示するように
 - Enhance: リモートのユーザーはメニューから直接リモートで表示できるように
+- Enhance: リモートへの引用リノートと同一のリンクにはリンクプレビューを表示しないように
+- Enhance: コードのシンタックスハイライトにテーマを適用できるように
+- Enhance: リアクション権限がない場合、ハートにフォールバックするのではなくリアクションピッカーなどから打てないように
+  - リモートのユーザーにローカルのみのカスタム絵文字をリアクションしようとした場合
+  - センシティブなリアクションを認めていないユーザーにセンシティブなカスタム絵文字をリアクションしようとした場合
+  - ロールが必要な絵文字をリアクションしようとした場合
 - Fix: ネイティブモードの絵文字がモノクロにならないように
 - Fix: v2023.12.0で追加された「モデレーターがユーザーのアイコンもしくはバナー画像を未設定状態にできる機能」が管理画面上で正しく表示されていない問題を修正
 - Fix: AiScriptの`readline`関数が不正な値を返すことがある問題のv2023.12.0時点での修正がPlay以外に適用されていないのを修正
@@ -62,6 +68,11 @@
 - Enhance: ページ遷移時にPlayerを閉じるように
 - Fix: iOSで大きな画像を変換してアップロードできない問題を修正
 - Fix: 「アニメーション画像を再生しない」もしくは「データセーバー(アイコン)」を有効にしていても、アイコンデコレーションのアニメーションが停止されない問題を修正
+- Fix: 画像をクロップするとクロップ後の解像度が異様に低くなる問題の修正
+- Fix: 画像をクロップ時、正常に完了できない問題の修正
+- Fix: キャプションが空の画像をクロップするとキャプションにnullという文字列が入ってしまう問題の修正
+- Fix: プロフィールを編集してもリロードするまで反映されない問題を修正
+- Fix: エラー画像URLを設定した後解除すると,デフォルトの画像が表示されない問題の修正
 
 ### Server
 - Enhance: 連合先のレートリミットに引っかかった際にリトライするようになりました
diff --git a/locales/ca-ES.yml b/locales/ca-ES.yml
index eeafcafed2..02e1d3e362 100644
--- a/locales/ca-ES.yml
+++ b/locales/ca-ES.yml
@@ -1157,6 +1157,12 @@ edited: "Editat"
 notificationRecieveConfig: "Paràmetres de notificacions"
 mutualFollow: "Seguidor mutu"
 fileAttachedOnly: "Només notes amb adjunts"
+showRepliesToOthersInTimeline: "Mostrar les respostes a altres a la línia de temps"
+hideRepliesToOthersInTimeline: "Amagar les respostes a altres a la línia de temps"
+showRepliesToOthersInTimelineAll: "Mostrar les respostes a altres a usuaris que segueixes a la línia de temps"
+hideRepliesToOthersInTimelineAll: "Ocultar les teves respostes a tots els usuaris que segueixes a la línia de temps"
+confirmShowRepliesAll: "Aquesta opció no té marxa enrere. Vols mostrar les teves respostes a tots els que segueixes a la teva línia de temps?"
+confirmHideRepliesAll: "Aquesta opció no té marxa enrere. Vols ocultar les teves respostes a tots els usuaris que segueixes a la línia de temps?"
 externalServices: "Serveis externs"
 impressum: "Impressum"
 impressumUrl: "Adreça URL impressum"
@@ -1187,7 +1193,25 @@ seasonalScreenEffect: "Efectes de pantalla segons les estacions"
 decorate: "Decorar"
 addMfmFunction: "Afegeix funcions MFM"
 enableQuickAddMfmFunction: "Activar accés ràpid per afegir funcions MFM"
+bubbleGame: "Bubble Game"
+sfx: "Efectes de so"
+soundWillBePlayed: "Es reproduiran efectes de so"
+showReplay: "Veure reproducció"
+replay: "Reproduir"
+replaying: "Reproduint"
+ranking: "Classificació"
 lastNDays: "Últims {n} dies"
+backToTitle: "Torna al títol"
+hemisphere: "Geolocalització"
+withSensitive: "Incloure notes amb fitxers sensibles"
+userSaysSomethingSensitive: "La publicació de {name} conte material sensible"
+enableHorizontalSwipe: "Lliscar per canviar de pestanya"
+_bubbleGame:
+  howToPlay: "Com es juga"
+  _howToPlay:
+    section1: "Ajusta la posició i deixa caure l'objecte dintre la caixa."
+    section2: "Quan dos objectes del mateix tipus es toquen, canviaran en un objecte diferent i guanyares punts."
+    section3: "El joc s'acabarà quan els objectes sobresurtin de la caixa. Intenta aconseguir la puntuació més gran possible fusionant objectes mentre impedeixes que sobresurtin de la caixa!"
 _announcement:
   forExistingUsers: "Anunci per usuaris registrats"
   forExistingUsersDescription: "Aquest avís només es mostrarà als usuaris existents fins al moment de la publicació. Si no també es mostrarà als usuaris que es registrin després de la publicació."
@@ -1209,8 +1233,32 @@ _initialAccountSetting:
   privacySetting: "Configuració de seguretat"
   theseSettingsCanEditLater: "Aquests ajustos es poden canviar més tard."
   youCanEditMoreSettingsInSettingsPageLater: "A més d'això, es poden fer diferents configuracions a través de la pàgina de configuració. Assegureu-vos de comprovar-ho més tard."
+  followUsers: "Prova de seguir usuaris que t'interessin per construir la teva línia de temps."
+  pushNotificationDescription: "Activant les notificacions emergents et permetrà rebre notificacions de {name} directament al teu dispositiu."
+  initialAccountSettingCompleted: "Configuració del perfil completada!"
+  haveFun: "Disfruta {name}!"
+  youCanContinueTutorial: "Pots continuar amb un tutorial per aprendre a Fer servir {name} (MissKey) o tu pots estalviar i començar a fer-lo servir ja."
+  startTutorial: "Començar el tutorial"
+  skipAreYouSure: "Et vols saltar la configuració del perfil?"
+  laterAreYouSure: "Vols continuar la configuració del perfil més tard?"
 _initialTutorial:
+  launchTutorial: "Començar tutorial"
+  title: "Tutorial"
+  wellDone: "Ben fet!"
+  skipAreYouSure: "Sortir del tutorial?"
+  _landing:
+    title: "Benvingut al tutorial"
+    description: "Aquí aprendràs el bàsic per poder fer servir Misskey i les seves característiques."
+  _note:
+    title: "Què és una Nota?"
+    description: "Les publicacions a Misskey es diuen 'Notes'. Les Notes s'ordenen cronològicament a la línia de temps i s'actualitzen de forma automàtica."
+    reply: "Fes clic en aquest botó per contestar a un missatge. També és possible contestar a una contestació, continuant la conversació en forma de fil."
+    renote: "Pots compartir una Nota a la teva pròpia línia de temps. Inclús pots citar-les amb els teus comentaris."
+    reaction: "Pots afegir reaccions a les Notes. Entrarem més en detall a la pròxima pàgina."
+    menu: "Pots veure els detalls de les Notes, copiar enllaços i fer diferents accions."
   _reaction:
+    title: "Què són les Reaccions?"
+    description: "Es poden reaccionar a les Notes amb diferents emoticones. Les reaccions et permeten expressar matisos que hi són més enllà d'un simple m'agrada."
     letsTryReacting: "Es poden afegir reaccions fent clic al botó '+'. Prova reaccionant a aquesta nota!"
     reactToContinue: "Afegeix una reacció per continuar."
     reactNotification: "Rebràs notificacions en temps real quan un usuari reaccioni a les teves notes."
@@ -1272,9 +1320,75 @@ _serverSettings:
   shortName: "Nom curt"
   shortNameDescription: "Una abreviatura del nom de la instància que es poguí mostrar en cas que el nom oficial sigui massa llarg"
   fanoutTimelineDescription: "Quan es troba activat millora bastant el rendiment quan es recuperen les línies de temps i redueix la carrega de la base de dades. Com a contrapunt, l'ús de memòria de Redis es veurà incrementada. Considera d'estabilitat aquesta opció en cas de tenir un servidor amb poca memòria o si tens problemes de inestabilitat."
+  fanoutTimelineDbFallback: "Carregar de la base de dades"
+  fanoutTimelineDbFallbackDescription: "Quan s'activa, la línia de temps fa servir la base de dades per consultes adicionals si la línia de temps no es troba a la memòria cau. Si és desactiva la càrrega del servidor és veure reduïda, però també és reduirà el nombre de línies de temps que és poden obtenir."
+_accountMigration:
+  moveFrom: "Migrar un altre compte a aquest"
+  moveFromSub: "Crear un àlies per un altre compte"
+  moveFromLabel: "Compte original #{n}"
+  moveFromDescription: "Has de crear un àlies del compte que vols migrar en aquest compte.\nFes servir aquest format per posar el compte que vols migrar: @nomusuari@servidor.exemple.com\nPer esborrar l'àlies deixa el camp en blanc (no és recomanable de fer)"
+  moveTo: "Migrar aquest compte a un altre"
+  moveToLabel: "Compte al qual es vol migrar:"
+  moveCannotBeUndone: "Les migracions dels comptes no es poden desfer."
+  moveAccountDescription: "Això migrarà la teva compte a un altre diferent.\n ・Els seguidors d'aquest compte és passaran al compte nou de forma automàtica\n ・Es deixaran de seguir a tots els usuaris que es segueixen actualment en aquest compte\n ・No es poden crear notes noves, etc. en aquest compte\n\nSi bé la migració de seguidors es automàtica, has de preparar alguns pasos manualment per migrar la llista d'usuaris que segueixes. Per fer això has d'exportar els seguidors que després importaraes al compte nou mitjançant el menú de configuració. El mateix procediment s'ha de seguir per less teves llistes i els teus usuaris silenciats i bloquejats.\n\n(Aquesta explicació s'aplica a Misskey v13.12.0 i posteriors. Altres aplicacions, com Mastodon, poden funcionar diferent.)"
+  moveAccountHowTo: "Per fer la migració, primer has de crear un àlies per aquest compte al compte al qual vols migrar.\nDesprés de crear l'àlies, introdueix el compte al qual vols migrar amb el format següent: @nomusuari@servidor.exemple.com"
+  startMigration: "Migrar"
+  migrationConfirm: "Vols migrar aquest compte a {account}? Una vegada comenci la migració no es podrà parar O fer marxa enrere i no podràs tornar a fer servir aquest compte mai més."
+  movedAndCannotBeUndone: "Aquest compte ha migrat.\nLes migracions no es poden desfer."
+  postMigrationNote: "Aquest compte deixarà de seguir tots els comptes que segueix 24 hores després de germinar la migració.\nEl nombre de seguidors i seguits passarà a ser de zero. Per evitar que els teus seguidors no puguin veure les publicacions marcades com a només seguidors continuaren seguint aquest compte."
+  movedTo: "Nou compte:"
 _achievements:
+  earnedAt: "Desbloquejat el"
   _types:
+    _notes1:
+      title: "Aquí, configurant el meu msky"
+      description: "Publica la teva primera Nota"
+      flavor: "Passa-t'ho bé fent servir Miskey!"
+    _notes10:
+      title: "Algunes notes"
+      description: "Publica 10 notes"
+    _notes100:
+      title: "Un piló de notes"
+      description: "Publica 100 notes"
+    _notes500:
+      title: "Cobert de notes"
+      description: "Publica 500 notes"
+    _notes1000:
+      title: "Un piló de notes"
+      description: "1 000 notes publicades"
+    _notes5000:
+      title: "Desbordament de notes"
+      description: "5 000 notes publicades"
+    _notes10000:
+      title: "Supernota"
+      description: "10 000 notes publicades"
+    _notes20000:
+      title: "Necessito... Més... Notes!"
+      description: "20 000 notes publicades"
+    _notes30000:
+      title: "Notes notes notes!"
+      description: "30 000 notes publicades"
+    _notes40000:
+      title: "Fàbrica de notes"
+      description: "40 000 notes publicades"
+    _notes50000:
+      title: "Planeta de notes"
+      description: "50 000 notes publicades"
+    _notes60000:
+      title: "Quàsar de notes"
+      description: "60 000 notes publicades"
+    _notes70000:
+      title: "Forat negre de notes"
+      description: "70 000 notes publicades"
+    _notes80000:
+      title: "Galàxia de notes"
+      description: "80 000 notes publicades"
+    _notes90000:
+      title: "Univers de notes"
+      description: "90 000 notes publicades"
     _notes100000:
+      title: "ALL YOUR NOTE ARE BELONG TO US"
+      description: "100 000 notes publicades"
       flavor: "Segur que tens moltes coses a dir?"
     _login3:
       title: "Principiant I"
@@ -1347,13 +1461,90 @@ _achievements:
       description: "És la primera vegada que et segueixo"
     _following10:
       title: "Segueix-me... Segueix-me..."
+      description: "Seguir 10 usuaris"
+    _following50:
+      title: "Molts amics"
+      description: "Seguir 50 comptes"
+    _following100:
+      title: "100 amics"
+      description: "Segueixes 100 comptes"
+    _following300:
+      title: "Sobrecàrrega d'amics"
+      description: "Segueixes 300 comptes"
+    _followers1:
+      title: "Primer seguidor"
+      description: "1 seguidor guanyat"
+    _followers10:
+      title: "Segueix-me!"
+      description: "10 seguidors guanyats"
+    _followers50:
+      title: "Venen en manada"
+      description: "50 seguidors guanyats"
+    _followers100:
+      title: "Popular"
+      description: "100 seguidors guanyats"
+    _followers300:
+      title: "Si us plau, d'un en un!"
+      description: "300 seguidors guanyats"
+    _followers500:
+      title: "Torre de ràdio"
+      description: "500 seguidors guanyats"
+    _followers1000:
+      title: "Influenciador"
+      description: "1 000 seguidors guanyats"
+    _collectAchievements30:
+      title: "Col·leccionista d'èxits "
+      description: "Desbloqueja 30 assoliments"
+    _viewAchievements3min:
+      title: "M'agraden els èxits "
+      description: "Mira la teva llista d'assoliments durant més de 3 minuts"
+    _iLoveMisskey:
+      title: "Estimo Misskey"
+      description: "Publica \"I ❤ #Misskey\""
+      flavor: "L'equip de desenvolupament de Misskey agraeix el vostre suport!"
+    _foundTreasure:
+      title: "A la Recerca del Tresor"
+      description: "Has trobat el tresor amagat"
+    _client30min:
+      title: "Parem una estona"
+      description: "Mantingues obert Misskey per 30 minuts"
+    _client60min:
+      title: "A totes amb Misskey"
+      description: "Mantingues Misskey obert per 60 minuts"
+    _noteDeletedWithin1min:
+      title: "No et preocupis"
+      description: "Esborra una nota al minut de publicar-la"
+    _postedAtLateNight:
+      title: "Nocturn"
+      description: "Publica una nota a altes hores de la nit "
+      flavor: "És hora d'anar a dormir."
     _open3windows:
       title: "Multi finestres"
       description: "I va obrir més de tres finestres"
     _driveFolderCircularReference:
       title: "Consulteu la secció de bucle"
 _role:
+  permission: "Permisos de rol"
+  descriptionOfPermission: "Els <b>Moderadors</b> poden fer operacions bàsiques de moderació.\nEls <b>Administradors</b> poden canviar tots els ajustos del servidor."
   assignTarget: "Assignar "
+  descriptionOfAssignTarget: "<b>Manual</b> per canviar manualment qui és part d'aquest rol i qui no.\n<b>Condicional</b> per afegir o eliminar de manera automàtica els usuaris d'aquest rol basat en una determinada condició."
+  manual: "Manual"
+  manualRoles: "Rols manuals"
+  conditional: "Condicional"
+  conditionalRoles: "Rols condicionals"
+  condition: "Condició"
+  isConditionalRole: "Aquest és un rol condicional"
+  isPublic: "Rol públic"
+  descriptionOfIsPublic: "Aquest rol es mostrarà al perfil dels usuaris al que se'ls assigni."
+  options: "Opcions"
+  policies: "Polítiques"
+  baseRole: "Plantilla de rols"
+  useBaseValue: "Fer servir els valors de la plantilla de rols"
+  chooseRoleToAssign: "Selecciona els rols a assignar"
+  iconUrl: "URL de la icona "
+  asBadge: "Mostrar com a insígnia "
+  descriptionOfAsBadge: "La icona d'aquest rol es mostrarà al costat dels noms d'usuaris que tinguin assignats aquest rol."
+  isExplorable: "Fer el rol explorable"
   priority: "Prioritat"
   _priority:
     low: "Baixa"
diff --git a/locales/it-IT.yml b/locales/it-IT.yml
index 0c03337343..db98eaaa24 100644
--- a/locales/it-IT.yml
+++ b/locales/it-IT.yml
@@ -103,7 +103,7 @@ defaultNoteVisibility: "Privacy predefinita delle note"
 follow: "Segui"
 followRequest: "Richiesta di follow"
 followRequests: "Richieste di follow"
-unfollow: "Interrompi following"
+unfollow: "Smetti di seguire"
 followRequestPending: "Richiesta in approvazione"
 enterEmoji: "Inserisci emoji"
 renote: "Rinota"
@@ -381,9 +381,11 @@ hcaptcha: "hCaptcha"
 enableHcaptcha: "Abilita hCaptcha"
 hcaptchaSiteKey: "Chiave del sito"
 hcaptchaSecretKey: "Chiave segreta"
+mcaptcha: "mCaptcha"
 enableMcaptcha: "Abilita hCaptcha"
 mcaptchaSiteKey: "Chiave del sito"
 mcaptchaSecretKey: "Chiave segreta"
+mcaptchaInstanceUrl: "URL della istanza mCaptcha"
 recaptcha: "reCAPTCHA"
 enableRecaptcha: "Abilita reCAPTCHA"
 recaptchaSiteKey: "Chiave del sito"
@@ -631,6 +633,7 @@ medium: "Medio"
 small: "Piccolo"
 generateAccessToken: "Genera token di accesso"
 permission: "Autorizzazioni "
+adminPermission: "Privilegi amministrativi"
 enableAll: "Abilita tutto"
 disableAll: "Disabilita tutto"
 tokenRequested: "Autorizza accesso al profilo"
@@ -674,6 +677,7 @@ useGlobalSettingDesc: "Quando attiva, verranno utilizzate le impostazioni notifi
 other: "Ulteriori"
 regenerateLoginToken: "Genera di nuovo un token di connessione"
 regenerateLoginTokenDescription: "Genera un nuovo token di autenticazione. Solitamente questa operazione non è necessaria: quando si genera un nuovo token, tutti i dispositivi vanno disconnessi."
+theKeywordWhenSearchingForCustomEmoji: "Questa sarà la parola chiave durante la ricerca di emoji personalizzate"
 setMultipleBySeparatingWithSpace: "È possibile creare multiple voci separate da spazi."
 fileIdOrUrl: "ID o URL del file"
 behavior: "Comportamento"
@@ -872,7 +876,7 @@ pubSub: "Publish/Subscribe del profilo"
 lastCommunication: "La comunicazione più recente"
 resolved: "Risolto"
 unresolved: "Non risolto"
-breakFollow: "Interrompi follow"
+breakFollow: "Impedire di seguirmi"
 breakFollowConfirm: "Vuoi davvero che questo profilo smetta di seguirti?"
 itsOn: "Abilitato"
 itsOff: "Disabilitato"
@@ -888,6 +892,8 @@ makeReactionsPublicDescription: "La lista delle reazioni che avete fatto è a di
 classic: "Classico"
 muteThread: "Silenzia conversazione"
 unmuteThread: "Riattiva la conversazione"
+followingVisibility: "Visibilità dei profili seguiti"
+followersVisibility: "Visibilità dei profili che ti seguono"
 continueThread: "Altre conversazioni"
 deleteAccountConfirm: "Così verrà eliminato il profilo. Vuoi procedere?"
 incorrectPassword: "La password è errata."
@@ -1057,6 +1063,8 @@ limitWidthOfReaction: "Limita la larghezza delle reazioni e ridimensionale"
 noteIdOrUrl: "ID della Nota o URL"
 video: "Video"
 videos: "Video"
+audio: "Audio"
+audioFiles: "Audio"
 dataSaver: "Risparmia dati"
 accountMigration: "Migrazione del profilo"
 accountMoved: "Questo profilo ha migrato altrove:"
@@ -1187,7 +1195,27 @@ remainingN: "Rimangono: {n}"
 overwriteContentConfirm: "Vuoi davvero sostituire l'attuale contenuto?"
 seasonalScreenEffect: "Schermate in base alla stagione"
 decorate: "Decora"
+addMfmFunction: "Aggiungi decorazioni"
+enableQuickAddMfmFunction: "Attiva il selettore di funzioni MFM"
+bubbleGame: "Bubble Game"
+sfx: "Effetti sonori"
+soundWillBePlayed: "Verrà riprodotto il suono"
+showReplay: "Vedi i replay"
+replay: "Replay"
+replaying: "Replay in corso"
+ranking: "Classifica"
 lastNDays: "Ultimi {n} giorni"
+backToTitle: "Torna al titolo"
+hemisphere: "Geolocalizzazione"
+withSensitive: "Mostra le Note con allegati espliciti"
+userSaysSomethingSensitive: "Note da {name} con allegati espliciti"
+enableHorizontalSwipe: "Trascina per invertire i tab"
+_bubbleGame:
+  howToPlay: "Come giocare"
+  _howToPlay:
+    section1: "Regola la posizione e rilascia l'oggetto nella casella."
+    section2: "Ottieni un punteggio, quando due oggetti dello stesso tipo si toccano e si trasformano in un oggetto diverso."
+    section3: "Se gli oggetti traboccano dalla scatola, il gioco finisce. Cerca di ottenere un punteggio elevato fondendo gli oggetti, evitando che escano dalla scatola!"
 _announcement:
   forExistingUsers: "Solo ai profili attuali"
   forExistingUsersDescription: "L'annuncio sarà visibile solo ai profili esistenti in questo momento. Se disabilitato, sarà visibile anche ai profili che verranno creati dopo la pubblicazione di questo annuncio."
@@ -1558,6 +1586,13 @@ _achievements:
     _tutorialCompleted:
       title: "Attestato di partecipazione al corso per principianti di Misskey"
       description: "Ha completato il tutorial"
+    _bubbleGameExplodingHead:
+      title: "🤯"
+      description: "Estrai l'oggetto più grande dal Bubble Game"
+    _bubbleGameDoubleExplodingHead:
+      title: "Doppio 🤯"
+      description: "Due oggetti più grossi contemporaneamente nel Bubble Game"
+      flavor: "Ha le dimensioni di una bento-box 🤯 🤯"
 _role:
   new: "Nuovo ruolo"
   edit: "Modifica ruolo"
@@ -1648,6 +1683,7 @@ _emailUnavailable:
   disposable: "Indirizzo email non utilizzabile"
   mx: "Server email non corretto"
   smtp: "Il server email non risponde"
+  banned: "Non puoi registrarti con questo indirizzo email"
 _ffVisibility:
   public: "Pubblica"
   followers: "Mostra solo ai follower"
@@ -1940,6 +1976,26 @@ _permissions:
   "write:flash": "Modifica Play"
   "read:flash-likes": "Visualizza lista di Play piaciuti"
   "write:flash-likes": "Modifica lista di Play piaciuti"
+  "read:admin:abuse-user-reports": "Mostra i report dai profili utente"
+  "write:admin:delete-account": "Elimina l'account utente"
+  "write:admin:delete-all-files-of-a-user": "Elimina i file dell'account utente"
+  "read:admin:index-stats": "Visualizza informazioni sugli indici del database"
+  "read:admin:table-stats": "Visualizza informazioni sulle tabelle del database"
+  "read:admin:user-ips": "Visualizza indirizzi IP degli account"
+  "read:admin:meta": "Visualizza i metadati dell'istanza"
+  "write:admin:reset-password": "Ripristina la password dell'account utente"
+  "write:admin:resolve-abuse-user-report": "Risolvere le segnalazioni dagli account utente"
+  "write:admin:send-email": "Spedire email"
+  "read:admin:server-info": "Vedere le informazioni sul server"
+  "read:admin:show-moderation-log": "Vedere lo storico di moderazione"
+  "read:admin:show-user": "Vedere le informazioni private degli account utente"
+  "read:admin:show-users": "Vedere le informazioni private degli account utente"
+  "write:admin:suspend-user": "Sospendere i profili"
+  "write:admin:unset-user-avatar": "Rimuovere la foto profilo dai profili"
+  "write:admin:unset-user-banner": "Rimuovere l'immagine testata dai profili"
+  "write:admin:unsuspend-user": "Togliere la sospensione ai profili"
+  "write:admin:meta": "Modificare i metadati dell'istanza"
+  "write:admin:user-note": "Scrivere annotazioni di moderazione"
 _auth:
   shareAccessTitle: "Permessi dell'applicazione"
   shareAccess: "Vuoi autorizzare {name} ad accedere al tuo profilo?"
diff --git a/locales/ko-GS.yml b/locales/ko-GS.yml
index 29bfe5394d..b1702114be 100644
--- a/locales/ko-GS.yml
+++ b/locales/ko-GS.yml
@@ -40,7 +40,7 @@ favorites: "질겨찾기"
 unfavorite: "질겨찾기서 어ᇝ애기"
 favorited: "질겨찾기에 담앗십니다."
 alreadyFavorited: "벌시로 질겨찾기에 담기 잇십니다."
-cantFavorite: "질겨찾기에 몬 담았십니다."
+cantFavorite: "질겨찾기에 몬 담앗십니다."
 pin: "프로필에 붙이기"
 unpin: "프로필서 띠기"
 copyContent: "내용 복사하기"
@@ -124,6 +124,7 @@ reactions: "반엉"
 reactionSettingDescription2: "꺼시서 두고, 누질라서 뭉캐고,  ‘+’럴 누질라서 옇십니다."
 rememberNoteVisibility: "공개 범위럴 기억하기"
 attachCancel: "붙임 빼기"
+deleteFile: "파일 뭉캐기"
 markAsSensitive: "수ᇚ힘 설정"
 unmarkAsSensitive: "수ᇚ힘 무루기"
 enterFileName: "파일 이럼 서기"
@@ -463,6 +464,8 @@ onlyOneFileCanBeAttached: "메시지엔 파일 하나까제밖에 몬 넣십니
 invitations: "초대하기"
 invitationCode: "초대장"
 checking: "학인하고 잇십니다"
+tooShort: "억수로 짜립니다"
+tooLong: "억수로 집니다"
 passwordMatched: "맞십니다"
 passwordNotMatched: "안 맞십니다"
 signinFailed: "로그인 몬 했십니다. 고 이름이랑 비밀번호 제대로 썼는가 확인해 주이소."
@@ -571,7 +574,11 @@ userSilenced: "요 게정은... 수ᇚ혀 있십니다."
 relays: "릴레이"
 addRelay: "릴레이 옇기"
 addedRelays: "옇은 릴레이"
+deletedNote: "뭉캔 걸"
 enableInfiniteScroll: "알아서 더 보기"
+useCw: "내용 수ᇚ후기"
+description: "설멩"
+describeFile: "캡션 옇기"
 author: "맨던 사람"
 manage: "간리"
 emailServer: "전자우펜 서버"
@@ -600,6 +607,7 @@ renotesCount: "리노트한 수"
 renotedCount: "리노트덴 수"
 followingCount: "팔로우 수"
 followersCount: "팔로워 수"
+noteFavoritesCount: "질겨찾기한 노트 수"
 clips: "클립 맨걸기"
 clearCache: "캐시 비우기"
 unlikeConfirm: "좋네예럴 무룹니꺼?"
@@ -608,6 +616,7 @@ user: "사용자"
 administration: "간리"
 on: "í‚´"
 off: "껌"
+hide: "수ᇚ후기"
 clickToFinishEmailVerification: "[{ok}]럴 누질라서 전자우펜 정멩얼 껕내이소."
 searchByGoogle: "찾기"
 tenMinutes: "십 분"
@@ -626,9 +635,11 @@ role: "옉할"
 noRole: "옉할이 없십니다"
 thisPostMayBeAnnoyingCancel: "아이예"
 likeOnly: "좋네예마"
+myClips: "내 클립"
 icon: "아바타"
 replies: "답하기"
 renotes: "리노트"
+attach: "옇기"
 _initialAccountSetting:
   startTutorial: "길라잡이 하기"
 _initialTutorial:
@@ -641,9 +652,52 @@ _initialTutorial:
     title: "길라잡이가 껕낫십니다!🎉"
 _achievements:
   _types:
+    _notes1:
+      description: "첫 노트럴 섯어예"
+    _notes10:
+      description: "노트럴 10번 섰어예"
+    _notes100:
+      description: "노트럴 100번 섰어예"
+    _notes500:
+      description: "노트럴 500번 섰어예"
+    _notes1000:
+      description: "노트럴 1,000번 섰어예"
+    _notes5000:
+      description: "노트럴 5,000번 섰어예"
+    _notes10000:
+      description: "노트럴 10,000번 섰어예"
+    _notes20000:
+      description: "노트럴 20,000번 섰어예"
+    _notes30000:
+      description: "노트럴 30,000번 섰어예"
+    _notes40000:
+      description: "노트럴 40,000번 섰어예"
+    _notes50000:
+      description: "노트럴 50,000번 섰어예"
+    _notes60000:
+      description: "노트럴 60,000번 섰어예"
+    _notes70000:
+      description: "노트럴 70,000번 섰어예"
+    _notes80000:
+      description: "노트럴 80,000번 섰어예"
+    _notes90000:
+      description: "노트럴 90,000번 섰어예"
+    _notes100000:
+      description: "노트럴 100,000번 섰어예"
+    _noteClipped1:
+      description: "첫 노트럴 클립햇어예"
+    _noteFavorited1:
+      description: "첫 노트럴 질겨찾기에 담앗어예"
+    _myNoteFavorited1:
+      description: "다런 사람이 내 노트럴 질겨찾기에 담앗십니다"
+    _iLoveMisskey:
+      description: "“I ❤ #Misskey”럴 섰어예"
+    _postedAt0min0sec:
+      description: "0분 0초에 노트를 섰어예"
     _tutorialCompleted:
       description: "길라잡이럴 껕냇십니다"
 _gallery:
+  my: "내 걸"
   liked: "좋네예한 걸"
   like: "좋네예!"
   unlike: "좋네예 무루기"
@@ -654,7 +708,12 @@ _serverDisconnectedBehavior:
   reload: "알아서 새로곤침"
 _channel:
   removeBanner: "배너 뭉캐기"
+  usersCount: "{n}명 참여"
+  notesCount: "노트 {n}개"
+_menuDisplay:
+  hide: "수ᇚ후기"
 _theme:
+  description: "설멩"
   keys:
     mention: "멘션"
 _sfx:
@@ -663,6 +722,9 @@ _sfx:
 _2fa:
   step3Title: "학인 기호럴 서기"
   renewTOTPCancel: "뎃어예"
+_permissions:
+  "read:favorites": "질겨찾기 보기"
+  "write:favorites": "질겨찾기 곤치기"
 _widgets:
   profile: "프로필"
   instanceInfo: "서버 정보"
@@ -674,7 +736,10 @@ _widgets:
   _userList:
     chooseList: "리스트 개리기"
 _cw:
+  hide: "수ᇚ후기"
   show: "더 볼래예"
+  chars: "걸자 {count}개"
+  files: "파일 {count}개"
 _visibility:
   home: "덜머리"
   followers: "팔로워"
@@ -682,6 +747,7 @@ _profile:
   name: "이럼"
   username: "사용자 이럼"
 _exportOrImport:
+  favoritedNotes: "질겨찾기한 노트"
   clips: "클립 맨걸기"
   followingList: "팔로잉"
   muteList: "수ᇚ후기"
@@ -692,16 +758,20 @@ _charts:
 _timelines:
   home: "덜머리"
 _play:
+  my: "내 플레이"
   script: "스크립트"
+  summary: "설멩"
 _pages:
   like: "좋네예"
   unlike: "좋네예 무루기"
+  my: "내 페이지"
   blocks:
     image: "이미지"
     _note:
       id: "노트 아이디"
 _notification:
   youWereFollowed: "새 팔로워가 잇십니다"
+  newNote: "새 걸"
   _types:
     follow: "팔로잉"
     mention: "멘션"
diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml
index 718d04caae..2a59ab9a23 100644
--- a/locales/ko-KR.yml
+++ b/locales/ko-KR.yml
@@ -279,7 +279,7 @@ uploadFromUrl: "URL 업로드"
 uploadFromUrlDescription: "업로드하려는 파일의 URL"
 uploadFromUrlRequested: "업로드를 요청했습니다"
 uploadFromUrlMayTakeTime: "업로드가 완료될 때까지 시간이 소요될 수 있습니다."
-explore: "발견하기"
+explore: "둘러보기"
 messageRead: "읽음"
 noMoreHistory: "이것보다 과거의 기록이 없습니다"
 startMessaging: "대화 시작하기"
@@ -1022,7 +1022,7 @@ internalServerError: "내부 서버 오류"
 internalServerErrorDescription: "내부 서버에서 예기치 않은 오류가 발생했습니다."
 copyErrorInfo: "오류 정보 복사"
 joinThisServer: "이 서버에 가입"
-exploreOtherServers: "다른 서버 둘러보기"
+exploreOtherServers: "다른 서버 찾기"
 letsLookAtTimeline: "타임라인 구경하기"
 disableFederationConfirm: "정말로 연합을 끄시겠습니까?"
 disableFederationConfirmWarn: "연합을 끄더라도 게시물이 비공개로 전환되는 것은 아닙니다. 대부분의 경우 연합을 비활성화할 필요가 없습니다."
@@ -1797,7 +1797,7 @@ _instanceMute:
   title: "지정한 서버의 노트를 숨깁니다."
   heading: "뮤트할 서버"
 _theme:
-  explore: "테마 찾아보기"
+  explore: "테마 둘러보기"
   install: "테마 설치"
   manage: "테마 관리"
   code: "테마 코드"
@@ -2022,7 +2022,7 @@ _permissions:
   "write:report-abuse": "위반 내용 신고하기"
 _auth:
   shareAccessTitle: "어플리케이션의 접근 허가"
-  shareAccess: "\"{name}\" 이 계정에 접근하는 것을 허용하시겠습니까?"
+  shareAccess: "‘{name}’에서 계정에 접근하는 것을 허용하시겠습니까?"
   shareAccessAsk: "이 애플리케이션이 계정에 접근하는 것을 허용하시겠습니까?"
   permission: "{name}에서 다음 권한을 요청하였습니다"
   permissionAsk: "이 앱은 다음의 권한을 요청합니다"
diff --git a/package.json b/package.json
index 7af66b8e37..171adc0da3 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "sharkey",
-	"version": "2024.2.0-beta.9",
+	"version": "2024.2.0-beta.10",
 	"codename": "shonk",
 	"repository": {
 		"type": "git",
diff --git a/packages/backend/src/core/EmailService.ts b/packages/backend/src/core/EmailService.ts
index 8daee148eb..89722965c1 100644
--- a/packages/backend/src/core/EmailService.ts
+++ b/packages/backend/src/core/EmailService.ts
@@ -40,6 +40,8 @@ export class EmailService {
 	public async sendEmail(to: string, subject: string, html: string, text: string) {
 		const meta = await this.metaService.fetch(true);
 
+		if (!meta.enableEmail) return;
+
 		const iconUrl = `${this.config.url}/static-assets/mi-white.png`;
 		const emailSettingUrl = `${this.config.url}/settings/email`;
 
diff --git a/packages/backend/src/core/InstanceActorService.ts b/packages/backend/src/core/InstanceActorService.ts
index b40fd46291..7ce8dc96a1 100644
--- a/packages/backend/src/core/InstanceActorService.ts
+++ b/packages/backend/src/core/InstanceActorService.ts
@@ -4,7 +4,7 @@
  */
 
 import { Inject, Injectable } from '@nestjs/common';
-import { IsNull } from 'typeorm';
+import { IsNull, Not } from 'typeorm';
 import type { MiLocalUser } from '@/models/User.js';
 import type { UsersRepository } from '@/models/_.js';
 import { MemorySingleCache } from '@/misc/cache.js';
@@ -27,6 +27,14 @@ export class InstanceActorService {
 		this.cache = new MemorySingleCache<MiLocalUser>(Infinity);
 	}
 
+	@bindThis
+	public async realLocalUsersPresent(): Promise<boolean> {
+		return await this.usersRepository.existsBy({
+			host: IsNull(),
+			username: Not(ACTOR_USERNAME),
+		});
+	}
+
 	@bindThis
 	public async getInstanceActor(): Promise<MiLocalUser> {
 		const cached = this.cache.get();
diff --git a/packages/backend/src/core/SignupService.ts b/packages/backend/src/core/SignupService.ts
index 32e3dee937..6b8c64488f 100644
--- a/packages/backend/src/core/SignupService.ts
+++ b/packages/backend/src/core/SignupService.ts
@@ -17,6 +17,7 @@ import { MiUserKeypair } from '@/models/UserKeypair.js';
 import { MiUsedUsername } from '@/models/UsedUsername.js';
 import generateUserToken from '@/misc/generate-native-user-token.js';
 import { UserEntityService } from '@/core/entities/UserEntityService.js';
+import { InstanceActorService } from '@/core/InstanceActorService.js';
 import { bindThis } from '@/decorators.js';
 import UsersChart from '@/core/chart/charts/users.js';
 import { UtilityService } from '@/core/UtilityService.js';
@@ -38,6 +39,7 @@ export class SignupService {
 		private userEntityService: UserEntityService,
 		private idService: IdService,
 		private metaService: MetaService,
+		private instanceActorService: InstanceActorService,
 		private usersChart: UsersChart,
 	) {
 	}
@@ -84,7 +86,7 @@ export class SignupService {
 			throw new Error('USED_USERNAME');
 		}
 
-		const isTheFirstUser = (await this.usersRepository.countBy({ host: IsNull() })) === 0;
+		const isTheFirstUser = !await this.instanceActorService.realLocalUsersPresent();
 
 		if (!opts.ignorePreservedUsernames && !isTheFirstUser) {
 			const isPreserved = instance.preservedUsernames.map(x => x.toLowerCase()).includes(username.toLowerCase());
diff --git a/packages/backend/src/core/activitypub/ApRendererService.ts b/packages/backend/src/core/activitypub/ApRendererService.ts
index 19899629c3..3f6ddd8027 100644
--- a/packages/backend/src/core/activitypub/ApRendererService.ts
+++ b/packages/backend/src/core/activitypub/ApRendererService.ts
@@ -796,6 +796,7 @@ export class ApRendererService {
 				'https://www.w3.org/ns/activitystreams',
 				'https://w3id.org/security/v1',
 				{
+					Key: 'sec:Key',
 					// as non-standards
 					manuallyApprovesFollowers: 'as:manuallyApprovesFollowers',
 					sensitive: 'as:sensitive',
diff --git a/packages/backend/src/core/activitypub/models/ApPersonService.ts b/packages/backend/src/core/activitypub/models/ApPersonService.ts
index 8507c6f949..a4d497a7ff 100644
--- a/packages/backend/src/core/activitypub/models/ApPersonService.ts
+++ b/packages/backend/src/core/activitypub/models/ApPersonService.ts
@@ -225,23 +225,42 @@ export class ApPersonService implements OnModuleInit {
 		return null;
 	}
 
-	private async resolveAvatarAndBanner(user: MiRemoteUser, icon: any, image: any, bgimg: any): Promise<Pick<MiRemoteUser, 'avatarId' | 'bannerId' | 'backgroundId' | 'avatarUrl' | 'bannerUrl' | 'backgroundUrl' | 'avatarBlurhash' | 'bannerBlurhash' | 'backgroundBlurhash'>> {
+	private async resolveAvatarAndBanner(user: MiRemoteUser, icon: any, image: any, bgimg: any): Promise<Partial<Pick<MiRemoteUser, 'avatarId' | 'bannerId' | 'backgroundId' | 'avatarUrl' | 'bannerUrl' | 'backgroundUrl' | 'avatarBlurhash' | 'bannerBlurhash' | 'backgroundBlurhash'>>> {
+		if (user == null) throw new Error('failed to create user: user is null');
+
 		const [avatar, banner, background] = await Promise.all([icon, image, bgimg].map(img => {
-			if (img == null) return null;
-			if (user == null) throw new Error('failed to create user: user is null');
+			// if we have an explicitly missing image, return an
+			// explicitly-null set of values
+			if ((img == null) || (typeof img === 'object' && img.url == null)) {
+				return { id: null, url: null, blurhash: null };
+			}
+
 			return this.apImageService.resolveImage(user, img).catch(() => null);
 		}));
 
+		/*
+			we don't want to return nulls on errors! if the database fields
+			are already null, nothing changes; if the database has old
+			values, we should keep those. The exception is if the remote has
+			actually removed the images: in that case, the block above
+			returns the special {id:null}&c value, and we return those
+		*/
 		return {
-			avatarId: avatar?.id ?? null,
-			bannerId: banner?.id ?? null,
-			backgroundId: background?.id ?? null,
-			avatarUrl: avatar ? this.driveFileEntityService.getPublicUrl(avatar, 'avatar') : null,
-			bannerUrl: banner ? this.driveFileEntityService.getPublicUrl(banner) : null,
-			backgroundUrl: background ? this.driveFileEntityService.getPublicUrl(background) : null,
-			avatarBlurhash: avatar?.blurhash ?? null,
-			bannerBlurhash: banner?.blurhash ?? null,
-			backgroundBlurhash: background?.blurhash ?? null
+			...( avatar ? {
+				avatarId: avatar.id,
+				avatarUrl: avatar.url ? this.driveFileEntityService.getPublicUrl(avatar, 'avatar') : null,
+				avatarBlurhash: avatar.blurhash,
+			} : {}),
+			...( banner ? {
+				bannerId: banner.id,
+				bannerUrl: banner.url ? this.driveFileEntityService.getPublicUrl(banner) : null,
+				bannerBlurhash: banner.blurhash,
+			} : {}),
+			...( background ? {
+				backgroundId: background.id,
+				backgroundUrl: background.url ? this.driveFileEntityService.getPublicUrl(background) : null,
+				backgroundBlurhash: background.blurhash,
+			} : {}),
 		};
 	}
 
diff --git a/packages/backend/src/core/entities/EmojiEntityService.ts b/packages/backend/src/core/entities/EmojiEntityService.ts
index 5b97cfad5e..655c4c5ada 100644
--- a/packages/backend/src/core/entities/EmojiEntityService.ts
+++ b/packages/backend/src/core/entities/EmojiEntityService.ts
@@ -31,6 +31,7 @@ export class EmojiEntityService {
 			category: emoji.category,
 			// || emoji.originalUrl してるのは後方互換性のため(publicUrlはstringなので??はだめ)
 			url: emoji.publicUrl || emoji.originalUrl,
+			localOnly: emoji.localOnly ? true : undefined,
 			isSensitive: emoji.isSensitive ? true : undefined,
 			roleIdsThatCanBeUsedThisEmojiAsReaction: emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.length > 0 ? emoji.roleIdsThatCanBeUsedThisEmojiAsReaction : undefined,
 		};
diff --git a/packages/backend/src/models/json-schema/emoji.ts b/packages/backend/src/models/json-schema/emoji.ts
index 99a58f8773..954eb98d57 100644
--- a/packages/backend/src/models/json-schema/emoji.ts
+++ b/packages/backend/src/models/json-schema/emoji.ts
@@ -27,6 +27,10 @@ export const packedEmojiSimpleSchema = {
 			type: 'string',
 			optional: false, nullable: false,
 		},
+		localOnly: {
+			type: 'boolean',
+			optional: true, nullable: false,
+		},
 		isSensitive: {
 			type: 'boolean',
 			optional: true, nullable: false,
diff --git a/packages/backend/src/server/api/endpoints/admin/accounts/create.ts b/packages/backend/src/server/api/endpoints/admin/accounts/create.ts
index b18a7e0e41..14fd69a1a2 100644
--- a/packages/backend/src/server/api/endpoints/admin/accounts/create.ts
+++ b/packages/backend/src/server/api/endpoints/admin/accounts/create.ts
@@ -9,6 +9,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js';
 import type { UsersRepository } from '@/models/_.js';
 import { SignupService } from '@/core/SignupService.js';
 import { UserEntityService } from '@/core/entities/UserEntityService.js';
+import { InstanceActorService } from '@/core/InstanceActorService.js';
 import { localUsernameSchema, passwordSchema } from '@/models/User.js';
 import { DI } from '@/di-symbols.js';
 import { Packed } from '@/misc/json-schema.js';
@@ -46,13 +47,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 
 		private userEntityService: UserEntityService,
 		private signupService: SignupService,
+		private instanceActorService: InstanceActorService,
 	) {
 		super(meta, paramDef, async (ps, _me, token) => {
 			const me = _me ? await this.usersRepository.findOneByOrFail({ id: _me.id }) : null;
-			const noUsers = (await this.usersRepository.countBy({
-				host: IsNull(),
-			})) === 0;
-			if ((!noUsers && !me?.isRoot) || token !== null) throw new Error('access denied');
+			const realUsers = await this.instanceActorService.realLocalUsersPresent();
+			if ((realUsers && !me?.isRoot) || token !== null) throw new Error('access denied');
 
 			const { account, secret } = await this.signupService.signup({
 				username: ps.username,
diff --git a/packages/backend/src/server/api/endpoints/meta.ts b/packages/backend/src/server/api/endpoints/meta.ts
index cb52bf6b51..6d553e5d79 100644
--- a/packages/backend/src/server/api/endpoints/meta.ts
+++ b/packages/backend/src/server/api/endpoints/meta.ts
@@ -6,10 +6,11 @@
 import { IsNull, LessThanOrEqual, MoreThan, Brackets } from 'typeorm';
 import { Inject, Injectable } from '@nestjs/common';
 import JSON5 from 'json5';
-import type { AdsRepository, UsersRepository } from '@/models/_.js';
+import type { AdsRepository } from '@/models/_.js';
 import { Endpoint } from '@/server/api/endpoint-base.js';
 import { UserEntityService } from '@/core/entities/UserEntityService.js';
 import { MetaService } from '@/core/MetaService.js';
+import { InstanceActorService } from '@/core/InstanceActorService.js';
 import type { Config } from '@/config.js';
 import { DI } from '@/di-symbols.js';
 import { DEFAULT_POLICIES } from '@/core/RoleService.js';
@@ -337,14 +338,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 		@Inject(DI.config)
 		private config: Config,
 
-		@Inject(DI.usersRepository)
-		private usersRepository: UsersRepository,
-
 		@Inject(DI.adsRepository)
 		private adsRepository: AdsRepository,
 
 		private userEntityService: UserEntityService,
 		private metaService: MetaService,
+		private instanceActorService: InstanceActorService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
 			const instance = await this.metaService.fetch(true);
@@ -427,9 +426,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				...(ps.detail ? {
 					cacheRemoteFiles: instance.cacheRemoteFiles,
 					cacheRemoteSensitiveFiles: instance.cacheRemoteSensitiveFiles,
-					requireSetup: (await this.usersRepository.countBy({
-						host: IsNull(),
-					})) === 0,
+					requireSetup: !await this.instanceActorService.realLocalUsersPresent(),
 				} : {}),
 			};
 
diff --git a/packages/frontend/src/components/MkCode.core.vue b/packages/frontend/src/components/MkCode.core.vue
index b06bb70e99..0da256866e 100644
--- a/packages/frontend/src/components/MkCode.core.vue
+++ b/packages/frontend/src/components/MkCode.core.vue
@@ -5,14 +5,15 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <!-- eslint-disable vue/no-v-html -->
 <template>
-<div :class="[$style.codeBlockRoot, { [$style.codeEditor]: codeEditor }]" v-html="html"></div>
+<div :class="[$style.codeBlockRoot, { [$style.codeEditor]: codeEditor }, (darkMode ? $style.dark : $style.light)]" v-html="html"></div>
 </template>
 
 <script lang="ts" setup>
 import { ref, computed, watch } from 'vue';
 import { bundledLanguagesInfo } from 'shiki';
 import type { BuiltinLanguage } from 'shiki';
-import { getHighlighter } from '@/scripts/code-highlighter.js';
+import { getHighlighter, getTheme } from '@/scripts/code-highlighter.js';
+import { defaultStore } from '@/store.js';
 
 const props = defineProps<{
 	code: string;
@@ -21,11 +22,23 @@ const props = defineProps<{
 }>();
 
 const highlighter = await getHighlighter();
-
+const darkMode = defaultStore.reactiveState.darkMode;
 const codeLang = ref<BuiltinLanguage | 'aiscript'>('js');
+
+const [lightThemeName, darkThemeName] = await Promise.all([
+	getTheme('light', true),
+	getTheme('dark', true),
+]);
+
 const html = computed(() => highlighter.codeToHtml(props.code, {
 	lang: codeLang.value,
-	theme: 'dark-plus',
+	themes: {
+		fallback: 'dark-plus',
+		light: lightThemeName,
+		dark: darkThemeName,
+	},
+	defaultColor: false,
+	cssVariablePrefix: '--shiki-',
 }));
 
 async function fetchLanguage(to: string): Promise<void> {
@@ -79,6 +92,15 @@ watch(() => props.lang, (to) => {
 	margin: .5em 0;
 	overflow: auto;
 	border-radius: var(--radius-sm);
+	border: 1px solid var(--divider);
+
+	color: var(--shiki-fallback);
+	background-color: var(--shiki-fallback-bg);
+
+	& span {
+		color: var(--shiki-fallback);
+		background-color: var(--shiki-fallback-bg);
+	}
 
 	& pre,
 	& code {
@@ -86,6 +108,26 @@ watch(() => props.lang, (to) => {
 	}
 }
 
+.light.codeBlockRoot :global(.shiki) {
+	color: var(--shiki-light);
+	background-color: var(--shiki-light-bg);
+
+	& span {
+		color: var(--shiki-light);
+		background-color: var(--shiki-light-bg);
+	}
+}
+
+.dark.codeBlockRoot :global(.shiki) {
+	color: var(--shiki-dark);
+	background-color: var(--shiki-dark-bg);
+
+	& span {
+		color: var(--shiki-dark);
+		background-color: var(--shiki-dark-bg);
+	}
+}
+
 .codeBlockRoot.codeEditor {
 	min-width: 100%;
 	height: 100%;
@@ -94,6 +136,7 @@ watch(() => props.lang, (to) => {
 		padding: 12px;
 		margin: 0;
 		border-radius: var(--radius-sm);
+		border: none;
 		min-height: 130px;
 		pointer-events: none;
 		min-width: calc(100% - 24px);
@@ -105,6 +148,11 @@ watch(() => props.lang, (to) => {
 		text-rendering: inherit;
     text-transform: inherit;
     white-space: pre;
+
+		& span {
+			display: inline-block;
+			min-height: 1em;
+		}
 	}
 }
 </style>
diff --git a/packages/frontend/src/components/MkCode.vue b/packages/frontend/src/components/MkCode.vue
index 05b5fe8da1..b34eb8aaf6 100644
--- a/packages/frontend/src/components/MkCode.vue
+++ b/packages/frontend/src/components/MkCode.vue
@@ -53,7 +53,6 @@ function copy() {
 }
 
 .codeBlockCopyButton {
-	color: #D4D4D4;
 	position: absolute;
 	top: 8px;
 	right: 8px;
@@ -67,8 +66,7 @@ function copy() {
 .codeBlockFallbackRoot {
 	display: block;
 	overflow-wrap: anywhere;
-	color: #D4D4D4;
-	background: #1E1E1E;
+	background: var(--bg);
 	padding: 1em;
 	margin: .5em 0;
 	overflow: auto;
@@ -93,8 +91,8 @@ function copy() {
 	border-radius: var(--radius-sm);
 	padding: 24px;
 	margin-top: 4px;
-	color: #D4D4D4;
-	background: #1E1E1E;
+	color: var(--fg);
+	background: var(--bg);
 }
 
 .codePlaceholderContainer {
diff --git a/packages/frontend/src/components/MkCodeEditor.vue b/packages/frontend/src/components/MkCodeEditor.vue
index 7334fc4d0e..5ff6c801a5 100644
--- a/packages/frontend/src/components/MkCodeEditor.vue
+++ b/packages/frontend/src/components/MkCodeEditor.vue
@@ -196,10 +196,11 @@ watch(v, newValue => {
 	resize: none;
 	text-align: left;
 	color: transparent;
-	caret-color: rgb(225, 228, 232);
+	caret-color: var(--fg);
 	background-color: transparent;
 	border: 0;
 	border-radius: var(--radius-sm);
+	box-sizing: border-box;
 	outline: 0;
 	min-width: calc(100% - 24px);
 	height: 100%;
@@ -212,6 +213,6 @@ watch(v, newValue => {
 }
 
 .textarea::selection {
-	color: #fff;
+	color: var(--bg);
 }
 </style>
diff --git a/packages/frontend/src/components/MkCodeInline.vue b/packages/frontend/src/components/MkCodeInline.vue
index 5340c1fd5f..6a9d97ab5a 100644
--- a/packages/frontend/src/components/MkCodeInline.vue
+++ b/packages/frontend/src/components/MkCodeInline.vue
@@ -18,8 +18,7 @@ const props = defineProps<{
 	display: inline-block;
 	font-family: Consolas, Monaco, Andale Mono, Ubuntu Mono, monospace;
 	overflow-wrap: anywhere;
-	color: #D4D4D4;
-	background: #1E1E1E;
+	background: var(--bg);
 	padding: .1em;
 	border-radius: .3em;
 }
diff --git a/packages/frontend/src/components/MkCropperDialog.vue b/packages/frontend/src/components/MkCropperDialog.vue
index 0a1ddd3171..745453646c 100644
--- a/packages/frontend/src/components/MkCropperDialog.vue
+++ b/packages/frontend/src/components/MkCropperDialog.vue
@@ -63,18 +63,25 @@ const loading = ref(true);
 
 const ok = async () => {
 	const promise = new Promise<Misskey.entities.DriveFile>(async (res) => {
-		const croppedCanvas = await cropper?.getCropperSelection()?.$toCanvas();
+		const croppedImage = await cropper?.getCropperImage();
+		const croppedSection = await cropper?.getCropperSelection();
+
+		// 拡大率を計算し、(ほぼ)元の大きさに戻す
+		const zoomedRate = croppedImage.getBoundingClientRect().width / croppedImage.clientWidth;
+		const widthToRender = croppedSection.getBoundingClientRect().width / zoomedRate;
+
+		const croppedCanvas = await croppedSection?.$toCanvas({ width: widthToRender });
 		croppedCanvas?.toBlob(blob => {
 			if (!blob) return;
 			const formData = new FormData();
 			formData.append('file', blob);
 			formData.append('name', `cropped_${props.file.name}`);
 			formData.append('isSensitive', props.file.isSensitive ? 'true' : 'false');
-			formData.append('comment', props.file.comment ?? 'null');
+			if (props.file.comment) { formData.append('comment', props.file.comment);}
 			formData.append('i', $i!.token);
-			if (props.uploadFolder || props.uploadFolder === null) {
-				formData.append('folderId', props.uploadFolder ?? 'null');
-			} else if (defaultStore.state.uploadFolder) {
+			if (props.uploadFolder) {
+				formData.append('folderId', props.uploadFolder);
+			} else if (props.uploadFolder !== null && defaultStore.state.uploadFolder) {
 				formData.append('folderId', defaultStore.state.uploadFolder);
 			}
 
diff --git a/packages/frontend/src/components/MkEmojiPicker.vue b/packages/frontend/src/components/MkEmojiPicker.vue
index 1db03a5eb9..a4c5e07cd9 100644
--- a/packages/frontend/src/components/MkEmojiPicker.vue
+++ b/packages/frontend/src/components/MkEmojiPicker.vue
@@ -118,6 +118,7 @@ import { i18n } from '@/i18n.js';
 import { defaultStore } from '@/store.js';
 import { customEmojiCategories, customEmojis, customEmojisMap } from '@/custom-emojis.js';
 import { $i } from '@/account.js';
+import { checkReactionPermissions } from '@/scripts/check-reaction-permissions.js';
 
 const props = withDefaults(defineProps<{
 	showPinned?: boolean;
@@ -126,6 +127,7 @@ const props = withDefaults(defineProps<{
 	asDrawer?: boolean;
 	asWindow?: boolean;
 	asReactionPicker?: boolean; // 今は使われてないが将来的に使いそう
+	targetNote?: Misskey.entities.Note;
 }>(), {
 	showPinned: true,
 });
@@ -340,7 +342,7 @@ watch(q, () => {
 });
 
 function filterAvailable(emoji: Misskey.entities.EmojiSimple): boolean {
-	return ((emoji.roleIdsThatCanBeUsedThisEmojiAsReaction == null || emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.length === 0) || ($i && $i.roles.some(r => emoji.roleIdsThatCanBeUsedThisEmojiAsReaction?.includes(r.id)))) ?? false;
+	return !props.targetNote || checkReactionPermissions($i!, props.targetNote, emoji);
 }
 
 function focus() {
diff --git a/packages/frontend/src/components/MkEmojiPickerDialog.vue b/packages/frontend/src/components/MkEmojiPickerDialog.vue
index 4068a79f08..1c0f9a5a33 100644
--- a/packages/frontend/src/components/MkEmojiPickerDialog.vue
+++ b/packages/frontend/src/components/MkEmojiPickerDialog.vue
@@ -24,6 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 		:showPinned="showPinned"
 		:pinnedEmojis="pinnedEmojis"
 		:asReactionPicker="asReactionPicker"
+		:targetNote="targetNote"
 		:asDrawer="type === 'drawer'"
 		:max-height="maxHeight"
 		@chosen="chosen"
@@ -32,6 +33,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
+import * as Misskey from 'misskey-js';
 import { shallowRef } from 'vue';
 import MkModal from '@/components/MkModal.vue';
 import MkEmojiPicker from '@/components/MkEmojiPicker.vue';
@@ -43,6 +45,7 @@ const props = withDefaults(defineProps<{
 	showPinned?: boolean;
   pinnedEmojis?: string[],
 	asReactionPicker?: boolean;
+	targetNote?: Misskey.entities.Note;
   choseAndClose?: boolean;
 }>(), {
 	manualShowing: null,
diff --git a/packages/frontend/src/components/MkEmojiPickerWindow.vue b/packages/frontend/src/components/MkEmojiPickerWindow.vue
index 1a2c55e785..2a6828f242 100644
--- a/packages/frontend/src/components/MkEmojiPickerWindow.vue
+++ b/packages/frontend/src/components/MkEmojiPickerWindow.vue
@@ -13,12 +13,13 @@ SPDX-License-Identifier: AGPL-3.0-only
 	:front="true"
 	@closed="emit('closed')"
 >
-	<MkEmojiPicker :showPinned="showPinned" :asReactionPicker="asReactionPicker" asWindow :class="$style.picker" @chosen="chosen"/>
+	<MkEmojiPicker :showPinned="showPinned" :asReactionPicker="asReactionPicker" :targetNote="targetNote" asWindow :class="$style.picker" @chosen="chosen"/>
 </MkWindow>
 </template>
 
 <script lang="ts" setup>
 import { } from 'vue';
+import * as Misskey from 'misskey-js';
 import MkWindow from '@/components/MkWindow.vue';
 import MkEmojiPicker from '@/components/MkEmojiPicker.vue';
 
@@ -26,6 +27,7 @@ withDefaults(defineProps<{
 	src?: HTMLElement;
 	showPinned?: boolean;
 	asReactionPicker?: boolean;
+	targetNote?: Misskey.entities.Note
 }>(), {
 	showPinned: true,
 });
diff --git a/packages/frontend/src/components/MkLaunchPad.vue b/packages/frontend/src/components/MkLaunchPad.vue
index 79b2b91154..02bf987bde 100644
--- a/packages/frontend/src/components/MkLaunchPad.vue
+++ b/packages/frontend/src/components/MkLaunchPad.vue
@@ -119,6 +119,7 @@ function close() {
 				margin-top: 12px;
 				font-size: 0.8em;
 				line-height: 1.5em;
+				text-align: center;
 			}
 
 			> .indicatorWithValue {
diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue
index 0abad81d97..ff9bf3c395 100644
--- a/packages/frontend/src/components/MkNote.vue
+++ b/packages/frontend/src/components/MkNote.vue
@@ -285,13 +285,11 @@ const quoteButton = shallowRef<HTMLElement>();
 const clipButton = shallowRef<HTMLElement>();
 const likeButton = shallowRef<HTMLElement>();
 const appearNote = computed(() => isRenote ? note.value.renote as Misskey.entities.Note : note.value);
-const renoteUrl = appearNote.value.renote ? appearNote.value.renote.url : null;
-const renoteUri = appearNote.value.renote ? appearNote.value.renote.uri : null;
 
 const isMyRenote = $i && ($i.id === note.value.userId);
 const showContent = ref(defaultStore.state.uncollapseCW);
 const parsed = computed(() => appearNote.value.text ? mfm.parse(appearNote.value.text) : null);
-const urls = computed(() => parsed.value ? extractUrlFromMfm(parsed.value).filter(u => u !== renoteUrl && u !== renoteUri) : null);
+const urls = computed(() => parsed.value ? extractUrlFromMfm(parsed.value).filter((url) => appearNote.value.renote?.url !== url && appearNote.value.renote?.uri !== url) : null);
 const isLong = shouldCollapsed(appearNote.value, urls.value ?? []);
 const collapsed = ref(defaultStore.state.expandLongNote && appearNote.value.cw == null && isLong ? false : appearNote.value.cw == null && isLong);
 const isDeleted = ref(false);
@@ -624,7 +622,7 @@ function react(viaKeyboard = false): void {
 		}
 	} else {
 		blur();
-		reactionPicker.show(reactButton.value ?? null, reaction => {
+		reactionPicker.show(reactButton.value ?? null, note.value, reaction => {
 			sound.playMisskeySfx('reaction');
 
 			if (props.mock) {
diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue
index d31f77bbc2..a2e3a747d9 100644
--- a/packages/frontend/src/components/MkNoteDetailed.vue
+++ b/packages/frontend/src/components/MkNoteDetailed.vue
@@ -303,8 +303,6 @@ const quoteButton = shallowRef<HTMLElement>();
 const clipButton = shallowRef<HTMLElement>();
 const likeButton = shallowRef<HTMLElement>();
 const appearNote = computed(() => isRenote ? note.value.renote as Misskey.entities.Note : note.value);
-const renoteUrl = appearNote.value.renote ? appearNote.value.renote.url : null;
-const renoteUri = appearNote.value.renote ? appearNote.value.renote.uri : null;
 const isMyRenote = $i && ($i.id === note.value.userId);
 const showContent = ref(defaultStore.state.uncollapseCW);
 const isDeleted = ref(false);
@@ -313,7 +311,7 @@ const muted = ref($i ? checkWordMute(appearNote.value, $i, $i.mutedWords) : fals
 const translation = ref<Misskey.entities.NotesTranslateResponse | null>(null);
 const translating = ref(false);
 const parsed = appearNote.value.text ? mfm.parse(appearNote.value.text) : null;
-const urls = parsed ? extractUrlFromMfm(parsed).filter(u => u !== renoteUrl && u !== renoteUri) : null;
+const urls = parsed ? extractUrlFromMfm(parsed).filter((url) => appearNote.value.renote?.url !== url && appearNote.value.renote?.uri !== url) : null;
 const animated = computed(() => parsed ? checkAnimationFromMfm(parsed) : null);
 const allowAnim = ref(defaultStore.state.advancedMfm && defaultStore.state.animatedMfm ? true : false);
 const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && appearNote.value.user.instance);
@@ -612,7 +610,7 @@ function react(viaKeyboard = false): void {
 		}
 	} else {
 		blur();
-		reactionPicker.show(reactButton.value ?? null, reaction => {
+		reactionPicker.show(reactButton.value ?? null, note.value, reaction => {
 			sound.playMisskeySfx('reaction');
 
 			misskeyApi('notes/reactions/create', {
diff --git a/packages/frontend/src/components/MkPageWindow.vue b/packages/frontend/src/components/MkPageWindow.vue
index cb2db0b6a5..ee0963a9b5 100644
--- a/packages/frontend/src/components/MkPageWindow.vue
+++ b/packages/frontend/src/components/MkPageWindow.vue
@@ -76,7 +76,7 @@ const buttonsLeft = computed(() => {
 });
 const buttonsRight = computed(() => {
 	const buttons = [{
-		icon: 'ph-arrow-clockwise ph-bold ph-lg',
+		icon: 'ph-arrows-clockwise ph-bold ph-lg',
 		title: i18n.ts.reload,
 		onClick: reload,
 	}, {
diff --git a/packages/frontend/src/components/MkReactionsViewer.reaction.vue b/packages/frontend/src/components/MkReactionsViewer.reaction.vue
index e43841e5c9..356020a0de 100644
--- a/packages/frontend/src/components/MkReactionsViewer.reaction.vue
+++ b/packages/frontend/src/components/MkReactionsViewer.reaction.vue
@@ -32,6 +32,8 @@ import { claimAchievement } from '@/scripts/achievements.js';
 import { defaultStore } from '@/store.js';
 import { i18n } from '@/i18n.js';
 import * as sound from '@/scripts/sound.js';
+import { checkReactionPermissions } from '@/scripts/check-reaction-permissions.js';
+import { customEmojis } from '@/custom-emojis.js';
 
 const props = defineProps<{
 	reaction: string;
@@ -48,13 +50,19 @@ const emit = defineEmits<{
 
 const buttonEl = shallowRef<HTMLElement>();
 
-const canToggle = computed(() => !props.reaction.match(/@\w/) && $i);
+const isCustomEmoji = computed(() => props.reaction.includes(':'));
+const emoji = computed(() => isCustomEmoji.value ? customEmojis.value.find(emoji => emoji.name === props.reaction.replace(/:/g, '').replace(/@\./, '')) : null);
+
+const canToggle = computed(() => {
+	return !props.reaction.match(/@\w/) && $i
+			&& (emoji.value && checkReactionPermissions($i, props.note, emoji.value))
+			|| !isCustomEmoji.value;
+});
+const canGetInfo = computed(() => !props.reaction.match(/@\w/) && props.reaction.includes(':'));
 
 async function toggleReaction() {
 	if (!canToggle.value) return;
 
-	// TODO: その絵文字を使う権限があるかどうか確認
-
 	const oldReaction = props.note.myReaction;
 	if (oldReaction) {
 		const confirm = await os.confirm({
@@ -101,8 +109,8 @@ async function toggleReaction() {
 }
 
 async function menu(ev) {
-	if (!canToggle.value) return;
-	if (!props.reaction.includes(':')) return;
+	if (!canGetInfo.value) return;
+
 	os.popupMenu([{
 		text: i18n.ts.info,
 		icon: 'ph-info ph-bold ph-lg',
diff --git a/packages/frontend/src/components/MkSelect.vue b/packages/frontend/src/components/MkSelect.vue
index 6ac8297c02..1ed6c0d822 100644
--- a/packages/frontend/src/components/MkSelect.vue
+++ b/packages/frontend/src/components/MkSelect.vue
@@ -27,7 +27,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 	</div>
 	<div :class="$style.caption"><slot name="caption"></slot></div>
 
-	<MkButton v-if="manualSave && changed" primary @click="updated"><i class="ph-floppy-disk ph-bold ph-lg"></i> {{ i18n.ts.save }}</MkButton>
+	<MkButton v-if="manualSave && changed" primary :class="$style.save" @click="updated"><i class="ph-floppy-disk ph-bold ph-lg"></i> {{ i18n.ts.save }}</MkButton>
 </div>
 </template>
 
@@ -138,6 +138,7 @@ function show() {
 			active: computed(() => v.value === option.props?.value),
 			action: () => {
 				v.value = option.props?.value;
+				changed.value = true;
 				emit('changeByUser', v.value);
 			},
 		});
@@ -288,6 +289,10 @@ function show() {
 	padding-left: 6px;
 }
 
+.save {
+	margin: 8px 0 0 0;
+}
+
 .chevron {
 	transition: transform 0.1s ease-out;
 }
diff --git a/packages/frontend/src/components/SkNote.vue b/packages/frontend/src/components/SkNote.vue
index 84bcd3886d..61d32d721e 100644
--- a/packages/frontend/src/components/SkNote.vue
+++ b/packages/frontend/src/components/SkNote.vue
@@ -286,13 +286,11 @@ const quoteButton = shallowRef<HTMLElement>();
 const clipButton = shallowRef<HTMLElement>();
 const likeButton = shallowRef<HTMLElement>();
 const appearNote = computed(() => isRenote ? note.value.renote as Misskey.entities.Note : note.value);
-const renoteUrl = appearNote.value.renote ? appearNote.value.renote.url : null;
-const renoteUri = appearNote.value.renote ? appearNote.value.renote.uri : null;
 
 const isMyRenote = $i && ($i.id === note.value.userId);
 const showContent = ref(defaultStore.state.uncollapseCW);
 const parsed = computed(() => appearNote.value.text ? mfm.parse(appearNote.value.text) : null);
-const urls = computed(() => parsed.value ? extractUrlFromMfm(parsed.value).filter(u => u !== renoteUrl && u !== renoteUri) : null);
+const urls = computed(() => parsed.value ? extractUrlFromMfm(parsed.value).filter((url) => appearNote.value.renote?.url !== url && appearNote.value.renote?.uri !== url) : null);
 const isLong = shouldCollapsed(appearNote.value, urls.value ?? []);
 const collapsed = ref(defaultStore.state.expandLongNote && appearNote.value.cw == null && isLong ? false : appearNote.value.cw == null && isLong);
 const isDeleted = ref(false);
diff --git a/packages/frontend/src/components/SkNoteDetailed.vue b/packages/frontend/src/components/SkNoteDetailed.vue
index ca3ea09b2a..c1c20e5b29 100644
--- a/packages/frontend/src/components/SkNoteDetailed.vue
+++ b/packages/frontend/src/components/SkNoteDetailed.vue
@@ -312,8 +312,6 @@ const quoteButton = shallowRef<HTMLElement>();
 const clipButton = shallowRef<HTMLElement>();
 const likeButton = shallowRef<HTMLElement>();
 const appearNote = computed(() => isRenote ? note.value.renote as Misskey.entities.Note : note.value);
-const renoteUrl = appearNote.value.renote ? appearNote.value.renote.url : null;
-const renoteUri = appearNote.value.renote ? appearNote.value.renote.uri : null;
 const isMyRenote = $i && ($i.id === note.value.userId);
 const showContent = ref(defaultStore.state.uncollapseCW);
 const isDeleted = ref(false);
@@ -322,7 +320,7 @@ const muted = ref($i ? checkWordMute(appearNote.value, $i, $i.mutedWords) : fals
 const translation = ref<Misskey.entities.NotesTranslateResponse | null>(null);
 const translating = ref(false);
 const parsed = appearNote.value.text ? mfm.parse(appearNote.value.text) : null;
-const urls = parsed ? extractUrlFromMfm(parsed).filter(u => u !== renoteUrl && u !== renoteUri) : null;
+const urls = parsed ? extractUrlFromMfm(parsed).filter((url) => appearNote.value.renote?.url !== url && appearNote.value.renote?.uri !== url) : null;
 const animated = computed(() => parsed ? checkAnimationFromMfm(parsed) : null);
 const allowAnim = ref(defaultStore.state.advancedMfm && defaultStore.state.animatedMfm ? true : false);
 const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && appearNote.value.user.instance);
diff --git a/packages/frontend/src/components/global/RouterView.vue b/packages/frontend/src/components/global/RouterView.vue
index dc7474835d..aeb87e659b 100644
--- a/packages/frontend/src/components/global/RouterView.vue
+++ b/packages/frontend/src/components/global/RouterView.vue
@@ -4,7 +4,10 @@ SPDX-License-Identifier: AGPL-3.0-only
 -->
 
 <template>
-<KeepAlive :max="defaultStore.state.numberOfPageCache">
+<KeepAlive
+	:max="defaultStore.state.numberOfPageCache"
+	:exclude="pageCacheController"
+>
 	<Suspense :timeout="0">
 		<component :is="currentPageComponent" :key="key" v-bind="Object.fromEntries(currentPageProps)"/>
 
@@ -16,9 +19,11 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { inject, onBeforeUnmount, provide, ref, shallowRef } from 'vue';
-import { IRouter, Resolved } from '@/nirax.js';
+import { inject, onBeforeUnmount, provide, ref, shallowRef, computed, nextTick } from 'vue';
+import { IRouter, Resolved, RouteDef } from '@/nirax.js';
 import { defaultStore } from '@/store.js';
+import { globalEvents } from '@/events.js';
+import MkLoadingPage from '@/pages/_loading_.vue';
 
 const props = defineProps<{
 	router?: IRouter;
@@ -46,20 +51,47 @@ function resolveNested(current: Resolved, d = 0): Resolved | null {
 }
 
 const current = resolveNested(router.current)!;
-const currentPageComponent = shallowRef(current.route.component);
+const currentPageComponent = shallowRef('component' in current.route ? current.route.component : MkLoadingPage);
 const currentPageProps = ref(current.props);
 const key = ref(current.route.path + JSON.stringify(Object.fromEntries(current.props)));
 
 function onChange({ resolved, key: newKey }) {
 	const current = resolveNested(resolved);
-	if (current == null) return;
+	if (current == null || 'redirect' in current.route) return;
 	currentPageComponent.value = current.route.component;
 	currentPageProps.value = current.props;
 	key.value = current.route.path + JSON.stringify(Object.fromEntries(current.props));
+
+	nextTick(() => {
+		// ページ遷移完了後に再びキャッシュを有効化
+		if (clearCacheRequested.value) {
+			clearCacheRequested.value = false;
+		}
+	});
 }
 
 router.addListener('change', onChange);
 
+// #region キャッシュ制御
+
+/**
+ * キャッシュクリアが有効になったら、全キャッシュをクリアする
+ * 
+ * keepAlive側にwatcherがあるのですぐ消えるとはおもうけど、念のためページ遷移完了まではキャッシュを無効化しておく。
+ * キャッシュ有効時向けにexcludeを使いたい場合は、pageCacheControllerに並列に突っ込むのではなく、下に追記すること
+ */
+const pageCacheController = computed(() => clearCacheRequested.value ? /.*/ : undefined);
+const clearCacheRequested = ref(false);
+
+globalEvents.on('requestClearPageCache', () => {
+	if (_DEV_) console.log('clear page cache requested');
+	if (!clearCacheRequested.value) {
+		clearCacheRequested.value = true;
+	}
+});
+
+// #endregion
+
 onBeforeUnmount(() => {
 	router.removeListener('change', onChange);
 });
diff --git a/packages/frontend/src/events.ts b/packages/frontend/src/events.ts
index 90d5f6eede..46faec8d3e 100644
--- a/packages/frontend/src/events.ts
+++ b/packages/frontend/src/events.ts
@@ -4,6 +4,10 @@
  */
 
 import { EventEmitter } from 'eventemitter3';
+import * as Misskey from 'misskey-js';
 
-// TODO: 型付け
-export const globalEvents = new EventEmitter();
+export const globalEvents = new EventEmitter<{
+	themeChanged: () => void;
+	clientNotification: (notification: Misskey.entities.Notification) => void;
+	requestClearPageCache: () => void;
+}>();
diff --git a/packages/frontend/src/os.ts b/packages/frontend/src/os.ts
index 7cce77cdf7..010fdbb6d8 100644
--- a/packages/frontend/src/os.ts
+++ b/packages/frontend/src/os.ts
@@ -128,9 +128,10 @@ export function promiseDialog<T extends Promise<any>>(
 
 let popupIdCount = 0;
 export const popups = ref([]) as Ref<{
-	id: any;
-	component: any;
+	id: number;
+	component: Component;
 	props: Record<string, any>;
+	events: Record<string, any>;
 }[]>;
 
 const zIndexes = {
@@ -144,7 +145,18 @@ export function claimZIndex(priority: keyof typeof zIndexes = 'low'): number {
 	return zIndexes[priority];
 }
 
-export async function popup<T extends Component>(component: T, props: ComponentProps<T>, events = {}, disposeEvent?: string) {
+// InstanceType<typeof Component>['$emit'] だとインターセクション型が返ってきて
+// 使い物にならないので、代わりに ['$props'] から色々省くことで emit の型を生成する
+// FIXME: 何故か *.ts ファイルからだと型がうまく取れない?ことがあるのをなんとかしたい
+type ComponentEmit<T> = T extends new () => { $props: infer Props }
+	? EmitsExtractor<Props>
+	: never;
+
+type EmitsExtractor<T> = {
+	[K in keyof T as K extends `onVnode${string}` ? never : K extends `on${infer E}` ? Uncapitalize<E> : K extends string ? never : K]: T[K];
+};
+
+export async function popup<T extends Component>(component: T, props: ComponentProps<T>, events: ComponentEmit<T> = {} as ComponentEmit<T>, disposeEvent?: keyof ComponentEmit<T>) {
 	markRaw(component);
 
 	const id = ++popupIdCount;
diff --git a/packages/frontend/src/pages/admin/bot-protection.vue b/packages/frontend/src/pages/admin/bot-protection.vue
index 080a81767b..b39c825152 100644
--- a/packages/frontend/src/pages/admin/bot-protection.vue
+++ b/packages/frontend/src/pages/admin/bot-protection.vue
@@ -31,15 +31,15 @@ SPDX-License-Identifier: AGPL-3.0-only
 			</template>
 			<template v-else-if="provider === 'mcaptcha'">
 				<MkInput v-model="mcaptchaSiteKey">
-					<template #prefix><i class="ti ti-key"></i></template>
+					<template #prefix><i class="ph-key ph-bold ph-lg"></i></template>
 					<template #label>{{ i18n.ts.mcaptchaSiteKey }}</template>
 				</MkInput>
 				<MkInput v-model="mcaptchaSecretKey">
-					<template #prefix><i class="ti ti-key"></i></template>
+					<template #prefix><i class="ph-key ph-bold ph-lg"></i></template>
 					<template #label>{{ i18n.ts.mcaptchaSecretKey }}</template>
 				</MkInput>
 				<MkInput v-model="mcaptchaInstanceUrl">
-					<template #prefix><i class="ti ti-link"></i></template>
+					<template #prefix><i class="ph-globe-simple ph-bold ph-lg"></i></template>
 					<template #label>{{ i18n.ts.mcaptchaInstanceUrl }}</template>
 				</MkInput>
 				<FormSlot v-if="mcaptchaSiteKey && mcaptchaInstanceUrl">
diff --git a/packages/frontend/src/pages/admin/branding.vue b/packages/frontend/src/pages/admin/branding.vue
index 4238589f18..d0fce6407b 100644
--- a/packages/frontend/src/pages/admin/branding.vue
+++ b/packages/frontend/src/pages/admin/branding.vue
@@ -158,9 +158,9 @@ function save() {
 		themeColor: themeColor.value === '' ? null : themeColor.value,
 		defaultLightTheme: defaultLightTheme.value === '' ? null : defaultLightTheme.value,
 		defaultDarkTheme: defaultDarkTheme.value === '' ? null : defaultDarkTheme.value,
-		infoImageUrl: infoImageUrl.value,
-		notFoundImageUrl: notFoundImageUrl.value,
-		serverErrorImageUrl: serverErrorImageUrl.value,
+		infoImageUrl: infoImageUrl.value === '' ? null : infoImageUrl.value,
+		notFoundImageUrl: notFoundImageUrl.value === '' ? null : notFoundImageUrl.value,
+		serverErrorImageUrl: serverErrorImageUrl.value === '' ? null : serverErrorImageUrl.value,
 		manifestJsonOverride: manifestJsonOverride.value === '' ? '{}' : JSON.stringify(JSON5.parse(manifestJsonOverride.value)),
 	}).then(() => {
 		fetchInstance();
diff --git a/packages/frontend/src/pages/admin/security.vue b/packages/frontend/src/pages/admin/security.vue
index 7190dd436d..b1083636db 100644
--- a/packages/frontend/src/pages/admin/security.vue
+++ b/packages/frontend/src/pages/admin/security.vue
@@ -42,14 +42,14 @@ SPDX-License-Identifier: AGPL-3.0-only
 							<template #label>Use TrueMail API</template>
 						</MkSwitch>
 						<MkInput v-model="truemailInstance">
-							<template #prefix><i class="ti ti-key"></i></template>
+							<template #prefix><i class="ph-key ph-bold ph-lg"></i></template>
 							<template #label>TrueMail API Instance</template>
 						</MkInput>
 						<MkInput v-model="truemailAuthKey">
-							<template #prefix><i class="ti ti-key"></i></template>
+							<template #prefix><i class="ph-key ph-bold ph-lg"></i></template>
 							<template #label>TrueMail API Auth Key</template>
 						</MkInput>
-						<MkButton primary @click="save"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton>
+						<MkButton primary @click="save"><i class="ph-floppy-disk ph-bold ph-lg"></i> {{ i18n.ts.save }}</MkButton>
 					</div>
 				</MkFolder>
 
diff --git a/packages/frontend/src/pages/drop-and-fusion.game.vue b/packages/frontend/src/pages/drop-and-fusion.game.vue
index 6d805862e2..513b656097 100644
--- a/packages/frontend/src/pages/drop-and-fusion.game.vue
+++ b/packages/frontend/src/pages/drop-and-fusion.game.vue
@@ -893,7 +893,6 @@ function getGameImageDriveFile() {
 				formData.append('file', blob);
 				formData.append('name', `bubble-game-${Date.now()}.png`);
 				formData.append('isSensitive', 'false');
-				formData.append('comment', 'null');
 				formData.append('i', $i.token);
 				if (defaultStore.state.uploadFolder) {
 					formData.append('folderId', defaultStore.state.uploadFolder);
diff --git a/packages/frontend/src/pages/instance-info.vue b/packages/frontend/src/pages/instance-info.vue
index 4a4953bbc2..e8b29e9ba3 100644
--- a/packages/frontend/src/pages/instance-info.vue
+++ b/packages/frontend/src/pages/instance-info.vue
@@ -39,7 +39,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 						<MkSwitch v-model="isBlocked" :disabled="!meta || !instance" @update:modelValue="toggleBlock">{{ i18n.ts.blockThisInstance }}</MkSwitch>
 						<MkSwitch v-model="isSilenced" :disabled="!meta || !instance" @update:modelValue="toggleSilenced">{{ i18n.ts.silenceThisInstance }}</MkSwitch>
 						<MkSwitch v-model="isNSFW" :disabled="!instance" @update:modelValue="toggleNSFW">Mark as NSFW</MkSwitch>
-						<MkButton @click="refreshMetadata"><i class="ti ti-refresh"></i> Refresh metadata</MkButton>
+						<MkButton @click="refreshMetadata"><i class="ph-arrows-clockwise ph-bold ph-lg"></i> Refresh metadata</MkButton>
 					</div>
 				</FormSection>
 
diff --git a/packages/frontend/src/pages/reversi/index.vue b/packages/frontend/src/pages/reversi/index.vue
index fb812589b4..634a950391 100644
--- a/packages/frontend/src/pages/reversi/index.vue
+++ b/packages/frontend/src/pages/reversi/index.vue
@@ -36,13 +36,13 @@ SPDX-License-Identifier: AGPL-3.0-only
 					<div :class="$style.gamePreviews">
 						<MkA v-for="g in items" :key="g.id" v-panel :class="[$style.gamePreview, !g.isStarted && !g.isEnded && $style.gamePreviewWaiting, g.isStarted && !g.isEnded && $style.gamePreviewActive]" tabindex="-1" :to="`/reversi/g/${g.id}`">
 							<div :class="$style.gamePreviewPlayers">
-								<span v-if="g.winnerId === g.user1Id" style="margin-right: 0.75em; color: var(--accent); font-weight: bold;"><i class="ti ti-trophy"></i></span>
-								<span v-if="g.winnerId === g.user2Id" style="margin-right: 0.75em; visibility: hidden;"><i class="ti ti-x"></i></span>
+								<span v-if="g.winnerId === g.user1Id" style="margin-right: 0.75em; color: var(--accent); font-weight: bold;"><i class="ph-trophy ph-bold ph-lg"></i></span>
+								<span v-if="g.winnerId === g.user2Id" style="margin-right: 0.75em; visibility: hidden;"><i class="ph-x ph-bold ph-lg"></i></span>
 								<MkAvatar :class="$style.gamePreviewPlayersAvatar" :user="g.user1"/>
 								<span style="margin: 0 1em;">vs</span>
 								<MkAvatar :class="$style.gamePreviewPlayersAvatar" :user="g.user2"/>
-								<span v-if="g.winnerId === g.user1Id" style="margin-left: 0.75em; visibility: hidden;"><i class="ti ti-x"></i></span>
-								<span v-if="g.winnerId === g.user2Id" style="margin-left: 0.75em; color: var(--accent); font-weight: bold;"><i class="ti ti-trophy"></i></span>
+								<span v-if="g.winnerId === g.user1Id" style="margin-left: 0.75em; visibility: hidden;"><i class="ph-x ph-bold ph-lg"></i></span>
+								<span v-if="g.winnerId === g.user2Id" style="margin-left: 0.75em; color: var(--accent); font-weight: bold;"><i class="ph-trophy ph-bold ph-lg"></i></span>
 							</div>
 							<div :class="$style.gamePreviewFooter">
 								<span v-if="g.isStarted && !g.isEnded" :class="$style.gamePreviewStatusActive">{{ i18n.ts._reversi.playing }}</span>
@@ -63,13 +63,13 @@ SPDX-License-Identifier: AGPL-3.0-only
 					<div :class="$style.gamePreviews">
 						<MkA v-for="g in items" :key="g.id" v-panel :class="[$style.gamePreview, !g.isStarted && !g.isEnded && $style.gamePreviewWaiting, g.isStarted && !g.isEnded && $style.gamePreviewActive]" tabindex="-1" :to="`/reversi/g/${g.id}`">
 							<div :class="$style.gamePreviewPlayers">
-								<span v-if="g.winnerId === g.user1Id" style="margin-right: 0.75em; color: var(--accent); font-weight: bold;"><i class="ti ti-trophy"></i></span>
-								<span v-if="g.winnerId === g.user2Id" style="margin-right: 0.75em; visibility: hidden;"><i class="ti ti-x"></i></span>
+								<span v-if="g.winnerId === g.user1Id" style="margin-right: 0.75em; color: var(--accent); font-weight: bold;"><i class="ph-trophy ph-bold ph-lg"></i></span>
+								<span v-if="g.winnerId === g.user2Id" style="margin-right: 0.75em; visibility: hidden;"><i class="ph-x ph-bold ph-lg"></i></span>
 								<MkAvatar :class="$style.gamePreviewPlayersAvatar" :user="g.user1"/>
 								<span style="margin: 0 1em;">vs</span>
 								<MkAvatar :class="$style.gamePreviewPlayersAvatar" :user="g.user2"/>
-								<span v-if="g.winnerId === g.user1Id" style="margin-left: 0.75em; visibility: hidden;"><i class="ti ti-x"></i></span>
-								<span v-if="g.winnerId === g.user2Id" style="margin-left: 0.75em; color: var(--accent); font-weight: bold;"><i class="ti ti-trophy"></i></span>
+								<span v-if="g.winnerId === g.user1Id" style="margin-left: 0.75em; visibility: hidden;"><i class="ph-x ph-bold ph-lg"></i></span>
+								<span v-if="g.winnerId === g.user2Id" style="margin-left: 0.75em; color: var(--accent); font-weight: bold;"><i class="ph-trophy ph-bold ph-lg"></i></span>
 							</div>
 							<div :class="$style.gamePreviewFooter">
 								<span v-if="g.isStarted && !g.isEnded" :class="$style.gamePreviewStatusActive">{{ i18n.ts._reversi.playing }}</span>
diff --git a/packages/frontend/src/pages/settings/emoji-picker.vue b/packages/frontend/src/pages/settings/emoji-picker.vue
index 40bb823ac6..383841d02f 100644
--- a/packages/frontend/src/pages/settings/emoji-picker.vue
+++ b/packages/frontend/src/pages/settings/emoji-picker.vue
@@ -172,7 +172,7 @@ const chooseEmoji = (ev: MouseEvent) => pickEmoji(pinnedEmojis, ev);
 const setDefaultEmoji = () => setDefault(pinnedEmojis);
 
 function previewReaction(ev: MouseEvent) {
-	reactionPicker.show(getHTMLElement(ev));
+	reactionPicker.show(getHTMLElement(ev), null);
 }
 
 function previewEmoji(ev: MouseEvent) {
diff --git a/packages/frontend/src/pages/settings/profile.vue b/packages/frontend/src/pages/settings/profile.vue
index abf929e30b..37fca4f8ae 100644
--- a/packages/frontend/src/pages/settings/profile.vue
+++ b/packages/frontend/src/pages/settings/profile.vue
@@ -132,6 +132,7 @@ import { langmap } from '@/scripts/langmap.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import { claimAchievement } from '@/scripts/achievements.js';
 import { defaultStore } from '@/store.js';
+import { globalEvents } from '@/events.js';
 import MkInfo from '@/components/MkInfo.vue';
 import MkTextarea from '@/components/MkTextarea.vue';
 
@@ -158,7 +159,7 @@ const profile = reactive({
 	lang: $i.lang,
 	isBot: $i.isBot ?? false,
 	isCat: $i.isCat ?? false,
-	speakAsCat: $i.speakAsCat,
+	speakAsCat: $i.speakAsCat ?? false,
 });
 
 watch(() => profile, () => {
@@ -190,6 +191,7 @@ function saveFields() {
 	os.apiWithDialog('i/update', {
 		fields: fields.value.filter(field => field.name !== '' && field.value !== '').map(field => ({ name: field.name, value: field.value })),
 	});
+	globalEvents.emit('requestClearPageCache');
 }
 
 function save() {
@@ -217,6 +219,7 @@ function save() {
 		isCat: !!profile.isCat,
 		speakAsCat: !!profile.speakAsCat,
 	});
+	globalEvents.emit('requestClearPageCache');
 	claimAchievement('profileFilled');
 	if (profile.name === 'syuilo' || profile.name === 'しゅいろ') {
 		claimAchievement('setNameToSyuilo');
@@ -248,6 +251,7 @@ function changeAvatar(ev) {
 		});
 		$i.avatarId = i.avatarId;
 		$i.avatarUrl = i.avatarUrl;
+		globalEvents.emit('requestClearPageCache');
 		claimAchievement('profileFilled');
 	});
 }
@@ -278,6 +282,7 @@ function changeBanner(ev) {
 					});
 					$i.bannerId = i.bannerId;
 					$i.bannerUrl = i.bannerUrl;
+					globalEvents.emit('requestClearPageCache');
 				});
 			},
 		}, {
@@ -288,6 +293,7 @@ function changeBanner(ev) {
 				});
 				$i.bannerId = i.bannerId;
 				$i.bannerUrl = i.bannerUrl;
+				globalEvents.emit('requestClearPageCache');
 			},
 		}], ev.currentTarget ?? ev.target);
 	} else {
@@ -312,6 +318,7 @@ function changeBanner(ev) {
 			});
 			$i.bannerId = i.bannerId;
 			$i.bannerUrl = i.bannerUrl;
+			globalEvents.emit('requestClearPageCache');
 		});
 	}
 }
@@ -342,6 +349,7 @@ function changeBackground(ev) {
 					});
 					$i.backgroundId = i.backgroundId;
 					$i.backgroundUrl = i.backgroundUrl;
+					globalEvents.emit('requestClearPageCache');
 				});
 			},
 		}, {
@@ -352,6 +360,7 @@ function changeBackground(ev) {
 				});
 				$i.backgroundId = i.backgroundId;
 				$i.backgroundUrl = i.backgroundUrl;
+				globalEvents.emit('requestClearPageCache');
 			},
 		}], ev.currentTarget ?? ev.target);
 	} else {
@@ -376,6 +385,7 @@ function changeBackground(ev) {
 			});
 			$i.backgroundId = i.backgroundId;
 			$i.backgroundUrl = i.backgroundUrl;
+			globalEvents.emit('requestClearPageCache');
 		});
 	}
 }
diff --git a/packages/frontend/src/pages/settings/theme.vue b/packages/frontend/src/pages/settings/theme.vue
index cb9c714441..f01ebc1377 100644
--- a/packages/frontend/src/pages/settings/theme.vue
+++ b/packages/frontend/src/pages/settings/theme.vue
@@ -88,6 +88,18 @@ import { uniqueBy } from '@/scripts/array.js';
 import { fetchThemes, getThemes } from '@/theme-store.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import { miLocalStorage } from '@/local-storage.js';
+import { unisonReload } from '@/scripts/unison-reload.js';
+import * as os from '@/os.js';
+
+async function reloadAsk() {
+	const { canceled } = await os.confirm({
+		type: 'info',
+		text: i18n.ts.reloadToApplySetting,
+	});
+	if (canceled) return;
+
+	unisonReload();
+}
 
 const installedThemes = ref(getThemes());
 const builtinThemes = getBuiltinThemesRef();
@@ -124,6 +136,7 @@ const lightThemeId = computed({
 		}
 	},
 });
+
 const darkMode = computed(defaultStore.makeGetterSetter('darkMode'));
 const syncDeviceDarkMode = computed(ColdDeviceStorage.makeGetterSetter('syncDeviceDarkMode'));
 const wallpaper = ref(miLocalStorage.getItem('wallpaper'));
@@ -141,7 +154,7 @@ watch(wallpaper, () => {
 	} else {
 		miLocalStorage.setItem('wallpaper', wallpaper.value);
 	}
-	location.reload();
+	reloadAsk();
 });
 
 onActivated(() => {
diff --git a/packages/frontend/src/pizzax.ts b/packages/frontend/src/pizzax.ts
index 68c36ca1b4..199addaefd 100644
--- a/packages/frontend/src/pizzax.ts
+++ b/packages/frontend/src/pizzax.ts
@@ -13,6 +13,7 @@ import { get, set } from '@/scripts/idb-proxy.js';
 import { defaultStore } from '@/store.js';
 import { useStream } from '@/stream.js';
 import { deepClone } from '@/scripts/clone.js';
+import { deepMerge } from '@/scripts/merge.js';
 
 type StateDef = Record<string, {
 	where: 'account' | 'device' | 'deviceAccount';
@@ -84,29 +85,9 @@ export class Storage<T extends StateDef> {
 		return typeof value === 'object' && value !== null && !Array.isArray(value);
 	}
 
-	/**
-	 * valueにないキーをdefからもらう(再帰的)\
-	 * nullはそのまま、undefinedはdefの値
-	 **/
-	private mergeObject<X>(value: X, def: X): X {
-		if (this.isPureObject(value) && this.isPureObject(def)) {
-			const result = structuredClone(value) as X;
-			for (const [k, v] of Object.entries(def) as [keyof X, X[keyof X]][]) {
-				if (!Object.prototype.hasOwnProperty.call(value, k) || value[k] === undefined) {
-					result[k] = v;
-				} else if (this.isPureObject(v) && this.isPureObject(result[k])) {
-					const child = structuredClone(result[k]) as X[keyof X] & Record<string | number | symbol, unknown>;
-					result[k] = this.mergeObject<typeof v>(child, v);
-				}
-			}
-			return result;
-		}
-		return value;
-	}
-
 	private mergeState<X>(value: X, def: X): X {
 		if (this.isPureObject(value) && this.isPureObject(def)) {
-			const merged = this.mergeObject(value, def);
+			const merged = deepMerge(value, def);
 
 			if (_DEV_) console.log('Merging state. Incoming: ', value, ' Default: ', def, ' Result: ', merged);
 
@@ -258,7 +239,7 @@ export class Storage<T extends StateDef> {
 
 	/**
 	 * 特定のキーの、簡易的なgetter/setterを作ります
-	 * 主にvue場で設定コントロールのmodelとして使う用
+	 * 主にvue上で設定コントロールのmodelとして使う用
 	 */
 	public makeGetterSetter<K extends keyof T>(key: K, getter?: (v: T[K]) => unknown, setter?: (v: unknown) => T[K]): {
 		get: () => T[K]['default'];
diff --git a/packages/frontend/src/router/main.ts b/packages/frontend/src/router/main.ts
index 5adb3f606f..c6a520e913 100644
--- a/packages/frontend/src/router/main.ts
+++ b/packages/frontend/src/router/main.ts
@@ -80,6 +80,10 @@ class MainRouterProxy implements IRouter {
 		return this.supplier().resolve(path);
 	}
 
+	init(): void {
+		this.supplier().init();
+	}
+
 	eventNames(): Array<EventEmitter.EventNames<RouterEvent>> {
 		return this.supplier().eventNames();
 	}
diff --git a/packages/frontend/src/scripts/check-reaction-permissions.ts b/packages/frontend/src/scripts/check-reaction-permissions.ts
new file mode 100644
index 0000000000..c9d2a5bfc6
--- /dev/null
+++ b/packages/frontend/src/scripts/check-reaction-permissions.ts
@@ -0,0 +1,8 @@
+import * as Misskey from 'misskey-js';
+
+export function checkReactionPermissions(me: Misskey.entities.MeDetailed, note: Misskey.entities.Note, emoji: Misskey.entities.EmojiSimple): boolean {
+  const roleIdsThatCanBeUsedThisEmojiAsReaction = emoji.roleIdsThatCanBeUsedThisEmojiAsReaction ?? [];
+  return !(emoji.localOnly && note.user.host !== me.host)
+      && !(emoji.isSensitive && (note.reactionAcceptance === 'nonSensitiveOnly' || note.reactionAcceptance === 'nonSensitiveOnlyForLocalLikeOnlyForRemote'))
+      && (roleIdsThatCanBeUsedThisEmojiAsReaction.length === 0 || me.roles.some(role => roleIdsThatCanBeUsedThisEmojiAsReaction.includes(role.id)));
+}
diff --git a/packages/frontend/src/scripts/clone.ts b/packages/frontend/src/scripts/clone.ts
index ac38faefaa..6d3a1c8c79 100644
--- a/packages/frontend/src/scripts/clone.ts
+++ b/packages/frontend/src/scripts/clone.ts
@@ -8,13 +8,13 @@
 // あと、Vue RefをIndexedDBに保存しようとしてstructredCloneを使ったらエラーになった
 // https://github.com/misskey-dev/misskey/pull/8098#issuecomment-1114144045
 
-type Cloneable = string | number | boolean | null | undefined | { [key: string]: Cloneable } | Cloneable[];
+export type Cloneable = string | number | boolean | null | undefined | { [key: string]: Cloneable } | { [key: number]: Cloneable } | { [key: symbol]: Cloneable } | Cloneable[];
 
 export function deepClone<T extends Cloneable>(x: T): T {
 	if (typeof x === 'object') {
 		if (x === null) return x;
 		if (Array.isArray(x)) return x.map(deepClone) as T;
-		const obj = {} as Record<string, Cloneable>;
+		const obj = {} as Record<string | number | symbol, Cloneable>;
 		for (const [k, v] of Object.entries(x)) {
 			obj[k] = v === undefined ? undefined : deepClone(v);
 		}
diff --git a/packages/frontend/src/scripts/code-highlighter.ts b/packages/frontend/src/scripts/code-highlighter.ts
index bc05ec94d5..b11dfed41a 100644
--- a/packages/frontend/src/scripts/code-highlighter.ts
+++ b/packages/frontend/src/scripts/code-highlighter.ts
@@ -1,9 +1,51 @@
+import { bundledThemesInfo } from 'shiki';
 import { getHighlighterCore, loadWasm } from 'shiki/core';
 import darkPlus from 'shiki/themes/dark-plus.mjs';
-import type { Highlighter, LanguageRegistration } from 'shiki';
+import { unique } from './array.js';
+import { deepClone } from './clone.js';
+import { deepMerge } from './merge.js';
+import type { Highlighter, LanguageRegistration, ThemeRegistration, ThemeRegistrationRaw } from 'shiki';
+import { ColdDeviceStorage } from '@/store.js';
+import lightTheme from '@/themes/_light.json5';
+import darkTheme from '@/themes/_dark.json5';
 
 let _highlighter: Highlighter | null = null;
 
+export async function getTheme(mode: 'light' | 'dark', getName: true): Promise<string>;
+export async function getTheme(mode: 'light' | 'dark', getName?: false): Promise<ThemeRegistration | ThemeRegistrationRaw>;
+export async function getTheme(mode: 'light' | 'dark', getName = false): Promise<ThemeRegistration | ThemeRegistrationRaw | string | null> {
+	const theme = deepClone(ColdDeviceStorage.get(mode === 'light' ? 'lightTheme' : 'darkTheme'));
+
+	if (theme.base) {
+		const base = [lightTheme, darkTheme].find(x => x.id === theme.base);
+		if (base && base.codeHighlighter) theme.codeHighlighter = Object.assign({}, base.codeHighlighter, theme.codeHighlighter);
+	}
+	
+	if (theme.codeHighlighter) {
+		let _res: ThemeRegistration = {};
+		if (theme.codeHighlighter.base === '_none_') {
+			_res = deepClone(theme.codeHighlighter.overrides);
+		} else {
+			const base = await bundledThemesInfo.find(t => t.id === theme.codeHighlighter!.base)?.import() ?? darkPlus;
+			_res = deepMerge(theme.codeHighlighter.overrides ?? {}, 'default' in base ? base.default : base);
+		}
+		if (_res.name == null) {
+			_res.name = theme.id;
+		}
+		_res.type = mode;
+
+		if (getName) {
+			return _res.name;
+		}
+		return _res;
+	}
+
+	if (getName) {
+		return 'dark-plus';
+	}
+	return darkPlus;
+}
+
 export async function getHighlighter(): Promise<Highlighter> {
 	if (!_highlighter) {
 		return await initHighlighter();
@@ -13,11 +55,17 @@ export async function getHighlighter(): Promise<Highlighter> {
 
 export async function initHighlighter() {
 	const aiScriptGrammar = await import('aiscript-vscode/aiscript/syntaxes/aiscript.tmLanguage.json');
-
+	
 	await loadWasm(import('shiki/onig.wasm?init'));
 
+	// テーマの重複を消す
+	const themes = unique([
+		darkPlus,
+		...(await Promise.all([getTheme('light'), getTheme('dark')])),
+	]);
+
 	const highlighter = await getHighlighterCore({
-		themes: [darkPlus],
+		themes,
 		langs: [
 			import('shiki/langs/javascript.mjs'),
 			{
@@ -27,6 +75,20 @@ export async function initHighlighter() {
 		],
 	});
 
+	ColdDeviceStorage.watch('lightTheme', async () => {
+		const newTheme = await getTheme('light');
+		if (newTheme.name && !highlighter.getLoadedThemes().includes(newTheme.name)) {
+			highlighter.loadTheme(newTheme);
+		}
+	});
+
+	ColdDeviceStorage.watch('darkTheme', async () => {
+		const newTheme = await getTheme('dark');
+		if (newTheme.name && !highlighter.getLoadedThemes().includes(newTheme.name)) {
+			highlighter.loadTheme(newTheme);
+		}
+	});
+
 	_highlighter = highlighter;
 
 	return highlighter;
diff --git a/packages/frontend/src/scripts/merge.ts b/packages/frontend/src/scripts/merge.ts
new file mode 100644
index 0000000000..60097051fa
--- /dev/null
+++ b/packages/frontend/src/scripts/merge.ts
@@ -0,0 +1,31 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { deepClone } from './clone.js';
+import type { Cloneable } from './clone.js';
+
+function isPureObject(value: unknown): value is Record<string | number | symbol, unknown> {
+	return typeof value === 'object' && value !== null && !Array.isArray(value);
+}
+
+/**
+ * valueにないキーをdefからもらう(再帰的)\
+ * nullはそのまま、undefinedはdefの値
+ **/
+export function deepMerge<X extends Record<string | number | symbol, unknown>>(value: X, def: X): X {
+	if (isPureObject(value) && isPureObject(def)) {
+		const result = deepClone(value as Cloneable) as X;
+		for (const [k, v] of Object.entries(def) as [keyof X, X[keyof X]][]) {
+			if (!Object.prototype.hasOwnProperty.call(value, k) || value[k] === undefined) {
+				result[k] = v;
+			} else if (isPureObject(v) && isPureObject(result[k])) {
+				const child = deepClone(result[k] as Cloneable) as X[keyof X] & Record<string | number | symbol, unknown>;
+				result[k] = deepMerge<typeof v>(child, v);
+			}
+		}
+		return result;
+	}
+	return value;
+}
diff --git a/packages/frontend/src/scripts/reaction-picker.ts b/packages/frontend/src/scripts/reaction-picker.ts
index a13351b536..193ac838a2 100644
--- a/packages/frontend/src/scripts/reaction-picker.ts
+++ b/packages/frontend/src/scripts/reaction-picker.ts
@@ -3,6 +3,7 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
+import * as Misskey from 'misskey-js';
 import { defineAsyncComponent, Ref, ref } from 'vue';
 import { popup } from '@/os.js';
 import { defaultStore } from '@/store.js';
@@ -10,6 +11,7 @@ import { defaultStore } from '@/store.js';
 class ReactionPicker {
 	private src: Ref<HTMLElement | null> = ref(null);
 	private manualShowing = ref(false);
+	private targetNote: Ref<Misskey.entities.Note | null> = ref(null);
 	private onChosen?: (reaction: string) => void;
 	private onClosed?: () => void;
 
@@ -23,6 +25,7 @@ class ReactionPicker {
 			src: this.src,
 			pinnedEmojis: reactionsRef,
 			asReactionPicker: true,
+			targetNote: this.targetNote,
 			manualShowing: this.manualShowing,
 		}, {
 			done: reaction => {
@@ -38,8 +41,9 @@ class ReactionPicker {
 		});
 	}
 
-	public show(src: HTMLElement | null, onChosen?: ReactionPicker['onChosen'], onClosed?: ReactionPicker['onClosed']) {
+	public show(src: HTMLElement | null, targetNote: Misskey.entities.Note | null, onChosen?: ReactionPicker['onChosen'], onClosed?: ReactionPicker['onClosed']) {
 		this.src.value = src;
+		this.targetNote.value = targetNote;
 		this.manualShowing.value = true;
 		this.onChosen = onChosen;
 		this.onClosed = onClosed;
diff --git a/packages/frontend/src/scripts/theme.ts b/packages/frontend/src/scripts/theme.ts
index a174f51756..05ccd3dc38 100644
--- a/packages/frontend/src/scripts/theme.ts
+++ b/packages/frontend/src/scripts/theme.ts
@@ -6,6 +6,7 @@
 import { ref } from 'vue';
 import tinycolor from 'tinycolor2';
 import { deepClone } from './clone.js';
+import type { BuiltinTheme } from 'shiki';
 import { globalEvents } from '@/events.js';
 import lightTheme from '@/themes/_light.json5';
 import darkTheme from '@/themes/_dark.json5';
@@ -18,6 +19,13 @@ export type Theme = {
 	desc?: string;
 	base?: 'dark' | 'light';
 	props: Record<string, string>;
+	codeHighlighter?: {
+		base: BuiltinTheme;
+		overrides?: Record<string, any>;
+	} | {
+		base: '_none_';
+		overrides: Record<string, any>;
+	};
 };
 
 export const themeProps = Object.keys(lightTheme.props).filter(key => !key.startsWith('X'));
@@ -57,7 +65,7 @@ export const getBuiltinThemesRef = () => {
 
 const themeFontFaceName = 'sharkey-theme-font-face';
 
-let timeout = null;
+let timeout: number | null = null;
 
 export function applyTheme(theme: Theme, persist = true) {
 	if (timeout) window.clearTimeout(timeout);
diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts
index 4dad6ce406..d695caa95f 100644
--- a/packages/frontend/src/store.ts
+++ b/packages/frontend/src/store.ts
@@ -7,6 +7,7 @@ import { markRaw, ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import { miLocalStorage } from './local-storage.js';
 import type { SoundType } from '@/scripts/sound.js';
+import type { BuiltinTheme as ShikiBuiltinTheme } from 'shiki';
 import { Storage } from '@/pizzax.js';
 import { hemisphere } from '@/scripts/intl-const.js';
 
diff --git a/packages/frontend/src/themes/_dark.json5 b/packages/frontend/src/themes/_dark.json5
index 8544572718..7b70aa1e09 100644
--- a/packages/frontend/src/themes/_dark.json5
+++ b/packages/frontend/src/themes/_dark.json5
@@ -95,4 +95,8 @@
 		X16: ':alpha<0.7<@panel',
 		X17: ':alpha<0.8<@bg',
 	},
+
+	codeHighlighter: {
+		base: 'one-dark-pro',
+	},
 }
diff --git a/packages/frontend/src/themes/_light.json5 b/packages/frontend/src/themes/_light.json5
index 2f3783310f..d797aec734 100644
--- a/packages/frontend/src/themes/_light.json5
+++ b/packages/frontend/src/themes/_light.json5
@@ -95,4 +95,8 @@
 		X16: ':alpha<0.7<@panel',
 		X17: ':alpha<0.8<@bg',
 	},
+
+	codeHighlighter: {
+		base: 'catppuccin-latte',
+	},
 }
diff --git a/packages/misskey-js/generator/src/generator.ts b/packages/misskey-js/generator/src/generator.ts
index 7e72359167..f091e599a9 100644
--- a/packages/misskey-js/generator/src/generator.ts
+++ b/packages/misskey-js/generator/src/generator.ts
@@ -4,22 +4,6 @@ import { toPascal } from 'ts-case-convert';
 import OpenAPIParser from '@readme/openapi-parser';
 import openapiTS from 'openapi-typescript';
 
-function generateVersionHeaderComment(openApiDocs: OpenAPIV3_1.Document): string {
-	const contents = {
-		version: openApiDocs.info.version,
-		generatedAt: new Date().toISOString(),
-	};
-
-	const lines: string[] = [];
-	lines.push('/*');
-	for (const [key, value] of Object.entries(contents)) {
-		lines.push(` * ${key}: ${value}`);
-	}
-	lines.push(' */');
-
-	return lines.join('\n');
-}
-
 async function generateBaseTypes(
 	openApiDocs: OpenAPIV3_1.Document,
 	openApiJsonPath: string,
@@ -36,9 +20,6 @@ async function generateBaseTypes(
 	}
 	lines.push('');
 
-	lines.push(generateVersionHeaderComment(openApiDocs));
-	lines.push('');
-
 	const generatedTypes = await openapiTS(openApiJsonPath, { exportType: true });
 	lines.push(generatedTypes);
 	lines.push('');
@@ -59,8 +40,6 @@ async function generateSchemaEntities(
 	const schemaNames = Object.keys(schemas);
 	const typeAliasLines: string[] = [];
 
-	typeAliasLines.push(generateVersionHeaderComment(openApiDocs));
-	typeAliasLines.push('');
 	typeAliasLines.push(`import { components } from '${toImportPath(typeFileName)}';`);
 	typeAliasLines.push(
 		...schemaNames.map(it => `export type ${it} = components['schemas']['${it}'];`),
@@ -119,9 +98,6 @@ async function generateEndpoints(
 
 	const entitiesOutputLine: string[] = [];
 
-	entitiesOutputLine.push(generateVersionHeaderComment(openApiDocs));
-	entitiesOutputLine.push('');
-
 	entitiesOutputLine.push(`import { operations } from '${toImportPath(typeFileName)}';`);
 	entitiesOutputLine.push('');
 
@@ -139,9 +115,6 @@ async function generateEndpoints(
 
 	const endpointOutputLine: string[] = [];
 
-	endpointOutputLine.push(generateVersionHeaderComment(openApiDocs));
-	endpointOutputLine.push('');
-
 	endpointOutputLine.push('import type {');
 	endpointOutputLine.push(
 		...[emptyRequest, emptyResponse, ...entities].map(it => '\t' + it.generateName() + ','),
@@ -187,9 +160,6 @@ async function generateApiClientJSDoc(
 
 	const endpointOutputLine: string[] = [];
 
-	endpointOutputLine.push(generateVersionHeaderComment(openApiDocs));
-	endpointOutputLine.push('');
-
 	endpointOutputLine.push(`import type { SwitchCaseResponseType } from '${toImportPath(apiClientFileName)}';`);
 	endpointOutputLine.push(`import type { Endpoints } from '${toImportPath(endpointsFileName)}';`);
 	endpointOutputLine.push('');
diff --git a/packages/misskey-js/src/autogen/apiClientJSDoc.ts b/packages/misskey-js/src/autogen/apiClientJSDoc.ts
index 205fba69a7..2d9d78228e 100644
--- a/packages/misskey-js/src/autogen/apiClientJSDoc.ts
+++ b/packages/misskey-js/src/autogen/apiClientJSDoc.ts
@@ -1,8 +1,3 @@
-/*
- * version: 2024.2.0-beta2
- * generatedAt: 2024-02-03T19:17:05.681Z
- */
-
 import type { SwitchCaseResponseType } from '../api.js';
 import type { Endpoints } from './endpoint.js';
 
diff --git a/packages/misskey-js/src/autogen/endpoint.ts b/packages/misskey-js/src/autogen/endpoint.ts
index 28fc82c609..3f95d3cf4b 100644
--- a/packages/misskey-js/src/autogen/endpoint.ts
+++ b/packages/misskey-js/src/autogen/endpoint.ts
@@ -1,8 +1,3 @@
-/*
- * version: 2024.2.0-beta2
- * generatedAt: 2024-02-03T19:17:05.679Z
- */
-
 import type {
 	EmptyRequest,
 	EmptyResponse,
diff --git a/packages/misskey-js/src/autogen/entities.ts b/packages/misskey-js/src/autogen/entities.ts
index 17471a27f0..3e96fcc61c 100644
--- a/packages/misskey-js/src/autogen/entities.ts
+++ b/packages/misskey-js/src/autogen/entities.ts
@@ -1,8 +1,3 @@
-/*
- * version: 2024.2.0-beta2
- * generatedAt: 2024-02-03T19:17:05.678Z
- */
-
 import { operations } from './types.js';
 
 export type EmptyRequest = Record<string, unknown> | undefined;
diff --git a/packages/misskey-js/src/autogen/models.ts b/packages/misskey-js/src/autogen/models.ts
index 5d87df588c..8d594e61bb 100644
--- a/packages/misskey-js/src/autogen/models.ts
+++ b/packages/misskey-js/src/autogen/models.ts
@@ -1,8 +1,3 @@
-/*
- * version: 2024.2.0-beta2
- * generatedAt: 2024-02-03T19:17:05.676Z
- */
-
 import { components } from './types.js';
 export type Error = components['schemas']['Error'];
 export type UserLite = components['schemas']['UserLite'];
diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts
index 54765e86f2..08a2bd7412 100644
--- a/packages/misskey-js/src/autogen/types.ts
+++ b/packages/misskey-js/src/autogen/types.ts
@@ -1,11 +1,6 @@
 /* eslint @typescript-eslint/naming-convention: 0 */
 /* eslint @typescript-eslint/no-explicit-any: 0 */
 
-/*
- * version: 2024.2.0-beta2
- * generatedAt: 2024-02-03T19:17:05.578Z
- */
-
 /**
  * This file was auto-generated by openapi-typescript.
  * Do not make direct changes to the file.
@@ -4556,6 +4551,7 @@ export type components = {
       name: string;
       category: string | null;
       url: string;
+      localOnly?: boolean;
       isSensitive?: boolean;
       roleIdsThatCanBeUsedThisEmojiAsReaction?: string[];
     };
-- 
GitLab


From 1fa347390a7cb65ca71446c812d3a92d117fd970 Mon Sep 17 00:00:00 2001
From: Marie <marie@kaifa.ch>
Date: Tue, 6 Feb 2024 21:27:42 +0100
Subject: [PATCH 20/40] fix: add missing diff between SkNote* and MkNote*

---
 packages/frontend/src/components/SkNote.vue         | 2 +-
 packages/frontend/src/components/SkNoteDetailed.vue | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/packages/frontend/src/components/SkNote.vue b/packages/frontend/src/components/SkNote.vue
index 61d32d721e..dc74c4928d 100644
--- a/packages/frontend/src/components/SkNote.vue
+++ b/packages/frontend/src/components/SkNote.vue
@@ -623,7 +623,7 @@ function react(viaKeyboard = false): void {
 		}
 	} else {
 		blur();
-		reactionPicker.show(reactButton.value ?? null, reaction => {
+		reactionPicker.show(reactButton.value ?? null, note.value, reaction => {
 			sound.playMisskeySfx('reaction');
 
 			if (props.mock) {
diff --git a/packages/frontend/src/components/SkNoteDetailed.vue b/packages/frontend/src/components/SkNoteDetailed.vue
index c1c20e5b29..ef17a2e8a6 100644
--- a/packages/frontend/src/components/SkNoteDetailed.vue
+++ b/packages/frontend/src/components/SkNoteDetailed.vue
@@ -619,7 +619,7 @@ function react(viaKeyboard = false): void {
 		}
 	} else {
 		blur();
-		reactionPicker.show(reactButton.value ?? null, reaction => {
+		reactionPicker.show(reactButton.value ?? null, note.value, reaction => {
 			sound.playMisskeySfx('reaction');
 
 			misskeyApi('notes/reactions/create', {
-- 
GitLab


From e89d7602403718c9ec5412db14e1288857d6c9a5 Mon Sep 17 00:00:00 2001
From: MeiMei <30769358+mei23@users.noreply.github.com>
Date: Wed, 7 Feb 2024 19:57:59 +0900
Subject: [PATCH 21/40] =?UTF-8?q?Enhance:=20=E9=80=A3=E5=90=88=E5=90=91?=
 =?UTF-8?q?=E3=81=91=E3=81=AE=E3=83=8E=E3=83=BC=E3=83=88=E9=85=8D=E4=BF=A1?=
 =?UTF-8?q?=E3=82=92=E8=BB=BD=E9=87=8F=E5=8C=96=20(#13192)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* AP HTML表現をシンプルに

* a

* CHANGELOG

* リンク
---
 CHANGELOG.md                                  |  1 +
 packages/backend/src/core/MfmService.ts       |  4 ++
 .../src/core/activitypub/ApMfmService.ts      | 19 ++++++--
 .../src/core/activitypub/ApRendererService.ts | 23 +++++-----
 packages/backend/test/unit/ApMfmService.ts    | 44 +++++++++++++++++++
 packages/backend/test/unit/MfmService.ts      |  6 +++
 6 files changed, 81 insertions(+), 16 deletions(-)
 create mode 100644 packages/backend/test/unit/ApMfmService.ts

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 63bf42c72f..cb4a17d95b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -87,6 +87,7 @@
 - Fix: properly handle cc followers
 - Fix: ジョブに関する設定の名前を修正 relashionshipJobPerSec -> relationshipJobPerSec
 - Fix: コントロールパネル->モデレーション->「誰でも新規登録できるようにする」の初期値をONからOFFに変更 #13122
+- Enhance: 連合向けのノート配信を軽量化 #13192
 
 ### Service Worker
 - Enhance: オフライン表示のデザインを改善・多言語対応
diff --git a/packages/backend/src/core/MfmService.ts b/packages/backend/src/core/MfmService.ts
index e74c62e1a8..b2d8382bf6 100644
--- a/packages/backend/src/core/MfmService.ts
+++ b/packages/backend/src/core/MfmService.ts
@@ -419,6 +419,10 @@ export class MfmService {
 			},
 
 			text: (node) => {
+				if (!node.props.text.match(/[\r\n]/)) {
+					return doc.createTextNode(node.props.text);
+				}
+
 				const el = doc.createElement('span');
 				const nodes = node.props.text.split(/\r\n|\r|\n/).map(x => doc.createTextNode(x));
 
diff --git a/packages/backend/src/core/activitypub/ApMfmService.ts b/packages/backend/src/core/activitypub/ApMfmService.ts
index 60868627a2..737c34f3c5 100644
--- a/packages/backend/src/core/activitypub/ApMfmService.ts
+++ b/packages/backend/src/core/activitypub/ApMfmService.ts
@@ -25,8 +25,21 @@ export class ApMfmService {
 	}
 
 	@bindThis
-	public getNoteHtml(note: MiNote): string | null {
-		if (!note.text) return '';
-		return this.mfmService.toHtml(mfm.parse(note.text), JSON.parse(note.mentionedRemoteUsers));
+	public getNoteHtml(note: MiNote, apAppend?: string) {
+		let noMisskeyContent = false;
+		const srcMfm = (note.text ?? '') + (apAppend ?? '');
+
+		const parsed = mfm.parse(srcMfm);
+
+		if (!apAppend && parsed?.every(n => ['text', 'unicodeEmoji', 'emojiCode', 'mention', 'hashtag', 'url'].includes(n.type))) {
+			noMisskeyContent = true;
+		}
+
+		const content = this.mfmService.toHtml(parsed, JSON.parse(note.mentionedRemoteUsers));
+
+		return {
+			content,
+			noMisskeyContent,
+		};
 	}
 }
diff --git a/packages/backend/src/core/activitypub/ApRendererService.ts b/packages/backend/src/core/activitypub/ApRendererService.ts
index 530e4a3c33..211f4f4a44 100644
--- a/packages/backend/src/core/activitypub/ApRendererService.ts
+++ b/packages/backend/src/core/activitypub/ApRendererService.ts
@@ -389,17 +389,15 @@ export class ApRendererService {
 			poll = await this.pollsRepository.findOneBy({ noteId: note.id });
 		}
 
-		let apText = text;
+		let apAppend = '';
 
 		if (quote) {
-			apText += `\n\nRE: ${quote}`;
+			apAppend += `\n\nRE: ${quote}`;
 		}
 
 		const summary = note.cw === '' ? String.fromCharCode(0x200B) : note.cw;
 
-		const content = this.apMfmService.getNoteHtml(Object.assign({}, note, {
-			text: apText,
-		}));
+		const { content, noMisskeyContent } = this.apMfmService.getNoteHtml(note, apAppend);
 
 		const emojis = await this.getEmojis(note.emojis);
 		const apemojis = emojis.filter(emoji => !emoji.localOnly).map(emoji => this.renderEmoji(emoji));
@@ -412,9 +410,6 @@ export class ApRendererService {
 
 		const asPoll = poll ? {
 			type: 'Question',
-			content: this.apMfmService.getNoteHtml(Object.assign({}, note, {
-				text: text,
-			})),
 			[poll.expiresAt && poll.expiresAt < new Date() ? 'closed' : 'endTime']: poll.expiresAt,
 			[poll.multiple ? 'anyOf' : 'oneOf']: poll.choices.map((text, i) => ({
 				type: 'Note',
@@ -432,11 +427,13 @@ export class ApRendererService {
 			attributedTo,
 			summary: summary ?? undefined,
 			content: content ?? undefined,
-			_misskey_content: text,
-			source: {
-				content: text,
-				mediaType: 'text/x.misskeymarkdown',
-			},
+			...(noMisskeyContent ? {} : {
+				_misskey_content: text,
+				source: {
+					content: text,
+					mediaType: 'text/x.misskeymarkdown',
+				},
+			}),
 			_misskey_quote: quote,
 			quoteUrl: quote,
 			published: this.idService.parse(note.id).date.toISOString(),
diff --git a/packages/backend/test/unit/ApMfmService.ts b/packages/backend/test/unit/ApMfmService.ts
new file mode 100644
index 0000000000..2b79041c86
--- /dev/null
+++ b/packages/backend/test/unit/ApMfmService.ts
@@ -0,0 +1,44 @@
+import * as assert from 'assert';
+import { Test } from '@nestjs/testing';
+
+import { CoreModule } from '@/core/CoreModule.js';
+import { ApMfmService } from '@/core/activitypub/ApMfmService.js';
+import { GlobalModule } from '@/GlobalModule.js';
+import { MiNote } from '@/models/Note.js';
+
+describe('ApMfmService', () => {
+	let apMfmService: ApMfmService;
+
+	beforeAll(async () => {
+		const app = await Test.createTestingModule({
+			imports: [GlobalModule, CoreModule],
+		}).compile();
+		apMfmService = app.get<ApMfmService>(ApMfmService);
+	});
+
+	describe('getNoteHtml', () => {
+		test('Do not provide _misskey_content for simple text', () => {
+			const note: MiNote = {
+				text: 'テキスト #タグ @mention 🍊 :emoji: https://example.com',
+				mentionedRemoteUsers: '[]',
+			} as any;
+
+			const { content, noMisskeyContent } = apMfmService.getNoteHtml(note);
+
+			assert.equal(noMisskeyContent, true, 'noMisskeyContent');
+			assert.equal(content, '<p>テキスト <a href="http://misskey.local/tags/タグ" rel="tag">#タグ</a> <a href="http://misskey.local/@mention" class="u-url mention">@mention</a> 🍊 ​:emoji:​ <a href="https://example.com">https://example.com</a></p>', 'content');
+		});
+
+		test('Provide _misskey_content for MFM', () => {
+			const note: MiNote = {
+				text: '$[tada foo]',
+				mentionedRemoteUsers: '[]',
+			} as any;
+
+			const { content, noMisskeyContent } = apMfmService.getNoteHtml(note);
+
+			assert.equal(noMisskeyContent, false, 'noMisskeyContent');
+			assert.equal(content, '<p><i>foo</i></p>', 'content');
+		});
+	});
+});
diff --git a/packages/backend/test/unit/MfmService.ts b/packages/backend/test/unit/MfmService.ts
index bb8e6981d5..c3827b20d2 100644
--- a/packages/backend/test/unit/MfmService.ts
+++ b/packages/backend/test/unit/MfmService.ts
@@ -33,6 +33,12 @@ describe('MfmService', () => {
 			const output = '<p><span>foo<br>bar<br>baz</span></p>';
 			assert.equal(mfmService.toHtml(mfm.parse(input)), output);
 		});
+
+		test('Do not generate unnecessary span', () => {
+			const input = 'foo $[tada bar]';
+			const output = '<p>foo <i>bar</i></p>';
+			assert.equal(mfmService.toHtml(mfm.parse(input)), output);
+		});
 	});
 
 	describe('fromHtml', () => {
-- 
GitLab


From 56d7f5862683b2136976608c5f4fa21dfc6748e2 Mon Sep 17 00:00:00 2001
From: 1Step621 <86859447+1STEP621@users.noreply.github.com>
Date: Wed, 7 Feb 2024 19:58:21 +0900
Subject: [PATCH 22/40] =?UTF-8?q?Fix(frontend):=20MkCodeEditor=E3=81=A7?=
 =?UTF-8?q?=E8=A1=8C=E3=81=8C=E3=81=9A=E3=82=8C=E3=81=A6=E3=81=84=E3=81=8F?=
 =?UTF-8?q?=E3=81=AE=E3=82=92=E4=BF=AE=E6=AD=A3=20(#13188)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* MkCodeEditorで行がずれていくのを修正

* update CHANGELOG.md
---
 CHANGELOG.md                                     | 1 +
 packages/frontend/src/components/MkCode.core.vue | 1 +
 2 files changed, 2 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index cb4a17d95b..652c7ea7c5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -73,6 +73,7 @@
 - Fix: キャプションが空の画像をクロップするとキャプションにnullという文字列が入ってしまう問題の修正
 - Fix: プロフィールを編集してもリロードするまで反映されない問題を修正
 - Fix: エラー画像URLを設定した後解除すると,デフォルトの画像が表示されない問題の修正
+- Fix: MkCodeEditorで行がずれていってしまう問題の修正
 
 ### Server
 - Enhance: 連合先のレートリミットに引っかかった際にリトライするようになりました
diff --git a/packages/frontend/src/components/MkCode.core.vue b/packages/frontend/src/components/MkCode.core.vue
index 68c50c4c69..02a0ea475e 100644
--- a/packages/frontend/src/components/MkCode.core.vue
+++ b/packages/frontend/src/components/MkCode.core.vue
@@ -78,6 +78,7 @@ watch(() => props.lang, (to) => {
 	overflow: auto;
 	border-radius: 8px;
 	border: 1px solid var(--divider);
+	font-family: Consolas, Monaco, Andale Mono, Ubuntu Mono, monospace;
 
 	color: var(--shiki-fallback);
 	background-color: var(--shiki-fallback-bg);
-- 
GitLab


From 313ce821926efc0e01fd815c7582d8bb30f7e935 Mon Sep 17 00:00:00 2001
From: MeiMei <30769358+mei23@users.noreply.github.com>
Date: Wed, 7 Feb 2024 19:59:06 +0900
Subject: [PATCH 23/40] =?UTF-8?q?=E6=AD=A3=E3=81=97=E3=81=84=202024.2.0-be?=
 =?UTF-8?q?ta.10=20=E6=94=B9=E7=89=88=E6=89=8B=E9=A0=86=EF=BC=9F=20(#13173?=
 =?UTF-8?q?)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* 正しい 2024.2.0-beta.10 改版手順?

* run build-misskey-js-with-types
---
 packages/misskey-js/package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/misskey-js/package.json b/packages/misskey-js/package.json
index 03952c7ac1..0c4dd52d14 100644
--- a/packages/misskey-js/package.json
+++ b/packages/misskey-js/package.json
@@ -1,7 +1,7 @@
 {
 	"type": "module",
 	"name": "misskey-js",
-	"version": "2024.2.0-beta.8",
+	"version": "2024.2.0-beta.10",
 	"description": "Misskey SDK for JavaScript",
 	"types": "./built/dts/index.d.ts",
 	"exports": {
-- 
GitLab


From 155896a851f2a1060454ff614b5fecde4a8dd016 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?=
 <67428053+kakkokari-gtyih@users.noreply.github.com>
Date: Wed, 7 Feb 2024 20:02:29 +0900
Subject: [PATCH 24/40] =?UTF-8?q?enhance(frontend/HorizontalSwipe):=20?=
 =?UTF-8?q?=E6=93=8D=E4=BD=9C=E6=80=A7=E3=81=AE=E6=94=B9=E5=96=84=20(#1303?=
 =?UTF-8?q?8)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* Update swipe thresholds and touch-action

* スワイプ中にPullToRefreshが反応しないように

* 横スワイプに関与する可能性のある要素がある場合はスワイプを発火しないように

* update threshold

* isSwipingを外部化

* rename

---------

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
---
 .../src/components/MkHorizontalSwipe.vue      | 34 ++++++++++++++++---
 .../src/components/MkPullToRefresh.vue        |  7 +++-
 packages/frontend/src/scripts/touch.ts        |  4 +++
 3 files changed, 39 insertions(+), 6 deletions(-)

diff --git a/packages/frontend/src/components/MkHorizontalSwipe.vue b/packages/frontend/src/components/MkHorizontalSwipe.vue
index 67d32c505a..bf7d43fd43 100644
--- a/packages/frontend/src/components/MkHorizontalSwipe.vue
+++ b/packages/frontend/src/components/MkHorizontalSwipe.vue
@@ -25,11 +25,11 @@ SPDX-License-Identifier: AGPL-3.0-only
 	</Transition>
 </div>
 </template>
-
 <script lang="ts" setup>
 import { ref, shallowRef, computed, nextTick, watch } from 'vue';
 import type { Tab } from '@/components/global/MkPageHeader.tabs.vue';
 import { defaultStore } from '@/store.js';
+import { isHorizontalSwipeSwiping as isSwiping } from '@/scripts/touch.js';
 
 const rootEl = shallowRef<HTMLDivElement>();
 
@@ -49,16 +49,16 @@ const shouldAnimate = computed(() => defaultStore.reactiveState.enableHorizontal
 // ▼ しきい値 ▼ //
 
 // スワイプと判定される最小の距離
-const MIN_SWIPE_DISTANCE = 50;
+const MIN_SWIPE_DISTANCE = 20;
 
 // スワイプ時の動作を発火する最小の距離
-const SWIPE_DISTANCE_THRESHOLD = 125;
+const SWIPE_DISTANCE_THRESHOLD = 70;
 
 // スワイプを中断するY方向の移動距離
 const SWIPE_ABORT_Y_THRESHOLD = 75;
 
 // スワイプできる最大の距離
-const MAX_SWIPE_DISTANCE = 150;
+const MAX_SWIPE_DISTANCE = 120;
 
 // ▲ しきい値 ▲ //
 
@@ -68,7 +68,6 @@ let startScreenY: number | null = null;
 const currentTabIndex = computed(() => props.tabs.findIndex(tab => tab.key === tabModel.value));
 
 const pullDistance = ref(0);
-const isSwiping = ref(false);
 const isSwipingForClass = ref(false);
 let swipeAborted = false;
 
@@ -77,6 +76,8 @@ function touchStart(event: TouchEvent) {
 
 	if (event.touches.length !== 1) return;
 
+	if (hasSomethingToDoWithXSwipe(event.target as HTMLElement)) return;
+
 	startScreenX = event.touches[0].screenX;
 	startScreenY = event.touches[0].screenY;
 }
@@ -90,6 +91,8 @@ function touchMove(event: TouchEvent) {
 
 	if (swipeAborted) return;
 
+	if (hasSomethingToDoWithXSwipe(event.target as HTMLElement)) return;
+
 	let distanceX = event.touches[0].screenX - startScreenX;
 	let distanceY = event.touches[0].screenY - startScreenY;
 
@@ -139,6 +142,8 @@ function touchEnd(event: TouchEvent) {
 
 	if (!isSwiping.value) return;
 
+	if (hasSomethingToDoWithXSwipe(event.target as HTMLElement)) return;
+
 	const distance = event.changedTouches[0].screenX - startScreenX;
 
 	if (Math.abs(distance) > SWIPE_DISTANCE_THRESHOLD) {
@@ -162,6 +167,24 @@ function touchEnd(event: TouchEvent) {
 	}, 400);
 }
 
+/** 横スワイプに関与する可能性のある要素を調べる */
+function hasSomethingToDoWithXSwipe(el: HTMLElement) {
+	if (['INPUT', 'TEXTAREA'].includes(el.tagName)) return true;
+	if (el.isContentEditable) return true;
+	if (el.scrollWidth > el.clientWidth) return true;
+
+	const style = window.getComputedStyle(el);
+	if (['absolute', 'fixed', 'sticky'].includes(style.position)) return true;
+	if (['scroll', 'auto'].includes(style.overflowX)) return true;
+	if (style.touchAction === 'pan-x') return true;
+
+	if (el.parentElement && el.parentElement !== rootEl.value) {
+		return hasSomethingToDoWithXSwipe(el.parentElement);
+	} else {
+		return false;
+	}
+}
+
 const transitionName = ref<'swipeAnimationLeft' | 'swipeAnimationRight' | undefined>(undefined);
 
 watch(tabModel, (newTab, oldTab) => {
@@ -182,6 +205,7 @@ watch(tabModel, (newTab, oldTab) => {
 
 <style lang="scss" module>
 .transitionRoot {
+	touch-action: pan-y pinch-zoom;
 	display: grid;
 	grid-template-columns: 100%;
 	overflow: clip;
diff --git a/packages/frontend/src/components/MkPullToRefresh.vue b/packages/frontend/src/components/MkPullToRefresh.vue
index 54ef117d77..e730d63afe 100644
--- a/packages/frontend/src/components/MkPullToRefresh.vue
+++ b/packages/frontend/src/components/MkPullToRefresh.vue
@@ -26,6 +26,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { onMounted, onUnmounted, ref, shallowRef } from 'vue';
 import { i18n } from '@/i18n.js';
 import { getScrollContainer } from '@/scripts/scroll.js';
+import { isHorizontalSwipeSwiping } from '@/scripts/touch.js';
 
 const SCROLL_STOP = 10;
 const MAX_PULL_DISTANCE = Infinity;
@@ -129,7 +130,7 @@ function moveEnd() {
 function moving(event: TouchEvent | PointerEvent) {
 	if (!isPullStart.value || isRefreshing.value || disabled) return;
 
-	if ((scrollEl?.scrollTop ?? 0) > (supportPointerDesktop ? SCROLL_STOP : SCROLL_STOP + pullDistance.value)) {
+	if ((scrollEl?.scrollTop ?? 0) > (supportPointerDesktop ? SCROLL_STOP : SCROLL_STOP + pullDistance.value) || isHorizontalSwipeSwiping.value) {
 		pullDistance.value = 0;
 		isPullEnd.value = false;
 		moveEnd();
@@ -148,6 +149,10 @@ function moving(event: TouchEvent | PointerEvent) {
 		if (event.cancelable) event.preventDefault();
 	}
 
+	if (pullDistance.value > SCROLL_STOP) {
+		event.stopPropagation();
+	}
+
 	isPullEnd.value = pullDistance.value >= FIRE_THRESHOLD;
 }
 
diff --git a/packages/frontend/src/scripts/touch.ts b/packages/frontend/src/scripts/touch.ts
index 05f379e4aa..4fd7d500c4 100644
--- a/packages/frontend/src/scripts/touch.ts
+++ b/packages/frontend/src/scripts/touch.ts
@@ -3,6 +3,7 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
+import { ref } from 'vue';
 import { deviceKind } from '@/scripts/device-kind.js';
 
 const isTouchSupported = 'maxTouchPoints' in navigator && navigator.maxTouchPoints > 0;
@@ -16,3 +17,6 @@ if (isTouchSupported && !isTouchUsing) {
 		isTouchUsing = true;
 	}, { passive: true });
 }
+
+/** (MkHorizontalSwipe) 横スワイプ中か? */
+export const isHorizontalSwipeSwiping = ref(false);
-- 
GitLab


From 52bf808d8981a72693580d111f38d371f4bdf740 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Wed, 7 Feb 2024 20:52:23 +0900
Subject: [PATCH 25/40] typo

---
 packages/frontend/src/pages/about-misskey.vue | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/packages/frontend/src/pages/about-misskey.vue b/packages/frontend/src/pages/about-misskey.vue
index acaae9f1d7..5c00f94cc2 100644
--- a/packages/frontend/src/pages/about-misskey.vue
+++ b/packages/frontend/src/pages/about-misskey.vue
@@ -213,13 +213,13 @@ const patronsWithIcon = [{
 	icon: 'https://assets.misskey-hub.net/patrons/302dce2898dd457ba03c3f7dc037900b.jpg',
 }, {
 	name: 'taichan',
-	icon: 'https://assets.misskey-hub.net/patrons/f981ab0159fb4e2c998e05f7263e1cd9.png',
+	icon: 'https://assets.misskey-hub.net/patrons/f981ab0159fb4e2c998e05f7263e1cd9.jpg',
 }, {
 	name: '猫吉よりお',
-	icon: 'https://assets.misskey-hub.net/patrons/a11518b3b34b4536a4bdd7178ba76a7b.png',
+	icon: 'https://assets.misskey-hub.net/patrons/a11518b3b34b4536a4bdd7178ba76a7b.jpg',
 }, {
 	name: '有栖かずみ',
-	icon: 'https://assets.misskey-hub.net/patrons/9240e8e0ba294a8884143e99ac7ed6a0.png',
+	icon: 'https://assets.misskey-hub.net/patrons/9240e8e0ba294a8884143e99ac7ed6a0.jpg',
 }];
 
 const patrons = [
-- 
GitLab


From 82c34f7f45888d07f153c7ea0f313f0a231fc26c Mon Sep 17 00:00:00 2001
From: MeiMei <30769358+mei23@users.noreply.github.com>
Date: Thu, 8 Feb 2024 13:16:22 +0900
Subject: [PATCH 26/40] =?UTF-8?q?Fix:=20Summaly=20proxy=E5=88=A9=E7=94=A8?=
 =?UTF-8?q?=E6=99=82=E3=81=AB=E3=83=97=E3=83=AC=E3=82=A4=E3=83=A4=E3=83=BC?=
 =?UTF-8?q?=E3=81=8C=E5=8B=95=E4=BD=9C=E3=81=97=E3=81=AA=E3=81=84=E3=81=93?=
 =?UTF-8?q?=E3=81=A8=E3=81=8C=E3=81=82=E3=82=8B=E3=81=AE=E3=82=92=E4=BF=AE?=
 =?UTF-8?q?=E6=AD=A3=20(#13196)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* Fix: Summaly proxy利用時にプレイヤーが動作しないことがあるのを修正

* CHANGELOG
---
 CHANGELOG.md                                  |  1 +
 .../frontend/src/components/MkUrlPreview.vue  |  2 +-
 packages/frontend/test/url-preview.test.ts    | 28 +++++++++++++++++++
 3 files changed, 30 insertions(+), 1 deletion(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 652c7ea7c5..a32c557c94 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -74,6 +74,7 @@
 - Fix: プロフィールを編集してもリロードするまで反映されない問題を修正
 - Fix: エラー画像URLを設定した後解除すると,デフォルトの画像が表示されない問題の修正
 - Fix: MkCodeEditorで行がずれていってしまう問題の修正
+- Fix: Summaly proxy利用時にプレイヤーが動作しないことがあるのを修正 #13196
 
 ### Server
 - Enhance: 連合先のレートリミットに引っかかった際にリトライするようになりました
diff --git a/packages/frontend/src/components/MkUrlPreview.vue b/packages/frontend/src/components/MkUrlPreview.vue
index 2dcb5f226d..eb5e90ea9d 100644
--- a/packages/frontend/src/components/MkUrlPreview.vue
+++ b/packages/frontend/src/components/MkUrlPreview.vue
@@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 			v-if="player.url.startsWith('http://') || player.url.startsWith('https://')"
 			sandbox="allow-popups allow-scripts allow-storage-access-by-user-activation allow-same-origin"
 			scrolling="no"
-			:allow="player.allow.join(';')"
+			:allow="player.allow == null ? 'autoplay;encrypted-media;fullscreen' : player.allow.filter(x => ['autoplay', 'clipboard-write', 'fullscreen', 'encrypted-media', 'picture-in-picture', 'web-share'].includes(x)).join(';')"
 			:class="$style.playerIframe"
 			:src="player.url + (player.url.match(/\?/) ? '&autoplay=1&auto_play=1' : '?autoplay=1&auto_play=1')"
 			:style="{ border: 0 }"
diff --git a/packages/frontend/test/url-preview.test.ts b/packages/frontend/test/url-preview.test.ts
index 6cf8317c07..b7587754c6 100644
--- a/packages/frontend/test/url-preview.test.ts
+++ b/packages/frontend/test/url-preview.test.ts
@@ -116,6 +116,34 @@ describe('MkUrlPreview', () => {
 		assert.strictEqual(iframe?.allow, 'fullscreen;web-share');
 	});
 
+	test('A Summaly proxy response without allow falls back to the default', async () => {
+		const iframe = await renderAndOpenPreview({
+			url: 'https://example.local',
+			player: {
+				url: 'https://example.local/player',
+				width: null,
+				height: null,
+				allow: undefined as any,
+			},
+		});
+		assert.exists(iframe, 'iframe should exist');
+		assert.strictEqual(iframe?.allow, 'autoplay;encrypted-media;fullscreen');
+	});
+
+	test('Filtering the allow list from the Summaly proxy', async () => {
+		const iframe = await renderAndOpenPreview({
+			url: 'https://example.local',
+			player: {
+				url: 'https://example.local/player',
+				width: null,
+				height: null,
+				allow: ['autoplay', 'camera', 'fullscreen'],
+			},
+		});
+		assert.exists(iframe, 'iframe should exist');
+		assert.strictEqual(iframe?.allow, 'autoplay;fullscreen');
+	});
+
 	test('Having a player width should keep the fixed aspect ratio', async () => {
 		const iframe = await renderAndOpenPreview({
 			url: 'https://example.local',
-- 
GitLab


From 5299d17060d72f1a81edffccedff2f1c78a9c520 Mon Sep 17 00:00:00 2001
From: zyoshoka <107108195+zyoshoka@users.noreply.github.com>
Date: Thu, 8 Feb 2024 13:28:49 +0900
Subject: [PATCH 27/40] test(frontend): migrate MSW in Storybook to v2 (#13195)

---
 CONTRIBUTING.md                               |   9 +-
 packages/frontend/.storybook/mocks.ts         |  32 +++--
 packages/frontend/package.json                |   4 +-
 .../components/MkAbuseReport.stories.impl.ts  |   8 +-
 .../MkAbuseReportWindow.stories.impl.ts       |   8 +-
 .../components/MkAchievements.stories.impl.ts |  10 +-
 .../components/MkAutocomplete.stories.impl.ts |  14 +-
 .../src/components/MkAvatars.stories.impl.ts  |   8 +-
 .../components/MkInviteCode.stories.impl.ts   |   6 +-
 .../MkUserSetupDialog.Follow.stories.impl.ts  |  14 +-
 .../MkUserSetupDialog.stories.impl.ts         |  14 +-
 .../components/global/MkUrl.stories.impl.ts   |   8 +-
 .../src/pages/user/home.stories.impl.ts       |  24 ++--
 pnpm-lock.yaml                                | 124 ++++++++----------
 14 files changed, 140 insertions(+), 143 deletions(-)

diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 7f6c1f4f82..ac0a1ba3c1 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -286,18 +286,17 @@ export const argTypes = {
 			min: 1,
 			max: 4,
 		},
+	},
 };
 ```
 
 Also, you can use msw to mock API requests in the storybook. Creating a `MyComponent.stories.msw.ts` file to define the mock handlers.
 
 ```ts
-import { rest } from 'msw';
+import { HttpResponse, http } from 'msw';
 export const handlers = [
-	rest.post('/api/notes/timeline', (req, res, ctx) => {
-		return res(
-			ctx.json([]),
-		);
+	http.post('/api/notes/timeline', ({ request }) => {
+		return HttpResponse.json([]);
 	}),
 ];
 ```
diff --git a/packages/frontend/.storybook/mocks.ts b/packages/frontend/.storybook/mocks.ts
index 80e5157c5a..f0feff9f78 100644
--- a/packages/frontend/.storybook/mocks.ts
+++ b/packages/frontend/.storybook/mocks.ts
@@ -3,7 +3,7 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-import { type SharedOptions, rest } from 'msw';
+import { type SharedOptions, http, HttpResponse } from 'msw';
 
 export const onUnhandledRequest = ((req, print) => {
 	if (req.url.hostname !== 'localhost' || /^\/(?:client-assets\/|fluent-emojis?\/|iframe.html$|node_modules\/|src\/|sb-|static-assets\/|vite\/)/.test(req.url.pathname)) {
@@ -13,19 +13,31 @@ export const onUnhandledRequest = ((req, print) => {
 }) satisfies SharedOptions['onUnhandledRequest'];
 
 export const commonHandlers = [
-	rest.get('/fluent-emoji/:codepoints.png', async (req, res, ctx) => {
-		const { codepoints } = req.params;
+	http.get('/fluent-emoji/:codepoints.png', async ({ params }) => {
+		const { codepoints } = params;
 		const value = await fetch(`https://raw.githubusercontent.com/misskey-dev/emojis/main/dist/${codepoints}.png`).then((response) => response.blob());
-		return res(ctx.set('Content-Type', 'image/png'), ctx.body(value));
+		return new HttpResponse(value, {
+			headers: {
+				'Content-Type': 'image/png',
+			},
+		});
 	}),
-	rest.get('/fluent-emojis/:codepoints.png', async (req, res, ctx) => {
-		const { codepoints } = req.params;
+	http.get('/fluent-emojis/:codepoints.png', async ({ params }) => {
+		const { codepoints } = params;
 		const value = await fetch(`https://raw.githubusercontent.com/misskey-dev/emojis/main/dist/${codepoints}.png`).then((response) => response.blob());
-		return res(ctx.set('Content-Type', 'image/png'), ctx.body(value));
+		return new HttpResponse(value, {
+			headers: {
+				'Content-Type': 'image/png',
+			},
+		});
 	}),
-	rest.get('/twemoji/:codepoints.svg', async (req, res, ctx) => {
-		const { codepoints } = req.params;
+	http.get('/twemoji/:codepoints.svg', async ({ params }) => {
+		const { codepoints } = params;
 		const value = await fetch(`https://unpkg.com/@discordapp/twemoji@15.0.2/dist/svg/${codepoints}.svg`).then((response) => response.blob());
-		return res(ctx.set('Content-Type', 'image/svg+xml'), ctx.body(value));
+		return new HttpResponse(value, {
+			headers: {
+				'Content-Type': 'image/svg+xml',
+			},
+		});
 	}),
 ];
diff --git a/packages/frontend/package.json b/packages/frontend/package.json
index d614f75886..9e88c6c036 100644
--- a/packages/frontend/package.json
+++ b/packages/frontend/package.json
@@ -122,8 +122,8 @@
 		"happy-dom": "10.0.3",
 		"intersection-observer": "0.12.2",
 		"micromatch": "4.0.5",
-		"msw": "2.1.2",
-		"msw-storybook-addon": "1.10.0",
+		"msw": "2.1.7",
+		"msw-storybook-addon": "2.0.0-beta.1",
 		"nodemon": "3.0.3",
 		"prettier": "3.2.4",
 		"react": "18.2.0",
diff --git a/packages/frontend/src/components/MkAbuseReport.stories.impl.ts b/packages/frontend/src/components/MkAbuseReport.stories.impl.ts
index 77e7c84d5c..dc2697f25c 100644
--- a/packages/frontend/src/components/MkAbuseReport.stories.impl.ts
+++ b/packages/frontend/src/components/MkAbuseReport.stories.impl.ts
@@ -6,7 +6,7 @@
 /* eslint-disable @typescript-eslint/explicit-function-return-type */
 import { action } from '@storybook/addon-actions';
 import { StoryObj } from '@storybook/vue3';
-import { rest } from 'msw';
+import { HttpResponse, http } from 'msw';
 import { abuseUserReport } from '../../.storybook/fakes.js';
 import { commonHandlers } from '../../.storybook/mocks.js';
 import MkAbuseReport from './MkAbuseReport.vue';
@@ -44,9 +44,9 @@ export const Default = {
 		msw: {
 			handlers: [
 				...commonHandlers,
-				rest.post('/api/admin/resolve-abuse-user-report', async (req, res, ctx) => {
-					action('POST /api/admin/resolve-abuse-user-report')(await req.json());
-					return res(ctx.json({}));
+				http.post('/api/admin/resolve-abuse-user-report', async ({ request }) => {
+					action('POST /api/admin/resolve-abuse-user-report')(await request.json());
+					return HttpResponse.json({});
 				}),
 			],
 		},
diff --git a/packages/frontend/src/components/MkAbuseReportWindow.stories.impl.ts b/packages/frontend/src/components/MkAbuseReportWindow.stories.impl.ts
index dc842b3d1b..771452cb5f 100644
--- a/packages/frontend/src/components/MkAbuseReportWindow.stories.impl.ts
+++ b/packages/frontend/src/components/MkAbuseReportWindow.stories.impl.ts
@@ -6,7 +6,7 @@
 /* eslint-disable @typescript-eslint/explicit-function-return-type */
 import { action } from '@storybook/addon-actions';
 import { StoryObj } from '@storybook/vue3';
-import { rest } from 'msw';
+import { HttpResponse, http } from 'msw';
 import { userDetailed } from '../../.storybook/fakes.js';
 import { commonHandlers } from '../../.storybook/mocks.js';
 import MkAbuseReportWindow from './MkAbuseReportWindow.vue';
@@ -44,9 +44,9 @@ export const Default = {
 		msw: {
 			handlers: [
 				...commonHandlers,
-				rest.post('/api/users/report-abuse', async (req, res, ctx) => {
-					action('POST /api/users/report-abuse')(await req.json());
-					return res(ctx.json({}));
+				http.post('/api/users/report-abuse', async ({ request }) => {
+					action('POST /api/users/report-abuse')(await request.json());
+					return HttpResponse.json({});
 				}),
 			],
 		},
diff --git a/packages/frontend/src/components/MkAchievements.stories.impl.ts b/packages/frontend/src/components/MkAchievements.stories.impl.ts
index 6d972467b1..81e9529de2 100644
--- a/packages/frontend/src/components/MkAchievements.stories.impl.ts
+++ b/packages/frontend/src/components/MkAchievements.stories.impl.ts
@@ -5,7 +5,7 @@
 
 /* eslint-disable @typescript-eslint/explicit-function-return-type */
 import { StoryObj } from '@storybook/vue3';
-import { rest } from 'msw';
+import { HttpResponse, http } from 'msw';
 import { userDetailed } from '../../.storybook/fakes.js';
 import { commonHandlers } from '../../.storybook/mocks.js';
 import MkAchievements from './MkAchievements.vue';
@@ -39,8 +39,8 @@ export const Empty = {
 		msw: {
 			handlers: [
 				...commonHandlers,
-				rest.post('/api/users/achievements', (req, res, ctx) => {
-					return res(ctx.json([]));
+				http.post('/api/users/achievements', () => {
+					return HttpResponse.json([]);
 				}),
 			],
 		},
@@ -52,8 +52,8 @@ export const All = {
 		msw: {
 			handlers: [
 				...commonHandlers,
-				rest.post('/api/users/achievements', (req, res, ctx) => {
-					return res(ctx.json(ACHIEVEMENT_TYPES.map((name) => ({ name, unlockedAt: 0 }))));
+				http.post('/api/users/achievements', () => {
+					return HttpResponse.json(ACHIEVEMENT_TYPES.map((name) => ({ name, unlockedAt: 0 })));
 				}),
 			],
 		},
diff --git a/packages/frontend/src/components/MkAutocomplete.stories.impl.ts b/packages/frontend/src/components/MkAutocomplete.stories.impl.ts
index 969519386f..3ca8c5b864 100644
--- a/packages/frontend/src/components/MkAutocomplete.stories.impl.ts
+++ b/packages/frontend/src/components/MkAutocomplete.stories.impl.ts
@@ -8,7 +8,7 @@ import { action } from '@storybook/addon-actions';
 import { expect } from '@storybook/jest';
 import { userEvent, waitFor, within } from '@storybook/testing-library';
 import { StoryObj } from '@storybook/vue3';
-import { rest } from 'msw';
+import { HttpResponse, http } from 'msw';
 import { userDetailed } from '../../.storybook/fakes.js';
 import { commonHandlers } from '../../.storybook/mocks.js';
 import MkAutocomplete from './MkAutocomplete.vue';
@@ -99,11 +99,11 @@ export const User = {
 		msw: {
 			handlers: [
 				...commonHandlers,
-				rest.post('/api/users/search-by-username-and-host', (req, res, ctx) => {
-					return res(ctx.json([
+				http.post('/api/users/search-by-username-and-host', () => {
+					return HttpResponse.json([
 						userDetailed('44', 'mizuki', 'misskey-hub.net', 'Mizuki'),
 						userDetailed('49', 'momoko', 'misskey-hub.net', 'Momoko'),
-					]));
+					]);
 				}),
 			],
 		},
@@ -132,12 +132,12 @@ export const Hashtag = {
 		msw: {
 			handlers: [
 				...commonHandlers,
-				rest.post('/api/hashtags/search', (req, res, ctx) => {
-					return res(ctx.json([
+				http.post('/api/hashtags/search', () => {
+					return HttpResponse.json([
 						'気象警報注意報',
 						'気象警報',
 						'気象情報',
-					]));
+					]);
 				}),
 			],
 		},
diff --git a/packages/frontend/src/components/MkAvatars.stories.impl.ts b/packages/frontend/src/components/MkAvatars.stories.impl.ts
index d41b64695f..a9b4540ca9 100644
--- a/packages/frontend/src/components/MkAvatars.stories.impl.ts
+++ b/packages/frontend/src/components/MkAvatars.stories.impl.ts
@@ -5,7 +5,7 @@
 
 /* eslint-disable @typescript-eslint/explicit-function-return-type */
 import { StoryObj } from '@storybook/vue3';
-import { rest } from 'msw';
+import { HttpResponse, http } from 'msw';
 import { userDetailed } from '../../.storybook/fakes.js';
 import { commonHandlers } from '../../.storybook/mocks.js';
 import MkAvatars from './MkAvatars.vue';
@@ -38,12 +38,12 @@ export const Default = {
 		msw: {
 			handlers: [
 				...commonHandlers,
-				rest.post('/api/users/show', (req, res, ctx) => {
-					return res(ctx.json([
+				http.post('/api/users/show', () => {
+					return HttpResponse.json([
 						userDetailed('17'),
 						userDetailed('20'),
 						userDetailed('18'),
-					]));
+					]);
 				}),
 			],
 		},
diff --git a/packages/frontend/src/components/MkInviteCode.stories.impl.ts b/packages/frontend/src/components/MkInviteCode.stories.impl.ts
index 2ea32dd3b6..2abe1a8770 100644
--- a/packages/frontend/src/components/MkInviteCode.stories.impl.ts
+++ b/packages/frontend/src/components/MkInviteCode.stories.impl.ts
@@ -5,7 +5,7 @@
 
 /* eslint-disable @typescript-eslint/explicit-function-return-type */
 import { StoryObj } from '@storybook/vue3';
-import { rest } from 'msw';
+import { HttpResponse, http } from 'msw';
 import { userDetailed, inviteCode } from '../../.storybook/fakes.js';
 import { commonHandlers } from '../../.storybook/mocks.js';
 import MkInviteCode from './MkInviteCode.vue';
@@ -39,8 +39,8 @@ export const Default = {
 		msw: {
 			handlers: [
 				...commonHandlers,
-				rest.post('/api/users/show', (req, res, ctx) => {
-					return res(ctx.json(userDetailed(req.params.userId as string)));
+				http.post('/api/users/show', ({ params }) => {
+					return HttpResponse.json(userDetailed(params.userId as string));
 				}),
 			],
 		},
diff --git a/packages/frontend/src/components/MkUserSetupDialog.Follow.stories.impl.ts b/packages/frontend/src/components/MkUserSetupDialog.Follow.stories.impl.ts
index 45c7da40ce..c1b380bd1b 100644
--- a/packages/frontend/src/components/MkUserSetupDialog.Follow.stories.impl.ts
+++ b/packages/frontend/src/components/MkUserSetupDialog.Follow.stories.impl.ts
@@ -5,7 +5,7 @@
 
 /* eslint-disable @typescript-eslint/explicit-function-return-type */
 import { StoryObj } from '@storybook/vue3';
-import { rest } from 'msw';
+import { HttpResponse, http } from 'msw';
 import { commonHandlers } from '../../.storybook/mocks.js';
 import { userDetailed } from '../../.storybook/fakes.js';
 import MkUserSetupDialog_Follow from './MkUserSetupDialog.Follow.vue';
@@ -38,17 +38,17 @@ export const Default = {
 		msw: {
 			handlers: [
 				...commonHandlers,
-				rest.post('/api/users', (req, res, ctx) => {
-					return res(ctx.json([
+				http.post('/api/users', () => {
+					return HttpResponse.json([
 						userDetailed('44'),
 						userDetailed('49'),
-					]));
+					]);
 				}),
-				rest.post('/api/pinned-users', (req, res, ctx) => {
-					return res(ctx.json([
+				http.post('/api/pinned-users', () => {
+					return HttpResponse.json([
 						userDetailed('44'),
 						userDetailed('49'),
-					]));
+					]);
 				}),
 			],
 		},
diff --git a/packages/frontend/src/components/MkUserSetupDialog.stories.impl.ts b/packages/frontend/src/components/MkUserSetupDialog.stories.impl.ts
index 5182db12b2..7177d256e1 100644
--- a/packages/frontend/src/components/MkUserSetupDialog.stories.impl.ts
+++ b/packages/frontend/src/components/MkUserSetupDialog.stories.impl.ts
@@ -5,7 +5,7 @@
 
 /* eslint-disable @typescript-eslint/explicit-function-return-type */
 import { StoryObj } from '@storybook/vue3';
-import { rest } from 'msw';
+import { HttpResponse, http } from 'msw';
 import { commonHandlers } from '../../.storybook/mocks.js';
 import { userDetailed } from '../../.storybook/fakes.js';
 import MkUserSetupDialog from './MkUserSetupDialog.vue';
@@ -38,17 +38,17 @@ export const Default = {
 		msw: {
 			handlers: [
 				...commonHandlers,
-				rest.post('/api/users', (req, res, ctx) => {
-					return res(ctx.json([
+				http.post('/api/users', () => {
+					return HttpResponse.json([
 						userDetailed('44'),
 						userDetailed('49'),
-					]));
+					]);
 				}),
-				rest.post('/api/pinned-users', (req, res, ctx) => {
-					return res(ctx.json([
+				http.post('/api/pinned-users', () => {
+					return HttpResponse.json([
 						userDetailed('44'),
 						userDetailed('49'),
-					]));
+					]);
 				}),
 			],
 		},
diff --git a/packages/frontend/src/components/global/MkUrl.stories.impl.ts b/packages/frontend/src/components/global/MkUrl.stories.impl.ts
index b35b6114fd..d053069087 100644
--- a/packages/frontend/src/components/global/MkUrl.stories.impl.ts
+++ b/packages/frontend/src/components/global/MkUrl.stories.impl.ts
@@ -7,7 +7,7 @@
 import { expect } from '@storybook/jest';
 import { userEvent, waitFor, within } from '@storybook/testing-library';
 import { StoryObj } from '@storybook/vue3';
-import { rest } from 'msw';
+import { HttpResponse, http } from 'msw';
 import { commonHandlers } from '../../../.storybook/mocks.js';
 import MkUrl from './MkUrl.vue';
 export const Default = {
@@ -59,8 +59,8 @@ export const Default = {
 		msw: {
 			handlers: [
 				...commonHandlers,
-				rest.get('/url', (req, res, ctx) => {
-					return res(ctx.json({
+				http.get('/url', () => {
+					return HttpResponse.json({
 						title: 'Misskey Hub',
 						icon: 'https://misskey-hub.net/favicon.ico',
 						description: 'Misskeyはオープンソースの分散型ソーシャルネットワーキングプラットフォームです。',
@@ -74,7 +74,7 @@ export const Default = {
 						sitename: 'misskey-hub.net',
 						sensitive: false,
 						url: 'https://misskey-hub.net/',
-					}));
+					});
 				}),
 			],
 		},
diff --git a/packages/frontend/src/pages/user/home.stories.impl.ts b/packages/frontend/src/pages/user/home.stories.impl.ts
index a2ef5d50d1..1e67d96c71 100644
--- a/packages/frontend/src/pages/user/home.stories.impl.ts
+++ b/packages/frontend/src/pages/user/home.stories.impl.ts
@@ -5,7 +5,7 @@
 
 /* eslint-disable @typescript-eslint/explicit-function-return-type */
 import { StoryObj } from '@storybook/vue3';
-import { rest } from 'msw';
+import { HttpResponse, http } from 'msw';
 import { userDetailed } from '../../../.storybook/fakes.js';
 import { commonHandlers } from '../../../.storybook/mocks.js';
 import home_ from './home.vue';
@@ -39,12 +39,13 @@ export const Default = {
 		msw: {
 			handlers: [
 				...commonHandlers,
-				rest.post('/api/users/notes', (req, res, ctx) => {
-					return res(ctx.json([]));
+				http.post('/api/users/notes', () => {
+					return HttpResponse.json([]);
 				}),
-				rest.get('/api/charts/user/notes', (req, res, ctx) => {
-					const length = Math.max(Math.min(parseInt(req.url.searchParams.get('limit') ?? '30', 10), 1), 300);
-					return res(ctx.json({
+				http.get('/api/charts/user/notes', ({ request }) => {
+					const url = new URL(request.url);
+					const length = Math.max(Math.min(parseInt(url.searchParams.get('limit') ?? '30', 10), 1), 300);
+					return HttpResponse.json({
 						total: Array.from({ length }, () => 0),
 						inc: Array.from({ length }, () => 0),
 						dec: Array.from({ length }, () => 0),
@@ -54,11 +55,12 @@ export const Default = {
 							renote: Array.from({ length }, () => 0),
 							withFile: Array.from({ length }, () => 0),
 						},
-					}));
+					});
 				}),
-				rest.get('/api/charts/user/pv', (req, res, ctx) => {
-					const length = Math.max(Math.min(parseInt(req.url.searchParams.get('limit') ?? '30', 10), 1), 300);
-					return res(ctx.json({
+				http.get('/api/charts/user/pv', ({ request }) => {
+					const url = new URL(request.url);
+					const length = Math.max(Math.min(parseInt(url.searchParams.get('limit') ?? '30', 10), 1), 300);
+					return HttpResponse.json({
 						upv: {
 							user: Array.from({ length }, () => 0),
 							visitor: Array.from({ length }, () => 0),
@@ -67,7 +69,7 @@ export const Default = {
 							user: Array.from({ length }, () => 0),
 							visitor: Array.from({ length }, () => 0),
 						},
-					}));
+					});
 				}),
 			],
 		},
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 0561e8b01e..291ec305c7 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -978,11 +978,11 @@ importers:
         specifier: 4.0.5
         version: 4.0.5
       msw:
-        specifier: 2.1.2
-        version: 2.1.2(typescript@5.3.3)
+        specifier: 2.1.7
+        version: 2.1.7(typescript@5.3.3)
       msw-storybook-addon:
-        specifier: 1.10.0
-        version: 1.10.0(msw@2.1.2)
+        specifier: 2.0.0-beta.1
+        version: 2.0.0-beta.1(msw@2.1.7)
       nodemon:
         specifier: 3.0.3
         version: 3.0.3
@@ -1909,7 +1909,7 @@ packages:
       '@babel/traverse': 7.22.11
       '@babel/types': 7.22.17
       convert-source-map: 1.9.0
-      debug: 4.3.4(supports-color@5.5.0)
+      debug: 4.3.4(supports-color@8.1.1)
       gensync: 1.0.0-beta.2
       json5: 2.2.3
       semver: 6.3.1
@@ -1932,7 +1932,7 @@ packages:
       '@babel/traverse': 7.23.5
       '@babel/types': 7.23.5
       convert-source-map: 2.0.0
-      debug: 4.3.4(supports-color@5.5.0)
+      debug: 4.3.4(supports-color@8.1.1)
       gensync: 1.0.0-beta.2
       json5: 2.2.3
       semver: 6.3.1
@@ -2034,7 +2034,7 @@ packages:
       '@babel/core': 7.23.5
       '@babel/helper-compilation-targets': 7.22.15
       '@babel/helper-plugin-utils': 7.22.5
-      debug: 4.3.4(supports-color@5.5.0)
+      debug: 4.3.4(supports-color@8.1.1)
       lodash.debounce: 4.0.8
       resolve: 1.22.8
     transitivePeerDependencies:
@@ -3433,7 +3433,7 @@ packages:
       '@babel/helper-split-export-declaration': 7.22.6
       '@babel/parser': 7.23.5
       '@babel/types': 7.22.17
-      debug: 4.3.4(supports-color@5.5.0)
+      debug: 4.3.4(supports-color@8.1.1)
       globals: 11.12.0
     transitivePeerDependencies:
       - supports-color
@@ -3451,7 +3451,7 @@ packages:
       '@babel/helper-split-export-declaration': 7.22.6
       '@babel/parser': 7.23.6
       '@babel/types': 7.23.5
-      debug: 4.3.4(supports-color@5.5.0)
+      debug: 4.3.4(supports-color@8.1.1)
       globals: 11.12.0
     transitivePeerDependencies:
       - supports-color
@@ -3521,12 +3521,6 @@ packages:
       cookie: 0.5.0
     dev: true
 
-  /@bundled-es-modules/js-levenshtein@2.0.1:
-    resolution: {integrity: sha512-DERMS3yfbAljKsQc0U2wcqGKUWpdFjwqWuoMugEJlqBnKO180/n+4SR/J8MRDt1AN48X1ovgoD9KrdVXcaa3Rg==}
-    dependencies:
-      js-levenshtein: 1.1.6
-    dev: true
-
   /@bundled-es-modules/statuses@1.0.1:
     resolution: {integrity: sha512-yn7BklA5acgcBr+7w064fGV+SGIFySjCKpqjcWgBAIfrAkY+4GQTJJHQMeT3V/sgz23VTEVV8TtOmkvJAhFVfg==}
     dependencies:
@@ -4158,7 +4152,7 @@ packages:
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
     dependencies:
       ajv: 6.12.6
-      debug: 4.3.4(supports-color@5.5.0)
+      debug: 4.3.4(supports-color@8.1.1)
       espree: 9.6.1
       globals: 13.19.0
       ignore: 5.2.4
@@ -4175,7 +4169,7 @@ packages:
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
     dependencies:
       ajv: 6.12.6
-      debug: 4.3.4(supports-color@5.5.0)
+      debug: 4.3.4(supports-color@8.1.1)
       espree: 9.6.1
       globals: 13.19.0
       ignore: 5.2.4
@@ -4410,7 +4404,7 @@ packages:
     engines: {node: '>=10.10.0'}
     dependencies:
       '@humanwhocodes/object-schema': 2.0.1
-      debug: 4.3.4(supports-color@5.5.0)
+      debug: 4.3.4(supports-color@8.1.1)
       minimatch: 3.1.2
     transitivePeerDependencies:
       - supports-color
@@ -4974,8 +4968,8 @@ packages:
     engines: {node: '>=18'}
     dev: true
 
-  /@mswjs/interceptors@0.25.14:
-    resolution: {integrity: sha512-2dnIxl+obqIqjoPXTFldhe6pcdOrqiz+GcLaQQ6hmL02OldAF7nIC+rUgTWm+iF6lvmyCVhFFqbgbapNhR8eag==}
+  /@mswjs/interceptors@0.25.16:
+    resolution: {integrity: sha512-8QC8JyKztvoGAdPgyZy49c9vSHHAZjHagwl4RY9E8carULk8ym3iTaiawrT1YoLF/qb449h48f71XDPgkUSOUg==}
     engines: {node: '>=18'}
     dependencies:
       '@open-draft/deferred-promise': 2.2.0
@@ -8207,10 +8201,6 @@ packages:
       pretty-format: 29.7.0
     dev: true
 
-  /@types/js-levenshtein@1.1.3:
-    resolution: {integrity: sha512-jd+Q+sD20Qfu9e2aEXogiO3vpOC1PYJOUdyN9gvs4Qrvkg4wF43L5OhqrPeokdv8TL0/mXoYfpkcoGZMNN2pkQ==}
-    dev: true
-
   /@types/js-yaml@4.0.9:
     resolution: {integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==}
     dev: true
@@ -8593,7 +8583,7 @@ packages:
       '@typescript-eslint/type-utils': 6.11.0(eslint@8.53.0)(typescript@5.3.3)
       '@typescript-eslint/utils': 6.11.0(eslint@8.53.0)(typescript@5.3.3)
       '@typescript-eslint/visitor-keys': 6.11.0
-      debug: 4.3.4(supports-color@5.5.0)
+      debug: 4.3.4(supports-color@8.1.1)
       eslint: 8.53.0
       graphemer: 1.4.0
       ignore: 5.2.4
@@ -8622,7 +8612,7 @@ packages:
       '@typescript-eslint/type-utils': 6.18.1(eslint@8.56.0)(typescript@5.3.3)
       '@typescript-eslint/utils': 6.18.1(eslint@8.56.0)(typescript@5.3.3)
       '@typescript-eslint/visitor-keys': 6.18.1
-      debug: 4.3.4(supports-color@5.5.0)
+      debug: 4.3.4(supports-color@8.1.1)
       eslint: 8.56.0
       graphemer: 1.4.0
       ignore: 5.2.4
@@ -8648,7 +8638,7 @@ packages:
       '@typescript-eslint/types': 6.11.0
       '@typescript-eslint/typescript-estree': 6.11.0(typescript@5.3.3)
       '@typescript-eslint/visitor-keys': 6.11.0
-      debug: 4.3.4(supports-color@5.5.0)
+      debug: 4.3.4(supports-color@8.1.1)
       eslint: 8.53.0
       typescript: 5.3.3
     transitivePeerDependencies:
@@ -8669,7 +8659,7 @@ packages:
       '@typescript-eslint/types': 6.18.1
       '@typescript-eslint/typescript-estree': 6.18.1(typescript@5.3.3)
       '@typescript-eslint/visitor-keys': 6.18.1
-      debug: 4.3.4(supports-color@5.5.0)
+      debug: 4.3.4(supports-color@8.1.1)
       eslint: 8.56.0
       typescript: 5.3.3
     transitivePeerDependencies:
@@ -8704,7 +8694,7 @@ packages:
     dependencies:
       '@typescript-eslint/typescript-estree': 6.11.0(typescript@5.3.3)
       '@typescript-eslint/utils': 6.11.0(eslint@8.53.0)(typescript@5.3.3)
-      debug: 4.3.4(supports-color@5.5.0)
+      debug: 4.3.4(supports-color@8.1.1)
       eslint: 8.53.0
       ts-api-utils: 1.0.1(typescript@5.3.3)
       typescript: 5.3.3
@@ -8724,7 +8714,7 @@ packages:
     dependencies:
       '@typescript-eslint/typescript-estree': 6.18.1(typescript@5.3.3)
       '@typescript-eslint/utils': 6.18.1(eslint@8.56.0)(typescript@5.3.3)
-      debug: 4.3.4(supports-color@5.5.0)
+      debug: 4.3.4(supports-color@8.1.1)
       eslint: 8.56.0
       ts-api-utils: 1.0.1(typescript@5.3.3)
       typescript: 5.3.3
@@ -8753,7 +8743,7 @@ packages:
     dependencies:
       '@typescript-eslint/types': 6.11.0
       '@typescript-eslint/visitor-keys': 6.11.0
-      debug: 4.3.4(supports-color@5.5.0)
+      debug: 4.3.4(supports-color@8.1.1)
       globby: 11.1.0
       is-glob: 4.0.3
       semver: 7.5.4
@@ -8774,7 +8764,7 @@ packages:
     dependencies:
       '@typescript-eslint/types': 6.18.1
       '@typescript-eslint/visitor-keys': 6.18.1
-      debug: 4.3.4(supports-color@5.5.0)
+      debug: 4.3.4(supports-color@8.1.1)
       globby: 11.1.0
       is-glob: 4.0.3
       minimatch: 9.0.3
@@ -9215,7 +9205,7 @@ packages:
     engines: {node: '>= 6.0.0'}
     requiresBuild: true
     dependencies:
-      debug: 4.3.4(supports-color@5.5.0)
+      debug: 4.3.4(supports-color@8.1.1)
     transitivePeerDependencies:
       - supports-color
 
@@ -9223,7 +9213,7 @@ packages:
     resolution: {integrity: sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==}
     engines: {node: '>= 14'}
     dependencies:
-      debug: 4.3.4(supports-color@5.5.0)
+      debug: 4.3.4(supports-color@8.1.1)
     transitivePeerDependencies:
       - supports-color
     dev: false
@@ -9605,7 +9595,7 @@ packages:
     resolution: {integrity: sha512-TAlMYvOuwGyLK3PfBb5WKBXZmXz2fVCgv23d6zZFdle/q3gPjmxBaeuC0pY0Dzs5PWMSgfqqEZkrye19GlDTgw==}
     dependencies:
       archy: 1.0.0
-      debug: 4.3.4(supports-color@5.5.0)
+      debug: 4.3.4(supports-color@8.1.1)
       fastq: 1.15.0
     transitivePeerDependencies:
       - supports-color
@@ -11057,6 +11047,7 @@ packages:
     dependencies:
       ms: 2.1.2
       supports-color: 5.5.0
+    dev: true
 
   /debug@4.3.4(supports-color@8.1.1):
     resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
@@ -11069,7 +11060,6 @@ packages:
     dependencies:
       ms: 2.1.2
       supports-color: 8.1.1
-    dev: true
 
   /decamelize-keys@1.1.1:
     resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==}
@@ -11286,7 +11276,7 @@ packages:
     hasBin: true
     dependencies:
       address: 1.2.2
-      debug: 4.3.4(supports-color@5.5.0)
+      debug: 4.3.4(supports-color@8.1.1)
     transitivePeerDependencies:
       - supports-color
     dev: true
@@ -11610,7 +11600,7 @@ packages:
     peerDependencies:
       esbuild: '>=0.12 <1'
     dependencies:
-      debug: 4.3.4(supports-color@5.5.0)
+      debug: 4.3.4(supports-color@8.1.1)
       esbuild: 0.18.20
     transitivePeerDependencies:
       - supports-color
@@ -11919,7 +11909,7 @@ packages:
       ajv: 6.12.6
       chalk: 4.1.2
       cross-spawn: 7.0.3
-      debug: 4.3.4(supports-color@5.5.0)
+      debug: 4.3.4(supports-color@8.1.1)
       doctrine: 3.0.0
       escape-string-regexp: 4.0.0
       eslint-scope: 7.2.2
@@ -11966,7 +11956,7 @@ packages:
       ajv: 6.12.6
       chalk: 4.1.2
       cross-spawn: 7.0.3
-      debug: 4.3.4(supports-color@5.5.0)
+      debug: 4.3.4(supports-color@8.1.1)
       doctrine: 3.0.0
       escape-string-regexp: 4.0.0
       eslint-scope: 7.2.2
@@ -12597,7 +12587,7 @@ packages:
       debug:
         optional: true
     dependencies:
-      debug: 4.3.4(supports-color@5.5.0)
+      debug: 4.3.4(supports-color@8.1.1)
 
   /for-each@0.3.3:
     resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==}
@@ -13153,6 +13143,7 @@ packages:
   /has-flag@3.0.0:
     resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==}
     engines: {node: '>=4'}
+    dev: true
 
   /has-flag@4.0.0:
     resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
@@ -13290,7 +13281,7 @@ packages:
     engines: {node: '>= 14'}
     dependencies:
       agent-base: 7.1.0
-      debug: 4.3.4(supports-color@5.5.0)
+      debug: 4.3.4(supports-color@8.1.1)
     transitivePeerDependencies:
       - supports-color
     dev: false
@@ -13350,7 +13341,7 @@ packages:
     engines: {node: '>= 6.0.0'}
     dependencies:
       agent-base: 5.1.1
-      debug: 4.3.4(supports-color@5.5.0)
+      debug: 4.3.4(supports-color@8.1.1)
     transitivePeerDependencies:
       - supports-color
     dev: true
@@ -13360,7 +13351,7 @@ packages:
     engines: {node: '>= 6'}
     dependencies:
       agent-base: 6.0.2
-      debug: 4.3.4(supports-color@5.5.0)
+      debug: 4.3.4(supports-color@8.1.1)
     transitivePeerDependencies:
       - supports-color
 
@@ -13369,7 +13360,7 @@ packages:
     engines: {node: '>= 14'}
     dependencies:
       agent-base: 7.1.0
-      debug: 4.3.4(supports-color@5.5.0)
+      debug: 4.3.4(supports-color@8.1.1)
     transitivePeerDependencies:
       - supports-color
     dev: false
@@ -13529,7 +13520,7 @@ packages:
     dependencies:
       '@ioredis/commands': 1.2.0
       cluster-key-slot: 1.1.2
-      debug: 4.3.4(supports-color@5.5.0)
+      debug: 4.3.4(supports-color@8.1.1)
       denque: 2.1.0
       lodash.defaults: 4.2.0
       lodash.isarguments: 3.1.0
@@ -13975,7 +13966,7 @@ packages:
     resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==}
     engines: {node: '>=10'}
     dependencies:
-      debug: 4.3.4(supports-color@5.5.0)
+      debug: 4.3.4(supports-color@8.1.1)
       istanbul-lib-coverage: 3.2.2
       source-map: 0.6.1
     transitivePeerDependencies:
@@ -14532,11 +14523,6 @@ packages:
       nopt: 6.0.0
     dev: true
 
-  /js-levenshtein@1.1.6:
-    resolution: {integrity: sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==}
-    engines: {node: '>=0.10.0'}
-    dev: true
-
   /js-stringify@1.0.2:
     resolution: {integrity: sha512-rtS5ATOo2Q5k1G+DADISilDA6lv79zIiwFd6CcjuIxGKLFm5C+RLImRscVap9k55i+MOZwgliw+NejvkLuGD5g==}
 
@@ -15485,17 +15471,17 @@ packages:
       msgpackr-extract: 3.0.2
     dev: false
 
-  /msw-storybook-addon@1.10.0(msw@2.1.2):
-    resolution: {integrity: sha512-soCTMTf7DnLeaMnFHPrtVgbyeFTJALVvnDHpzzXpJad+HOzJgQdwU4EAzVfDs1q+X5cVEgxOdAhSMC7ljvnSXg==}
+  /msw-storybook-addon@2.0.0-beta.1(msw@2.1.7):
+    resolution: {integrity: sha512-DRyIAMK3waEfC+pKTyiIq68OZfiZ4WZGUVAn6J4YwCRpDdoCvLzzoC2spN0Jgegx4dEmJ7589ATnS14NxqeBig==}
     peerDependencies:
-      msw: '>=0.35.0 <2.0.0'
+      msw: ^2.0.0
     dependencies:
       is-node-process: 1.2.0
-      msw: 2.1.2(typescript@5.3.3)
+      msw: 2.1.7(typescript@5.3.3)
     dev: true
 
-  /msw@2.1.2(typescript@5.3.3):
-    resolution: {integrity: sha512-7OKbeZNFQTCPFe++o+zZHMkQRIUi4D/5N1dAD3lOlaV+2Wpv3Srp3VFrFeH+2ftZJbb4jAiC0caZoW1efr80KQ==}
+  /msw@2.1.7(typescript@5.3.3):
+    resolution: {integrity: sha512-yTIYqEMqDSrdbVMrfmqP6rTKQsnIbglTvVmAHDWwNegyXPXRcV+RjsaFEqubRS266gwWCDLm9YdOkWSKLdDvJQ==}
     engines: {node: '>=18'}
     hasBin: true
     requiresBuild: true
@@ -15506,13 +15492,11 @@ packages:
         optional: true
     dependencies:
       '@bundled-es-modules/cookie': 2.0.0
-      '@bundled-es-modules/js-levenshtein': 2.0.1
       '@bundled-es-modules/statuses': 1.0.1
       '@mswjs/cookies': 1.1.0
-      '@mswjs/interceptors': 0.25.14
+      '@mswjs/interceptors': 0.25.16
       '@open-draft/until': 2.1.0
       '@types/cookie': 0.6.0
-      '@types/js-levenshtein': 1.1.3
       '@types/statuses': 2.0.4
       chalk: 4.1.2
       chokidar: 3.5.3
@@ -15520,7 +15504,6 @@ packages:
       headers-polyfill: 4.0.2
       inquirer: 8.2.5
       is-node-process: 1.2.0
-      js-levenshtein: 1.1.6
       outvariant: 1.4.2
       path-to-regexp: 6.2.1
       strict-event-emitter: 0.5.1
@@ -17286,7 +17269,7 @@ packages:
     engines: {node: '>=8.16.0'}
     dependencies:
       '@types/mime-types': 2.1.4
-      debug: 4.3.4(supports-color@5.5.0)
+      debug: 4.3.4(supports-color@8.1.1)
       extract-zip: 1.7.0
       https-proxy-agent: 4.0.0
       mime: 2.6.0
@@ -18287,7 +18270,7 @@ packages:
     dependencies:
       '@hapi/hoek': 10.0.1
       '@hapi/wreck': 18.0.1
-      debug: 4.3.4(supports-color@5.5.0)
+      debug: 4.3.4(supports-color@8.1.1)
       joi: 17.7.0
     transitivePeerDependencies:
       - supports-color
@@ -18487,7 +18470,7 @@ packages:
     engines: {node: '>= 14'}
     dependencies:
       agent-base: 7.1.0
-      debug: 4.3.4(supports-color@5.5.0)
+      debug: 4.3.4(supports-color@8.1.1)
       socks: 2.7.1
     transitivePeerDependencies:
       - supports-color
@@ -18640,7 +18623,7 @@ packages:
       arg: 5.0.2
       bluebird: 3.7.2
       check-more-types: 2.24.0
-      debug: 4.3.4(supports-color@5.5.0)
+      debug: 4.3.4(supports-color@8.1.1)
       execa: 5.1.1
       lazy-ass: 1.6.0
       ps-tree: 1.2.0
@@ -18898,6 +18881,7 @@ packages:
     engines: {node: '>=4'}
     dependencies:
       has-flag: 3.0.0
+    dev: true
 
   /supports-color@7.2.0:
     resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
@@ -19518,7 +19502,7 @@ packages:
       chalk: 4.1.2
       cli-highlight: 2.1.11
       dayjs: 1.11.10
-      debug: 4.3.4(supports-color@5.5.0)
+      debug: 4.3.4(supports-color@8.1.1)
       dotenv: 16.0.3
       glob: 10.3.10
       ioredis: 5.3.2
@@ -19878,7 +19862,7 @@ packages:
     hasBin: true
     dependencies:
       cac: 6.7.14
-      debug: 4.3.4(supports-color@5.5.0)
+      debug: 4.3.4(supports-color@8.1.1)
       mlly: 1.5.0
       pathe: 1.1.2
       picocolors: 1.0.0
@@ -19990,7 +19974,7 @@ packages:
       acorn-walk: 8.3.2
       cac: 6.7.14
       chai: 4.3.10
-      debug: 4.3.4(supports-color@5.5.0)
+      debug: 4.3.4(supports-color@8.1.1)
       happy-dom: 10.0.3
       local-pkg: 0.4.3
       magic-string: 0.30.5
@@ -20064,7 +20048,7 @@ packages:
     peerDependencies:
       eslint: '>=6.0.0'
     dependencies:
-      debug: 4.3.4(supports-color@5.5.0)
+      debug: 4.3.4(supports-color@8.1.1)
       eslint: 8.56.0
       eslint-scope: 7.2.2
       eslint-visitor-keys: 3.4.3
-- 
GitLab


From 90b633b5a629aa16444bfa12fdb4d57f92fc9f4b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?=
 <67428053+kakkokari-gtyih@users.noreply.github.com>
Date: Thu, 8 Feb 2024 14:15:56 +0900
Subject: [PATCH 28/40] fix(frontend) misskey-js type (#13202)

---
 packages/frontend/src/components/MkTimeline.vue          | 9 ++++-----
 .../frontend/src/components/MkUserSetupDialog.Follow.vue | 2 +-
 2 files changed, 5 insertions(+), 6 deletions(-)

diff --git a/packages/frontend/src/components/MkTimeline.vue b/packages/frontend/src/components/MkTimeline.vue
index 3c77878379..8b6d1bd8b6 100644
--- a/packages/frontend/src/components/MkTimeline.vue
+++ b/packages/frontend/src/components/MkTimeline.vue
@@ -18,8 +18,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script lang="ts" setup>
 import { computed, watch, onUnmounted, provide, ref, shallowRef } from 'vue';
-import Misskey from 'misskey-js';
-import { Connection } from 'misskey-js/built/streaming.js';
+import * as Misskey from 'misskey-js';
 import MkNotes from '@/components/MkNotes.vue';
 import MkPullToRefresh from '@/components/MkPullToRefresh.vue';
 import { useStream } from '@/stream.js';
@@ -87,8 +86,8 @@ function prepend(note) {
 	}
 }
 
-let connection: Connection;
-let connection2: Connection;
+let connection: Misskey.ChannelConnection | null = null;
+let connection2: Misskey.ChannelConnection | null = null;
 let paginationQuery: Paging | null = null;
 
 const stream = useStream();
@@ -151,7 +150,7 @@ function connectChannel() {
 			roleId: props.role,
 		});
 	}
-	if (props.src !== 'directs' && props.src !== 'mentions') connection.on('note', prepend);
+	if (props.src !== 'directs' && props.src !== 'mentions') connection?.on('note', prepend);
 }
 
 function disconnectChannel() {
diff --git a/packages/frontend/src/components/MkUserSetupDialog.Follow.vue b/packages/frontend/src/components/MkUserSetupDialog.Follow.vue
index 46459df6a6..86a5c812bd 100644
--- a/packages/frontend/src/components/MkUserSetupDialog.Follow.vue
+++ b/packages/frontend/src/components/MkUserSetupDialog.Follow.vue
@@ -34,7 +34,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import Misskey from 'misskey-js';
+import * as Misskey from 'misskey-js';
 import { i18n } from '@/i18n.js';
 import MkFolder from '@/components/MkFolder.vue';
 import XUser from '@/components/MkUserSetupDialog.User.vue';
-- 
GitLab


From 0f7918c51b990c1b307360e5ae5d2397df1e00e1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?=
 <67428053+kakkokari-gtyih@users.noreply.github.com>
Date: Thu, 8 Feb 2024 16:04:41 +0900
Subject: [PATCH 29/40] refactor(backend): exist -> exists (#13203)

* refactor(backend): exist -> exists

* fix
---
 packages/backend/src/core/CustomEmojiService.ts  |  2 +-
 packages/backend/src/core/NoteCreateService.ts   |  4 ++--
 packages/backend/src/core/NoteReadService.ts     |  4 ++--
 packages/backend/src/core/SignupService.ts       |  4 ++--
 .../backend/src/core/UserFollowingService.ts     | 10 +++++-----
 .../src/core/activitypub/ApInboxService.ts       |  6 +++---
 .../src/core/activitypub/ApRendererService.ts    |  2 +-
 .../src/core/entities/ChannelEntityService.ts    |  4 ++--
 .../src/core/entities/ClipEntityService.ts       |  2 +-
 .../src/core/entities/FlashEntityService.ts      |  2 +-
 .../core/entities/GalleryPostEntityService.ts    |  2 +-
 .../src/core/entities/NoteEntityService.ts       |  2 +-
 .../src/core/entities/PageEntityService.ts       |  2 +-
 .../src/core/entities/UserEntityService.ts       | 16 ++++++++--------
 .../backend/src/server/api/SignupApiService.ts   |  4 ++--
 .../server/api/endpoints/admin/promo/create.ts   |  2 +-
 .../src/server/api/endpoints/auth/accept.ts      |  2 +-
 .../src/server/api/endpoints/blocking/create.ts  |  2 +-
 .../src/server/api/endpoints/blocking/delete.ts  |  2 +-
 .../src/server/api/endpoints/clips/favorite.ts   |  2 +-
 .../api/endpoints/drive/files/check-existence.ts |  2 +-
 .../src/server/api/endpoints/flash/like.ts       |  2 +-
 .../src/server/api/endpoints/following/create.ts |  2 +-
 .../src/server/api/endpoints/following/delete.ts |  2 +-
 .../server/api/endpoints/gallery/posts/like.ts   |  2 +-
 .../server/api/endpoints/i/import-antennas.ts    |  2 +-
 .../src/server/api/endpoints/i/revoke-token.ts   |  4 ++--
 .../src/server/api/endpoints/mute/create.ts      |  2 +-
 .../src/server/api/endpoints/notes/create.ts     |  4 ++--
 .../api/endpoints/notes/favorites/create.ts      |  2 +-
 .../src/server/api/endpoints/pages/like.ts       |  2 +-
 .../src/server/api/endpoints/promo/read.ts       |  2 +-
 .../src/server/api/endpoints/users/followers.ts  |  2 +-
 .../src/server/api/endpoints/users/following.ts  |  2 +-
 .../endpoints/users/lists/create-from-public.ts  |  6 +++---
 .../server/api/endpoints/users/lists/favorite.ts |  4 ++--
 .../src/server/api/endpoints/users/lists/push.ts |  4 ++--
 .../src/server/api/endpoints/users/lists/show.ts |  2 +-
 .../api/endpoints/users/lists/unfavorite.ts      |  2 +-
 .../src/server/api/stream/channels/user-list.ts  |  2 +-
 40 files changed, 64 insertions(+), 64 deletions(-)

diff --git a/packages/backend/src/core/CustomEmojiService.ts b/packages/backend/src/core/CustomEmojiService.ts
index 9a8267b466..f87136e20a 100644
--- a/packages/backend/src/core/CustomEmojiService.ts
+++ b/packages/backend/src/core/CustomEmojiService.ts
@@ -385,7 +385,7 @@ export class CustomEmojiService implements OnApplicationShutdown {
 	 */
 	@bindThis
 	public checkDuplicate(name: string): Promise<boolean> {
-		return this.emojisRepository.exist({ where: { name, host: IsNull() } });
+		return this.emojisRepository.exists({ where: { name, host: IsNull() } });
 	}
 
 	@bindThis
diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts
index 30f6d07118..f7e870831d 100644
--- a/packages/backend/src/core/NoteCreateService.ts
+++ b/packages/backend/src/core/NoteCreateService.ts
@@ -603,7 +603,7 @@ export class NoteCreateService implements OnApplicationShutdown {
 			if (data.reply) {
 				// 通知
 				if (data.reply.userHost === null) {
-					const isThreadMuted = await this.noteThreadMutingsRepository.exist({
+					const isThreadMuted = await this.noteThreadMutingsRepository.exists({
 						where: {
 							userId: data.reply.userId,
 							threadId: data.reply.threadId ?? data.reply.id,
@@ -741,7 +741,7 @@ export class NoteCreateService implements OnApplicationShutdown {
 	@bindThis
 	private async createMentionedEvents(mentionedUsers: MinimumUser[], note: MiNote, nm: NotificationManager) {
 		for (const u of mentionedUsers.filter(u => this.userEntityService.isLocalUser(u))) {
-			const isThreadMuted = await this.noteThreadMutingsRepository.exist({
+			const isThreadMuted = await this.noteThreadMutingsRepository.exists({
 				where: {
 					userId: u.id,
 					threadId: note.threadId ?? note.id,
diff --git a/packages/backend/src/core/NoteReadService.ts b/packages/backend/src/core/NoteReadService.ts
index c73cf76592..11791a4412 100644
--- a/packages/backend/src/core/NoteReadService.ts
+++ b/packages/backend/src/core/NoteReadService.ts
@@ -49,7 +49,7 @@ export class NoteReadService implements OnApplicationShutdown {
 		//#endregion
 
 		// スレッドミュート
-		const isThreadMuted = await this.noteThreadMutingsRepository.exist({
+		const isThreadMuted = await this.noteThreadMutingsRepository.exists({
 			where: {
 				userId: userId,
 				threadId: note.threadId ?? note.id,
@@ -70,7 +70,7 @@ export class NoteReadService implements OnApplicationShutdown {
 
 		// 2秒経っても既読にならなかったら「未読の投稿がありますよ」イベントを発行する
 		setTimeout(2000, 'unread note', { signal: this.#shutdownController.signal }).then(async () => {
-			const exist = await this.noteUnreadsRepository.exist({ where: { id: unread.id } });
+			const exist = await this.noteUnreadsRepository.exists({ where: { id: unread.id } });
 
 			if (!exist) return;
 
diff --git a/packages/backend/src/core/SignupService.ts b/packages/backend/src/core/SignupService.ts
index 81c2b241eb..0a119a90d5 100644
--- a/packages/backend/src/core/SignupService.ts
+++ b/packages/backend/src/core/SignupService.ts
@@ -74,12 +74,12 @@ export class SignupService {
 		const secret = generateUserToken();
 
 		// Check username duplication
-		if (await this.usersRepository.exist({ where: { usernameLower: username.toLowerCase(), host: IsNull() } })) {
+		if (await this.usersRepository.exists({ where: { usernameLower: username.toLowerCase(), host: IsNull() } })) {
 			throw new Error('DUPLICATED_USERNAME');
 		}
 
 		// Check deleted username duplication
-		if (await this.usedUsernamesRepository.exist({ where: { username: username.toLowerCase() } })) {
+		if (await this.usedUsernamesRepository.exists({ where: { username: username.toLowerCase() } })) {
 			throw new Error('USED_USERNAME');
 		}
 
diff --git a/packages/backend/src/core/UserFollowingService.ts b/packages/backend/src/core/UserFollowingService.ts
index 93e9fbbd70..e82a7e06f9 100644
--- a/packages/backend/src/core/UserFollowingService.ts
+++ b/packages/backend/src/core/UserFollowingService.ts
@@ -144,7 +144,7 @@ export class UserFollowingService implements OnModuleInit {
 			let autoAccept = false;
 
 			// 鍵アカウントであっても、既にフォローされていた場合はスルー
-			const isFollowing = await this.followingsRepository.exist({
+			const isFollowing = await this.followingsRepository.exists({
 				where: {
 					followerId: follower.id,
 					followeeId: followee.id,
@@ -156,7 +156,7 @@ export class UserFollowingService implements OnModuleInit {
 
 			// フォローしているユーザーは自動承認オプション
 			if (!autoAccept && (this.userEntityService.isLocalUser(followee) && followeeProfile.autoAcceptFollowed)) {
-				const isFollowed = await this.followingsRepository.exist({
+				const isFollowed = await this.followingsRepository.exists({
 					where: {
 						followerId: followee.id,
 						followeeId: follower.id,
@@ -170,7 +170,7 @@ export class UserFollowingService implements OnModuleInit {
 			if (followee.isLocked && !autoAccept) {
 				autoAccept = !!(await this.accountMoveService.validateAlsoKnownAs(
 					follower,
-					(oldSrc, newSrc) => this.followingsRepository.exist({
+					(oldSrc, newSrc) => this.followingsRepository.exists({
 						where: {
 							followeeId: followee.id,
 							followerId: newSrc.id,
@@ -233,7 +233,7 @@ export class UserFollowingService implements OnModuleInit {
 
 		this.cacheService.userFollowingsCache.refresh(follower.id);
 
-		const requestExist = await this.followRequestsRepository.exist({
+		const requestExist = await this.followRequestsRepository.exists({
 			where: {
 				followeeId: followee.id,
 				followerId: follower.id,
@@ -531,7 +531,7 @@ export class UserFollowingService implements OnModuleInit {
 			}
 		}
 
-		const requestExist = await this.followRequestsRepository.exist({
+		const requestExist = await this.followRequestsRepository.exists({
 			where: {
 				followeeId: followee.id,
 				followerId: follower.id,
diff --git a/packages/backend/src/core/activitypub/ApInboxService.ts b/packages/backend/src/core/activitypub/ApInboxService.ts
index a0c63bdbf7..5e06f29946 100644
--- a/packages/backend/src/core/activitypub/ApInboxService.ts
+++ b/packages/backend/src/core/activitypub/ApInboxService.ts
@@ -629,7 +629,7 @@ export class ApInboxService {
 			return 'skip: follower not found';
 		}
 
-		const isFollowing = await this.followingsRepository.exist({
+		const isFollowing = await this.followingsRepository.exists({
 			where: {
 				followerId: follower.id,
 				followeeId: actor.id,
@@ -686,14 +686,14 @@ export class ApInboxService {
 			return 'skip: フォロー解除しようとしているユーザーはローカルユーザーではありません';
 		}
 
-		const requestExist = await this.followRequestsRepository.exist({
+		const requestExist = await this.followRequestsRepository.exists({
 			where: {
 				followerId: actor.id,
 				followeeId: followee.id,
 			},
 		});
 
-		const isFollowing = await this.followingsRepository.exist({
+		const isFollowing = await this.followingsRepository.exists({
 			where: {
 				followerId: actor.id,
 				followeeId: followee.id,
diff --git a/packages/backend/src/core/activitypub/ApRendererService.ts b/packages/backend/src/core/activitypub/ApRendererService.ts
index 211f4f4a44..87b603b905 100644
--- a/packages/backend/src/core/activitypub/ApRendererService.ts
+++ b/packages/backend/src/core/activitypub/ApRendererService.ts
@@ -325,7 +325,7 @@ export class ApRendererService {
 			inReplyToNote = await this.notesRepository.findOneBy({ id: note.replyId });
 
 			if (inReplyToNote != null) {
-				const inReplyToUserExist = await this.usersRepository.exist({ where: { id: inReplyToNote.userId } });
+				const inReplyToUserExist = await this.usersRepository.exists({ where: { id: inReplyToNote.userId } });
 
 				if (inReplyToUserExist) {
 					if (inReplyToNote.uri) {
diff --git a/packages/backend/src/core/entities/ChannelEntityService.ts b/packages/backend/src/core/entities/ChannelEntityService.ts
index 305946b8a6..f358875f72 100644
--- a/packages/backend/src/core/entities/ChannelEntityService.ts
+++ b/packages/backend/src/core/entities/ChannelEntityService.ts
@@ -51,14 +51,14 @@ export class ChannelEntityService {
 
 		const banner = channel.bannerId ? await this.driveFilesRepository.findOneBy({ id: channel.bannerId }) : null;
 
-		const isFollowing = meId ? await this.channelFollowingsRepository.exist({
+		const isFollowing = meId ? await this.channelFollowingsRepository.exists({
 			where: {
 				followerId: meId,
 				followeeId: channel.id,
 			},
 		}) : false;
 
-		const isFavorited = meId ? await this.channelFavoritesRepository.exist({
+		const isFavorited = meId ? await this.channelFavoritesRepository.exists({
 			where: {
 				userId: meId,
 				channelId: channel.id,
diff --git a/packages/backend/src/core/entities/ClipEntityService.ts b/packages/backend/src/core/entities/ClipEntityService.ts
index 96422894fd..2133f80f1a 100644
--- a/packages/backend/src/core/entities/ClipEntityService.ts
+++ b/packages/backend/src/core/entities/ClipEntityService.ts
@@ -46,7 +46,7 @@ export class ClipEntityService {
 			description: clip.description,
 			isPublic: clip.isPublic,
 			favoritedCount: await this.clipFavoritesRepository.countBy({ clipId: clip.id }),
-			isFavorited: meId ? await this.clipFavoritesRepository.exist({ where: { clipId: clip.id, userId: meId } }) : undefined,
+			isFavorited: meId ? await this.clipFavoritesRepository.exists({ where: { clipId: clip.id, userId: meId } }) : undefined,
 		});
 	}
 
diff --git a/packages/backend/src/core/entities/FlashEntityService.ts b/packages/backend/src/core/entities/FlashEntityService.ts
index 70faa2b380..c1b9f9a791 100644
--- a/packages/backend/src/core/entities/FlashEntityService.ts
+++ b/packages/backend/src/core/entities/FlashEntityService.ts
@@ -47,7 +47,7 @@ export class FlashEntityService {
 			summary: flash.summary,
 			script: flash.script,
 			likedCount: flash.likedCount,
-			isLiked: meId ? await this.flashLikesRepository.exist({ where: { flashId: flash.id, userId: meId } }) : undefined,
+			isLiked: meId ? await this.flashLikesRepository.exists({ where: { flashId: flash.id, userId: meId } }) : undefined,
 		});
 	}
 
diff --git a/packages/backend/src/core/entities/GalleryPostEntityService.ts b/packages/backend/src/core/entities/GalleryPostEntityService.ts
index d7b960e0d9..2a615a9216 100644
--- a/packages/backend/src/core/entities/GalleryPostEntityService.ts
+++ b/packages/backend/src/core/entities/GalleryPostEntityService.ts
@@ -53,7 +53,7 @@ export class GalleryPostEntityService {
 			tags: post.tags.length > 0 ? post.tags : undefined,
 			isSensitive: post.isSensitive,
 			likedCount: post.likedCount,
-			isLiked: meId ? await this.galleryLikesRepository.exist({ where: { postId: post.id, userId: meId } }) : undefined,
+			isLiked: meId ? await this.galleryLikesRepository.exists({ where: { postId: post.id, userId: meId } }) : undefined,
 		});
 	}
 
diff --git a/packages/backend/src/core/entities/NoteEntityService.ts b/packages/backend/src/core/entities/NoteEntityService.ts
index a095a5daf7..6cf737a906 100644
--- a/packages/backend/src/core/entities/NoteEntityService.ts
+++ b/packages/backend/src/core/entities/NoteEntityService.ts
@@ -108,7 +108,7 @@ export class NoteEntityService implements OnModuleInit {
 				hide = false;
 			} else {
 				// フォロワーかどうか
-				const isFollowing = await this.followingsRepository.exist({
+				const isFollowing = await this.followingsRepository.exists({
 					where: {
 						followeeId: packedNote.userId,
 						followerId: meId,
diff --git a/packages/backend/src/core/entities/PageEntityService.ts b/packages/backend/src/core/entities/PageEntityService.ts
index bc26362aba..27c9011f6d 100644
--- a/packages/backend/src/core/entities/PageEntityService.ts
+++ b/packages/backend/src/core/entities/PageEntityService.ts
@@ -104,7 +104,7 @@ export class PageEntityService {
 			eyeCatchingImage: page.eyeCatchingImageId ? await this.driveFileEntityService.pack(page.eyeCatchingImageId) : null,
 			attachedFiles: this.driveFileEntityService.packMany((await Promise.all(attachedFiles)).filter((x): x is MiDriveFile => x != null)),
 			likedCount: page.likedCount,
-			isLiked: meId ? await this.pageLikesRepository.exist({ where: { pageId: page.id, userId: meId } }) : undefined,
+			isLiked: meId ? await this.pageLikesRepository.exists({ where: { pageId: page.id, userId: meId } }) : undefined,
 		});
 	}
 
diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts
index 9d73dc607d..2496180ec1 100644
--- a/packages/backend/src/core/entities/UserEntityService.ts
+++ b/packages/backend/src/core/entities/UserEntityService.ts
@@ -152,43 +152,43 @@ export class UserEntityService implements OnModuleInit {
 				followerId: me,
 				followeeId: target,
 			}),
-			this.followingsRepository.exist({
+			this.followingsRepository.exists({
 				where: {
 					followerId: target,
 					followeeId: me,
 				},
 			}),
-			this.followRequestsRepository.exist({
+			this.followRequestsRepository.exists({
 				where: {
 					followerId: me,
 					followeeId: target,
 				},
 			}),
-			this.followRequestsRepository.exist({
+			this.followRequestsRepository.exists({
 				where: {
 					followerId: target,
 					followeeId: me,
 				},
 			}),
-			this.blockingsRepository.exist({
+			this.blockingsRepository.exists({
 				where: {
 					blockerId: me,
 					blockeeId: target,
 				},
 			}),
-			this.blockingsRepository.exist({
+			this.blockingsRepository.exists({
 				where: {
 					blockerId: target,
 					blockeeId: me,
 				},
 			}),
-			this.mutingsRepository.exist({
+			this.mutingsRepository.exists({
 				where: {
 					muterId: me,
 					muteeId: target,
 				},
 			}),
-			this.renoteMutingsRepository.exist({
+			this.renoteMutingsRepository.exists({
 				where: {
 					muterId: me,
 					muteeId: target,
@@ -215,7 +215,7 @@ export class UserEntityService implements OnModuleInit {
 		/*
 		const myAntennas = (await this.antennaService.getAntennas()).filter(a => a.userId === userId);
 
-		const isUnread = (myAntennas.length > 0 ? await this.antennaNotesRepository.exist({
+		const isUnread = (myAntennas.length > 0 ? await this.antennaNotesRepository.exists({
 			where: {
 				antennaId: In(myAntennas.map(x => x.id)),
 				read: false,
diff --git a/packages/backend/src/server/api/SignupApiService.ts b/packages/backend/src/server/api/SignupApiService.ts
index 95016cdba6..f025999c7a 100644
--- a/packages/backend/src/server/api/SignupApiService.ts
+++ b/packages/backend/src/server/api/SignupApiService.ts
@@ -163,12 +163,12 @@ export class SignupApiService {
 		}
 
 		if (instance.emailRequiredForSignup) {
-			if (await this.usersRepository.exist({ where: { usernameLower: username.toLowerCase(), host: IsNull() } })) {
+			if (await this.usersRepository.exists({ where: { usernameLower: username.toLowerCase(), host: IsNull() } })) {
 				throw new FastifyReplyError(400, 'DUPLICATED_USERNAME');
 			}
 
 			// Check deleted username duplication
-			if (await this.usedUsernamesRepository.exist({ where: { username: username.toLowerCase() } })) {
+			if (await this.usedUsernamesRepository.exists({ where: { username: username.toLowerCase() } })) {
 				throw new FastifyReplyError(400, 'USED_USERNAME');
 			}
 
diff --git a/packages/backend/src/server/api/endpoints/admin/promo/create.ts b/packages/backend/src/server/api/endpoints/admin/promo/create.ts
index ab69dfba96..339b7a8aa4 100644
--- a/packages/backend/src/server/api/endpoints/admin/promo/create.ts
+++ b/packages/backend/src/server/api/endpoints/admin/promo/create.ts
@@ -55,7 +55,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				throw e;
 			});
 
-			const exist = await this.promoNotesRepository.exist({ where: { noteId: note.id } });
+			const exist = await this.promoNotesRepository.exists({ where: { noteId: note.id } });
 
 			if (exist) {
 				throw new ApiError(meta.errors.alreadyPromoted);
diff --git a/packages/backend/src/server/api/endpoints/auth/accept.ts b/packages/backend/src/server/api/endpoints/auth/accept.ts
index e0baeb3565..602c34b1e6 100644
--- a/packages/backend/src/server/api/endpoints/auth/accept.ts
+++ b/packages/backend/src/server/api/endpoints/auth/accept.ts
@@ -62,7 +62,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			const accessToken = secureRndstr(32);
 
 			// Fetch exist access token
-			const exist = await this.accessTokensRepository.exist({
+			const exist = await this.accessTokensRepository.exists({
 				where: {
 					appId: session.appId,
 					userId: me.id,
diff --git a/packages/backend/src/server/api/endpoints/blocking/create.ts b/packages/backend/src/server/api/endpoints/blocking/create.ts
index 1dc4563180..ea7d2076b0 100644
--- a/packages/backend/src/server/api/endpoints/blocking/create.ts
+++ b/packages/backend/src/server/api/endpoints/blocking/create.ts
@@ -88,7 +88,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			});
 
 			// Check if already blocking
-			const exist = await this.blockingsRepository.exist({
+			const exist = await this.blockingsRepository.exists({
 				where: {
 					blockerId: blocker.id,
 					blockeeId: blockee.id,
diff --git a/packages/backend/src/server/api/endpoints/blocking/delete.ts b/packages/backend/src/server/api/endpoints/blocking/delete.ts
index a6e6bcb5b3..b0d66fd05c 100644
--- a/packages/backend/src/server/api/endpoints/blocking/delete.ts
+++ b/packages/backend/src/server/api/endpoints/blocking/delete.ts
@@ -88,7 +88,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			});
 
 			// Check not blocking
-			const exist = await this.blockingsRepository.exist({
+			const exist = await this.blockingsRepository.exists({
 				where: {
 					blockerId: blocker.id,
 					blockeeId: blockee.id,
diff --git a/packages/backend/src/server/api/endpoints/clips/favorite.ts b/packages/backend/src/server/api/endpoints/clips/favorite.ts
index 015b2cfa85..b4c6a4940b 100644
--- a/packages/backend/src/server/api/endpoints/clips/favorite.ts
+++ b/packages/backend/src/server/api/endpoints/clips/favorite.ts
@@ -62,7 +62,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				throw new ApiError(meta.errors.noSuchClip);
 			}
 
-			const exist = await this.clipFavoritesRepository.exist({
+			const exist = await this.clipFavoritesRepository.exists({
 				where: {
 					clipId: clip.id,
 					userId: me.id,
diff --git a/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts b/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts
index 85e6312b6a..8c1f491f8d 100644
--- a/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts
+++ b/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts
@@ -38,7 +38,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 		private driveFilesRepository: DriveFilesRepository,
 	) {
 		super(meta, paramDef, async (ps, me) => {
-			const exist = await this.driveFilesRepository.exist({
+			const exist = await this.driveFilesRepository.exists({
 				where: {
 					md5: ps.md5,
 					userId: me.id,
diff --git a/packages/backend/src/server/api/endpoints/flash/like.ts b/packages/backend/src/server/api/endpoints/flash/like.ts
index 1003249c0c..5878200828 100644
--- a/packages/backend/src/server/api/endpoints/flash/like.ts
+++ b/packages/backend/src/server/api/endpoints/flash/like.ts
@@ -70,7 +70,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			}
 
 			// if already liked
-			const exist = await this.flashLikesRepository.exist({
+			const exist = await this.flashLikesRepository.exists({
 				where: {
 					flashId: flash.id,
 					userId: me.id,
diff --git a/packages/backend/src/server/api/endpoints/following/create.ts b/packages/backend/src/server/api/endpoints/following/create.ts
index 9037944ef9..1d0691407d 100644
--- a/packages/backend/src/server/api/endpoints/following/create.ts
+++ b/packages/backend/src/server/api/endpoints/following/create.ts
@@ -101,7 +101,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			});
 
 			// Check if already following
-			const exist = await this.followingsRepository.exist({
+			const exist = await this.followingsRepository.exists({
 				where: {
 					followerId: follower.id,
 					followeeId: followee.id,
diff --git a/packages/backend/src/server/api/endpoints/following/delete.ts b/packages/backend/src/server/api/endpoints/following/delete.ts
index f44692ba6d..f761968c90 100644
--- a/packages/backend/src/server/api/endpoints/following/delete.ts
+++ b/packages/backend/src/server/api/endpoints/following/delete.ts
@@ -85,7 +85,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			});
 
 			// Check not following
-			const exist = await this.followingsRepository.exist({
+			const exist = await this.followingsRepository.exists({
 				where: {
 					followerId: follower.id,
 					followeeId: followee.id,
diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/like.ts b/packages/backend/src/server/api/endpoints/gallery/posts/like.ts
index cc424261b4..576cff4e91 100644
--- a/packages/backend/src/server/api/endpoints/gallery/posts/like.ts
+++ b/packages/backend/src/server/api/endpoints/gallery/posts/like.ts
@@ -72,7 +72,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			}
 
 			// if already liked
-			const exist = await this.galleryLikesRepository.exist({
+			const exist = await this.galleryLikesRepository.exists({
 				where: {
 					postId: post.id,
 					userId: me.id,
diff --git a/packages/backend/src/server/api/endpoints/i/import-antennas.ts b/packages/backend/src/server/api/endpoints/i/import-antennas.ts
index 71db8710af..771f3395ce 100644
--- a/packages/backend/src/server/api/endpoints/i/import-antennas.ts
+++ b/packages/backend/src/server/api/endpoints/i/import-antennas.ts
@@ -71,7 +71,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 		private downloadService: DownloadService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
-			const userExist = await this.usersRepository.exist({ where: { id: me.id } });
+			const userExist = await this.usersRepository.exists({ where: { id: me.id } });
 			if (!userExist) throw new ApiError(meta.errors.noSuchUser);
 			const file = await this.driveFilesRepository.findOneBy({ id: ps.fileId });
 			if (file === null) throw new ApiError(meta.errors.noSuchFile);
diff --git a/packages/backend/src/server/api/endpoints/i/revoke-token.ts b/packages/backend/src/server/api/endpoints/i/revoke-token.ts
index 98d866f867..545d16082c 100644
--- a/packages/backend/src/server/api/endpoints/i/revoke-token.ts
+++ b/packages/backend/src/server/api/endpoints/i/revoke-token.ts
@@ -34,7 +34,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 	) {
 		super(meta, paramDef, async (ps, me) => {
 			if (ps.tokenId) {
-				const tokenExist = await this.accessTokensRepository.exist({ where: { id: ps.tokenId } });
+				const tokenExist = await this.accessTokensRepository.exists({ where: { id: ps.tokenId } });
 
 				if (tokenExist) {
 					await this.accessTokensRepository.delete({
@@ -43,7 +43,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 					});
 				}
 			} else if (ps.token) {
-				const tokenExist = await this.accessTokensRepository.exist({ where: { token: ps.token } });
+				const tokenExist = await this.accessTokensRepository.exists({ where: { token: ps.token } });
 
 				if (tokenExist) {
 					await this.accessTokensRepository.delete({
diff --git a/packages/backend/src/server/api/endpoints/mute/create.ts b/packages/backend/src/server/api/endpoints/mute/create.ts
index 49c2b5707d..1d931150e1 100644
--- a/packages/backend/src/server/api/endpoints/mute/create.ts
+++ b/packages/backend/src/server/api/endpoints/mute/create.ts
@@ -83,7 +83,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			});
 
 			// Check if already muting
-			const exist = await this.mutingsRepository.exist({
+			const exist = await this.mutingsRepository.exists({
 				where: {
 					muterId: muter.id,
 					muteeId: mutee.id,
diff --git a/packages/backend/src/server/api/endpoints/notes/create.ts b/packages/backend/src/server/api/endpoints/notes/create.ts
index 29a0f7418c..787cda3834 100644
--- a/packages/backend/src/server/api/endpoints/notes/create.ts
+++ b/packages/backend/src/server/api/endpoints/notes/create.ts
@@ -260,7 +260,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 
 				// Check blocking
 				if (renote.userId !== me.id) {
-					const blockExist = await this.blockingsRepository.exist({
+					const blockExist = await this.blockingsRepository.exists({
 						where: {
 							blockerId: renote.userId,
 							blockeeId: me.id,
@@ -308,7 +308,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 
 				// Check blocking
 				if (reply.userId !== me.id) {
-					const blockExist = await this.blockingsRepository.exist({
+					const blockExist = await this.blockingsRepository.exists({
 						where: {
 							blockerId: reply.userId,
 							blockeeId: me.id,
diff --git a/packages/backend/src/server/api/endpoints/notes/favorites/create.ts b/packages/backend/src/server/api/endpoints/notes/favorites/create.ts
index ed3dce7f35..bfa621aa38 100644
--- a/packages/backend/src/server/api/endpoints/notes/favorites/create.ts
+++ b/packages/backend/src/server/api/endpoints/notes/favorites/create.ts
@@ -67,7 +67,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			});
 
 			// if already favorited
-			const exist = await this.noteFavoritesRepository.exist({
+			const exist = await this.noteFavoritesRepository.exists({
 				where: {
 					noteId: note.id,
 					userId: me.id,
diff --git a/packages/backend/src/server/api/endpoints/pages/like.ts b/packages/backend/src/server/api/endpoints/pages/like.ts
index 8c18982b50..bee60f080d 100644
--- a/packages/backend/src/server/api/endpoints/pages/like.ts
+++ b/packages/backend/src/server/api/endpoints/pages/like.ts
@@ -70,7 +70,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			}
 
 			// if already liked
-			const exist = await this.pageLikesRepository.exist({
+			const exist = await this.pageLikesRepository.exists({
 				where: {
 					pageId: page.id,
 					userId: me.id,
diff --git a/packages/backend/src/server/api/endpoints/promo/read.ts b/packages/backend/src/server/api/endpoints/promo/read.ts
index f427939a7a..4899408ddd 100644
--- a/packages/backend/src/server/api/endpoints/promo/read.ts
+++ b/packages/backend/src/server/api/endpoints/promo/read.ts
@@ -49,7 +49,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				throw err;
 			});
 
-			const exist = await this.promoReadsRepository.exist({
+			const exist = await this.promoReadsRepository.exists({
 				where: {
 					noteId: note.id,
 					userId: me.id,
diff --git a/packages/backend/src/server/api/endpoints/users/followers.ts b/packages/backend/src/server/api/endpoints/users/followers.ts
index 5706e46b96..314a45ed61 100644
--- a/packages/backend/src/server/api/endpoints/users/followers.ts
+++ b/packages/backend/src/server/api/endpoints/users/followers.ts
@@ -101,7 +101,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				if (me == null) {
 					throw new ApiError(meta.errors.forbidden);
 				} else if (me.id !== user.id) {
-					const isFollowing = await this.followingsRepository.exist({
+					const isFollowing = await this.followingsRepository.exists({
 						where: {
 							followeeId: user.id,
 							followerId: me.id,
diff --git a/packages/backend/src/server/api/endpoints/users/following.ts b/packages/backend/src/server/api/endpoints/users/following.ts
index 794fb04f10..86f55c5a12 100644
--- a/packages/backend/src/server/api/endpoints/users/following.ts
+++ b/packages/backend/src/server/api/endpoints/users/following.ts
@@ -109,7 +109,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				if (me == null) {
 					throw new ApiError(meta.errors.forbidden);
 				} else if (me.id !== user.id) {
-					const isFollowing = await this.followingsRepository.exist({
+					const isFollowing = await this.followingsRepository.exists({
 						where: {
 							followeeId: user.id,
 							followerId: me.id,
diff --git a/packages/backend/src/server/api/endpoints/users/lists/create-from-public.ts b/packages/backend/src/server/api/endpoints/users/lists/create-from-public.ts
index fa2e3338b8..dd9b459a1f 100644
--- a/packages/backend/src/server/api/endpoints/users/lists/create-from-public.ts
+++ b/packages/backend/src/server/api/endpoints/users/lists/create-from-public.ts
@@ -90,7 +90,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 		private roleService: RoleService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
-			const listExist = await this.userListsRepository.exist({
+			const listExist = await this.userListsRepository.exists({
 				where: {
 					id: ps.listId,
 					isPublic: true,
@@ -121,7 +121,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				});
 
 				if (currentUser.id !== me.id) {
-					const blockExist = await this.blockingsRepository.exist({
+					const blockExist = await this.blockingsRepository.exists({
 						where: {
 							blockerId: currentUser.id,
 							blockeeId: me.id,
@@ -132,7 +132,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 					}
 				}
 
-				const exist = await this.userListMembershipsRepository.exist({
+				const exist = await this.userListMembershipsRepository.exists({
 					where: {
 						userListId: userList.id,
 						userId: currentUser.id,
diff --git a/packages/backend/src/server/api/endpoints/users/lists/favorite.ts b/packages/backend/src/server/api/endpoints/users/lists/favorite.ts
index 864cdc2ee0..e5b3a73b55 100644
--- a/packages/backend/src/server/api/endpoints/users/lists/favorite.ts
+++ b/packages/backend/src/server/api/endpoints/users/lists/favorite.ts
@@ -47,7 +47,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 		private idService: IdService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
-			const userListExist = await this.userListsRepository.exist({
+			const userListExist = await this.userListsRepository.exists({
 				where: {
 					id: ps.listId,
 					isPublic: true,
@@ -58,7 +58,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 				throw new ApiError(meta.errors.noSuchList);
 			}
 
-			const exist = await this.userListFavoritesRepository.exist({
+			const exist = await this.userListFavoritesRepository.exists({
 				where: {
 					userId: me.id,
 					userListId: ps.listId,
diff --git a/packages/backend/src/server/api/endpoints/users/lists/push.ts b/packages/backend/src/server/api/endpoints/users/lists/push.ts
index c4ceec575b..0984270943 100644
--- a/packages/backend/src/server/api/endpoints/users/lists/push.ts
+++ b/packages/backend/src/server/api/endpoints/users/lists/push.ts
@@ -104,7 +104,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 
 			// Check blocking
 			if (user.id !== me.id) {
-				const blockExist = await this.blockingsRepository.exist({
+				const blockExist = await this.blockingsRepository.exists({
 					where: {
 						blockerId: user.id,
 						blockeeId: me.id,
@@ -115,7 +115,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				}
 			}
 
-			const exist = await this.userListMembershipsRepository.exist({
+			const exist = await this.userListMembershipsRepository.exists({
 				where: {
 					userListId: userList.id,
 					userId: user.id,
diff --git a/packages/backend/src/server/api/endpoints/users/lists/show.ts b/packages/backend/src/server/api/endpoints/users/lists/show.ts
index df44870b04..10efbaafd8 100644
--- a/packages/backend/src/server/api/endpoints/users/lists/show.ts
+++ b/packages/backend/src/server/api/endpoints/users/lists/show.ts
@@ -74,7 +74,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 					userListId: ps.listId,
 				});
 				if (me !== null) {
-					additionalProperties.isLiked = await this.userListFavoritesRepository.exist({
+					additionalProperties.isLiked = await this.userListFavoritesRepository.exists({
 						where: {
 							userId: me.id,
 							userListId: ps.listId,
diff --git a/packages/backend/src/server/api/endpoints/users/lists/unfavorite.ts b/packages/backend/src/server/api/endpoints/users/lists/unfavorite.ts
index d51d57343e..0935584cbc 100644
--- a/packages/backend/src/server/api/endpoints/users/lists/unfavorite.ts
+++ b/packages/backend/src/server/api/endpoints/users/lists/unfavorite.ts
@@ -45,7 +45,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 		private userListFavoritesRepository: UserListFavoritesRepository,
 	) {
 		super(meta, paramDef, async (ps, me) => {
-			const userListExist = await this.userListsRepository.exist({
+			const userListExist = await this.userListsRepository.exists({
 				where: {
 					id: ps.listId,
 					isPublic: true,
diff --git a/packages/backend/src/server/api/stream/channels/user-list.ts b/packages/backend/src/server/api/stream/channels/user-list.ts
index e0245814c4..0434833df8 100644
--- a/packages/backend/src/server/api/stream/channels/user-list.ts
+++ b/packages/backend/src/server/api/stream/channels/user-list.ts
@@ -43,7 +43,7 @@ class UserListChannel extends Channel {
 		this.withRenotes = params.withRenotes ?? true;
 
 		// Check existence and owner
-		const listExist = await this.userListsRepository.exist({
+		const listExist = await this.userListsRepository.exists({
 			where: {
 				id: this.listId,
 				userId: this.user!.id,
-- 
GitLab


From d40612ac522df8f67758daa0ad17eea7cb2d6da2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?=
 <67428053+kakkokari-gtyih@users.noreply.github.com>
Date: Fri, 9 Feb 2024 00:08:33 +0900
Subject: [PATCH 30/40] =?UTF-8?q?fix(frontend):=20aiscript=E3=81=AE?=
 =?UTF-8?q?=E3=82=B3=E3=83=BC=E3=83=89=E3=83=96=E3=83=AD=E3=83=83=E3=82=AF?=
 =?UTF-8?q?=E3=81=A7=E3=81=AE=E3=83=8F=E3=82=A4=E3=83=A9=E3=82=A4=E3=83=88?=
 =?UTF-8?q?=E6=8C=87=E5=AE=9A=E3=82=92=E4=BF=AE=E6=AD=A3=20(#13208)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 packages/frontend/package.json                |  2 +-
 .../frontend/src/scripts/code-highlighter.ts  |  9 +--
 pnpm-lock.yaml                                | 62 +++++++++++++++++--
 3 files changed, 61 insertions(+), 12 deletions(-)

diff --git a/packages/frontend/package.json b/packages/frontend/package.json
index 9e88c6c036..d8ba08501e 100644
--- a/packages/frontend/package.json
+++ b/packages/frontend/package.json
@@ -29,7 +29,7 @@
 		"@twemoji/parser": "15.0.0",
 		"@vitejs/plugin-vue": "5.0.3",
 		"@vue/compiler-sfc": "3.4.15",
-		"aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.0.6",
+		"aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.2",
 		"astring": "1.8.6",
 		"broadcast-channel": "7.0.0",
 		"buraha": "0.0.1",
diff --git a/packages/frontend/src/scripts/code-highlighter.ts b/packages/frontend/src/scripts/code-highlighter.ts
index b11dfed41a..2733897bab 100644
--- a/packages/frontend/src/scripts/code-highlighter.ts
+++ b/packages/frontend/src/scripts/code-highlighter.ts
@@ -20,7 +20,7 @@ export async function getTheme(mode: 'light' | 'dark', getName = false): Promise
 		const base = [lightTheme, darkTheme].find(x => x.id === theme.base);
 		if (base && base.codeHighlighter) theme.codeHighlighter = Object.assign({}, base.codeHighlighter, theme.codeHighlighter);
 	}
-	
+
 	if (theme.codeHighlighter) {
 		let _res: ThemeRegistration = {};
 		if (theme.codeHighlighter.base === '_none_') {
@@ -55,7 +55,7 @@ export async function getHighlighter(): Promise<Highlighter> {
 
 export async function initHighlighter() {
 	const aiScriptGrammar = await import('aiscript-vscode/aiscript/syntaxes/aiscript.tmLanguage.json');
-	
+
 	await loadWasm(import('shiki/onig.wasm?init'));
 
 	// テーマの重複を消す
@@ -68,10 +68,7 @@ export async function initHighlighter() {
 		themes,
 		langs: [
 			import('shiki/langs/javascript.mjs'),
-			{
-				aliases: ['is', 'ais'],
-				...aiScriptGrammar.default,
-			} as unknown as LanguageRegistration,
+			aiScriptGrammar.default as unknown as LanguageRegistration,
 		],
 	});
 
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 291ec305c7..bdc9a48464 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -704,8 +704,8 @@ importers:
         specifier: 3.4.15
         version: 3.4.15
       aiscript-vscode:
-        specifier: github:aiscript-dev/aiscript-vscode#v0.0.6
-        version: github.com/aiscript-dev/aiscript-vscode/b5a8aa0ad927831a0b867d1c183460a14e6c48cd
+        specifier: github:aiscript-dev/aiscript-vscode#v0.1.2
+        version: github.com/aiscript-dev/aiscript-vscode/793211d40243c8775f6b85f015c221c82cbffb07
       astring:
         specifier: 1.8.6
         version: 1.8.6
@@ -20001,6 +20001,42 @@ packages:
     resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==}
     engines: {node: '>=0.10.0'}
 
+  /vscode-jsonrpc@8.2.0:
+    resolution: {integrity: sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==}
+    engines: {node: '>=14.0.0'}
+    dev: false
+
+  /vscode-languageclient@9.0.1:
+    resolution: {integrity: sha512-JZiimVdvimEuHh5olxhxkht09m3JzUGwggb5eRUkzzJhZ2KjCN0nh55VfiED9oez9DyF8/fz1g1iBV3h+0Z2EA==}
+    engines: {vscode: ^1.82.0}
+    dependencies:
+      minimatch: 5.1.2
+      semver: 7.5.4
+      vscode-languageserver-protocol: 3.17.5
+    dev: false
+
+  /vscode-languageserver-protocol@3.17.5:
+    resolution: {integrity: sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==}
+    dependencies:
+      vscode-jsonrpc: 8.2.0
+      vscode-languageserver-types: 3.17.5
+    dev: false
+
+  /vscode-languageserver-textdocument@1.0.11:
+    resolution: {integrity: sha512-X+8T3GoiwTVlJbicx/sIAF+yuJAqz8VvwJyoMVhwEMoEKE/fkDmrqUgDMyBECcM2A2frVZIUj5HI/ErRXCfOeA==}
+    dev: false
+
+  /vscode-languageserver-types@3.17.5:
+    resolution: {integrity: sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==}
+    dev: false
+
+  /vscode-languageserver@9.0.1:
+    resolution: {integrity: sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==}
+    hasBin: true
+    dependencies:
+      vscode-languageserver-protocol: 3.17.5
+    dev: false
+
   /vue-component-type-helpers@1.8.27:
     resolution: {integrity: sha512-0vOfAtI67UjeO1G6UiX5Kd76CqaQ67wrRZiOe7UAb9Jm6GzlUr/fC7CV90XfwapJRjpCMaZFhv1V0ajWRmE9Dg==}
     dev: true
@@ -20560,11 +20596,27 @@ packages:
       readable-stream: 3.6.0
     dev: false
 
-  github.com/aiscript-dev/aiscript-vscode/b5a8aa0ad927831a0b867d1c183460a14e6c48cd:
-    resolution: {tarball: https://codeload.github.com/aiscript-dev/aiscript-vscode/tar.gz/b5a8aa0ad927831a0b867d1c183460a14e6c48cd}
+  '@github.com/aiscript-dev/aiscript-languageserver/releases/download/0.1.5/aiscript-dev-aiscript-languageserver-0.1.5.tgz':
+    resolution: {tarball: https://github.com/aiscript-dev/aiscript-languageserver/releases/download/0.1.5/aiscript-dev-aiscript-languageserver-0.1.5.tgz}
+    name: '@aiscript-dev/aiscript-languageserver'
+    version: 0.1.5
+    hasBin: true
+    dependencies:
+      seedrandom: 3.0.5
+      stringz: 2.1.0
+      uuid: 9.0.1
+      vscode-languageserver: 9.0.1
+      vscode-languageserver-textdocument: 1.0.11
+    dev: false
+
+  github.com/aiscript-dev/aiscript-vscode/793211d40243c8775f6b85f015c221c82cbffb07:
+    resolution: {tarball: https://codeload.github.com/aiscript-dev/aiscript-vscode/tar.gz/793211d40243c8775f6b85f015c221c82cbffb07}
     name: aiscript-vscode
-    version: 0.0.6
+    version: 0.1.2
     engines: {vscode: ^1.83.0}
+    dependencies:
+      '@aiscript-dev/aiscript-languageserver': '@github.com/aiscript-dev/aiscript-languageserver/releases/download/0.1.5/aiscript-dev-aiscript-languageserver-0.1.5.tgz'
+      vscode-languageclient: 9.0.1
     dev: false
 
   github.com/misskey-dev/storybook-addon-misskey-theme/cf583db098365b2ccc81a82f63ca9c93bc32b640(@storybook/blocks@7.6.10)(@storybook/components@7.6.10)(@storybook/core-events@7.6.10)(@storybook/manager-api@7.6.10)(@storybook/preview-api@7.6.10)(@storybook/theming@7.6.10)(@storybook/types@7.6.10)(react-dom@18.2.0)(react@18.2.0):
-- 
GitLab


From c0cb76f0ec63dec3dd1d8381a3395c8c84d43a97 Mon Sep 17 00:00:00 2001
From: tamaina <tamaina@hotmail.co.jp>
Date: Thu, 8 Feb 2024 17:24:51 +0000
Subject: [PATCH 31/40] chore: use vite@5.1.0 / pnpm@8.15.1

---
 package.json                   |  2 +-
 packages/frontend/package.json |  2 +-
 pnpm-lock.yaml                 | 66 +++++++++++++++++++---------------
 3 files changed, 39 insertions(+), 31 deletions(-)

diff --git a/package.json b/package.json
index bd4529f159..1a38627361 100644
--- a/package.json
+++ b/package.json
@@ -6,7 +6,7 @@
 		"type": "git",
 		"url": "https://github.com/misskey-dev/misskey.git"
 	},
-	"packageManager": "pnpm@8.12.1",
+	"packageManager": "pnpm@8.15.1",
 	"workspaces": [
 		"packages/frontend",
 		"packages/backend",
diff --git a/packages/frontend/package.json b/packages/frontend/package.json
index d8ba08501e..5aaddde5ca 100644
--- a/packages/frontend/package.json
+++ b/packages/frontend/package.json
@@ -71,7 +71,7 @@
 		"typescript": "5.3.3",
 		"uuid": "9.0.1",
 		"v-code-diff": "1.7.2",
-		"vite": "5.0.12",
+		"vite": "5.1.0",
 		"vue": "3.4.15",
 		"vuedraggable": "next"
 	},
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index bdc9a48464..eb0c613e97 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -699,7 +699,7 @@ importers:
         version: 15.0.0
       '@vitejs/plugin-vue':
         specifier: 5.0.3
-        version: 5.0.3(vite@5.0.12)(vue@3.4.15)
+        version: 5.0.3(vite@5.1.0)(vue@3.4.15)
       '@vue/compiler-sfc':
         specifier: 3.4.15
         version: 3.4.15
@@ -830,8 +830,8 @@ importers:
         specifier: 1.7.2
         version: 1.7.2(vue@3.4.15)
       vite:
-        specifier: 5.0.12
-        version: 5.0.12(@types/node@20.11.10)(sass@1.70.0)(terser@5.27.0)
+        specifier: 5.1.0
+        version: 5.1.0(@types/node@20.11.10)(sass@1.70.0)(terser@5.27.0)
       vue:
         specifier: 3.4.15
         version: 3.4.15(typescript@5.3.3)
@@ -883,7 +883,7 @@ importers:
         version: 7.6.10(react-dom@18.2.0)(react@18.2.0)(typescript@5.3.3)
       '@storybook/react-vite':
         specifier: 7.6.10
-        version: 7.6.10(react-dom@18.2.0)(react@18.2.0)(rollup@4.9.6)(typescript@5.3.3)(vite@5.0.12)
+        version: 7.6.10(react-dom@18.2.0)(react@18.2.0)(rollup@4.9.6)(typescript@5.3.3)(vite@5.1.0)
       '@storybook/testing-library':
         specifier: 0.2.2
         version: 0.2.2
@@ -898,7 +898,7 @@ importers:
         version: 7.6.10(vue@3.4.15)
       '@storybook/vue3-vite':
         specifier: 7.6.10
-        version: 7.6.10(typescript@5.3.3)(vite@5.0.12)(vue@3.4.15)
+        version: 7.6.10(typescript@5.3.3)(vite@5.1.0)(vue@3.4.15)
       '@testing-library/vue':
         specifier: 8.0.1
         version: 8.0.1(@vue/compiler-sfc@3.4.15)(vue@3.4.15)
@@ -4694,7 +4694,7 @@ packages:
       chalk: 4.1.2
     dev: true
 
-  /@joshwooding/vite-plugin-react-docgen-typescript@0.3.0(typescript@5.3.3)(vite@5.0.12):
+  /@joshwooding/vite-plugin-react-docgen-typescript@0.3.0(typescript@5.3.3)(vite@5.1.0):
     resolution: {integrity: sha512-2D6y7fNvFmsLmRt6UCOFJPvFoPMJGT0Uh1Wg0RaigUp7kdQPs6yYn8Dmx6GZkOH/NW0yMTwRz/p0SRMMRo50vA==}
     peerDependencies:
       typescript: '>= 4.3.x'
@@ -4708,7 +4708,7 @@ packages:
       magic-string: 0.27.0
       react-docgen-typescript: 2.2.2(typescript@5.3.3)
       typescript: 5.3.3
-      vite: 5.0.12(@types/node@20.11.10)(sass@1.70.0)(terser@5.27.0)
+      vite: 5.1.0(@types/node@20.11.10)(sass@1.70.0)(terser@5.27.0)
     dev: true
 
   /@jridgewell/gen-mapping@0.3.2:
@@ -6740,7 +6740,7 @@ packages:
       - supports-color
     dev: true
 
-  /@storybook/builder-vite@7.6.10(typescript@5.3.3)(vite@5.0.12):
+  /@storybook/builder-vite@7.6.10(typescript@5.3.3)(vite@5.1.0):
     resolution: {integrity: sha512-qxe19axiNJVdIKj943e1ucAmADwU42fTGgMSdBzzrvfH3pSOmx2057aIxRzd8YtBRnj327eeqpgCHYIDTunMYQ==}
     peerDependencies:
       '@preact/preset-vite': '*'
@@ -6772,7 +6772,7 @@ packages:
       magic-string: 0.30.5
       rollup: 3.29.4
       typescript: 5.3.3
-      vite: 5.0.12(@types/node@20.11.10)(sass@1.70.0)(terser@5.27.0)
+      vite: 5.1.0(@types/node@20.11.10)(sass@1.70.0)(terser@5.27.0)
     transitivePeerDependencies:
       - encoding
       - supports-color
@@ -7129,7 +7129,7 @@ packages:
       react-dom: 18.2.0(react@18.2.0)
     dev: true
 
-  /@storybook/react-vite@7.6.10(react-dom@18.2.0)(react@18.2.0)(rollup@4.9.6)(typescript@5.3.3)(vite@5.0.12):
+  /@storybook/react-vite@7.6.10(react-dom@18.2.0)(react@18.2.0)(rollup@4.9.6)(typescript@5.3.3)(vite@5.1.0):
     resolution: {integrity: sha512-YE2+J1wy8nO+c6Nv/hBMu91Edew3K184L1KSnfoZV8vtq2074k1Me/8pfe0QNuq631AncpfCYNb37yBAXQ/80w==}
     engines: {node: '>=16'}
     peerDependencies:
@@ -7137,16 +7137,16 @@ packages:
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
       vite: ^3.0.0 || ^4.0.0 || ^5.0.0
     dependencies:
-      '@joshwooding/vite-plugin-react-docgen-typescript': 0.3.0(typescript@5.3.3)(vite@5.0.12)
+      '@joshwooding/vite-plugin-react-docgen-typescript': 0.3.0(typescript@5.3.3)(vite@5.1.0)
       '@rollup/pluginutils': 5.1.0(rollup@4.9.6)
-      '@storybook/builder-vite': 7.6.10(typescript@5.3.3)(vite@5.0.12)
+      '@storybook/builder-vite': 7.6.10(typescript@5.3.3)(vite@5.1.0)
       '@storybook/react': 7.6.10(react-dom@18.2.0)(react@18.2.0)(typescript@5.3.3)
-      '@vitejs/plugin-react': 3.1.0(vite@5.0.12)
+      '@vitejs/plugin-react': 3.1.0(vite@5.1.0)
       magic-string: 0.30.5
       react: 18.2.0
       react-docgen: 7.0.1
       react-dom: 18.2.0(react@18.2.0)
-      vite: 5.0.12(@types/node@20.11.10)(sass@1.70.0)(terser@5.27.0)
+      vite: 5.1.0(@types/node@20.11.10)(sass@1.70.0)(terser@5.27.0)
     transitivePeerDependencies:
       - '@preact/preset-vite'
       - encoding
@@ -7261,18 +7261,18 @@ packages:
       file-system-cache: 2.3.0
     dev: true
 
-  /@storybook/vue3-vite@7.6.10(typescript@5.3.3)(vite@5.0.12)(vue@3.4.15):
+  /@storybook/vue3-vite@7.6.10(typescript@5.3.3)(vite@5.1.0)(vue@3.4.15):
     resolution: {integrity: sha512-5f0Rh4PTVEeAI86ybihfN+rHGXXLNiRsoGKinpJSb7hkfsq/L7u3sVCXJwH/qsG+rUJlZyHs3kfa4/Kgyyi3Mg==}
     engines: {node: ^14.18 || >=16}
     peerDependencies:
       vite: ^3.0.0 || ^4.0.0 || ^5.0.0
     dependencies:
-      '@storybook/builder-vite': 7.6.10(typescript@5.3.3)(vite@5.0.12)
+      '@storybook/builder-vite': 7.6.10(typescript@5.3.3)(vite@5.1.0)
       '@storybook/core-server': 7.6.10
       '@storybook/vue3': 7.6.10(vue@3.4.15)
-      '@vitejs/plugin-vue': 4.5.2(vite@5.0.12)(vue@3.4.15)
+      '@vitejs/plugin-vue': 4.5.2(vite@5.1.0)(vue@3.4.15)
       magic-string: 0.30.5
-      vite: 5.0.12(@types/node@20.11.10)(sass@1.70.0)(terser@5.27.0)
+      vite: 5.1.0(@types/node@20.11.10)(sass@1.70.0)(terser@5.27.0)
       vue-docgen-api: 4.64.1(vue@3.4.15)
     transitivePeerDependencies:
       - '@preact/preset-vite'
@@ -8833,7 +8833,7 @@ packages:
     resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==}
     dev: true
 
-  /@vitejs/plugin-react@3.1.0(vite@5.0.12):
+  /@vitejs/plugin-react@3.1.0(vite@5.1.0):
     resolution: {integrity: sha512-AfgcRL8ZBhAlc3BFdigClmTUMISmmzHn7sB2h9U1odvc5U/MjWXsAaz18b/WoppUTDBzxOJwo2VdClfUcItu9g==}
     engines: {node: ^14.18.0 || >=16.0.0}
     peerDependencies:
@@ -8844,30 +8844,30 @@ packages:
       '@babel/plugin-transform-react-jsx-source': 7.19.6(@babel/core@7.23.5)
       magic-string: 0.27.0
       react-refresh: 0.14.0
-      vite: 5.0.12(@types/node@20.11.10)(sass@1.70.0)(terser@5.27.0)
+      vite: 5.1.0(@types/node@20.11.10)(sass@1.70.0)(terser@5.27.0)
     transitivePeerDependencies:
       - supports-color
     dev: true
 
-  /@vitejs/plugin-vue@4.5.2(vite@5.0.12)(vue@3.4.15):
+  /@vitejs/plugin-vue@4.5.2(vite@5.1.0)(vue@3.4.15):
     resolution: {integrity: sha512-UGR3DlzLi/SaVBPX0cnSyE37vqxU3O6chn8l0HJNzQzDia6/Au2A4xKv+iIJW8w2daf80G7TYHhi1pAUjdZ0bQ==}
     engines: {node: ^14.18.0 || >=16.0.0}
     peerDependencies:
       vite: ^4.0.0 || ^5.0.0
       vue: ^3.2.25
     dependencies:
-      vite: 5.0.12(@types/node@20.11.10)(sass@1.70.0)(terser@5.27.0)
+      vite: 5.1.0(@types/node@20.11.10)(sass@1.70.0)(terser@5.27.0)
       vue: 3.4.15(typescript@5.3.3)
     dev: true
 
-  /@vitejs/plugin-vue@5.0.3(vite@5.0.12)(vue@3.4.15):
+  /@vitejs/plugin-vue@5.0.3(vite@5.1.0)(vue@3.4.15):
     resolution: {integrity: sha512-b8S5dVS40rgHdDrw+DQi/xOM9ed+kSRZzfm1T74bMmBDCd8XO87NKlFYInzCtwvtWwXZvo1QxE2OSspTATWrbA==}
     engines: {node: ^18.0.0 || >=20.0.0}
     peerDependencies:
       vite: ^5.0.0
       vue: ^3.2.25
     dependencies:
-      vite: 5.0.12(@types/node@20.11.10)(sass@1.70.0)(terser@5.27.0)
+      vite: 5.1.0(@types/node@20.11.10)(sass@1.70.0)(terser@5.27.0)
       vue: 3.4.15(typescript@5.3.3)
     dev: false
 
@@ -16910,6 +16910,14 @@ packages:
       picocolors: 1.0.0
       source-map-js: 1.0.2
 
+  /postcss@8.4.35:
+    resolution: {integrity: sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==}
+    engines: {node: ^10 || ^12 || >=14}
+    dependencies:
+      nanoid: 3.3.7
+      picocolors: 1.0.0
+      source-map-js: 1.0.2
+
   /postgres-array@2.0.0:
     resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==}
     engines: {node: '>=4'}
@@ -19866,7 +19874,7 @@ packages:
       mlly: 1.5.0
       pathe: 1.1.2
       picocolors: 1.0.0
-      vite: 5.0.12(@types/node@20.11.10)(sass@1.70.0)(terser@5.27.0)
+      vite: 5.1.0(@types/node@20.11.10)(sass@1.70.0)(terser@5.27.0)
     transitivePeerDependencies:
       - '@types/node'
       - less
@@ -19882,8 +19890,8 @@ packages:
     resolution: {integrity: sha512-p4D8CFVhZS412SyQX125qxyzOgIFouwOcvjZWk6bQbNPR1wtaEzFT6jZxAjf1dejlGqa6fqHcuCvQea6EWUkUA==}
     dev: true
 
-  /vite@5.0.12(@types/node@20.11.10)(sass@1.70.0)(terser@5.27.0):
-    resolution: {integrity: sha512-4hsnEkG3q0N4Tzf1+t6NdN9dg/L3BM+q8SWgbSPnJvrgH2kgdyzfVJwbR1ic69/4uMJJ/3dqDZZE5/WwqW8U1w==}
+  /vite@5.1.0(@types/node@20.11.10)(sass@1.70.0)(terser@5.27.0):
+    resolution: {integrity: sha512-STmSFzhY4ljuhz14bg9LkMTk3d98IO6DIArnTY6MeBwiD1Za2StcQtz7fzOUnRCqrHSD5+OS2reg4HOz1eoLnw==}
     engines: {node: ^18.0.0 || >=20.0.0}
     hasBin: true
     peerDependencies:
@@ -19912,7 +19920,7 @@ packages:
     dependencies:
       '@types/node': 20.11.10
       esbuild: 0.19.11
-      postcss: 8.4.33
+      postcss: 8.4.35
       rollup: 4.9.6
       sass: 1.70.0
       terser: 5.27.0
@@ -19984,7 +19992,7 @@ packages:
       strip-literal: 1.3.0
       tinybench: 2.6.0
       tinypool: 0.7.0
-      vite: 5.0.12(@types/node@20.11.10)(sass@1.70.0)(terser@5.27.0)
+      vite: 5.1.0(@types/node@20.11.10)(sass@1.70.0)(terser@5.27.0)
       vite-node: 0.34.6(@types/node@20.11.10)(sass@1.70.0)(terser@5.27.0)
       why-is-node-running: 2.2.2
     transitivePeerDependencies:
-- 
GitLab


From 614c9a0fc602586710e3f24bb26140bb49c2d54a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E3=81=8A=E3=81=95=E3=82=80=E3=81=AE=E3=81=B2=E3=81=A8?=
 <46447427+samunohito@users.noreply.github.com>
Date: Fri, 9 Feb 2024 10:07:18 +0900
Subject: [PATCH 32/40] =?UTF-8?q?fix:=20=E7=89=B9=E5=AE=9A=E6=96=87?=
 =?UTF-8?q?=E5=AD=97=E5=88=97=E3=82=92=E5=90=AB=E3=82=80=E3=83=8E=E3=83=BC?=
 =?UTF-8?q?=E3=83=88=E3=82=92=E6=8A=95=E7=A8=BF=E3=81=A7=E3=81=8D=E3=81=AA?=
 =?UTF-8?q?=E3=81=84=E3=82=88=E3=81=86=E3=81=AB=E3=81=99=E3=82=8B=E7=AE=A1?=
 =?UTF-8?q?=E7=90=86=E7=94=BB=E9=9D=A2=E7=94=A8=E8=A8=AD=E5=AE=9A=E9=A0=85?=
 =?UTF-8?q?=E7=9B=AE=E3=82=92=E8=BF=BD=E5=8A=A0=20(#13210)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* fix: 特定文字列を含むノートを投稿できないようにする管理画面用設定項目を追加

* Serviceでチェックするように変更
---
 CHANGELOG.md                                  |  2 +
 locales/index.d.ts                            | 12 +++
 locales/ja-JP.yml                             |  3 +
 .../1707429690000-prohibited-words.js         | 16 ++++
 packages/backend/src/core/HashtagService.ts   |  2 +-
 .../backend/src/core/NoteCreateService.ts     | 10 ++-
 packages/backend/src/core/UtilityService.ts   |  6 +-
 packages/backend/src/models/Meta.ts           |  5 ++
 .../src/server/api/endpoints/admin/meta.ts    |  8 ++
 .../server/api/endpoints/admin/update-meta.ts |  8 ++
 .../src/server/api/endpoints/notes/create.ts  | 67 ++++++++++-------
 packages/backend/test/e2e/note.ts             | 73 +++++++++++++++++++
 .../frontend/src/pages/admin/moderation.vue   |  8 ++
 packages/misskey-js/src/autogen/types.ts      |  2 +
 14 files changed, 192 insertions(+), 30 deletions(-)
 create mode 100644 packages/backend/migration/1707429690000-prohibited-words.js

diff --git a/CHANGELOG.md b/CHANGELOG.md
index a32c557c94..1d788e1522 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -24,6 +24,8 @@
 - Fix: リモートユーザーのリアクション一覧がすべて見えてしまうのを修正
   * すべてのリモートユーザーのリアクション一覧を見えないようにします
 - Enhance: モデレーターはすべてのユーザーのリアクション一覧を見られるように
+- Fix: 特定のキーワードを含むノートが投稿された際、エラーに出来るような設定項目を追加 #13207
+  * デフォルトは空欄なので適用前と同等の動作になります
 
 ### Client
 - Feat: 新しいゲームを追加
diff --git a/locales/index.d.ts b/locales/index.d.ts
index f8c4971655..8f4c9d18e4 100644
--- a/locales/index.d.ts
+++ b/locales/index.d.ts
@@ -4180,6 +4180,18 @@ export interface Locale extends ILocale {
      * スペースで区切るとAND指定になり、キーワードをスラッシュで囲むと正規表現になります。
      */
     "sensitiveWordsDescription2": string;
+    /**
+     * 禁止ワード
+     */
+    "prohibitedWords": string;
+    /**
+     * 設定したワードが含まれるノートを投稿しようとした際、エラーとなるようにします。改行で区切って複数設定できます。
+     */
+    "prohibitedWordsDescription": string;
+    /**
+     * スペースで区切るとAND指定になり、キーワードをスラッシュで囲むと正規表現になります。
+     */
+    "prohibitedWordsDescription2": string;
     /**
      * 非表示ハッシュタグ
      */
diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index cf45c13f75..5348502425 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -1041,6 +1041,9 @@ resetPasswordConfirm: "パスワードリセットしますか?"
 sensitiveWords: "センシティブワード"
 sensitiveWordsDescription: "設定したワードが含まれるノートの公開範囲をホームにします。改行で区切って複数設定できます。"
 sensitiveWordsDescription2: "スペースで区切るとAND指定になり、キーワードをスラッシュで囲むと正規表現になります。"
+prohibitedWords: "禁止ワード"
+prohibitedWordsDescription: "設定したワードが含まれるノートを投稿しようとした際、エラーとなるようにします。改行で区切って複数設定できます。"
+prohibitedWordsDescription2: "スペースで区切るとAND指定になり、キーワードをスラッシュで囲むと正規表現になります。"
 hiddenTags: "非表示ハッシュタグ"
 hiddenTagsDescription: "設定したタグをトレンドに表示させないようにします。改行で区切って複数設定できます。"
 notesSearchNotAvailable: "ノート検索は利用できません。"
diff --git a/packages/backend/migration/1707429690000-prohibited-words.js b/packages/backend/migration/1707429690000-prohibited-words.js
new file mode 100644
index 0000000000..2dd62d8ff8
--- /dev/null
+++ b/packages/backend/migration/1707429690000-prohibited-words.js
@@ -0,0 +1,16 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class prohibitedWords1707429690000 {
+    name = 'prohibitedWords1707429690000'
+
+    async up(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "meta" ADD "prohibitedWords" character varying(1024) array NOT NULL DEFAULT '{}'`);
+    }
+
+    async down(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "prohibitedWords"`);
+    }
+}
diff --git a/packages/backend/src/core/HashtagService.ts b/packages/backend/src/core/HashtagService.ts
index 5a2417c9cd..712530108e 100644
--- a/packages/backend/src/core/HashtagService.ts
+++ b/packages/backend/src/core/HashtagService.ts
@@ -163,7 +163,7 @@ export class HashtagService {
 		const instance = await this.metaService.fetch();
 		const hiddenTags = instance.hiddenTags.map(t => normalizeForSearch(t));
 		if (hiddenTags.includes(hashtag)) return;
-		if (this.utilityService.isSensitiveWordIncluded(hashtag, instance.sensitiveWords)) return;
+		if (this.utilityService.isKeyWordIncluded(hashtag, instance.sensitiveWords)) return;
 
 		// YYYYMMDDHHmm (10分間隔)
 		const now = new Date();
diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts
index f7e870831d..153a6406a9 100644
--- a/packages/backend/src/core/NoteCreateService.ts
+++ b/packages/backend/src/core/NoteCreateService.ts
@@ -151,6 +151,8 @@ type Option = {
 export class NoteCreateService implements OnApplicationShutdown {
 	#shutdownController = new AbortController();
 
+	public static ContainsProhibitedWordsError = class extends Error {};
+
 	constructor(
 		@Inject(DI.config)
 		private config: Config,
@@ -254,13 +256,19 @@ export class NoteCreateService implements OnApplicationShutdown {
 
 		if (data.visibility === 'public' && data.channel == null) {
 			const sensitiveWords = meta.sensitiveWords;
-			if (this.utilityService.isSensitiveWordIncluded(data.cw ?? data.text ?? '', sensitiveWords)) {
+			if (this.utilityService.isKeyWordIncluded(data.cw ?? data.text ?? '', sensitiveWords)) {
 				data.visibility = 'home';
 			} else if ((await this.roleService.getUserPolicies(user.id)).canPublicNote === false) {
 				data.visibility = 'home';
 			}
 		}
 
+		if (!user.host) {
+			if (this.utilityService.isKeyWordIncluded(data.cw ?? data.text ?? '', meta.prohibitedWords)) {
+				throw new NoteCreateService.ContainsProhibitedWordsError();
+			}
+		}
+
 		const inSilencedInstance = this.utilityService.isSilencedHost(meta.silencedHosts, user.host);
 
 		if (data.visibility === 'public' && inSilencedInstance && user.host !== null) {
diff --git a/packages/backend/src/core/UtilityService.ts b/packages/backend/src/core/UtilityService.ts
index 5dec36c89e..15b98abe63 100644
--- a/packages/backend/src/core/UtilityService.ts
+++ b/packages/backend/src/core/UtilityService.ts
@@ -43,13 +43,13 @@ export class UtilityService {
 	}
 
 	@bindThis
-	public isSensitiveWordIncluded(text: string, sensitiveWords: string[]): boolean {
-		if (sensitiveWords.length === 0) return false;
+	public isKeyWordIncluded(text: string, keyWords: string[]): boolean {
+		if (keyWords.length === 0) return false;
 		if (text === '') return false;
 
 		const regexpregexp = /^\/(.+)\/(.*)$/;
 
-		const matched = sensitiveWords.some(filter => {
+		const matched = keyWords.some(filter => {
 			// represents RegExp
 			const regexp = filter.match(regexpregexp);
 			// This should never happen due to input sanitisation.
diff --git a/packages/backend/src/models/Meta.ts b/packages/backend/src/models/Meta.ts
index 3265e85dd7..bcde2db0b7 100644
--- a/packages/backend/src/models/Meta.ts
+++ b/packages/backend/src/models/Meta.ts
@@ -76,6 +76,11 @@ export class MiMeta {
 	})
 	public sensitiveWords: string[];
 
+	@Column('varchar', {
+		length: 1024, array: true, default: '{}',
+	})
+	public prohibitedWords: string[];
+
 	@Column('varchar', {
 		length: 1024, array: true, default: '{}',
 	})
diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts
index 0627c5055c..2af9e7cd9a 100644
--- a/packages/backend/src/server/api/endpoints/admin/meta.ts
+++ b/packages/backend/src/server/api/endpoints/admin/meta.ts
@@ -156,6 +156,13 @@ export const meta = {
 					type: 'string',
 				},
 			},
+			prohibitedWords: {
+				type: 'array',
+				optional: false, nullable: false,
+				items: {
+					type: 'string',
+				},
+			},
 			bannedEmailDomains: {
 				type: 'array',
 				optional: true, nullable: false,
@@ -515,6 +522,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				blockedHosts: instance.blockedHosts,
 				silencedHosts: instance.silencedHosts,
 				sensitiveWords: instance.sensitiveWords,
+				prohibitedWords: instance.prohibitedWords,
 				preservedUsernames: instance.preservedUsernames,
 				hcaptchaSecretKey: instance.hcaptchaSecretKey,
 				mcaptchaSecretKey: instance.mcaptchaSecretKey,
diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts
index d76d3dfeea..ce8c8a505d 100644
--- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts
+++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts
@@ -41,6 +41,11 @@ export const paramDef = {
 				type: 'string',
 			},
 		},
+		prohibitedWords: {
+			type: 'array', nullable: true, items: {
+				type: 'string',
+			},
+		},
 		themeColor: { type: 'string', nullable: true, pattern: '^#[0-9a-fA-F]{6}$' },
 		mascotImageUrl: { type: 'string', nullable: true },
 		bannerUrl: { type: 'string', nullable: true },
@@ -177,6 +182,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			if (Array.isArray(ps.sensitiveWords)) {
 				set.sensitiveWords = ps.sensitiveWords.filter(Boolean);
 			}
+			if (Array.isArray(ps.prohibitedWords)) {
+				set.prohibitedWords = ps.prohibitedWords.filter(Boolean);
+			}
 			if (Array.isArray(ps.silencedHosts)) {
 				let lastValue = '';
 				set.silencedHosts = ps.silencedHosts.sort().filter((h) => {
diff --git a/packages/backend/src/server/api/endpoints/notes/create.ts b/packages/backend/src/server/api/endpoints/notes/create.ts
index 787cda3834..50969c71cc 100644
--- a/packages/backend/src/server/api/endpoints/notes/create.ts
+++ b/packages/backend/src/server/api/endpoints/notes/create.ts
@@ -17,6 +17,8 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
 import { NoteCreateService } from '@/core/NoteCreateService.js';
 import { DI } from '@/di-symbols.js';
 import { isPureRenote } from '@/misc/is-pure-renote.js';
+import { MetaService } from '@/core/MetaService.js';
+import { UtilityService } from '@/core/UtilityService.js';
 import { ApiError } from '../../error.js';
 
 export const meta = {
@@ -111,6 +113,12 @@ export const meta = {
 			code: 'CANNOT_RENOTE_OUTSIDE_OF_CHANNEL',
 			id: '33510210-8452-094c-6227-4a6c05d99f00',
 		},
+
+		containsProhibitedWords: {
+			message: 'Cannot post because it contains prohibited words.',
+			code: 'CONTAINS_PROHIBITED_WORDS',
+			id: 'aa6e01d3-a85c-669d-758a-76aab43af334',
+		},
 	},
 } as const;
 
@@ -340,31 +348,40 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			}
 
 			// 投稿を作成
-			const note = await this.noteCreateService.create(me, {
-				createdAt: new Date(),
-				files: files,
-				poll: ps.poll ? {
-					choices: ps.poll.choices,
-					multiple: ps.poll.multiple ?? false,
-					expiresAt: ps.poll.expiresAt ? new Date(ps.poll.expiresAt) : null,
-				} : undefined,
-				text: ps.text ?? undefined,
-				reply,
-				renote,
-				cw: ps.cw,
-				localOnly: ps.localOnly,
-				reactionAcceptance: ps.reactionAcceptance,
-				visibility: ps.visibility,
-				visibleUsers,
-				channel,
-				apMentions: ps.noExtractMentions ? [] : undefined,
-				apHashtags: ps.noExtractHashtags ? [] : undefined,
-				apEmojis: ps.noExtractEmojis ? [] : undefined,
-			});
-
-			return {
-				createdNote: await this.noteEntityService.pack(note, me),
-			};
+			try {
+				const note = await this.noteCreateService.create(me, {
+					createdAt: new Date(),
+					files: files,
+					poll: ps.poll ? {
+						choices: ps.poll.choices,
+						multiple: ps.poll.multiple ?? false,
+						expiresAt: ps.poll.expiresAt ? new Date(ps.poll.expiresAt) : null,
+					} : undefined,
+					text: ps.text ?? undefined,
+					reply,
+					renote,
+					cw: ps.cw,
+					localOnly: ps.localOnly,
+					reactionAcceptance: ps.reactionAcceptance,
+					visibility: ps.visibility,
+					visibleUsers,
+					channel,
+					apMentions: ps.noExtractMentions ? [] : undefined,
+					apHashtags: ps.noExtractHashtags ? [] : undefined,
+					apEmojis: ps.noExtractEmojis ? [] : undefined,
+				});
+
+				return {
+					createdNote: await this.noteEntityService.pack(note, me),
+				};
+			} catch (e) {
+				// TODO: 他のErrorもここでキャッチしてエラーメッセージを当てるようにしたい
+				if (e instanceof NoteCreateService.ContainsProhibitedWordsError) {
+					throw new ApiError(meta.errors.containsProhibitedWords);
+				}
+
+				throw e;
+			}
 		});
 	}
 }
diff --git a/packages/backend/test/e2e/note.ts b/packages/backend/test/e2e/note.ts
index 0280b051f5..1bc8cb591c 100644
--- a/packages/backend/test/e2e/note.ts
+++ b/packages/backend/test/e2e/note.ts
@@ -16,12 +16,14 @@ describe('Note', () => {
 
 	let alice: misskey.entities.SignupResponse;
 	let bob: misskey.entities.SignupResponse;
+	let tom: misskey.entities.SignupResponse;
 
 	beforeAll(async () => {
 		const connection = await initTestDb(true);
 		Notes = connection.getRepository(MiNote);
 		alice = await signup({ username: 'alice' });
 		bob = await signup({ username: 'bob' });
+		tom = await signup({ username: 'tom', host: 'example.com' });
 	}, 1000 * 60 * 2);
 
 	test('投稿できる', async () => {
@@ -607,6 +609,77 @@ describe('Note', () => {
 			assert.strictEqual(note2.status, 200);
 			assert.strictEqual(note2.body.createdNote.visibility, 'home');
 		});
+
+		test('禁止ワードを含む投稿はエラーになる (単語指定)', async () => {
+			const prohibited = await api('admin/update-meta', {
+				prohibitedWords: [
+					'test',
+				],
+			}, alice);
+
+			assert.strictEqual(prohibited.status, 204);
+
+			await new Promise(x => setTimeout(x, 2));
+
+			const note1 = await api('/notes/create', {
+				text: 'hogetesthuge',
+			}, alice);
+
+			assert.strictEqual(note1.status, 400);
+			assert.strictEqual(note1.body.error.code, 'CONTAINS_PROHIBITED_WORDS');
+		});
+
+		test('禁止ワードを含む投稿はエラーになる (正規表現)', async () => {
+			const prohibited = await api('admin/update-meta', {
+				prohibitedWords: [
+					'/Test/i',
+				],
+			}, alice);
+
+			assert.strictEqual(prohibited.status, 204);
+
+			const note2 = await api('/notes/create', {
+				text: 'hogetesthuge',
+			}, alice);
+
+			assert.strictEqual(note2.status, 400);
+			assert.strictEqual(note2.body.error.code, 'CONTAINS_PROHIBITED_WORDS');
+		});
+
+		test('禁止ワードを含む投稿はエラーになる (スペースアンド)', async () => {
+			const prohibited = await api('admin/update-meta', {
+				prohibitedWords: [
+					'Test hoge',
+				],
+			}, alice);
+
+			assert.strictEqual(prohibited.status, 204);
+
+			const note2 = await api('/notes/create', {
+				text: 'hogeTesthuge',
+			}, alice);
+
+			assert.strictEqual(note2.status, 400);
+			assert.strictEqual(note2.body.error.code, 'CONTAINS_PROHIBITED_WORDS');
+		});
+
+		test('禁止ワードを含んでいてもリモートノートはエラーにならない', async () => {
+			const prohibited = await api('admin/update-meta', {
+				prohibitedWords: [
+					'test',
+				],
+			}, alice);
+
+			assert.strictEqual(prohibited.status, 204);
+
+			await new Promise(x => setTimeout(x, 2));
+
+			const note1 = await api('/notes/create', {
+				text: 'hogetesthuge',
+			}, tom);
+
+			assert.strictEqual(note1.status, 200);
+		});
 	});
 
 	describe('notes/delete', () => {
diff --git a/packages/frontend/src/pages/admin/moderation.vue b/packages/frontend/src/pages/admin/moderation.vue
index 4915bee713..248b4c53ce 100644
--- a/packages/frontend/src/pages/admin/moderation.vue
+++ b/packages/frontend/src/pages/admin/moderation.vue
@@ -40,6 +40,11 @@ SPDX-License-Identifier: AGPL-3.0-only
 						<template #caption>{{ i18n.ts.sensitiveWordsDescription }}<br>{{ i18n.ts.sensitiveWordsDescription2 }}</template>
 					</MkTextarea>
 
+					<MkTextarea v-model="prohibitedWords">
+						<template #label>{{ i18n.ts.prohibitedWords }}</template>
+						<template #caption>{{ i18n.ts.prohibitedWordsDescription }}<br>{{ i18n.ts.prohibitedWordsDescription2 }}</template>
+					</MkTextarea>
+
 					<MkTextarea v-model="hiddenTags">
 						<template #label>{{ i18n.ts.hiddenTags }}</template>
 						<template #caption>{{ i18n.ts.hiddenTagsDescription }}</template>
@@ -76,6 +81,7 @@ import FormLink from '@/components/form/link.vue';
 const enableRegistration = ref<boolean>(false);
 const emailRequiredForSignup = ref<boolean>(false);
 const sensitiveWords = ref<string>('');
+const prohibitedWords = ref<string>('');
 const hiddenTags = ref<string>('');
 const preservedUsernames = ref<string>('');
 const tosUrl = ref<string | null>(null);
@@ -86,6 +92,7 @@ async function init() {
 	enableRegistration.value = !meta.disableRegistration;
 	emailRequiredForSignup.value = meta.emailRequiredForSignup;
 	sensitiveWords.value = meta.sensitiveWords.join('\n');
+	prohibitedWords.value = meta.prohibitedWords.join('\n');
 	hiddenTags.value = meta.hiddenTags.join('\n');
 	preservedUsernames.value = meta.preservedUsernames.join('\n');
 	tosUrl.value = meta.tosUrl;
@@ -99,6 +106,7 @@ function save() {
 		tosUrl: tosUrl.value,
 		privacyPolicyUrl: privacyPolicyUrl.value,
 		sensitiveWords: sensitiveWords.value.split('\n'),
+		prohibitedWords: prohibitedWords.value.split('\n'),
 		hiddenTags: hiddenTags.value.split('\n'),
 		preservedUsernames: preservedUsernames.value.split('\n'),
 	}).then(() => {
diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts
index b7d65406cb..94d6673ac5 100644
--- a/packages/misskey-js/src/autogen/types.ts
+++ b/packages/misskey-js/src/autogen/types.ts
@@ -4659,6 +4659,7 @@ export type operations = {
             hiddenTags: string[];
             blockedHosts: string[];
             sensitiveWords: string[];
+            prohibitedWords: string[];
             bannedEmailDomains?: string[];
             preservedUsernames: string[];
             hcaptchaSecretKey: string | null;
@@ -8413,6 +8414,7 @@ export type operations = {
           hiddenTags?: string[] | null;
           blockedHosts?: string[] | null;
           sensitiveWords?: string[] | null;
+          prohibitedWords?: string[] | null;
           themeColor?: string | null;
           mascotImageUrl?: string | null;
           bannerUrl?: string | null;
-- 
GitLab


From c23c97d303d227f6a0e2b3c775189f90772c1dfa Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Fri, 9 Feb 2024 16:25:58 +0900
Subject: [PATCH 33/40] =?UTF-8?q?perf(frontend):=20splash=20screen?=
 =?UTF-8?q?=E3=81=AEdom=E3=81=8C=E6=B6=88=E3=81=88=E3=81=AA=E3=81=84?=
 =?UTF-8?q?=E5=A0=B4=E5=90=88=E3=81=8C=E3=81=82=E3=82=8B=E3=81=AE=E3=82=92?=
 =?UTF-8?q?=E4=BF=AE=E6=AD=A3?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

https://github.com/misskey-dev/misskey/issues/10805
---
 packages/frontend/src/boot/common.ts | 11 +++++------
 1 file changed, 5 insertions(+), 6 deletions(-)

diff --git a/packages/frontend/src/boot/common.ts b/packages/frontend/src/boot/common.ts
index fd5a03a70d..295585af57 100644
--- a/packages/frontend/src/boot/common.ts
+++ b/packages/frontend/src/boot/common.ts
@@ -60,12 +60,6 @@ export async function common(createVue: () => App<Element>) {
 		});
 	}
 
-	const splash = document.getElementById('splash');
-	// 念のためnullチェック(HTMLが古い場合があるため(そのうち消す))
-	if (splash) splash.addEventListener('transitionend', () => {
-		splash.remove();
-	});
-
 	let isClientUpdated = false;
 
 	//#region クライアントが更新されたかチェック
@@ -289,5 +283,10 @@ function removeSplash() {
 	if (splash) {
 		splash.style.opacity = '0';
 		splash.style.pointerEvents = 'none';
+
+		// transitionendイベントが発火しない場合があるため
+		window.setTimeout(() => {
+			splash.remove();
+		}, 1000);
 	}
 }
-- 
GitLab


From 37d83df075214df0c4933ce28546cc8a2a9b95a2 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Fri, 9 Feb 2024 17:05:25 +0900
Subject: [PATCH 34/40] chore(deps): bump pnpm/action-setup from 2 to 3
 (#13215)

Bumps [pnpm/action-setup](https://github.com/pnpm/action-setup) from 2 to 3.
- [Release notes](https://github.com/pnpm/action-setup/releases)
- [Commits](https://github.com/pnpm/action-setup/compare/v2...v3)

---
updated-dependencies:
- dependency-name: pnpm/action-setup
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 .github/workflows/check-misskey-js-autogen.yml | 2 +-
 .github/workflows/get-api-diff.yml             | 2 +-
 .github/workflows/lint.yml                     | 6 +++---
 .github/workflows/on-release-created.yml       | 2 +-
 .github/workflows/test-backend.yml             | 4 ++--
 .github/workflows/test-frontend.yml            | 4 ++--
 .github/workflows/test-production.yml          | 2 +-
 .github/workflows/validate-api-json.yml        | 2 +-
 8 files changed, 12 insertions(+), 12 deletions(-)

diff --git a/.github/workflows/check-misskey-js-autogen.yml b/.github/workflows/check-misskey-js-autogen.yml
index 545b215756..4aaa8a5798 100644
--- a/.github/workflows/check-misskey-js-autogen.yml
+++ b/.github/workflows/check-misskey-js-autogen.yml
@@ -25,7 +25,7 @@ jobs:
           ref: ${{ github.event.pull_request.head.sha }}
 
       - name: setup pnpm
-        uses: pnpm/action-setup@v2
+        uses: pnpm/action-setup@v3
         with:
           version: 8
 
diff --git a/.github/workflows/get-api-diff.yml b/.github/workflows/get-api-diff.yml
index bf92e701b2..3f229c77a6 100644
--- a/.github/workflows/get-api-diff.yml
+++ b/.github/workflows/get-api-diff.yml
@@ -32,7 +32,7 @@ jobs:
         ref: ${{ matrix.ref }}
         submodules: true
     - name: Install pnpm
-      uses: pnpm/action-setup@v2
+      uses: pnpm/action-setup@v3
       with:
         version: 8
         run_install: false
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index 86fca995d2..5c36547323 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -27,7 +27,7 @@ jobs:
       with:
         fetch-depth: 0
         submodules: true
-    - uses: pnpm/action-setup@v2
+    - uses: pnpm/action-setup@v3
       with:
         version: 8
         run_install: false
@@ -54,7 +54,7 @@ jobs:
       with:
         fetch-depth: 0
         submodules: true
-    - uses: pnpm/action-setup@v2
+    - uses: pnpm/action-setup@v3
       with:
         version: 7
         run_install: false
@@ -80,7 +80,7 @@ jobs:
       with:
         fetch-depth: 0
         submodules: true
-    - uses: pnpm/action-setup@v2
+    - uses: pnpm/action-setup@v3
       with:
         version: 7
         run_install: false
diff --git a/.github/workflows/on-release-created.yml b/.github/workflows/on-release-created.yml
index 3cc0e5007b..d2508f1b77 100644
--- a/.github/workflows/on-release-created.yml
+++ b/.github/workflows/on-release-created.yml
@@ -24,7 +24,7 @@ jobs:
         with:
           submodules: true
       - name: Install pnpm
-        uses: pnpm/action-setup@v2
+        uses: pnpm/action-setup@v3
         with:
           version: 8
           run_install: false
diff --git a/.github/workflows/test-backend.yml b/.github/workflows/test-backend.yml
index 199443fca8..7bc3ad9a8f 100644
--- a/.github/workflows/test-backend.yml
+++ b/.github/workflows/test-backend.yml
@@ -41,7 +41,7 @@ jobs:
       with:
         submodules: true
     - name: Install pnpm
-      uses: pnpm/action-setup@v2
+      uses: pnpm/action-setup@v3
       with:
         version: 8
         run_install: false
@@ -91,7 +91,7 @@ jobs:
         with:
           submodules: true
       - name: Install pnpm
-        uses: pnpm/action-setup@v2
+        uses: pnpm/action-setup@v3
         with:
           version: 8
           run_install: false
diff --git a/.github/workflows/test-frontend.yml b/.github/workflows/test-frontend.yml
index caa3fceef5..8ede222e5d 100644
--- a/.github/workflows/test-frontend.yml
+++ b/.github/workflows/test-frontend.yml
@@ -33,7 +33,7 @@ jobs:
       with:
         submodules: true
     - name: Install pnpm
-      uses: pnpm/action-setup@v2
+      uses: pnpm/action-setup@v3
       with:
         version: 8
         run_install: false
@@ -91,7 +91,7 @@ jobs:
     #- uses: browser-actions/setup-firefox@latest
     #  if: ${{ matrix.browser == 'firefox' }}
     - name: Install pnpm
-      uses: pnpm/action-setup@v2
+      uses: pnpm/action-setup@v3
       with:
         version: 7
         run_install: false
diff --git a/.github/workflows/test-production.yml b/.github/workflows/test-production.yml
index 9e02c0d8f8..eac0a51c66 100644
--- a/.github/workflows/test-production.yml
+++ b/.github/workflows/test-production.yml
@@ -23,7 +23,7 @@ jobs:
       with:
         submodules: true
     - name: Install pnpm
-      uses: pnpm/action-setup@v2
+      uses: pnpm/action-setup@v3
       with:
         version: 8
         run_install: false
diff --git a/.github/workflows/validate-api-json.yml b/.github/workflows/validate-api-json.yml
index 93c4cf4cd1..08044322c9 100644
--- a/.github/workflows/validate-api-json.yml
+++ b/.github/workflows/validate-api-json.yml
@@ -24,7 +24,7 @@ jobs:
       with:
         submodules: true
     - name: Install pnpm
-      uses: pnpm/action-setup@v2
+      uses: pnpm/action-setup@v3
       with:
         version: 8
         run_install: false
-- 
GitLab


From b5b31bfd5be533b2b9041db8e2f83032e534b776 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Fri, 9 Feb 2024 17:05:37 +0900
Subject: [PATCH 35/40] New Crowdin updates (#13179)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (English)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (English)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (German)

* New translations ja-jp.yml (Korean)

* New translations ja-jp.yml (Chinese Simplified)

* New translations ja-jp.yml (Spanish)

* New translations ja-jp.yml (Czech)

* New translations ja-jp.yml (Italian)

* New translations ja-jp.yml (Russian)

* New translations ja-jp.yml (Chinese Traditional)

* New translations ja-jp.yml (English)

* New translations ja-jp.yml (Indonesian)

* New translations ja-jp.yml (Thai)

* New translations ja-jp.yml (Japanese, Kansai)

* New translations ja-jp.yml (Catalan)
---
 locales/ca-ES.yml | 299 ++++++++++++++++++++++++++++++++++++++++++++++
 locales/cs-CZ.yml |   1 +
 locales/de-DE.yml |   1 +
 locales/en-US.yml |  26 ++++
 locales/es-ES.yml |   1 +
 locales/id-ID.yml |   1 +
 locales/it-IT.yml |   1 +
 locales/ja-KS.yml |   1 +
 locales/ko-KR.yml |   1 +
 locales/ru-RU.yml |   1 +
 locales/th-TH.yml |   1 +
 locales/zh-CN.yml |   1 +
 locales/zh-TW.yml |   1 +
 13 files changed, 336 insertions(+)

diff --git a/locales/ca-ES.yml b/locales/ca-ES.yml
index 6c0d212ad2..fb43e2efff 100644
--- a/locales/ca-ES.yml
+++ b/locales/ca-ES.yml
@@ -1041,6 +1041,9 @@ resetPasswordConfirm: "Vols canviar la teva contrasenya?"
 sensitiveWords: "Paraules sensibles"
 sensitiveWordsDescription: "La visibilitat de totes les notes que continguin qualsevol de les paraules configurades seran, automàticament, afegides a \"Inici\". Pots llistar diferents paraules separant les per línies noves."
 sensitiveWordsDescription2: "Fent servir espais crearà expressions AND si l'expressió s'envolta amb barres inclinades es converteix en una expressió regular."
+prohibitedWords: "Paraules prohibides"
+prohibitedWordsDescription: "Quan intenteu publicar una Nota que conté una paraula prohibida, feu que es converteixi en un error. Es poden dividir i establir múltiples línies."
+prohibitedWordsDescription2: "Fent servir espais crearà expressions AND si l'expressió s'envolta amb barres inclinades es converteix en una expressió regular."
 hiddenTags: "Etiquetes ocultes"
 hiddenTagsDescription: "La visibilitat de totes les notes que continguin qualsevol de les paraules configurades seran, automàticament, afegides a \"Inici\". Pots llistar diferents paraules separant les per línies noves."
 notesSearchNotAvailable: "La cerca de notes no es troba disponible."
@@ -1518,12 +1521,82 @@ _achievements:
       title: "Nocturn"
       description: "Publica una nota a altes hores de la nit "
       flavor: "És hora d'anar a dormir."
+    _postedAt0min0sec:
+      title: "Rellotge xerraire"
+      description: "Publica una nota a les 0:00"
+      flavor: "Tic tac, tic tac, tic tac, DING!"
+    _selfQuote:
+      title: "Autoreferència "
+      description: "Cita una nota teva"
+    _htl20npm:
+      title: "Línia de temps fluida"
+      description: "La teva línia de temps va a més de 20npm (notes per minut)"
+    _viewInstanceChart:
+      title: "Analista "
+      description: "Mira els gràfics de la teva instància "
+    _outputHelloWorldOnScratchpad:
+      title: "Hola, món!"
+      description: "Escriu \"hola, món\" al bloc de notes"
     _open3windows:
       title: "Multi finestres"
       description: "I va obrir més de tres finestres"
     _driveFolderCircularReference:
       title: "Consulteu la secció de bucle"
+      description: "Intenta crear carpetes recursives al Disc"
+    _reactWithoutRead:
+      title: "De veritat has llegit això?"
+      description: "Reaccions a una nota de més de 100 caràcters publicada fa menys de 3 segons "
+    _clickedClickHere:
+      title: "Fer clic"
+      description: "Has fet clic aquí "
+    _justPlainLucky:
+      title: "Ha sigut sort"
+      description: "Oportunitat de guanyar-lo amb una probabilitat d'un 0.005% cada 10 segons"
+    _setNameToSyuilo:
+      title: "soc millor"
+      description: "Posat \"siuylo\" com a nom"
+    _passedSinceAccountCreated1:
+      title: "Primer aniversari"
+      description: "Ja ha passat un any d'ençà que vas crear el teu compte"
+    _passedSinceAccountCreated2:
+      title: "Segon aniversari"
+      description: "Ja han passat dos anys d'ençà que vas crear el teu compte"
+    _passedSinceAccountCreated3:
+      title: "Tres anys"
+      description: "Ja han passat tres anys d'ençà que vas crear el teu compte"
+    _loggedInOnBirthday:
+      title: "Felicitats!"
+      description: "T'has identificat el dia del teu aniversari"
+    _loggedInOnNewYearsDay:
+      title: "Bon any nou!"
+      description: "T'has identificat el primer dia de l'any "
+      flavor: "A per un altre any memorable a la teva instància   "
+    _cookieClicked:
+      title: "Un joc en què fas clic a les galetes"
+      description: "Pica galetes"
+      flavor: "Espera, ets al lloc web correcte?"
+    _brainDiver:
+      title: "Busseja Ments"
+      description: "Publica un enllaç al Busseja Ments"
+      flavor: "Misskey-Misskey La-Tu-Ma"
+    _smashTestNotificationButton:
+      title: "Sobrecàrrega de proves"
+      description: "Envia moltes notificacions de prova en un període de temps molt curt"
+    _tutorialCompleted:
+      title: "Diploma del Curs Elemental de Misskey"
+      description: "Has completat el tutorial"
+    _bubbleGameExplodingHead:
+      title: "🤯"
+      description: "L'objecte més gran del joc de la bombolla "
+    _bubbleGameDoubleExplodingHead:
+      title: "Doble 🤯"
+      description: "Dos dels objectes més grans del joc de la bombolla al mateix temps"
+      flavor: "Pots emplenar una carmanyola com aquesta 🤯🤯 una mica"
 _role:
+  new: "Nou rol"
+  edit: "Editar el rol"
+  name: "Nom del rol"
+  description: "Descripció del rol"
   permission: "Permisos de rol"
   descriptionOfPermission: "Els <b>Moderadors</b> poden fer operacions bàsiques de moderació.\nEls <b>Administradors</b> poden canviar tots els ajustos del servidor."
   assignTarget: "Assignar "
@@ -1545,35 +1618,259 @@ _role:
   asBadge: "Mostrar com a insígnia "
   descriptionOfAsBadge: "La icona d'aquest rol es mostrarà al costat dels noms d'usuaris que tinguin assignats aquest rol."
   isExplorable: "Fer el rol explorable"
+  descriptionOfIsExplorable: "La línia de temps d'aquest rol i la llista d'usuaris seran públics si s'activa."
+  displayOrder: "Posició "
+  descriptionOfDisplayOrder: "Com més gran és el número, més dalt la seva posició a la interfície."
+  canEditMembersByModerator: "Permetre que els moderadors editin la llista d'usuaris en aquest rol"
+  descriptionOfCanEditMembersByModerator: "Quan s'activa, els moderadors, així com els administradors, podran afegir i treure usuaris d'aquest rol. Si es troba desactivat, només els administradors poden assignar usuaris."
   priority: "Prioritat"
   _priority:
     low: "Baixa"
     middle: "Mitjà"
     high: "Alta"
   _options:
+    gtlAvailable: "Pot veure la línia de temps global"
+    ltlAvailable: "Pot veure la línia de temps local"
+    canPublicNote: "Pot enviar notes públiques"
+    canInvite: "Pot crear invitacions a la instància "
+    inviteLimit: "Límit d'invitacions "
+    inviteLimitCycle: "Temps de refresc de les invitacions"
+    inviteExpirationTime: "Interval de caducitat de les invitacions"
     canManageCustomEmojis: "Gestiona els emojis personalitzats"
     canManageAvatarDecorations: "Gestiona les decoracions dels avatars "
+    driveCapacity: "Capacitat del disc"
+    alwaysMarkNsfw: "Marca sempre els fitxers com a sensibles"
+    pinMax: "Nombre màxim de notes fixades"
     antennaMax: "Nombre màxim d'antenes"
+    wordMuteMax: "Nombre màxim de caràcters permesos a les paraules silenciades"
+    webhookMax: "Nombre màxim de Webhooks"
+    clipMax: "Nombre màxim de clips"
+    noteEachClipsMax: "Nombre màxim de notes dintre d'un clip"
+    userListMax: "Nombre màxim de llistes d'usuaris "
+    userEachUserListsMax: "Nombre màxim d'usuaris dintre d'una llista d'usuaris "
+    rateLimitFactor: "Limitador"
+    descriptionOfRateLimitFactor: "Límits baixos són menys restrictius, límits alts són més restrictius."
+    canHideAds: "Pot amagar els anuncis"
+    canSearchNotes: "Pot cercar notes"
+    canUseTranslator: "Pot fer servir el traductor"
+    avatarDecorationLimit: "Nombre màxim de decoracions que es poden aplicar els avatars"
+  _condition:
+    isLocal: "Usuari local"
+    isRemote: "Usuari remot"
+    createdLessThan: "Han passat menys de X a passat des de la creació del compte"
+    createdMoreThan: "Han passat més de X des de la creació del compte"
+    followersLessThanOrEq: "Té menys de X seguidors"
+    followersMoreThanOrEq: "Té X o més seguidors"
+    followingLessThanOrEq: "Segueix X o menys comptes"
+    followingMoreThanOrEq: "Segueix a X o més comptes"
+    notesLessThanOrEq: "Les publicacions són menys o igual a "
+    notesMoreThanOrEq: "Les publicacions són més o igual a "
+    and: "AND condicional "
+    or: "OR condicional"
+    not: "NOT condicional"
+_sensitiveMediaDetection:
+  description: "Redueix els esforços de moderació gràcies al reconeixement automàtic dels fitxers amb contingut sensible mitjançant Machine Learing. Això augmentarà la càrrega del servidor."
+  sensitivity: "Sensibilitat de la detecció "
+  sensitivityDescription: "Reduint la sensibilitat provocarà menys falsos positius. D'altra banda incrementant-ho generarà més falsos negatius."
+  setSensitiveFlagAutomatically: "Marcar com a sensible"
+  setSensitiveFlagAutomaticallyDescription: "Els resultats de la detecció interna seran desats, inclòs si aquesta opció es troba desactivada."
+  analyzeVideos: "Activar anàlisis de vídeos "
+  analyzeVideosDescription: "Analitzar els vídeos a més de les imatges. Això incrementarà lleugerament la càrrega del servidor."
+_emailUnavailable:
+  used: "Aquest correu electrònic ja s'està fent servir"
+  format: "El format del correu electrònic és invàlid "
+  disposable: "No es poden fer servir adreces de correu electrònic d'un sol ús "
+  mx: "Aquest servidor de correu electrònic no és vàlid "
+  smtp: "Aquest servidor de correu electrònic no respon"
+  banned: "No pots registrar-te amb aquesta adreça de correu electrònic "
 _ffVisibility:
   public: "Publicar"
+  followers: "Visible només per a seguidors "
+  private: "Privat"
+_signup:
+  almostThere: "Ja quasi estem"
+  emailAddressInfo: "Si us plau, escriu la teva adreça de correu electrònic. No es farà pública."
+  emailSent: "S'ha enviat un correu de confirmació a ({email}). Si us plau, fes clic a l'enllaç per completar el registre."
+_accountDelete:
+  accountDelete: "Eliminar el compte"
+  mayTakeTime: "Com l'eliminació d'un compte consumeix bastants recursos, pot trigar un temps perquè es completi l'esborrat, depenent si tens molt contingut i la quantitat de fitxer que hagis pujat."
+  sendEmail: "Una vegada hagi finalitzat l'esborrat del compte rebràs un correu electrònic a l'adreça que tinguis registrada en aquest compte."
+  requestAccountDelete: "Demanar l'eliminació del compte"
+  started: "Ha començat l'esborrat del compte."
+  inProgress: "L'esborrat es troba en procés "
 _ad:
   back: "Tornar"
+  reduceFrequencyOfThisAd: "Mostrar menys aquest anunci"
+  hide: "No mostrar mai"
+  timezoneinfo: "El dia de la setmana ve determinat del fus horari del servidor."
+  adsSettings: "Configuració d'anuncis "
+  notesPerOneAd: "Interval d'emplaçament d'anuncis en temps real (Notes per anuncis)"
+  setZeroToDisable: "Ajusta aquest valor a 0 per deshabilitar l'actualització d'anuncis en temps real"
+  adsTooClose: "L'interval actual pot fer que l'experiència de l'usuari sigui dolenta perquè l'interval és molt baix."
+_forgotPassword:
+  enterEmail: "Escriu l'adreça de correu electrònic amb la que et vas registrar. S'enviarà un correu electrònic amb un enllaç perquè puguis canviar-la."
+  ifNoEmail: "Si no vas fer servir una adreça de correu electrònic per registrar-te, si us plau posa't en contacte amb l'administrador."
+  contactAdmin: "Aquesta instància no suporta registrar-se amb correu electrònic. Si us plau, contacta amb l'administrador del servidor."
+_gallery:
+  my: "La meva Galeria "
+  liked: "Publicacions que t'han agradat"
+  like: "M'agrada "
+  unlike: "Ja no m'agrada"
 _email:
   _follow:
     title: "t'ha seguit"
+  _receiveFollowRequest:
+    title: "Has rebut una sol·licitud  de seguiment"
+_plugin:
+  install: "Instal·lar un afegit "
+  installWarn: "Si us plau, no instal·lis afegits que no siguin de confiança."
+  manage: "Gestionar els afegits"
+  viewSource: "Veure l'origen "
+_preferencesBackups:
+  list: "Llista de còpies de seguretat"
+  saveNew: "Fer una còpia de seguretat nova"
+  loadFile: "Carregar des d'un fitxer"
+  apply: "Aplicar en aquest dispositiu"
+  save: "Desar els canvis"
+  inputName: "Escriu un nom per aquesta còpia de seguretat"
+  cannotSave: "No s'ha pogut desar"
+  nameAlreadyExists: "Ja existeix una còpia de seguretat anomenada \"{name}\". Escriu un nom diferent."
+  applyConfirm: "Vols aplicar la còpia de seguretat \"{name}\" a aquest dispositiu? La configuració actual del dispositiu serà esborrada."
+  saveConfirm: "Desar còpia de seguretat com {name}?"
+  deleteConfirm: "Esborrar la còpia de seguretat {name}?"
+  renameConfirm: "Vols canvia el nom de la còpia de seguretat de \"{old}\" a \"{new}\"?"
+  noBackups: "No hi ha còpies de seguretat. Pots fer una còpia de seguretat de la configuració d'aquest dispositiu al servidor fent servir \"Crear nova còpia de seguretat\""
+  createdAt: "Creat el: {date} {time}"
+  updatedAt: "Actualitzat el: {date} {time}"
+  cannotLoad: "Hi ha hagut un error al carregar"
+  invalidFile: "Format del fitxer no vàlid "
+_registry:
+  scope: "Àmbit "
+  key: "Clau"
+  keys: "Claus"
+  domain: "Domini"
+  createKey: "Crear una clau"
+_aboutMisskey:
+  about: "Misskey és un programa de codi obert desenvolupar per syuilo des de 2014"
+  contributors: "Col·laboradors principals"
+  allContributors: "Tots els col·laboradors "
+  source: "Codi font"
+  translation: "Tradueix Misskey"
+  donate: "Fes un donatiu a Misskey"
+  morePatrons: "També agraïm el suport d'altres col·laboradors que no surten en aquesta llista. Gràcies! 🥰"
+  patrons: "Patrocinadors"
+  projectMembers: "Membres del projecte"
+_displayOfSensitiveMedia:
+  respect: "Ocultar imatges o vídeos marcats com a sensibles"
+  ignore: "Mostrar imatges o vídeos marcats com a sensibles"
+  force: "Ocultar totes les imatges o vídeos "
+_instanceTicker:
+  none: "No mostrar mai"
+  remote: "Mostrar per usuaris remots"
+  always: "Mostrar sempre"
+_serverDisconnectedBehavior:
+  reload: "Recarregar automàticament "
+  dialog: "Mostrar finestres de confirmació "
+  quiet: "Mostrar un avís que no molesti"
+_channel:
+  create: "Crear un canal"
+  edit: "Editar canal"
+  setBanner: "Estableix el bàner "
+  removeBanner: "Eliminar el.bàner"
+  featured: "Popular"
+  owned: "Propietat"
+  following: "Seguin"
+  usersCount: "{n} Participants"
+  notesCount: "{n} Notes"
+  nameAndDescription: "Nom i descripció "
+  nameOnly: "Nom només "
+  allowRenoteToExternal: "Permet la citació i l'impuls fora del canal"
 _instanceMute:
   instanceMuteDescription: "Silencia tots els impulsos dels servidors seleccionats, també els usuaris que responen a altres d'un servidor silenciat."
 _theme:
   description: "Descripció"
   keys:
+    navHoverFg: "Text barra lateral (en passar per sobre)"
+    navActive: "Text barra lateral (actiu)"
+    navIndicator: "Indicador barra lateral"
+    link: "Enllaç"
+    hashtag: "Etiqueta"
     mention: "Menció"
+    mentionMe: "Mencions (jo)"
     renote: "Renotar"
+    modalBg: "Fons del modal"
     divider: "Divisor"
+    scrollbarHandle: "Maneta de la barra de desplaçament"
+    scrollbarHandleHover: "Maneta de la barra de desplaçament (en passar-hi per sobre)"
+    dateLabelFg: "Text de l'etiqueta de la data"
+    infoBg: "Fons d'informació "
+    infoFg: "Text d'informació "
+    infoWarnBg: "Fons avís "
+    infoWarnFg: "Text avís "
+    toastBg: "Fons notificació "
+    toastFg: "Text notificació "
+    buttonBg: "Fons botó "
+    buttonHoverBg: "Fons botó (en passar-hi per sobre)"
+    inputBorder: "Contorn del cap d'introducció "
+    listItemHoverBg: "Fons dels elements d'una llista"
+    driveFolderBg: "Fons de la carpeta Disc"
+    wallpaperOverlay: "Superposició del fons de pantalla "
+    badge: "Insígnia "
+    messageBg: "Fons del xat"
+    accentDarken: "Accent (fosc)"
+    accentLighten: "Accent (clar)"
+    fgHighlighted: "Text ressaltat"
 _sfx:
   note: "Notes"
+  noteMy: "Nota (per mi)"
   notification: "Notificacions"
   antenna: "Antenes"
+  channel: "Notificacions dels canals"
+  reaction: "Quan se selecciona una reacció "
+_soundSettings:
+  driveFile: "Fer servir un fitxer d'àudio del disc"
+  driveFileWarn: "Seleccionar un fitxer d'àudio del disc"
+  driveFileTypeWarn: "Fitxer no suportat "
+  driveFileTypeWarnDescription: "Seleccionar un fitxer d'àudio "
+  driveFileDurationWarn: "L'àudio és massa llarg"
+  driveFileDurationWarnDescription: "Els àudios molt llargs pot interrompre l'ús de Misskey. Vols continuar?"
+_ago:
+  future: "Futur "
+  justNow: "Ara mateix"
+  secondsAgo: "Fa {n} segons"
+  minutesAgo: "Fa {n} minuts"
+  hoursAgo: "Fa {n} hores"
+  daysAgo: "Fa {n} dies"
+  weeksAgo: "Fa {n} setmanes"
+  monthsAgo: "Fa {n} mesos"
+  yearsAgo: "Fa {n} anys"
+  invalid: "Res"
+_timeIn:
+  seconds: "En {n} segons"
+  minutes: "En {n} minuts"
+  hours: "En {n} hores"
+  days: "En {n} dies"
+  weeks: "En {n} setmanes"
+  months: "En {n} mesos"
+  years: "En {n} anys"
+_time:
+  second: "Segon(s)"
+  minute: "Minut(s)"
+  hour: "Hor(a)(es)"
+  day: "Di(a)(es)"
 _2fa:
+  alreadyRegistered: "J has registrat un dispositiu d'autenticació de doble factor."
+  registerTOTP: "Registrar una aplicació autenticadora"
+  step1: "Primer instal·la una aplicació autenticadora (com {a} o {b}) al teu dispositiu."
+  step2: "Després escaneja el codi QR que es mostra en aquesta pantalla."
+  step2Click: "Fent clic en aquest codi QR et permetrà registrar l'autenticació de doble factor a la teva clau de seguretat o en l'aplicació d'autenticació del teu dispositiu."
+  step2Uri: "Escriu la següent URI si estàs fent servir una aplicació d'escriptori "
+  step3Title: "Escriu un codi d'autenticació"
+  step3: "Escriu el codi d'autenticació (token) que es mostra a la teva aplicació per finalitzar la configuració."
+  setupCompleted: "Configuració terminada"
+  step4: "D'ara endavant quan accedeixis se't demanarà el token que has introduït."
+  securityKeyNotSupported: "El teu navegador no suporta claus de seguretat"
+  removeKeyConfirm: "Esborrar la còpia de seguretat {name}?"
   renewTOTPCancel: "No, gràcies"
 _antennaSources:
   all: "Totes les publicacions"
@@ -1592,6 +1889,8 @@ _widgets:
     chooseList: "Tria una llista"
 _cw:
   show: "Carregar més"
+_poll:
+  deadlineTime: "Hor(a)(es)"
 _visibility:
   home: "Inici"
   followers: "Seguidors"
diff --git a/locales/cs-CZ.yml b/locales/cs-CZ.yml
index 8620a1207c..7da9461af1 100644
--- a/locales/cs-CZ.yml
+++ b/locales/cs-CZ.yml
@@ -1005,6 +1005,7 @@ resetPasswordConfirm: "Opravdu chcete resetovat heslo?"
 sensitiveWords: "Citlivá slova"
 sensitiveWordsDescription: "Viditelnost všech poznámek obsahujících některé z nakonfigurovaných slov bude automaticky nastavena na \"Domů\". Můžete jich uvést více tak, že je oddělíte pomocí řádků."
 sensitiveWordsDescription2: "Použití mezer vytvoří výrazy AND a obklopení klíčových slov lomítky je změní na regulární výraz."
+prohibitedWordsDescription2: "Použití mezer vytvoří výrazy AND a obklopení klíčových slov lomítky je změní na regulární výraz."
 notesSearchNotAvailable: "Vyhledávání poznámek je nedostupné."
 license: "Licence"
 unfavoriteConfirm: "Opravdu chcete odstranit z oblíbených?"
diff --git a/locales/de-DE.yml b/locales/de-DE.yml
index a8f8903cf1..a4412395f6 100644
--- a/locales/de-DE.yml
+++ b/locales/de-DE.yml
@@ -1036,6 +1036,7 @@ resetPasswordConfirm: "Wirklich Passwort zurücksetzen?"
 sensitiveWords: "Sensible Wörter"
 sensitiveWordsDescription: "Die Notizsichtbarkeit aller Notizen, die diese Wörter enthalten, wird automatisch auf \"Startseite\" gesetzt. Durch Zeilenumbrüche können mehrere konfiguriert werden."
 sensitiveWordsDescription2: "Durch die Verwendung von Leerzeichen können AND-Verknüpfungen angegeben werden und durch das Umgeben von Schrägstrichen können reguläre Ausdrücke verwendet werden."
+prohibitedWordsDescription2: "Durch die Verwendung von Leerzeichen können AND-Verknüpfungen angegeben werden und durch das Umgeben von Schrägstrichen können reguläre Ausdrücke verwendet werden."
 hiddenTags: "Ausgeblendete Hashtags"
 hiddenTagsDescription: "Die hier eingestellten Tags werden nicht mehr in den Trends angezeigt. Mit der Umschalttaste können mehrere ausgewählt werden."
 notesSearchNotAvailable: "Die Notizsuche ist nicht verfügbar."
diff --git a/locales/en-US.yml b/locales/en-US.yml
index 3a017b8044..f82ce21906 100644
--- a/locales/en-US.yml
+++ b/locales/en-US.yml
@@ -1041,6 +1041,7 @@ resetPasswordConfirm: "Really reset your password?"
 sensitiveWords: "Sensitive words"
 sensitiveWordsDescription: "The visibility of all notes containing any of the configured words will be set to \"Home\" automatically. You can list multiple by separating them via line breaks."
 sensitiveWordsDescription2: "Using spaces will create AND expressions and surrounding keywords with slashes will turn them into a regular expression."
+prohibitedWordsDescription2: "Using spaces will create AND expressions and surrounding keywords with slashes will turn them into a regular expression."
 hiddenTags: "Hidden hashtags"
 hiddenTagsDescription: "Select tags which will not shown on trend list.\nMultiple tags could be registered by lines."
 notesSearchNotAvailable: "Note search is unavailable."
@@ -1971,6 +1972,31 @@ _permissions:
   "write:flash": "Edit Plays"
   "read:flash-likes": "View list of liked Plays"
   "write:flash-likes": "Edit list of liked Plays"
+  "read:admin:abuse-user-reports": "View user reports"
+  "write:admin:delete-account": "Delete user account"
+  "write:admin:delete-all-files-of-a-user": "Delete all files of a user"
+  "read:admin:meta": "View instance metadata"
+  "write:admin:reset-password": "Reset user password"
+  "write:admin:send-email": "Send email"
+  "read:admin:server-info": "View server info"
+  "read:admin:show-moderation-log": "View moderation log"
+  "read:admin:show-user": "View private user info"
+  "read:admin:show-users": "View private user info"
+  "write:admin:suspend-user": "Suspend user"
+  "write:admin:unset-user-avatar": "Remove user avatar"
+  "write:admin:unset-user-banner": "Remove user banner"
+  "write:admin:unsuspend-user": "Unsuspend user"
+  "write:admin:meta": "Manage instance metadata"
+  "write:admin:user-note": "Manage moderation note"
+  "write:admin:roles": "Manage roles"
+  "read:admin:roles": "View roles"
+  "write:admin:relays": "Manage relays"
+  "read:admin:relays": "View relays"
+  "write:admin:invite-codes": "Manage invite codes"
+  "read:admin:invite-codes": "View invite codes"
+  "write:admin:announcements": "Manage announcements"
+  "read:admin:announcements": "View announcements"
+  "write:admin:avatar-decorations": "Manage avatar decorations"
 _auth:
   shareAccessTitle: "Granting application permissions"
   shareAccess: "Would you like to authorize \"{name}\" to access this account?"
diff --git a/locales/es-ES.yml b/locales/es-ES.yml
index d4c0d6c4ad..38cb0f6b21 100644
--- a/locales/es-ES.yml
+++ b/locales/es-ES.yml
@@ -1041,6 +1041,7 @@ resetPasswordConfirm: "¿Realmente quieres cambiar la contraseña?"
 sensitiveWords: "Palabras sensibles"
 sensitiveWordsDescription: "La visibilidad de todas las notas que contienen cualquiera de las palabras configuradas serán puestas en \"Inicio\" automáticamente. Puedes enumerás varias separándolas con saltos de línea"
 sensitiveWordsDescription2: "Si se usan espacios se crearán expresiones AND y las palabras subsecuentes con barras inclinadas se convertirán en expresiones regulares."
+prohibitedWordsDescription2: "Si se usan espacios se crearán expresiones AND y las palabras subsecuentes con barras inclinadas se convertirán en expresiones regulares."
 hiddenTags: "Hashtags ocultos"
 hiddenTagsDescription: "Selecciona las etiquetas que no se mostrarán en tendencias. Una etiqueta por línea."
 notesSearchNotAvailable: "No se puede buscar una nota"
diff --git a/locales/id-ID.yml b/locales/id-ID.yml
index 44a0503883..b38e95596b 100644
--- a/locales/id-ID.yml
+++ b/locales/id-ID.yml
@@ -1038,6 +1038,7 @@ resetPasswordConfirm: "Yakin untuk mereset kata sandimu?"
 sensitiveWords: "Kata sensitif"
 sensitiveWordsDescription: "Visibilitas dari semua catatan mengandung kata yang telah diatur akan dijadikan \"Beranda\" secara otomatis. Kamu dapat mendaftarkan kata tersebut lebih dari satu dengan menuliskannya di baris baru."
 sensitiveWordsDescription2: "Menggunakan spasi akan membuat ekspresi AND dan kata kunci disekitarnya dengan garis miring akan mengubahnya menjadi ekspresi reguler."
+prohibitedWordsDescription2: "Menggunakan spasi akan membuat ekspresi AND dan kata kunci disekitarnya dengan garis miring akan mengubahnya menjadi ekspresi reguler."
 hiddenTags: "Tagar tersembunyi"
 hiddenTagsDescription: "Pilih tanda yang mana akan tidak diperlihatkan dalam daftar tren.\nTanda lebih dari satu dapat didaftarkan dengan tiap baris."
 notesSearchNotAvailable: "Pencarian catatan tidak tersedia."
diff --git a/locales/it-IT.yml b/locales/it-IT.yml
index fdddf24360..5a7fc11a03 100644
--- a/locales/it-IT.yml
+++ b/locales/it-IT.yml
@@ -1041,6 +1041,7 @@ resetPasswordConfirm: "Vuoi davvero ripristinare la password?"
 sensitiveWords: "Parole esplicite"
 sensitiveWordsDescription: "Imposta automaticamente \"Home\" alla visibilità delle Note che contengono una qualsiasi parola tra queste configurate. Puoi separarle per riga."
 sensitiveWordsDescription2: "Gli spazi creano la relazione \"E\" tra parole (questo E quello). Racchiudere una parola nelle slash \"/\" la trasforma in Espressione Regolare."
+prohibitedWordsDescription2: "Gli spazi creano la relazione \"E\" tra parole (questo E quello). Racchiudere una parola nelle slash \"/\" la trasforma in Espressione Regolare."
 hiddenTags: "Hashtag nascosti"
 hiddenTagsDescription: "Impedire la visualizzazione del tag impostato nei trend. Puoi impostare più valori, uno per riga."
 notesSearchNotAvailable: "Non è possibile cercare tra le Note."
diff --git a/locales/ja-KS.yml b/locales/ja-KS.yml
index 4a3a79743d..c676bf4fdb 100644
--- a/locales/ja-KS.yml
+++ b/locales/ja-KS.yml
@@ -1041,6 +1041,7 @@ resetPasswordConfirm: "パスワード作り直すんでええな?"
 sensitiveWords: "けったいな単語"
 sensitiveWordsDescription: "設定した単語が入っとるノートの公開範囲をホームにしたるわ。改行で区切ったら複数設定できるで。"
 sensitiveWordsDescription2: "スペースで区切るとAND指定、キーワードをスラッシュで囲んだら正規表現や。"
+prohibitedWordsDescription2: "スペースで区切るとAND指定、キーワードをスラッシュで囲んだら正規表現や。"
 hiddenTags: "見えてへんハッシュタグ"
 hiddenTagsDescription: "設定したタグを最近流行りのとこに見えんようにすんで。複数設定するときは改行で区切ってな。"
 notesSearchNotAvailable: "なんかノート探せへん。"
diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml
index 2a59ab9a23..1231209b36 100644
--- a/locales/ko-KR.yml
+++ b/locales/ko-KR.yml
@@ -1041,6 +1041,7 @@ resetPasswordConfirm: "비밀번호를 재설정하시겠습니까?"
 sensitiveWords: "민감한 단어"
 sensitiveWordsDescription: "설정한 단어가 포함된 노트의 공개 범위를 '홈'으로 강제합니다. 개행으로 구분하여 여러 개를 지정할 수 있습니다."
 sensitiveWordsDescription2: "공백으로 구분하면 AND 지정이 되며, 키워드를 슬래시로 둘러싸면 정규 표현식이 됩니다."
+prohibitedWordsDescription2: "공백으로 구분하면 AND 지정이 되며, 키워드를 슬래시로 둘러싸면 정규 표현식이 됩니다."
 hiddenTags: "숨긴 해시태그"
 hiddenTagsDescription: "설정한 태그를 트렌드에 표시하지 않도록 합니다. 줄 바꿈으로 하나씩 나눠서 설정할 수 있습니다."
 notesSearchNotAvailable: "노트 검색을 이용하실 수 없습니다."
diff --git a/locales/ru-RU.yml b/locales/ru-RU.yml
index 60682fe961..d014b7fc25 100644
--- a/locales/ru-RU.yml
+++ b/locales/ru-RU.yml
@@ -1015,6 +1015,7 @@ resetPasswordConfirm: "Сбросить пароль?"
 sensitiveWords: "Чувствительные слова"
 sensitiveWordsDescription: "Установите общедоступный диапазон заметки, содержащей заданное слово, на домашний. Можно сделать несколько настроек, разделив их переносами строк."
 sensitiveWordsDescription2: "Разделение пробелом создаёт спецификацию AND, а разделение косой чертой создаёт регулярное выражение."
+prohibitedWordsDescription2: "Разделение пробелом создаёт спецификацию AND, а разделение косой чертой создаёт регулярное выражение."
 notesSearchNotAvailable: "Поиск заметок недоступен"
 license: "Лицензия"
 unfavoriteConfirm: "Удалить избранное?"
diff --git a/locales/th-TH.yml b/locales/th-TH.yml
index ed776e5ec7..90a3e93d64 100644
--- a/locales/th-TH.yml
+++ b/locales/th-TH.yml
@@ -1041,6 +1041,7 @@ resetPasswordConfirm: "รีเซ็ตรหัสผ่านของคุ
 sensitiveWords: "คำที่มีเนื้อหาละเอียดอ่อน"
 sensitiveWordsDescription: "การเปิดเผยโน้ตทั้งหมดที่มีคำที่กำหนดค่าไว้จะถูกตั้งค่าเป็น \"หน้าแรก\" โดยอัตโนมัติ คุณยังสามารถแสดงหลายรายการได้โดยแยกรายการโดยใช้ตัวแบ่งบรรทัดได้นะ"
 sensitiveWordsDescription2: "การใช้ช่องว่างนั้นอาจจะสร้างนิพจน์ AND และคำหลักที่มีเครื่องหมายทับล้อมรอบจะเปลี่ยนเป็นนิพจน์ทั่วไปนะ"
+prohibitedWordsDescription2: "การใช้ช่องว่างนั้นอาจจะสร้างนิพจน์ AND และคำหลักที่มีเครื่องหมายทับล้อมรอบจะเปลี่ยนเป็นนิพจน์ทั่วไปนะ"
 hiddenTags: "แฮชแท็กที่ซ่อนอยู่"
 hiddenTagsDescription: "เลือกแท็กที่จะไม่แสดงในรายการเทรนด์ สามารถลงทะเบียนหลายแท็กได้โดยขึ้นบรรทัดใหม่"
 notesSearchNotAvailable: "การค้นหาโน้ตไม่พร้อมใช้งาน"
diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml
index 09c210011c..4a36e30db8 100644
--- a/locales/zh-CN.yml
+++ b/locales/zh-CN.yml
@@ -1041,6 +1041,7 @@ resetPasswordConfirm: "确定重置密码?"
 sensitiveWords: "敏感词"
 sensitiveWordsDescription: "将包含设置词的帖子的可见范围设置为首页。可以通过用换行符分隔来设置多个。"
 sensitiveWordsDescription2: "AND 条件用空格分隔,正则表达式用斜线包裹。"
+prohibitedWordsDescription2: "AND 条件用空格分隔,正则表达式用斜线包裹。"
 hiddenTags: "隐藏标签"
 hiddenTagsDescription: "设定的标签将不会在时间线上显示。可使用换行来设置多个标签。"
 notesSearchNotAvailable: "帖子检索不可用"
diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml
index 872a90bc6a..ed2bd1cf3a 100644
--- a/locales/zh-TW.yml
+++ b/locales/zh-TW.yml
@@ -1041,6 +1041,7 @@ resetPasswordConfirm: "重設密碼?"
 sensitiveWords: "敏感詞"
 sensitiveWordsDescription: "將含有設定詞彙的貼文可見性設為發送至首頁。可以用換行來進行複數的設定。"
 sensitiveWordsDescription2: "空格代表「以及」(AND),斜線包圍關鍵字代表使用正規表達式。"
+prohibitedWordsDescription2: "空格代表「以及」(AND),斜線包圍關鍵字代表使用正規表達式。"
 hiddenTags: "隱藏標籤"
 hiddenTagsDescription: "設定的標籤不會在趨勢中顯示,換行可以設定多個標籤。"
 notesSearchNotAvailable: "無法使用搜尋貼文功能。"
-- 
GitLab


From 7fc1d77893631f7bc792c7d6d5398aba65a331c9 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Fri, 9 Feb 2024 17:10:16 +0900
Subject: [PATCH 36/40] update deps

---
 packages/backend/package.json    |  10 +-
 packages/frontend/package.json   |  14 +-
 packages/misskey-js/package.json |   2 +-
 pnpm-lock.yaml                   | 464 ++++++++++++++++++-------------
 4 files changed, 289 insertions(+), 201 deletions(-)

diff --git a/packages/backend/package.json b/packages/backend/package.json
index 31ca56be49..ee1bf676cb 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -85,7 +85,7 @@
 		"@nestjs/core": "10.2.10",
 		"@nestjs/testing": "10.2.10",
 		"@peertube/http-signature": "1.7.0",
-		"@simplewebauthn/server": "9.0.1",
+		"@simplewebauthn/server": "9.0.2",
 		"@sinonjs/fake-timers": "11.2.2",
 		"@smithy/node-http-handler": "2.1.10",
 		"@swc/cli": "0.1.63",
@@ -98,12 +98,12 @@
 		"bcryptjs": "2.4.3",
 		"blurhash": "2.0.5",
 		"body-parser": "1.20.2",
-		"bullmq": "5.1.5",
+		"bullmq": "5.1.9",
 		"cacheable-lookup": "7.0.0",
-		"cbor": "9.0.1",
+		"cbor": "9.0.2",
 		"chalk": "5.3.0",
 		"chalk-template": "1.1.0",
-		"chokidar": "3.5.3",
+		"chokidar": "3.6.0",
 		"cli-highlight": "2.1.11",
 		"color-convert": "2.0.1",
 		"content-disposition": "0.5.4",
@@ -203,7 +203,7 @@
 		"@types/jsrsasign": "10.5.12",
 		"@types/mime-types": "2.1.4",
 		"@types/ms": "0.7.34",
-		"@types/node": "20.11.10",
+		"@types/node": "20.11.17",
 		"@types/node-fetch": "3.0.3",
 		"@types/nodemailer": "6.4.14",
 		"@types/oauth": "0.9.4",
diff --git a/packages/frontend/package.json b/packages/frontend/package.json
index 5aaddde5ca..5a3a228f12 100644
--- a/packages/frontend/package.json
+++ b/packages/frontend/package.json
@@ -28,7 +28,7 @@
 		"@tabler/icons-webfont": "2.44.0",
 		"@twemoji/parser": "15.0.0",
 		"@vitejs/plugin-vue": "5.0.3",
-		"@vue/compiler-sfc": "3.4.15",
+		"@vue/compiler-sfc": "3.4.18",
 		"aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.2",
 		"astring": "1.8.6",
 		"broadcast-channel": "7.0.0",
@@ -72,7 +72,7 @@
 		"uuid": "9.0.1",
 		"v-code-diff": "1.7.2",
 		"vite": "5.1.0",
-		"vue": "3.4.15",
+		"vue": "3.4.18",
 		"vuedraggable": "next"
 	},
 	"devDependencies": {
@@ -96,12 +96,12 @@
 		"@storybook/types": "7.6.10",
 		"@storybook/vue3": "7.6.10",
 		"@storybook/vue3-vite": "7.6.10",
-		"@testing-library/vue": "8.0.1",
+		"@testing-library/vue": "8.0.2",
 		"@types/escape-regexp": "0.0.3",
 		"@types/estree": "1.0.5",
 		"@types/matter-js": "0.19.6",
 		"@types/micromatch": "4.0.6",
-		"@types/node": "20.11.10",
+		"@types/node": "20.11.17",
 		"@types/punycode": "2.1.3",
 		"@types/sanitize-html": "2.9.5",
 		"@types/throttle-debounce": "5.0.2",
@@ -111,10 +111,10 @@
 		"@typescript-eslint/eslint-plugin": "6.18.1",
 		"@typescript-eslint/parser": "6.18.1",
 		"@vitest/coverage-v8": "0.34.6",
-		"@vue/runtime-core": "3.4.15",
+		"@vue/runtime-core": "3.4.18",
 		"acorn": "8.11.3",
 		"cross-env": "7.0.3",
-		"cypress": "13.6.3",
+		"cypress": "13.6.4",
 		"eslint": "8.56.0",
 		"eslint-plugin-import": "2.29.1",
 		"eslint-plugin-vue": "9.20.1",
@@ -125,7 +125,7 @@
 		"msw": "2.1.7",
 		"msw-storybook-addon": "2.0.0-beta.1",
 		"nodemon": "3.0.3",
-		"prettier": "3.2.4",
+		"prettier": "3.2.5",
 		"react": "18.2.0",
 		"react-dom": "18.2.0",
 		"start-server-and-test": "2.0.3",
diff --git a/packages/misskey-js/package.json b/packages/misskey-js/package.json
index 0c4dd52d14..bc3001fc05 100644
--- a/packages/misskey-js/package.json
+++ b/packages/misskey-js/package.json
@@ -39,7 +39,7 @@
 		"@misskey-dev/eslint-plugin": "1.0.0",
 		"@swc/jest": "0.2.31",
 		"@types/jest": "29.5.11",
-		"@types/node": "20.11.10",
+		"@types/node": "20.11.17",
 		"@typescript-eslint/eslint-plugin": "6.18.1",
 		"@typescript-eslint/parser": "6.18.1",
 		"eslint": "8.56.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index eb0c613e97..640713067f 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -120,8 +120,8 @@ importers:
         specifier: 1.7.0
         version: 1.7.0
       '@simplewebauthn/server':
-        specifier: 9.0.1
-        version: 9.0.1
+        specifier: 9.0.2
+        version: 9.0.2
       '@sinonjs/fake-timers':
         specifier: 11.2.2
         version: 11.2.2
@@ -159,14 +159,14 @@ importers:
         specifier: 1.20.2
         version: 1.20.2
       bullmq:
-        specifier: 5.1.5
-        version: 5.1.5
+        specifier: 5.1.9
+        version: 5.1.9
       cacheable-lookup:
         specifier: 7.0.0
         version: 7.0.0
       cbor:
-        specifier: 9.0.1
-        version: 9.0.1
+        specifier: 9.0.2
+        version: 9.0.2
       chalk:
         specifier: 5.3.0
         version: 5.3.0
@@ -557,8 +557,8 @@ importers:
         specifier: 0.7.34
         version: 0.7.34
       '@types/node':
-        specifier: 20.11.10
-        version: 20.11.10
+        specifier: 20.11.17
+        version: 20.11.17
       '@types/node-fetch':
         specifier: 3.0.3
         version: 3.0.3
@@ -651,7 +651,7 @@ importers:
         version: 9.0.0
       jest:
         specifier: 29.7.0
-        version: 29.7.0(@types/node@20.11.10)
+        version: 29.7.0(@types/node@20.11.17)
       jest-mock:
         specifier: 29.7.0
         version: 29.7.0
@@ -699,10 +699,10 @@ importers:
         version: 15.0.0
       '@vitejs/plugin-vue':
         specifier: 5.0.3
-        version: 5.0.3(vite@5.1.0)(vue@3.4.15)
+        version: 5.0.3(vite@5.1.0)(vue@3.4.18)
       '@vue/compiler-sfc':
-        specifier: 3.4.15
-        version: 3.4.15
+        specifier: 3.4.18
+        version: 3.4.18
       aiscript-vscode:
         specifier: github:aiscript-dev/aiscript-vscode#v0.1.2
         version: github.com/aiscript-dev/aiscript-vscode/793211d40243c8775f6b85f015c221c82cbffb07
@@ -828,16 +828,16 @@ importers:
         version: 9.0.1
       v-code-diff:
         specifier: 1.7.2
-        version: 1.7.2(vue@3.4.15)
+        version: 1.7.2(vue@3.4.18)
       vite:
         specifier: 5.1.0
-        version: 5.1.0(@types/node@20.11.10)(sass@1.70.0)(terser@5.27.0)
+        version: 5.1.0(@types/node@20.11.17)(sass@1.70.0)(terser@5.27.0)
       vue:
-        specifier: 3.4.15
-        version: 3.4.15(typescript@5.3.3)
+        specifier: 3.4.18
+        version: 3.4.18(typescript@5.3.3)
       vuedraggable:
         specifier: next
-        version: 4.1.0(vue@3.4.15)
+        version: 4.1.0(vue@3.4.18)
     devDependencies:
       '@misskey-dev/eslint-plugin':
         specifier: 1.0.0
@@ -895,13 +895,13 @@ importers:
         version: 7.6.10
       '@storybook/vue3':
         specifier: 7.6.10
-        version: 7.6.10(vue@3.4.15)
+        version: 7.6.10(vue@3.4.18)
       '@storybook/vue3-vite':
         specifier: 7.6.10
-        version: 7.6.10(typescript@5.3.3)(vite@5.1.0)(vue@3.4.15)
+        version: 7.6.10(typescript@5.3.3)(vite@5.1.0)(vue@3.4.18)
       '@testing-library/vue':
-        specifier: 8.0.1
-        version: 8.0.1(@vue/compiler-sfc@3.4.15)(vue@3.4.15)
+        specifier: 8.0.2
+        version: 8.0.2(@vue/compiler-sfc@3.4.18)(vue@3.4.18)
       '@types/escape-regexp':
         specifier: 0.0.3
         version: 0.0.3
@@ -915,8 +915,8 @@ importers:
         specifier: 4.0.6
         version: 4.0.6
       '@types/node':
-        specifier: 20.11.10
-        version: 20.11.10
+        specifier: 20.11.17
+        version: 20.11.17
       '@types/punycode':
         specifier: 2.1.3
         version: 2.1.3
@@ -945,8 +945,8 @@ importers:
         specifier: 0.34.6
         version: 0.34.6(vitest@0.34.6)
       '@vue/runtime-core':
-        specifier: 3.4.15
-        version: 3.4.15
+        specifier: 3.4.18
+        version: 3.4.18
       acorn:
         specifier: 8.11.3
         version: 8.11.3
@@ -954,8 +954,8 @@ importers:
         specifier: 7.0.3
         version: 7.0.3
       cypress:
-        specifier: 13.6.3
-        version: 13.6.3
+        specifier: 13.6.4
+        version: 13.6.4
       eslint:
         specifier: 8.56.0
         version: 8.56.0
@@ -987,8 +987,8 @@ importers:
         specifier: 3.0.3
         version: 3.0.3
       prettier:
-        specifier: 3.2.4
-        version: 3.2.4
+        specifier: 3.2.5
+        version: 3.2.5
       react:
         specifier: 18.2.0
         version: 18.2.0
@@ -1086,7 +1086,7 @@ importers:
     devDependencies:
       '@microsoft/api-extractor':
         specifier: 7.39.1
-        version: 7.39.1(@types/node@20.11.10)
+        version: 7.39.1(@types/node@20.11.17)
       '@misskey-dev/eslint-plugin':
         specifier: 1.0.0
         version: 1.0.0(@typescript-eslint/eslint-plugin@6.18.1)(@typescript-eslint/parser@6.18.1)(eslint-plugin-import@2.29.1)(eslint@8.56.0)
@@ -1097,8 +1097,8 @@ importers:
         specifier: 29.5.11
         version: 29.5.11
       '@types/node':
-        specifier: 20.11.10
-        version: 20.11.10
+        specifier: 20.11.17
+        version: 20.11.17
       '@typescript-eslint/eslint-plugin':
         specifier: 6.18.1
         version: 6.18.1(@typescript-eslint/parser@6.18.1)(eslint@8.56.0)(typescript@5.3.3)
@@ -1110,7 +1110,7 @@ importers:
         version: 8.56.0
       jest:
         specifier: 29.7.0
-        version: 29.7.0(@types/node@20.11.10)
+        version: 29.7.0(@types/node@20.11.17)
       jest-fetch-mock:
         specifier: 3.0.3
         version: 3.0.3
@@ -2290,6 +2290,14 @@ packages:
     hasBin: true
     dependencies:
       '@babel/types': 7.23.5
+    dev: true
+
+  /@babel/parser@7.23.9:
+    resolution: {integrity: sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==}
+    engines: {node: '>=6.0.0'}
+    hasBin: true
+    dependencies:
+      '@babel/types': 7.23.5
 
   /@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.23.3(@babel/core@7.23.5):
     resolution: {integrity: sha512-iRkKcCqb7iGnq9+3G6rZ+Ciz5VywC4XNRHe57lKM+jOeYAoR0lVqdeeDRfh0tQcTfw/+vBhHn926FmQhLtlFLQ==}
@@ -4460,7 +4468,7 @@ packages:
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     dependencies:
       '@jest/types': 29.6.3
-      '@types/node': 20.11.10
+      '@types/node': 20.11.17
       chalk: 4.1.2
       jest-message-util: 29.7.0
       jest-util: 29.7.0
@@ -4481,14 +4489,14 @@ packages:
       '@jest/test-result': 29.7.0
       '@jest/transform': 29.7.0
       '@jest/types': 29.6.3
-      '@types/node': 20.11.10
+      '@types/node': 20.11.17
       ansi-escapes: 4.3.2
       chalk: 4.1.2
       ci-info: 3.7.1
       exit: 0.1.2
       graceful-fs: 4.2.11
       jest-changed-files: 29.7.0
-      jest-config: 29.7.0(@types/node@20.11.10)
+      jest-config: 29.7.0(@types/node@20.11.17)
       jest-haste-map: 29.7.0
       jest-message-util: 29.7.0
       jest-regex-util: 29.6.3
@@ -4523,7 +4531,7 @@ packages:
     dependencies:
       '@jest/fake-timers': 29.7.0
       '@jest/types': 29.6.3
-      '@types/node': 20.11.10
+      '@types/node': 20.11.17
       jest-mock: 29.7.0
     dev: true
 
@@ -4550,7 +4558,7 @@ packages:
     dependencies:
       '@jest/types': 29.6.3
       '@sinonjs/fake-timers': 10.3.0
-      '@types/node': 20.11.10
+      '@types/node': 20.11.17
       jest-message-util: 29.7.0
       jest-mock: 29.7.0
       jest-util: 29.7.0
@@ -4583,7 +4591,7 @@ packages:
       '@jest/transform': 29.7.0
       '@jest/types': 29.6.3
       '@jridgewell/trace-mapping': 0.3.18
-      '@types/node': 20.11.10
+      '@types/node': 20.11.17
       chalk: 4.1.2
       collect-v8-coverage: 1.0.1
       exit: 0.1.2
@@ -4677,7 +4685,7 @@ packages:
     dependencies:
       '@types/istanbul-lib-coverage': 2.0.4
       '@types/istanbul-reports': 3.0.1
-      '@types/node': 20.11.10
+      '@types/node': 20.11.17
       '@types/yargs': 16.0.5
       chalk: 4.1.2
     dev: true
@@ -4689,7 +4697,7 @@ packages:
       '@jest/schemas': 29.6.3
       '@types/istanbul-lib-coverage': 2.0.4
       '@types/istanbul-reports': 3.0.1
-      '@types/node': 20.11.10
+      '@types/node': 20.11.17
       '@types/yargs': 17.0.19
       chalk: 4.1.2
     dev: true
@@ -4708,7 +4716,7 @@ packages:
       magic-string: 0.27.0
       react-docgen-typescript: 2.2.2(typescript@5.3.3)
       typescript: 5.3.3
-      vite: 5.1.0(@types/node@20.11.10)(sass@1.70.0)(terser@5.27.0)
+      vite: 5.1.0(@types/node@20.11.17)(sass@1.70.0)(terser@5.27.0)
     dev: true
 
   /@jridgewell/gen-mapping@0.3.2:
@@ -4806,24 +4814,24 @@ packages:
       react: 18.2.0
     dev: true
 
-  /@microsoft/api-extractor-model@7.28.4(@types/node@20.11.10):
+  /@microsoft/api-extractor-model@7.28.4(@types/node@20.11.17):
     resolution: {integrity: sha512-vucgyPmgHrJ/D4/xQywAmjTmSfxAx2/aDmD6TkIoLu51FdsAfuWRbijWA48AePy60OO+l+mmy9p2P/CEeBZqig==}
     dependencies:
       '@microsoft/tsdoc': 0.14.2
       '@microsoft/tsdoc-config': 0.16.2
-      '@rushstack/node-core-library': 3.63.0(@types/node@20.11.10)
+      '@rushstack/node-core-library': 3.63.0(@types/node@20.11.17)
     transitivePeerDependencies:
       - '@types/node'
     dev: true
 
-  /@microsoft/api-extractor@7.39.1(@types/node@20.11.10):
+  /@microsoft/api-extractor@7.39.1(@types/node@20.11.17):
     resolution: {integrity: sha512-V0HtCufWa8hZZvSmlEzQZfINcJkHAU/bmpyJQj6w+zpI87EkR8DuBOW6RWrO9c7mUYFZoDaNgUTyKo83ytv+QQ==}
     hasBin: true
     dependencies:
-      '@microsoft/api-extractor-model': 7.28.4(@types/node@20.11.10)
+      '@microsoft/api-extractor-model': 7.28.4(@types/node@20.11.17)
       '@microsoft/tsdoc': 0.14.2
       '@microsoft/tsdoc-config': 0.16.2
-      '@rushstack/node-core-library': 3.63.0(@types/node@20.11.10)
+      '@rushstack/node-core-library': 3.63.0(@types/node@20.11.17)
       '@rushstack/rig-package': 0.5.1
       '@rushstack/ts-command-line': 4.17.1
       colors: 1.2.5
@@ -5911,7 +5919,7 @@ packages:
     requiresBuild: true
     optional: true
 
-  /@rushstack/node-core-library@3.63.0(@types/node@20.11.10):
+  /@rushstack/node-core-library@3.63.0(@types/node@20.11.17):
     resolution: {integrity: sha512-Q7B3dVpBQF1v+mUfxNcNZh5uHVR8ntcnkN5GYjbBLrxUYHBGKbnCM+OdcN+hzCpFlLBH6Ob0dEHhZ0spQwf24A==}
     peerDependencies:
       '@types/node': '*'
@@ -5919,7 +5927,7 @@ packages:
       '@types/node':
         optional: true
     dependencies:
-      '@types/node': 20.11.10
+      '@types/node': 20.11.17
       colors: 1.2.5
       fs-extra: 7.0.1
       import-lazy: 4.0.0
@@ -5963,8 +5971,8 @@ packages:
     resolution: {integrity: sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==}
     dev: true
 
-  /@simplewebauthn/server@9.0.1:
-    resolution: {integrity: sha512-XnilMoBygy2BOZjIHPxby+7ENx5ChN2wXfhd14mOgO/XitYMqdphTo/kwgxEI4/Je3lELK1h/eLDJqM2fIKS1w==}
+  /@simplewebauthn/server@9.0.2:
+    resolution: {integrity: sha512-aaWA+qVOU4byk5IDb/l+M1+7dmrAJhTb4ISJHucpsgRQcMMEes76tbGIqO2JQuA7N50tc/OBrnGKBjoKYG1kSw==}
     engines: {node: '>=16.0.0'}
     dependencies:
       '@hexagon/base64': 1.1.27
@@ -5973,15 +5981,15 @@ packages:
       '@peculiar/asn1-rsa': 2.3.8
       '@peculiar/asn1-schema': 2.3.8
       '@peculiar/asn1-x509': 2.3.8
-      '@simplewebauthn/types': 9.0.0
+      '@simplewebauthn/types': 9.0.1
       cbor-x: 1.5.4
       cross-fetch: 4.0.0
     transitivePeerDependencies:
       - encoding
     dev: false
 
-  /@simplewebauthn/types@9.0.0:
-    resolution: {integrity: sha512-Lo6LLNQee66D//KueYy9AyX7oiQ7BBKJgdLzP3l0HJDrV4GRSzSAii8AtigBGOeNc8hOQsF/D8itItyuZX9djA==}
+  /@simplewebauthn/types@9.0.1:
+    resolution: {integrity: sha512-tGSRP1QvsAvsJmnOlRQyw/mvK9gnPtjEc5fg2+m8n+QUa+D7rvrKkOYyfpy42GTs90X3RDOnqJgfHt+qO67/+w==}
     dev: false
 
   /@simplewebauthn/typescript-types@8.3.4:
@@ -6772,7 +6780,7 @@ packages:
       magic-string: 0.30.5
       rollup: 3.29.4
       typescript: 5.3.3
-      vite: 5.1.0(@types/node@20.11.10)(sass@1.70.0)(terser@5.27.0)
+      vite: 5.1.0(@types/node@20.11.17)(sass@1.70.0)(terser@5.27.0)
     transitivePeerDependencies:
       - encoding
       - supports-color
@@ -7146,7 +7154,7 @@ packages:
       react: 18.2.0
       react-docgen: 7.0.1
       react-dom: 18.2.0(react@18.2.0)
-      vite: 5.1.0(@types/node@20.11.10)(sass@1.70.0)(terser@5.27.0)
+      vite: 5.1.0(@types/node@20.11.17)(sass@1.70.0)(terser@5.27.0)
     transitivePeerDependencies:
       - '@preact/preset-vite'
       - encoding
@@ -7261,7 +7269,7 @@ packages:
       file-system-cache: 2.3.0
     dev: true
 
-  /@storybook/vue3-vite@7.6.10(typescript@5.3.3)(vite@5.1.0)(vue@3.4.15):
+  /@storybook/vue3-vite@7.6.10(typescript@5.3.3)(vite@5.1.0)(vue@3.4.18):
     resolution: {integrity: sha512-5f0Rh4PTVEeAI86ybihfN+rHGXXLNiRsoGKinpJSb7hkfsq/L7u3sVCXJwH/qsG+rUJlZyHs3kfa4/Kgyyi3Mg==}
     engines: {node: ^14.18 || >=16}
     peerDependencies:
@@ -7269,11 +7277,11 @@ packages:
     dependencies:
       '@storybook/builder-vite': 7.6.10(typescript@5.3.3)(vite@5.1.0)
       '@storybook/core-server': 7.6.10
-      '@storybook/vue3': 7.6.10(vue@3.4.15)
-      '@vitejs/plugin-vue': 4.5.2(vite@5.1.0)(vue@3.4.15)
+      '@storybook/vue3': 7.6.10(vue@3.4.18)
+      '@vitejs/plugin-vue': 4.5.2(vite@5.1.0)(vue@3.4.18)
       magic-string: 0.30.5
-      vite: 5.1.0(@types/node@20.11.10)(sass@1.70.0)(terser@5.27.0)
-      vue-docgen-api: 4.64.1(vue@3.4.15)
+      vite: 5.1.0(@types/node@20.11.17)(sass@1.70.0)(terser@5.27.0)
+      vue-docgen-api: 4.64.1(vue@3.4.18)
     transitivePeerDependencies:
       - '@preact/preset-vite'
       - bufferutil
@@ -7285,7 +7293,7 @@ packages:
       - vue
     dev: true
 
-  /@storybook/vue3@7.6.10(vue@3.4.15):
+  /@storybook/vue3@7.6.10(vue@3.4.18):
     resolution: {integrity: sha512-FeZ9zjuudQgCdKPs2K8sU6TgEyrMjKrCN3e8+XXX5CAMSwLDV8IfexaaMF0ehYW6Wp0dgCIm0cVNBV3u8vtRRw==}
     engines: {node: '>=16.0.0'}
     peerDependencies:
@@ -7300,7 +7308,7 @@ packages:
       lodash: 4.17.21
       ts-dedent: 2.2.0
       type-fest: 2.19.0
-      vue: 3.4.15(typescript@5.3.3)
+      vue: 3.4.18(typescript@5.3.3)
       vue-component-type-helpers: 1.8.27
     transitivePeerDependencies:
       - encoding
@@ -7870,8 +7878,8 @@ packages:
     resolution: {integrity: sha512-fB0R+fa3AUqbLHWyxXa2kGVtf1Fe1ZZFr0Zp6AIbIAzXb2mKbEXl+PCQNUOaq5lbTab5tfctfXRNsWXxa2f7Aw==}
     engines: {node: '>=14'}
     dependencies:
-      '@babel/code-frame': 7.22.13
-      '@babel/runtime': 7.23.2
+      '@babel/code-frame': 7.23.5
+      '@babel/runtime': 7.23.4
       '@types/aria-query': 5.0.1
       aria-query: 5.1.3
       chalk: 4.1.2
@@ -7919,18 +7927,21 @@ packages:
       '@testing-library/dom': 9.2.0
     dev: true
 
-  /@testing-library/vue@8.0.1(@vue/compiler-sfc@3.4.15)(vue@3.4.15):
-    resolution: {integrity: sha512-l51ZEpjTQ6glq3wM+asQ1GbKJMGcxwgHEygETx0aCRN4TjFEGvMZy4YdWKs/y7bu4bmLrxcxhbEPP7iPSW/2OQ==}
+  /@testing-library/vue@8.0.2(@vue/compiler-sfc@3.4.18)(vue@3.4.18):
+    resolution: {integrity: sha512-A8wWX+qQn0o0izpQWnGCpwQt8wAdpsVP8vPP2h5Q/jcGhZ5yKXz9PPUqhQv+45LTFaWlyRf8bArTVaB/KFFd5A==}
     engines: {node: '>=14'}
     peerDependencies:
       '@vue/compiler-sfc': '>= 3'
       vue: '>= 3'
+    peerDependenciesMeta:
+      '@vue/compiler-sfc':
+        optional: true
     dependencies:
-      '@babel/runtime': 7.23.2
+      '@babel/runtime': 7.23.4
       '@testing-library/dom': 9.3.3
-      '@vue/compiler-sfc': 3.4.15
-      '@vue/test-utils': 2.4.1(vue@3.4.15)
-      vue: 3.4.15(typescript@5.3.3)
+      '@vue/compiler-sfc': 3.4.18
+      '@vue/test-utils': 2.4.1(vue@3.4.18)
+      vue: 3.4.18(typescript@5.3.3)
     transitivePeerDependencies:
       - '@vue/server-renderer'
     dev: true
@@ -7956,7 +7967,7 @@ packages:
   /@types/accepts@1.3.7:
     resolution: {integrity: sha512-Pay9fq2lM2wXPWbteBsRAGiWH2hig4ZE2asK+mm7kUzlxRTfL961rj89I6zV/E3PcIkDqyuBEcMxFT7rccugeQ==}
     dependencies:
-      '@types/node': 20.11.10
+      '@types/node': 20.11.17
     dev: true
 
   /@types/archiver@6.0.2:
@@ -8010,7 +8021,7 @@ packages:
     resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==}
     dependencies:
       '@types/connect': 3.4.35
-      '@types/node': 20.11.10
+      '@types/node': 20.11.17
     dev: true
 
   /@types/braces@3.0.1:
@@ -8022,14 +8033,14 @@ packages:
     dependencies:
       '@types/http-cache-semantics': 4.0.1
       '@types/keyv': 3.1.4
-      '@types/node': 20.11.10
+      '@types/node': 20.11.17
       '@types/responselike': 1.0.0
     dev: false
 
   /@types/cbor@6.0.0:
     resolution: {integrity: sha512-mGQ1lbYOwVti5Xlarn1bTeBZqgY0kstsdjnkoEovgohYKdBjGejHyNGXHdMBeqyQazIv32Jjp33+5pBEaSRy2w==}
     dependencies:
-      cbor: 9.0.1
+      cbor: 9.0.2
     dev: true
 
   /@types/chai-subset@1.3.5:
@@ -8055,7 +8066,7 @@ packages:
   /@types/connect@3.4.35:
     resolution: {integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==}
     dependencies:
-      '@types/node': 20.11.10
+      '@types/node': 20.11.17
     dev: true
 
   /@types/content-disposition@0.5.8:
@@ -8069,7 +8080,7 @@ packages:
   /@types/cross-spawn@6.0.2:
     resolution: {integrity: sha512-KuwNhp3eza+Rhu8IFI5HUXRP0LIhqH5cAjubUvGXXthh4YYBuP2ntwEX+Cz8GJoZUHlKo247wPWOfA9LYEq4cw==}
     dependencies:
-      '@types/node': 20.11.10
+      '@types/node': 20.11.17
     dev: true
 
   /@types/detect-port@1.3.2:
@@ -8121,7 +8132,7 @@ packages:
   /@types/express-serve-static-core@4.17.33:
     resolution: {integrity: sha512-TPBqmR/HRYI3eC2E5hmiivIzv+bidAfXofM+sbonAGvyDhySGw9/PQZFt2BLOrjUUR++4eJVpx6KnLQK1Fk9tA==}
     dependencies:
-      '@types/node': 20.11.10
+      '@types/node': 20.11.17
       '@types/qs': 6.9.7
       '@types/range-parser': 1.2.4
     dev: true
@@ -8142,20 +8153,20 @@ packages:
   /@types/fluent-ffmpeg@2.1.24:
     resolution: {integrity: sha512-g5oQO8Jgi2kFS3tTub7wLvfLztr1s8tdXmRd8PiL/hLMLzTIAyMR2sANkTggM/rdEDAg3d63nYRRVepwBiCw5A==}
     dependencies:
-      '@types/node': 20.11.10
+      '@types/node': 20.11.17
     dev: true
 
   /@types/glob@7.2.0:
     resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==}
     dependencies:
       '@types/minimatch': 5.1.2
-      '@types/node': 20.11.10
+      '@types/node': 20.11.17
     dev: true
 
   /@types/graceful-fs@4.1.6:
     resolution: {integrity: sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==}
     dependencies:
-      '@types/node': 20.11.10
+      '@types/node': 20.11.17
     dev: true
 
   /@types/http-cache-semantics@4.0.1:
@@ -8168,7 +8179,7 @@ packages:
   /@types/http-link-header@1.0.5:
     resolution: {integrity: sha512-AxhIKR8UbyoqCTNp9rRepkktHuUOw3DjfOfDCaO9kwI8AYzjhxyrvZq4+mRw/2daD3hYDknrtSeV6SsPwmc71w==}
     dependencies:
-      '@types/node': 20.11.10
+      '@types/node': 20.11.17
     dev: true
 
   /@types/istanbul-lib-coverage@2.0.4:
@@ -8208,7 +8219,7 @@ packages:
   /@types/jsdom@21.1.6:
     resolution: {integrity: sha512-/7kkMsC+/kMs7gAYmmBR9P0vGTnOoLhQhyhQJSlXGI5bzTHp6xdo0TtKWQAsz6pmSAeVqKSbqeyP6hytqr9FDw==}
     dependencies:
-      '@types/node': 20.11.10
+      '@types/node': 20.11.17
       '@types/tough-cookie': 4.0.2
       parse5: 7.1.2
     dev: true
@@ -8232,7 +8243,7 @@ packages:
   /@types/keyv@3.1.4:
     resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==}
     dependencies:
-      '@types/node': 20.11.10
+      '@types/node': 20.11.17
     dev: false
 
   /@types/lodash@4.14.191:
@@ -8281,7 +8292,7 @@ packages:
   /@types/node-fetch@2.6.4:
     resolution: {integrity: sha512-1ZX9fcN4Rvkvgv4E6PAY5WXUFWFcRWxZa3EW83UjycOB9ljJCedb2CupIP4RZMEwF/M3eTcCihbBRgwtGbg5Rg==}
     dependencies:
-      '@types/node': 20.11.10
+      '@types/node': 20.11.17
       form-data: 3.0.1
 
   /@types/node-fetch@3.0.3:
@@ -8294,8 +8305,8 @@ packages:
     resolution: {integrity: sha512-2yrWpBk32tvV/JAd3HNHWuZn/VDN1P+72hWirHnvsvTGSqbANi+kSeuQR9yAHnbvaBvHDsoTdXV0Fe+iRtHLKA==}
     dev: true
 
-  /@types/node@20.11.10:
-    resolution: {integrity: sha512-rZEfe/hJSGYmdfX9tvcPMYeYPW2sNl50nsw4jZmRcaG0HIAb0WYEpsB05GOb53vjqpyE9GUhlDQ4jLSoB5q9kg==}
+  /@types/node@20.11.17:
+    resolution: {integrity: sha512-QmgQZGWu1Yw9TDyAP9ZzpFJKynYNeOvwMJmaxABfieQoVoiVOS6MN1WSpqpRcbeA5+RW82kraAVxCCJg+780Qw==}
     dependencies:
       undici-types: 5.26.5
 
@@ -8314,7 +8325,7 @@ packages:
   /@types/nodemailer@6.4.14:
     resolution: {integrity: sha512-fUWthHO9k9DSdPCSPRqcu6TWhYyxTBg382vlNIttSe9M7XfsT06y0f24KHXtbnijPGGRIcVvdKHTNikOI6qiHA==}
     dependencies:
-      '@types/node': 20.11.10
+      '@types/node': 20.11.17
     dev: true
 
   /@types/normalize-package-data@2.4.1:
@@ -8331,13 +8342,13 @@ packages:
     resolution: {integrity: sha512-Ali0fUUn+zgr4Yy/pCTFbuiaiJpq7l7OQwFnxYVchNbNGIx0c4Wkcdje6WO89I91RAaYF+gVc1pOaizA4YKZmA==}
     dependencies:
       '@types/express': 4.17.17
-      '@types/node': 20.11.10
+      '@types/node': 20.11.17
     dev: true
 
   /@types/oauth@0.9.4:
     resolution: {integrity: sha512-qk9orhti499fq5XxKCCEbd0OzdPZuancneyse3KtR+vgMiHRbh+mn8M4G6t64ob/Fg+GZGpa565MF/2dKWY32A==}
     dependencies:
-      '@types/node': 20.11.10
+      '@types/node': 20.11.17
     dev: true
 
   /@types/offscreencanvas@2019.3.0:
@@ -8353,7 +8364,7 @@ packages:
   /@types/pg@8.11.0:
     resolution: {integrity: sha512-sDAlRiBNthGjNFfvt0k6mtotoVYVQ63pA8R4EMWka7crawSR60waVYR0HAgmPRs/e2YaeJTD/43OoZ3PFw80pw==}
     dependencies:
-      '@types/node': 20.11.10
+      '@types/node': 20.11.17
       pg-protocol: 1.6.0
       pg-types: 4.0.1
     dev: true
@@ -8377,7 +8388,7 @@ packages:
   /@types/qrcode@1.5.5:
     resolution: {integrity: sha512-CdfBi/e3Qk+3Z/fXYShipBT13OJ2fDO2Q2w5CIP5anLTLIndQG9z6P1cnm+8zCWSpm5dnxMFd/uREtb0EXuQzg==}
     dependencies:
-      '@types/node': 20.11.10
+      '@types/node': 20.11.17
     dev: true
 
   /@types/qs@6.9.7:
@@ -8407,7 +8418,7 @@ packages:
   /@types/readdir-glob@1.1.1:
     resolution: {integrity: sha512-ImM6TmoF8bgOwvehGviEj3tRdRBbQujr1N+0ypaln/GWjaerOB26jb93vsRHmdMtvVQZQebOlqt2HROark87mQ==}
     dependencies:
-      '@types/node': 20.11.10
+      '@types/node': 20.11.17
     dev: true
 
   /@types/rename@1.0.7:
@@ -8421,7 +8432,7 @@ packages:
   /@types/responselike@1.0.0:
     resolution: {integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==}
     dependencies:
-      '@types/node': 20.11.10
+      '@types/node': 20.11.17
     dev: false
 
   /@types/sanitize-html@2.9.5:
@@ -8451,7 +8462,7 @@ packages:
     resolution: {integrity: sha512-NUo5XNiAdULrJENtJXZZ3fHtfMolzZwczzBbnAeBbqBwG+LaG6YaJtuwzwGSQZ2wsCrxjEhNNjAkKigy3n8teQ==}
     dependencies:
       '@types/mime': 3.0.1
-      '@types/node': 20.11.10
+      '@types/node': 20.11.17
     dev: true
 
   /@types/serviceworker@0.0.67:
@@ -8522,13 +8533,13 @@ packages:
   /@types/vary@1.1.3:
     resolution: {integrity: sha512-XJT8/ZQCL7NUut9QDLf6l24JfAEl7bnNdgxfj50cHIpEPRJLHHDDFOAq6i+GsEmeFfH7NamhBE4c4Thtb2egWg==}
     dependencies:
-      '@types/node': 20.11.10
+      '@types/node': 20.11.17
     dev: true
 
   /@types/web-push@3.6.3:
     resolution: {integrity: sha512-v3oT4mMJsHeJ/rraliZ+7TbZtr5bQQuxcgD7C3/1q/zkAj29c8RE0F9lVZVu3hiQe5Z9fYcBreV7TLnfKR+4mg==}
     dependencies:
-      '@types/node': 20.11.10
+      '@types/node': 20.11.17
     dev: true
 
   /@types/webgl-ext@0.0.30:
@@ -8539,7 +8550,7 @@ packages:
   /@types/ws@8.5.10:
     resolution: {integrity: sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==}
     dependencies:
-      '@types/node': 20.11.10
+      '@types/node': 20.11.17
     dev: true
 
   /@types/yargs-parser@21.0.0:
@@ -8562,7 +8573,7 @@ packages:
     resolution: {integrity: sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==}
     requiresBuild: true
     dependencies:
-      '@types/node': 20.11.10
+      '@types/node': 20.11.17
     dev: true
     optional: true
 
@@ -8844,31 +8855,31 @@ packages:
       '@babel/plugin-transform-react-jsx-source': 7.19.6(@babel/core@7.23.5)
       magic-string: 0.27.0
       react-refresh: 0.14.0
-      vite: 5.1.0(@types/node@20.11.10)(sass@1.70.0)(terser@5.27.0)
+      vite: 5.1.0(@types/node@20.11.17)(sass@1.70.0)(terser@5.27.0)
     transitivePeerDependencies:
       - supports-color
     dev: true
 
-  /@vitejs/plugin-vue@4.5.2(vite@5.1.0)(vue@3.4.15):
+  /@vitejs/plugin-vue@4.5.2(vite@5.1.0)(vue@3.4.18):
     resolution: {integrity: sha512-UGR3DlzLi/SaVBPX0cnSyE37vqxU3O6chn8l0HJNzQzDia6/Au2A4xKv+iIJW8w2daf80G7TYHhi1pAUjdZ0bQ==}
     engines: {node: ^14.18.0 || >=16.0.0}
     peerDependencies:
       vite: ^4.0.0 || ^5.0.0
       vue: ^3.2.25
     dependencies:
-      vite: 5.1.0(@types/node@20.11.10)(sass@1.70.0)(terser@5.27.0)
-      vue: 3.4.15(typescript@5.3.3)
+      vite: 5.1.0(@types/node@20.11.17)(sass@1.70.0)(terser@5.27.0)
+      vue: 3.4.18(typescript@5.3.3)
     dev: true
 
-  /@vitejs/plugin-vue@5.0.3(vite@5.1.0)(vue@3.4.15):
+  /@vitejs/plugin-vue@5.0.3(vite@5.1.0)(vue@3.4.18):
     resolution: {integrity: sha512-b8S5dVS40rgHdDrw+DQi/xOM9ed+kSRZzfm1T74bMmBDCd8XO87NKlFYInzCtwvtWwXZvo1QxE2OSspTATWrbA==}
     engines: {node: ^18.0.0 || >=20.0.0}
     peerDependencies:
       vite: ^5.0.0
       vue: ^3.2.25
     dependencies:
-      vite: 5.1.0(@types/node@20.11.10)(sass@1.70.0)(terser@5.27.0)
-      vue: 3.4.15(typescript@5.3.3)
+      vite: 5.1.0(@types/node@20.11.17)(sass@1.70.0)(terser@5.27.0)
+      vue: 3.4.18(typescript@5.3.3)
     dev: false
 
   /@vitest/coverage-v8@0.34.6(vitest@0.34.6):
@@ -8966,6 +8977,16 @@ packages:
       entities: 4.5.0
       estree-walker: 2.0.2
       source-map-js: 1.0.2
+    dev: true
+
+  /@vue/compiler-core@3.4.18:
+    resolution: {integrity: sha512-F7YK8lMK0iv6b9/Gdk15A67wM0KKZvxDxed0RR60C1z9tIJTKta+urs4j0RTN5XqHISzI3etN3mX0uHhjmoqjQ==}
+    dependencies:
+      '@babel/parser': 7.23.9
+      '@vue/shared': 3.4.18
+      entities: 4.5.0
+      estree-walker: 2.0.2
+      source-map-js: 1.0.2
 
   /@vue/compiler-core@3.4.3:
     resolution: {integrity: sha512-u8jzgFg0EDtSrb/hG53Wwh1bAOQFtc1ZCegBpA/glyvTlgHl+tq13o1zvRfLbegYUw/E4mSTGOiCnAJ9SJ+lsg==}
@@ -8989,25 +9010,32 @@ packages:
     dependencies:
       '@vue/compiler-core': 3.4.15
       '@vue/shared': 3.4.15
+    dev: true
 
-  /@vue/compiler-sfc@3.4.15:
-    resolution: {integrity: sha512-LCn5M6QpkpFsh3GQvs2mJUOAlBQcCco8D60Bcqmf3O3w5a+KWS5GvYbrrJBkgvL1BDnTp+e8q0lXCLgHhKguBA==}
+  /@vue/compiler-dom@3.4.18:
+    resolution: {integrity: sha512-24Eb8lcMfInefvQ6YlEVS18w5Q66f4+uXWVA+yb7praKbyjHRNuKVWGuinfSSjM0ZIiPi++QWukhkgznBaqpEA==}
     dependencies:
-      '@babel/parser': 7.23.6
-      '@vue/compiler-core': 3.4.15
-      '@vue/compiler-dom': 3.4.15
-      '@vue/compiler-ssr': 3.4.15
-      '@vue/shared': 3.4.15
+      '@vue/compiler-core': 3.4.18
+      '@vue/shared': 3.4.18
+
+  /@vue/compiler-sfc@3.4.18:
+    resolution: {integrity: sha512-rG5tqtnzwrVpMqAQ7FHtvHaV70G6LLfJIWLYZB/jZ9m/hrnZmIQh+H3ewnC5onwe/ibljm9+ZupxeElzqCkTAw==}
+    dependencies:
+      '@babel/parser': 7.23.9
+      '@vue/compiler-core': 3.4.18
+      '@vue/compiler-dom': 3.4.18
+      '@vue/compiler-ssr': 3.4.18
+      '@vue/shared': 3.4.18
       estree-walker: 2.0.2
-      magic-string: 0.30.5
+      magic-string: 0.30.7
       postcss: 8.4.33
       source-map-js: 1.0.2
 
-  /@vue/compiler-ssr@3.4.15:
-    resolution: {integrity: sha512-1jdeQyiGznr8gjFDadVmOJqZiLNSsMa5ZgqavkPZ8O2wjHv0tVuAEsw5hTdUoUW4232vpBbL/wJhzVW/JwY1Uw==}
+  /@vue/compiler-ssr@3.4.18:
+    resolution: {integrity: sha512-hSlv20oUhPxo2UYUacHgGaxtqP0tvFo6ixxxD6JlXIkwzwoZ9eKK6PFQN4hNK/R13JlNyldwWt/fqGBKgWJ6nQ==}
     dependencies:
-      '@vue/compiler-dom': 3.4.15
-      '@vue/shared': 3.4.15
+      '@vue/compiler-dom': 3.4.18
+      '@vue/shared': 3.4.18
 
   /@vue/language-core@1.8.27(typescript@5.3.3):
     resolution: {integrity: sha512-L8Kc27VdQserNaCUNiSFdDl9LWT24ly8Hpwf1ECy3aFb9m6bDhBGQYOujDm21N7EW3moKIOKEanQwe1q5BK+mA==}
@@ -9029,32 +9057,32 @@ packages:
       vue-template-compiler: 2.7.14
     dev: true
 
-  /@vue/reactivity@3.4.15:
-    resolution: {integrity: sha512-55yJh2bsff20K5O84MxSvXKPHHt17I2EomHznvFiJCAZpJTNW8IuLj1xZWMLELRhBK3kkFV/1ErZGHJfah7i7w==}
+  /@vue/reactivity@3.4.18:
+    resolution: {integrity: sha512-7uda2/I0jpLiRygprDo5Jxs2HJkOVXcOMlyVlY54yRLxoycBpwGJRwJT9EdGB4adnoqJDXVT2BilUAYwI7qvmg==}
     dependencies:
-      '@vue/shared': 3.4.15
+      '@vue/shared': 3.4.18
 
-  /@vue/runtime-core@3.4.15:
-    resolution: {integrity: sha512-6E3by5m6v1AkW0McCeAyhHTw+3y17YCOKG0U0HDKDscV4Hs0kgNT5G+GCHak16jKgcCDHpI9xe5NKb8sdLCLdw==}
+  /@vue/runtime-core@3.4.18:
+    resolution: {integrity: sha512-7mU9diCa+4e+8/wZ7Udw5pwTH10A11sZ1nldmHOUKJnzCwvZxfJqAtw31mIf4T5H2FsLCSBQT3xgioA9vIjyDQ==}
     dependencies:
-      '@vue/reactivity': 3.4.15
-      '@vue/shared': 3.4.15
+      '@vue/reactivity': 3.4.18
+      '@vue/shared': 3.4.18
 
-  /@vue/runtime-dom@3.4.15:
-    resolution: {integrity: sha512-EVW8D6vfFVq3V/yDKNPBFkZKGMFSvZrUQmx196o/v2tHKdwWdiZjYUBS+0Ez3+ohRyF8Njwy/6FH5gYJ75liUw==}
+  /@vue/runtime-dom@3.4.18:
+    resolution: {integrity: sha512-2y1Mkzcw1niSfG7z3Qx+2ir9Gb4hdTkZe5p/I8x1aTIKQE0vY0tPAEUPhZm5tx6183gG3D/KwHG728UR0sIufA==}
     dependencies:
-      '@vue/runtime-core': 3.4.15
-      '@vue/shared': 3.4.15
+      '@vue/runtime-core': 3.4.18
+      '@vue/shared': 3.4.18
       csstype: 3.1.3
 
-  /@vue/server-renderer@3.4.15(vue@3.4.15):
-    resolution: {integrity: sha512-3HYzaidu9cHjrT+qGUuDhFYvF/j643bHC6uUN9BgM11DVy+pM6ATsG6uPBLnkwOgs7BpJABReLmpL3ZPAsUaqw==}
+  /@vue/server-renderer@3.4.18(vue@3.4.18):
+    resolution: {integrity: sha512-YJd1wa7mzUN3NRqLEsrwEYWyO+PUBSROIGlCc3J/cvn7Zu6CxhNLgXa8Z4zZ5ja5/nviYO79J1InoPeXgwBTZA==}
     peerDependencies:
-      vue: 3.4.15
+      vue: 3.4.18
     dependencies:
-      '@vue/compiler-ssr': 3.4.15
-      '@vue/shared': 3.4.15
-      vue: 3.4.15(typescript@5.3.3)
+      '@vue/compiler-ssr': 3.4.18
+      '@vue/shared': 3.4.18
+      vue: 3.4.18(typescript@5.3.3)
 
   /@vue/shared@3.3.12:
     resolution: {integrity: sha512-6p0Yin0pclvnER7BLNOQuod9Z+cxSYh8pSh7CzHnWNjAIP6zrTlCdHRvSCb1aYEx6i3Q3kvfuWU7nG16CgG1ag==}
@@ -9062,12 +9090,16 @@ packages:
 
   /@vue/shared@3.4.15:
     resolution: {integrity: sha512-KzfPTxVaWfB+eGcGdbSf4CWdaXcGDqckoeXUh7SB3fZdEtzPCK2Vq9B/lRRL3yutax/LWITz+SwvgyOxz5V75g==}
+    dev: true
+
+  /@vue/shared@3.4.18:
+    resolution: {integrity: sha512-CxouGFxxaW5r1WbrSmWwck3No58rApXgRSBxrqgnY1K+jk20F6DrXJkHdH9n4HVT+/B6G2CAn213Uq3npWiy8Q==}
 
   /@vue/shared@3.4.3:
     resolution: {integrity: sha512-rIwlkkP1n4uKrRzivAKPZIEkHiuwY5mmhMJ2nZKCBLz8lTUlE73rQh4n1OnnMurXt1vcUNyH4ZPfdh8QweTjpQ==}
     dev: true
 
-  /@vue/test-utils@2.4.1(vue@3.4.15):
+  /@vue/test-utils@2.4.1(vue@3.4.18):
     resolution: {integrity: sha512-VO8nragneNzUZUah6kOjiFmD/gwRjUauG9DROh6oaOeFwX1cZRUNHhdeogE8635cISigXFTtGLUQWx5KCb0xeg==}
     peerDependencies:
       '@vue/server-renderer': ^3.0.1
@@ -9077,7 +9109,7 @@ packages:
         optional: true
     dependencies:
       js-beautify: 1.14.9
-      vue: 3.4.15(typescript@5.3.3)
+      vue: 3.4.18(typescript@5.3.3)
       vue-component-type-helpers: 1.8.4
     dev: true
 
@@ -10000,8 +10032,8 @@ packages:
     dependencies:
       node-gyp-build: 4.6.0
 
-  /bullmq@5.1.5:
-    resolution: {integrity: sha512-Rc9QGHrj/wJ8RMENKa839o1pJmdicg7KBTfmVU8YqYuEK2JcMSJaKMg2XrAi7sdYSawgOJgC/kiW9fCGYEj6Yg==}
+  /bullmq@5.1.9:
+    resolution: {integrity: sha512-9MfcQxYyfkG8kxpIxRsRXWYlTRQ1o8xWqgdoFR5pLClVTjtMI8qeDO5basRQLZPfp/uiPtv+gpzJ3OTNrm2ZNg==}
     dependencies:
       cron-parser: 4.8.1
       glob: 8.1.0
@@ -10179,8 +10211,8 @@ packages:
       cbor-extract: 2.1.1
     dev: false
 
-  /cbor@9.0.1:
-    resolution: {integrity: sha512-/TQOWyamDxvVIv+DY9cOLNuABkoyz8K/F3QE56539pGVYohx0+MEA1f4lChFTX79dBTBS7R1PF6ovH7G+VtBfQ==}
+  /cbor@9.0.2:
+    resolution: {integrity: sha512-JPypkxsB10s9QOWwa6zwPzqE1Md3vqpPc+cai4sAecuCsRyAtAl/pMyhPlMbT/xtPnm2dznJZYRLui57qiRhaQ==}
     engines: {node: '>=16'}
     dependencies:
       nofilter: 3.1.0
@@ -10717,7 +10749,7 @@ packages:
       readable-stream: 3.6.0
     dev: false
 
-  /create-jest@29.7.0(@types/node@20.11.10):
+  /create-jest@29.7.0(@types/node@20.11.17):
     resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==}
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     hasBin: true
@@ -10726,7 +10758,7 @@ packages:
       chalk: 4.1.2
       exit: 0.1.2
       graceful-fs: 4.2.11
-      jest-config: 29.7.0(@types/node@20.11.10)
+      jest-config: 29.7.0(@types/node@20.11.17)
       jest-util: 29.7.0
       prompts: 2.4.2
     transitivePeerDependencies:
@@ -10979,6 +11011,56 @@ packages:
       yauzl: 2.10.0
     dev: true
 
+  /cypress@13.6.4:
+    resolution: {integrity: sha512-pYJjCfDYB+hoOoZuhysbbYhEmNW7DEDsqn+ToCLwuVowxUXppIWRr7qk4TVRIU471ksfzyZcH+mkoF0CQUKnpw==}
+    engines: {node: ^16.0.0 || ^18.0.0 || >=20.0.0}
+    hasBin: true
+    requiresBuild: true
+    dependencies:
+      '@cypress/request': 3.0.0
+      '@cypress/xvfb': 1.2.4(supports-color@8.1.1)
+      '@types/sinonjs__fake-timers': 8.1.1
+      '@types/sizzle': 2.3.3
+      arch: 2.2.0
+      blob-util: 2.0.2
+      bluebird: 3.7.2
+      buffer: 5.7.1
+      cachedir: 2.3.0
+      chalk: 4.1.2
+      check-more-types: 2.24.0
+      cli-cursor: 3.1.0
+      cli-table3: 0.6.3
+      commander: 6.2.1
+      common-tags: 1.8.2
+      dayjs: 1.11.10
+      debug: 4.3.4(supports-color@8.1.1)
+      enquirer: 2.3.6
+      eventemitter2: 6.4.7
+      execa: 4.1.0
+      executable: 4.1.1
+      extract-zip: 2.0.1(supports-color@8.1.1)
+      figures: 3.2.0
+      fs-extra: 9.1.0
+      getos: 3.2.1
+      is-ci: 3.0.1
+      is-installed-globally: 0.4.0
+      lazy-ass: 1.6.0
+      listr2: 3.14.0(enquirer@2.3.6)
+      lodash: 4.17.21
+      log-symbols: 4.1.0
+      minimist: 1.2.8
+      ospath: 1.2.2
+      pretty-bytes: 5.6.0
+      process: 0.11.10
+      proxy-from-env: 1.0.0
+      request-progress: 3.0.0
+      semver: 7.5.4
+      supports-color: 8.1.1
+      tmp: 0.2.1
+      untildify: 4.0.0
+      yauzl: 2.10.0
+    dev: true
+
   /dashdash@1.14.1:
     resolution: {integrity: sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==}
     engines: {node: '>=0.10'}
@@ -14028,7 +14110,7 @@ packages:
       '@jest/expect': 29.7.0
       '@jest/test-result': 29.7.0
       '@jest/types': 29.6.3
-      '@types/node': 20.11.10
+      '@types/node': 20.11.17
       chalk: 4.1.2
       co: 4.6.0
       dedent: 1.3.0
@@ -14049,7 +14131,7 @@ packages:
       - supports-color
     dev: true
 
-  /jest-cli@29.7.0(@types/node@20.11.10):
+  /jest-cli@29.7.0(@types/node@20.11.17):
     resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==}
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     hasBin: true
@@ -14063,10 +14145,10 @@ packages:
       '@jest/test-result': 29.7.0
       '@jest/types': 29.6.3
       chalk: 4.1.2
-      create-jest: 29.7.0(@types/node@20.11.10)
+      create-jest: 29.7.0(@types/node@20.11.17)
       exit: 0.1.2
       import-local: 3.1.0
-      jest-config: 29.7.0(@types/node@20.11.10)
+      jest-config: 29.7.0(@types/node@20.11.17)
       jest-util: 29.7.0
       jest-validate: 29.7.0
       yargs: 17.6.2
@@ -14077,7 +14159,7 @@ packages:
       - ts-node
     dev: true
 
-  /jest-config@29.7.0(@types/node@20.11.10):
+  /jest-config@29.7.0(@types/node@20.11.17):
     resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==}
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     peerDependencies:
@@ -14092,7 +14174,7 @@ packages:
       '@babel/core': 7.22.11
       '@jest/test-sequencer': 29.7.0
       '@jest/types': 29.6.3
-      '@types/node': 20.11.10
+      '@types/node': 20.11.17
       babel-jest: 29.7.0(@babel/core@7.22.11)
       chalk: 4.1.2
       ci-info: 3.7.1
@@ -14172,7 +14254,7 @@ packages:
       '@jest/environment': 29.7.0
       '@jest/fake-timers': 29.7.0
       '@jest/types': 29.6.3
-      '@types/node': 20.11.10
+      '@types/node': 20.11.17
       jest-mock: 29.7.0
       jest-util: 29.7.0
     dev: true
@@ -14202,7 +14284,7 @@ packages:
     dependencies:
       '@jest/types': 29.6.3
       '@types/graceful-fs': 4.1.6
-      '@types/node': 20.11.10
+      '@types/node': 20.11.17
       anymatch: 3.1.3
       fb-watchman: 2.0.2
       graceful-fs: 4.2.11
@@ -14263,7 +14345,7 @@ packages:
     engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
     dependencies:
       '@jest/types': 27.5.1
-      '@types/node': 20.11.10
+      '@types/node': 20.11.17
     dev: true
 
   /jest-mock@29.7.0:
@@ -14271,7 +14353,7 @@ packages:
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     dependencies:
       '@jest/types': 29.6.3
-      '@types/node': 20.11.10
+      '@types/node': 20.11.17
       jest-util: 29.7.0
     dev: true
 
@@ -14326,7 +14408,7 @@ packages:
       '@jest/test-result': 29.7.0
       '@jest/transform': 29.7.0
       '@jest/types': 29.6.3
-      '@types/node': 20.11.10
+      '@types/node': 20.11.17
       chalk: 4.1.2
       emittery: 0.13.1
       graceful-fs: 4.2.11
@@ -14357,7 +14439,7 @@ packages:
       '@jest/test-result': 29.7.0
       '@jest/transform': 29.7.0
       '@jest/types': 29.6.3
-      '@types/node': 20.11.10
+      '@types/node': 20.11.17
       chalk: 4.1.2
       cjs-module-lexer: 1.2.2
       collect-v8-coverage: 1.0.1
@@ -14409,7 +14491,7 @@ packages:
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     dependencies:
       '@jest/types': 29.6.3
-      '@types/node': 20.11.10
+      '@types/node': 20.11.17
       chalk: 4.1.2
       ci-info: 3.7.1
       graceful-fs: 4.2.11
@@ -14434,7 +14516,7 @@ packages:
     dependencies:
       '@jest/test-result': 29.7.0
       '@jest/types': 29.6.3
-      '@types/node': 20.11.10
+      '@types/node': 20.11.17
       ansi-escapes: 4.3.2
       chalk: 4.1.2
       emittery: 0.13.1
@@ -14453,13 +14535,13 @@ packages:
     resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==}
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     dependencies:
-      '@types/node': 20.11.10
+      '@types/node': 20.11.17
       jest-util: 29.7.0
       merge-stream: 2.0.0
       supports-color: 8.1.1
     dev: true
 
-  /jest@29.7.0(@types/node@20.11.10):
+  /jest@29.7.0(@types/node@20.11.17):
     resolution: {integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==}
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     hasBin: true
@@ -14472,7 +14554,7 @@ packages:
       '@jest/core': 29.7.0
       '@jest/types': 29.6.3
       import-local: 3.1.0
-      jest-cli: 29.7.0(@types/node@20.11.10)
+      jest-cli: 29.7.0(@types/node@20.11.17)
     transitivePeerDependencies:
       - '@types/node'
       - babel-plugin-macros
@@ -15040,6 +15122,12 @@ packages:
     dependencies:
       '@jridgewell/sourcemap-codec': 1.4.15
 
+  /magic-string@0.30.7:
+    resolution: {integrity: sha512-8vBuFF/I/+OSLRmdf2wwFCJCz+nSn0m6DPvGH1fS/KiQoSaR+sETbov0eIk9KhEKy8CYqIkIAnbohxT/4H0kuA==}
+    engines: {node: '>=12'}
+    dependencies:
+      '@jridgewell/sourcemap-codec': 1.4.15
+
   /mailcheck@1.1.1:
     resolution: {integrity: sha512-3WjL8+ZDouZwKlyJBMp/4LeziLFXgleOdsYu87piGcMLqhBzCsy2QFdbtAwv757TFC/rtqd738fgJw1tFQCSgA==}
     dev: false
@@ -16995,8 +17083,8 @@ packages:
     hasBin: true
     dev: true
 
-  /prettier@3.2.4:
-    resolution: {integrity: sha512-FWu1oLHKCrtpO1ypU6J0SbK2d9Ckwysq6bHj/uaCP26DxrPpppCLQRGVuqAxSTvhF00AcvDRyYrLNW7ocBhFFQ==}
+  /prettier@3.2.5:
+    resolution: {integrity: sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==}
     engines: {node: '>=14'}
     hasBin: true
     dev: true
@@ -19805,7 +19893,7 @@ packages:
     resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==}
     hasBin: true
 
-  /v-code-diff@1.7.2(vue@3.4.15):
+  /v-code-diff@1.7.2(vue@3.4.18):
     resolution: {integrity: sha512-y+q8ZHf8GfphYLhcZbjAKcId/h6vZujS71Ryq5u+dI6Jg4ZLTdLrBNVSzYpHywHSSFFfBMdilm6XvVryEaH4+A==}
     requiresBuild: true
     peerDependencies:
@@ -19818,8 +19906,8 @@ packages:
       diff: 5.1.0
       diff-match-patch: 1.0.5
       highlight.js: 11.8.0
-      vue: 3.4.15(typescript@5.3.3)
-      vue-demi: 0.13.11(vue@3.4.15)
+      vue: 3.4.18(typescript@5.3.3)
+      vue-demi: 0.13.11(vue@3.4.18)
     dev: false
 
   /v8-to-istanbul@9.1.0:
@@ -19864,7 +19952,7 @@ packages:
       core-util-is: 1.0.2
       extsprintf: 1.3.0
 
-  /vite-node@0.34.6(@types/node@20.11.10)(sass@1.70.0)(terser@5.27.0):
+  /vite-node@0.34.6(@types/node@20.11.17)(sass@1.70.0)(terser@5.27.0):
     resolution: {integrity: sha512-nlBMJ9x6n7/Amaz6F3zJ97EBwR2FkzhBRxF5e+jE6LA3yi6Wtc2lyTij1OnDMIr34v5g/tVQtsVAzhT0jc5ygA==}
     engines: {node: '>=v14.18.0'}
     hasBin: true
@@ -19874,7 +19962,7 @@ packages:
       mlly: 1.5.0
       pathe: 1.1.2
       picocolors: 1.0.0
-      vite: 5.1.0(@types/node@20.11.10)(sass@1.70.0)(terser@5.27.0)
+      vite: 5.1.0(@types/node@20.11.17)(sass@1.70.0)(terser@5.27.0)
     transitivePeerDependencies:
       - '@types/node'
       - less
@@ -19890,7 +19978,7 @@ packages:
     resolution: {integrity: sha512-p4D8CFVhZS412SyQX125qxyzOgIFouwOcvjZWk6bQbNPR1wtaEzFT6jZxAjf1dejlGqa6fqHcuCvQea6EWUkUA==}
     dev: true
 
-  /vite@5.1.0(@types/node@20.11.10)(sass@1.70.0)(terser@5.27.0):
+  /vite@5.1.0(@types/node@20.11.17)(sass@1.70.0)(terser@5.27.0):
     resolution: {integrity: sha512-STmSFzhY4ljuhz14bg9LkMTk3d98IO6DIArnTY6MeBwiD1Za2StcQtz7fzOUnRCqrHSD5+OS2reg4HOz1eoLnw==}
     engines: {node: ^18.0.0 || >=20.0.0}
     hasBin: true
@@ -19918,7 +20006,7 @@ packages:
       terser:
         optional: true
     dependencies:
-      '@types/node': 20.11.10
+      '@types/node': 20.11.17
       esbuild: 0.19.11
       postcss: 8.4.35
       rollup: 4.9.6
@@ -19972,7 +20060,7 @@ packages:
     dependencies:
       '@types/chai': 4.3.11
       '@types/chai-subset': 1.3.5
-      '@types/node': 20.11.10
+      '@types/node': 20.11.17
       '@vitest/expect': 0.34.6
       '@vitest/runner': 0.34.6
       '@vitest/snapshot': 0.34.6
@@ -19992,8 +20080,8 @@ packages:
       strip-literal: 1.3.0
       tinybench: 2.6.0
       tinypool: 0.7.0
-      vite: 5.1.0(@types/node@20.11.10)(sass@1.70.0)(terser@5.27.0)
-      vite-node: 0.34.6(@types/node@20.11.10)(sass@1.70.0)(terser@5.27.0)
+      vite: 5.1.0(@types/node@20.11.17)(sass@1.70.0)(terser@5.27.0)
+      vite-node: 0.34.6(@types/node@20.11.17)(sass@1.70.0)(terser@5.27.0)
       why-is-node-running: 2.2.2
     transitivePeerDependencies:
       - less
@@ -20053,7 +20141,7 @@ packages:
     resolution: {integrity: sha512-6bnLkn8O0JJyiFSIF0EfCogzeqNXpnjJ0vW/SZzNHfe6sPx30lTtTXlE5TFs2qhJlAtDFybStVNpL73cPe3OMQ==}
     dev: true
 
-  /vue-demi@0.13.11(vue@3.4.15):
+  /vue-demi@0.13.11(vue@3.4.18):
     resolution: {integrity: sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==}
     engines: {node: '>=12'}
     hasBin: true
@@ -20065,23 +20153,23 @@ packages:
       '@vue/composition-api':
         optional: true
     dependencies:
-      vue: 3.4.15(typescript@5.3.3)
+      vue: 3.4.18(typescript@5.3.3)
     dev: false
 
-  /vue-docgen-api@4.64.1(vue@3.4.15):
+  /vue-docgen-api@4.64.1(vue@3.4.18):
     resolution: {integrity: sha512-jbOf7ByE3Zvtuk+429Jorl+eIeh2aB2Fx1GUo3xJd1aByJWE8KDlSEa6b11PB1ze8f0sRUBraRDinICCk0KY7g==}
     dependencies:
       '@babel/parser': 7.23.6
       '@babel/types': 7.23.5
       '@vue/compiler-dom': 3.4.15
-      '@vue/compiler-sfc': 3.4.15
+      '@vue/compiler-sfc': 3.4.18
       ast-types: 0.14.2
       hash-sum: 2.0.0
       lru-cache: 8.0.4
       pug: 3.0.2
       recast: 0.22.0
       ts-map: 1.0.3
-      vue-inbrowser-compiler-independent-utils: 4.64.1(vue@3.4.15)
+      vue-inbrowser-compiler-independent-utils: 4.64.1(vue@3.4.18)
     transitivePeerDependencies:
       - vue
     dev: true
@@ -20104,12 +20192,12 @@ packages:
       - supports-color
     dev: true
 
-  /vue-inbrowser-compiler-independent-utils@4.64.1(vue@3.4.15):
+  /vue-inbrowser-compiler-independent-utils@4.64.1(vue@3.4.18):
     resolution: {integrity: sha512-Hn32n07XZ8j9W8+fmOXPQL+i+W2e/8i6mkH4Ju3H6nR0+cfvmWM95GhczYi5B27+Y8JlCKgAo04IUiYce4mKAw==}
     peerDependencies:
       vue: '>=2'
     dependencies:
-      vue: 3.4.15(typescript@5.3.3)
+      vue: 3.4.18(typescript@5.3.3)
     dev: true
 
   /vue-template-compiler@2.7.14:
@@ -20131,28 +20219,28 @@ packages:
       typescript: 5.3.3
     dev: true
 
-  /vue@3.4.15(typescript@5.3.3):
-    resolution: {integrity: sha512-jC0GH4KkWLWJOEQjOpkqU1bQsBwf4R1rsFtw5GQJbjHVKWDzO6P0nWWBTmjp1xSemAioDFj1jdaK1qa3DnMQoQ==}
+  /vue@3.4.18(typescript@5.3.3):
+    resolution: {integrity: sha512-0zLRYamFRe0wF4q2L3O24KQzLyLpL64ye1RUToOgOxuWZsb/FhaNRdGmeozdtVYLz6tl94OXLaK7/WQIrVCw1A==}
     peerDependencies:
       typescript: '*'
     peerDependenciesMeta:
       typescript:
         optional: true
     dependencies:
-      '@vue/compiler-dom': 3.4.15
-      '@vue/compiler-sfc': 3.4.15
-      '@vue/runtime-dom': 3.4.15
-      '@vue/server-renderer': 3.4.15(vue@3.4.15)
-      '@vue/shared': 3.4.15
+      '@vue/compiler-dom': 3.4.18
+      '@vue/compiler-sfc': 3.4.18
+      '@vue/runtime-dom': 3.4.18
+      '@vue/server-renderer': 3.4.18(vue@3.4.18)
+      '@vue/shared': 3.4.18
       typescript: 5.3.3
 
-  /vuedraggable@4.1.0(vue@3.4.15):
+  /vuedraggable@4.1.0(vue@3.4.18):
     resolution: {integrity: sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==}
     peerDependencies:
       vue: ^3.0.1
     dependencies:
       sortablejs: 1.14.0
-      vue: 3.4.15(typescript@5.3.3)
+      vue: 3.4.18(typescript@5.3.3)
     dev: false
 
   /w3c-xmlserializer@5.0.0:
-- 
GitLab


From 639f14f7132840bfdcdb687dfaffe47c17ee6ade Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Fri, 9 Feb 2024 17:10:31 +0900
Subject: [PATCH 37/40] 2024.2.0-beta.11

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 1a38627361..3619366519 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "misskey",
-	"version": "2024.2.0-beta.10",
+	"version": "2024.2.0-beta.11",
 	"codename": "nasubi",
 	"repository": {
 		"type": "git",
-- 
GitLab


From 7a8cf274ca8a3a738f7bf7045ee7c97b22482d71 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?=
 <67428053+kakkokari-gtyih@users.noreply.github.com>
Date: Fri, 9 Feb 2024 18:08:08 +0900
Subject: [PATCH 38/40] fix misskey-js version

---
 packages/misskey-js/package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/misskey-js/package.json b/packages/misskey-js/package.json
index bc3001fc05..26b682f1c4 100644
--- a/packages/misskey-js/package.json
+++ b/packages/misskey-js/package.json
@@ -1,7 +1,7 @@
 {
 	"type": "module",
 	"name": "misskey-js",
-	"version": "2024.2.0-beta.10",
+	"version": "2024.2.0-beta.11",
 	"description": "Misskey SDK for JavaScript",
 	"types": "./built/dts/index.d.ts",
 	"exports": {
-- 
GitLab


From 48c1f94dc5ad6926bbf49d8d3b3a3e07878012b4 Mon Sep 17 00:00:00 2001
From: Marie <marie@kaifa.ch>
Date: Fri, 9 Feb 2024 19:39:58 +0100
Subject: [PATCH 39/40] fix: downgrade vue

---
 packages/frontend/package.json |   8 +-
 pnpm-lock.yaml                 | 271 +++++++++++++++------------------
 2 files changed, 128 insertions(+), 151 deletions(-)

diff --git a/packages/frontend/package.json b/packages/frontend/package.json
index 22553dc687..f5c968ec3c 100644
--- a/packages/frontend/package.json
+++ b/packages/frontend/package.json
@@ -29,7 +29,7 @@
 		"@phosphor-icons/web": "^2.0.3",
 		"@twemoji/parser": "15.0.0",
 		"@vitejs/plugin-vue": "5.0.3",
-		"@vue/compiler-sfc": "3.4.18",
+		"@vue/compiler-sfc": "3.4.15",
 		"aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.2",
 		"astring": "1.8.6",
 		"broadcast-channel": "7.0.0",
@@ -73,7 +73,7 @@
 		"uuid": "9.0.1",
 		"v-code-diff": "1.7.2",
 		"vite": "5.1.0",
-		"vue": "3.4.18",
+		"vue": "3.4.15",
 		"vuedraggable": "next"
 	},
 	"devDependencies": {
@@ -97,7 +97,7 @@
 		"@storybook/types": "7.6.10",
 		"@storybook/vue3": "7.6.10",
 		"@storybook/vue3-vite": "7.6.10",
-		"@testing-library/vue": "8.0.2",
+		"@testing-library/vue": "8.0.1",
 		"@types/escape-regexp": "0.0.3",
 		"@types/estree": "1.0.5",
 		"@types/matter-js": "0.19.6",
@@ -112,7 +112,7 @@
 		"@typescript-eslint/eslint-plugin": "6.18.1",
 		"@typescript-eslint/parser": "6.18.1",
 		"@vitest/coverage-v8": "0.34.6",
-		"@vue/runtime-core": "3.4.18",
+		"@vue/runtime-core": "3.4.15",
 		"acorn": "8.11.3",
 		"cross-env": "7.0.3",
 		"cypress": "13.6.4",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 7f8bc28742..2c15690f93 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -707,10 +707,10 @@ importers:
         version: 15.0.0
       '@vitejs/plugin-vue':
         specifier: 5.0.3
-        version: 5.0.3(vite@5.1.0)(vue@3.4.18)
+        version: 5.0.3(vite@5.1.0)(vue@3.4.15)
       '@vue/compiler-sfc':
-        specifier: 3.4.18
-        version: 3.4.18
+        specifier: 3.4.15
+        version: 3.4.15
       aiscript-vscode:
         specifier: github:aiscript-dev/aiscript-vscode#v0.1.2
         version: github.com/aiscript-dev/aiscript-vscode/793211d40243c8775f6b85f015c221c82cbffb07
@@ -836,16 +836,16 @@ importers:
         version: 9.0.1
       v-code-diff:
         specifier: 1.7.2
-        version: 1.7.2(vue@3.4.18)
+        version: 1.7.2(vue@3.4.15)
       vite:
         specifier: 5.1.0
         version: 5.1.0(@types/node@20.11.17)(sass@1.70.0)(terser@5.27.0)
       vue:
-        specifier: 3.4.18
-        version: 3.4.18(typescript@5.3.3)
+        specifier: 3.4.15
+        version: 3.4.15(typescript@5.3.3)
       vuedraggable:
         specifier: next
-        version: 4.1.0(vue@3.4.18)
+        version: 4.1.0(vue@3.4.15)
     devDependencies:
       '@misskey-dev/eslint-plugin':
         specifier: 1.0.0
@@ -903,13 +903,13 @@ importers:
         version: 7.6.10
       '@storybook/vue3':
         specifier: 7.6.10
-        version: 7.6.10(vue@3.4.18)
+        version: 7.6.10(vue@3.4.15)
       '@storybook/vue3-vite':
         specifier: 7.6.10
-        version: 7.6.10(typescript@5.3.3)(vite@5.1.0)(vue@3.4.18)
+        version: 7.6.10(typescript@5.3.3)(vite@5.1.0)(vue@3.4.15)
       '@testing-library/vue':
-        specifier: 8.0.2
-        version: 8.0.2(@vue/compiler-sfc@3.4.18)(vue@3.4.18)
+        specifier: 8.0.1
+        version: 8.0.1(@vue/compiler-sfc@3.4.15)(vue@3.4.15)
       '@types/escape-regexp':
         specifier: 0.0.3
         version: 0.0.3
@@ -953,8 +953,8 @@ importers:
         specifier: 0.34.6
         version: 0.34.6(vitest@0.34.6)
       '@vue/runtime-core':
-        specifier: 3.4.18
-        version: 3.4.18
+        specifier: 3.4.15
+        version: 3.4.15
       acorn:
         specifier: 8.11.3
         version: 8.11.3
@@ -1993,7 +1993,7 @@ packages:
       '@babel/traverse': 7.23.4
       '@babel/types': 7.23.4
       convert-source-map: 2.0.0
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       gensync: 1.0.0-beta.2
       json5: 2.2.3
       semver: 6.3.1
@@ -2085,7 +2085,7 @@ packages:
       '@babel/core': 7.23.3
       '@babel/helper-compilation-targets': 7.23.6
       '@babel/helper-plugin-utils': 7.22.5
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       lodash.debounce: 4.0.8
       resolve: 1.22.8
     transitivePeerDependencies:
@@ -3296,7 +3296,7 @@ packages:
       '@babel/helper-split-export-declaration': 7.22.6
       '@babel/parser': 7.23.4
       '@babel/types': 7.23.4
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       globals: 11.12.0
     transitivePeerDependencies:
       - supports-color
@@ -3998,7 +3998,7 @@ packages:
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
     dependencies:
       ajv: 6.12.6
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       espree: 9.6.1
       globals: 13.23.0
       ignore: 5.3.0
@@ -4015,7 +4015,7 @@ packages:
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
     dependencies:
       ajv: 6.12.6
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       espree: 9.6.1
       globals: 13.23.0
       ignore: 5.3.0
@@ -4256,7 +4256,7 @@ packages:
     engines: {node: '>=10.10.0'}
     dependencies:
       '@humanwhocodes/object-schema': 2.0.1
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       minimatch: 3.1.2
     transitivePeerDependencies:
       - supports-color
@@ -7095,7 +7095,7 @@ packages:
       file-system-cache: 2.3.0
     dev: true
 
-  /@storybook/vue3-vite@7.6.10(typescript@5.3.3)(vite@5.1.0)(vue@3.4.18):
+  /@storybook/vue3-vite@7.6.10(typescript@5.3.3)(vite@5.1.0)(vue@3.4.15):
     resolution: {integrity: sha512-5f0Rh4PTVEeAI86ybihfN+rHGXXLNiRsoGKinpJSb7hkfsq/L7u3sVCXJwH/qsG+rUJlZyHs3kfa4/Kgyyi3Mg==}
     engines: {node: ^14.18 || >=16}
     peerDependencies:
@@ -7103,11 +7103,11 @@ packages:
     dependencies:
       '@storybook/builder-vite': 7.6.10(typescript@5.3.3)(vite@5.1.0)
       '@storybook/core-server': 7.6.10
-      '@storybook/vue3': 7.6.10(vue@3.4.18)
-      '@vitejs/plugin-vue': 4.5.2(vite@5.1.0)(vue@3.4.18)
+      '@storybook/vue3': 7.6.10(vue@3.4.15)
+      '@vitejs/plugin-vue': 4.5.2(vite@5.1.0)(vue@3.4.15)
       magic-string: 0.30.5
       vite: 5.1.0(@types/node@20.11.17)(sass@1.70.0)(terser@5.27.0)
-      vue-docgen-api: 4.64.1(vue@3.4.18)
+      vue-docgen-api: 4.64.1(vue@3.4.15)
     transitivePeerDependencies:
       - '@preact/preset-vite'
       - bufferutil
@@ -7119,7 +7119,7 @@ packages:
       - vue
     dev: true
 
-  /@storybook/vue3@7.6.10(vue@3.4.18):
+  /@storybook/vue3@7.6.10(vue@3.4.15):
     resolution: {integrity: sha512-FeZ9zjuudQgCdKPs2K8sU6TgEyrMjKrCN3e8+XXX5CAMSwLDV8IfexaaMF0ehYW6Wp0dgCIm0cVNBV3u8vtRRw==}
     engines: {node: '>=16.0.0'}
     peerDependencies:
@@ -7134,7 +7134,7 @@ packages:
       lodash: 4.17.21
       ts-dedent: 2.2.0
       type-fest: 2.19.0
-      vue: 3.4.18(typescript@5.3.3)
+      vue: 3.4.15(typescript@5.3.3)
       vue-component-type-helpers: 1.8.27
     transitivePeerDependencies:
       - encoding
@@ -7624,21 +7624,18 @@ packages:
       '@testing-library/dom': 9.2.0
     dev: true
 
-  /@testing-library/vue@8.0.2(@vue/compiler-sfc@3.4.18)(vue@3.4.18):
-    resolution: {integrity: sha512-A8wWX+qQn0o0izpQWnGCpwQt8wAdpsVP8vPP2h5Q/jcGhZ5yKXz9PPUqhQv+45LTFaWlyRf8bArTVaB/KFFd5A==}
+  /@testing-library/vue@8.0.1(@vue/compiler-sfc@3.4.15)(vue@3.4.15):
+    resolution: {integrity: sha512-l51ZEpjTQ6glq3wM+asQ1GbKJMGcxwgHEygETx0aCRN4TjFEGvMZy4YdWKs/y7bu4bmLrxcxhbEPP7iPSW/2OQ==}
     engines: {node: '>=14'}
     peerDependencies:
       '@vue/compiler-sfc': '>= 3'
       vue: '>= 3'
-    peerDependenciesMeta:
-      '@vue/compiler-sfc':
-        optional: true
     dependencies:
       '@babel/runtime': 7.23.4
       '@testing-library/dom': 9.3.3
-      '@vue/compiler-sfc': 3.4.18
-      '@vue/test-utils': 2.4.1(vue@3.4.18)
-      vue: 3.4.18(typescript@5.3.3)
+      '@vue/compiler-sfc': 3.4.15
+      '@vue/test-utils': 2.4.1(vue@3.4.15)
+      vue: 3.4.15(typescript@5.3.3)
     transitivePeerDependencies:
       - '@vue/server-renderer'
     dev: true
@@ -8315,7 +8312,7 @@ packages:
       '@typescript-eslint/type-utils': 6.11.0(eslint@8.53.0)(typescript@5.3.3)
       '@typescript-eslint/utils': 6.11.0(eslint@8.53.0)(typescript@5.3.3)
       '@typescript-eslint/visitor-keys': 6.11.0
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       eslint: 8.53.0
       graphemer: 1.4.0
       ignore: 5.3.0
@@ -8344,7 +8341,7 @@ packages:
       '@typescript-eslint/type-utils': 6.12.0(eslint@8.54.0)(typescript@5.1.6)
       '@typescript-eslint/utils': 6.12.0(eslint@8.54.0)(typescript@5.1.6)
       '@typescript-eslint/visitor-keys': 6.12.0
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       eslint: 8.54.0
       graphemer: 1.4.0
       ignore: 5.3.0
@@ -8373,7 +8370,7 @@ packages:
       '@typescript-eslint/type-utils': 6.18.1(eslint@8.56.0)(typescript@5.3.3)
       '@typescript-eslint/utils': 6.18.1(eslint@8.56.0)(typescript@5.3.3)
       '@typescript-eslint/visitor-keys': 6.18.1
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       eslint: 8.56.0
       graphemer: 1.4.0
       ignore: 5.3.0
@@ -8399,7 +8396,7 @@ packages:
       '@typescript-eslint/types': 6.11.0
       '@typescript-eslint/typescript-estree': 6.11.0(typescript@5.3.3)
       '@typescript-eslint/visitor-keys': 6.11.0
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       eslint: 8.53.0
       typescript: 5.3.3
     transitivePeerDependencies:
@@ -8420,7 +8417,7 @@ packages:
       '@typescript-eslint/types': 6.12.0
       '@typescript-eslint/typescript-estree': 6.12.0(typescript@5.1.6)
       '@typescript-eslint/visitor-keys': 6.12.0
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       eslint: 8.54.0
       typescript: 5.1.6
     transitivePeerDependencies:
@@ -8441,7 +8438,7 @@ packages:
       '@typescript-eslint/types': 6.18.1
       '@typescript-eslint/typescript-estree': 6.18.1(typescript@5.3.3)
       '@typescript-eslint/visitor-keys': 6.18.1
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       eslint: 8.56.0
       typescript: 5.3.3
     transitivePeerDependencies:
@@ -8484,7 +8481,7 @@ packages:
     dependencies:
       '@typescript-eslint/typescript-estree': 6.11.0(typescript@5.3.3)
       '@typescript-eslint/utils': 6.11.0(eslint@8.53.0)(typescript@5.3.3)
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       eslint: 8.53.0
       ts-api-utils: 1.0.3(typescript@5.3.3)
       typescript: 5.3.3
@@ -8504,7 +8501,7 @@ packages:
     dependencies:
       '@typescript-eslint/typescript-estree': 6.12.0(typescript@5.1.6)
       '@typescript-eslint/utils': 6.12.0(eslint@8.54.0)(typescript@5.1.6)
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       eslint: 8.54.0
       ts-api-utils: 1.0.3(typescript@5.1.6)
       typescript: 5.1.6
@@ -8524,7 +8521,7 @@ packages:
     dependencies:
       '@typescript-eslint/typescript-estree': 6.18.1(typescript@5.3.3)
       '@typescript-eslint/utils': 6.18.1(eslint@8.56.0)(typescript@5.3.3)
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       eslint: 8.56.0
       ts-api-utils: 1.0.3(typescript@5.3.3)
       typescript: 5.3.3
@@ -8558,7 +8555,7 @@ packages:
     dependencies:
       '@typescript-eslint/types': 6.11.0
       '@typescript-eslint/visitor-keys': 6.11.0
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       globby: 11.1.0
       is-glob: 4.0.3
       semver: 7.5.4
@@ -8579,7 +8576,7 @@ packages:
     dependencies:
       '@typescript-eslint/types': 6.12.0
       '@typescript-eslint/visitor-keys': 6.12.0
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       globby: 11.1.0
       is-glob: 4.0.3
       semver: 7.5.4
@@ -8600,7 +8597,7 @@ packages:
     dependencies:
       '@typescript-eslint/types': 6.18.1
       '@typescript-eslint/visitor-keys': 6.18.1
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       globby: 11.1.0
       is-glob: 4.0.3
       minimatch: 9.0.3
@@ -8712,7 +8709,7 @@ packages:
       - supports-color
     dev: true
 
-  /@vitejs/plugin-vue@4.5.2(vite@5.1.0)(vue@3.4.18):
+  /@vitejs/plugin-vue@4.5.2(vite@5.1.0)(vue@3.4.15):
     resolution: {integrity: sha512-UGR3DlzLi/SaVBPX0cnSyE37vqxU3O6chn8l0HJNzQzDia6/Au2A4xKv+iIJW8w2daf80G7TYHhi1pAUjdZ0bQ==}
     engines: {node: ^14.18.0 || >=16.0.0}
     peerDependencies:
@@ -8720,10 +8717,10 @@ packages:
       vue: ^3.2.25
     dependencies:
       vite: 5.1.0(@types/node@20.11.17)(sass@1.70.0)(terser@5.27.0)
-      vue: 3.4.18(typescript@5.3.3)
+      vue: 3.4.15(typescript@5.3.3)
     dev: true
 
-  /@vitejs/plugin-vue@5.0.3(vite@5.1.0)(vue@3.4.18):
+  /@vitejs/plugin-vue@5.0.3(vite@5.1.0)(vue@3.4.15):
     resolution: {integrity: sha512-b8S5dVS40rgHdDrw+DQi/xOM9ed+kSRZzfm1T74bMmBDCd8XO87NKlFYInzCtwvtWwXZvo1QxE2OSspTATWrbA==}
     engines: {node: ^18.0.0 || >=20.0.0}
     peerDependencies:
@@ -8731,7 +8728,7 @@ packages:
       vue: ^3.2.25
     dependencies:
       vite: 5.1.0(@types/node@20.11.17)(sass@1.70.0)(terser@5.27.0)
-      vue: 3.4.18(typescript@5.3.3)
+      vue: 3.4.15(typescript@5.3.3)
     dev: false
 
   /@vitest/coverage-v8@0.34.6(vitest@0.34.6):
@@ -8812,24 +8809,14 @@ packages:
       path-browserify: 1.0.1
     dev: true
 
-  /@vue/compiler-core@3.3.12:
-    resolution: {integrity: sha512-qAtjyG3GBLG0chzp5xGCyRLLe6wFCHmjI82aGzwuGKyznNP+GJJMxjc0wOYWDB2YKfho7niJFdoFpo0CZZQg9w==}
-    dependencies:
-      '@babel/parser': 7.23.6
-      '@vue/shared': 3.3.12
-      estree-walker: 2.0.2
-      source-map-js: 1.0.2
-    dev: true
-
   /@vue/compiler-core@3.4.15:
     resolution: {integrity: sha512-XcJQVOaxTKCnth1vCxEChteGuwG6wqnUHxAm1DO3gCz0+uXKaJNx8/digSz4dLALCy8n2lKq24jSUs8segoqIw==}
     dependencies:
-      '@babel/parser': 7.23.6
+      '@babel/parser': 7.23.9
       '@vue/shared': 3.4.15
       entities: 4.5.0
       estree-walker: 2.0.2
       source-map-js: 1.0.2
-    dev: true
 
   /@vue/compiler-core@3.4.18:
     resolution: {integrity: sha512-F7YK8lMK0iv6b9/Gdk15A67wM0KKZvxDxed0RR60C1z9tIJTKta+urs4j0RTN5XqHISzI3etN3mX0uHhjmoqjQ==}
@@ -8839,6 +8826,7 @@ packages:
       entities: 4.5.0
       estree-walker: 2.0.2
       source-map-js: 1.0.2
+    dev: true
 
   /@vue/compiler-core@3.4.3:
     resolution: {integrity: sha512-u8jzgFg0EDtSrb/hG53Wwh1bAOQFtc1ZCegBpA/glyvTlgHl+tq13o1zvRfLbegYUw/E4mSTGOiCnAJ9SJ+lsg==}
@@ -8850,44 +8838,37 @@ packages:
       source-map-js: 1.0.2
     dev: true
 
-  /@vue/compiler-dom@3.3.12:
-    resolution: {integrity: sha512-RdJU9oEYaoPKUdGXCy0l+i4clesdDeLmbvRlszoc9iagsnBnMmQtYfCPVQ5BHB6o7K4SCucDdJM2Dh3oXB0D6g==}
-    dependencies:
-      '@vue/compiler-core': 3.3.12
-      '@vue/shared': 3.3.12
-    dev: true
-
   /@vue/compiler-dom@3.4.15:
     resolution: {integrity: sha512-wox0aasVV74zoXyblarOM3AZQz/Z+OunYcIHe1OsGclCHt8RsRm04DObjefaI82u6XDzv+qGWZ24tIsRAIi5MQ==}
     dependencies:
       '@vue/compiler-core': 3.4.15
       '@vue/shared': 3.4.15
-    dev: true
 
   /@vue/compiler-dom@3.4.18:
     resolution: {integrity: sha512-24Eb8lcMfInefvQ6YlEVS18w5Q66f4+uXWVA+yb7praKbyjHRNuKVWGuinfSSjM0ZIiPi++QWukhkgznBaqpEA==}
     dependencies:
       '@vue/compiler-core': 3.4.18
       '@vue/shared': 3.4.18
+    dev: true
 
-  /@vue/compiler-sfc@3.4.18:
-    resolution: {integrity: sha512-rG5tqtnzwrVpMqAQ7FHtvHaV70G6LLfJIWLYZB/jZ9m/hrnZmIQh+H3ewnC5onwe/ibljm9+ZupxeElzqCkTAw==}
+  /@vue/compiler-sfc@3.4.15:
+    resolution: {integrity: sha512-LCn5M6QpkpFsh3GQvs2mJUOAlBQcCco8D60Bcqmf3O3w5a+KWS5GvYbrrJBkgvL1BDnTp+e8q0lXCLgHhKguBA==}
     dependencies:
       '@babel/parser': 7.23.9
-      '@vue/compiler-core': 3.4.18
-      '@vue/compiler-dom': 3.4.18
-      '@vue/compiler-ssr': 3.4.18
-      '@vue/shared': 3.4.18
+      '@vue/compiler-core': 3.4.15
+      '@vue/compiler-dom': 3.4.15
+      '@vue/compiler-ssr': 3.4.15
+      '@vue/shared': 3.4.15
       estree-walker: 2.0.2
       magic-string: 0.30.7
       postcss: 8.4.33
       source-map-js: 1.0.2
 
-  /@vue/compiler-ssr@3.4.18:
-    resolution: {integrity: sha512-hSlv20oUhPxo2UYUacHgGaxtqP0tvFo6ixxxD6JlXIkwzwoZ9eKK6PFQN4hNK/R13JlNyldwWt/fqGBKgWJ6nQ==}
+  /@vue/compiler-ssr@3.4.15:
+    resolution: {integrity: sha512-1jdeQyiGznr8gjFDadVmOJqZiLNSsMa5ZgqavkPZ8O2wjHv0tVuAEsw5hTdUoUW4232vpBbL/wJhzVW/JwY1Uw==}
     dependencies:
-      '@vue/compiler-dom': 3.4.18
-      '@vue/shared': 3.4.18
+      '@vue/compiler-dom': 3.4.15
+      '@vue/shared': 3.4.15
 
   /@vue/language-core@1.8.27(typescript@5.3.3):
     resolution: {integrity: sha512-L8Kc27VdQserNaCUNiSFdDl9LWT24ly8Hpwf1ECy3aFb9m6bDhBGQYOujDm21N7EW3moKIOKEanQwe1q5BK+mA==}
@@ -8899,8 +8880,8 @@ packages:
     dependencies:
       '@volar/language-core': 1.11.1
       '@volar/source-map': 1.11.1
-      '@vue/compiler-dom': 3.3.12
-      '@vue/shared': 3.3.12
+      '@vue/compiler-dom': 3.4.18
+      '@vue/shared': 3.4.18
       computeds: 0.0.1
       minimatch: 9.0.3
       muggle-string: 0.3.1
@@ -8909,49 +8890,45 @@ packages:
       vue-template-compiler: 2.7.14
     dev: true
 
-  /@vue/reactivity@3.4.18:
-    resolution: {integrity: sha512-7uda2/I0jpLiRygprDo5Jxs2HJkOVXcOMlyVlY54yRLxoycBpwGJRwJT9EdGB4adnoqJDXVT2BilUAYwI7qvmg==}
+  /@vue/reactivity@3.4.15:
+    resolution: {integrity: sha512-55yJh2bsff20K5O84MxSvXKPHHt17I2EomHznvFiJCAZpJTNW8IuLj1xZWMLELRhBK3kkFV/1ErZGHJfah7i7w==}
     dependencies:
-      '@vue/shared': 3.4.18
+      '@vue/shared': 3.4.15
 
-  /@vue/runtime-core@3.4.18:
-    resolution: {integrity: sha512-7mU9diCa+4e+8/wZ7Udw5pwTH10A11sZ1nldmHOUKJnzCwvZxfJqAtw31mIf4T5H2FsLCSBQT3xgioA9vIjyDQ==}
+  /@vue/runtime-core@3.4.15:
+    resolution: {integrity: sha512-6E3by5m6v1AkW0McCeAyhHTw+3y17YCOKG0U0HDKDscV4Hs0kgNT5G+GCHak16jKgcCDHpI9xe5NKb8sdLCLdw==}
     dependencies:
-      '@vue/reactivity': 3.4.18
-      '@vue/shared': 3.4.18
+      '@vue/reactivity': 3.4.15
+      '@vue/shared': 3.4.15
 
-  /@vue/runtime-dom@3.4.18:
-    resolution: {integrity: sha512-2y1Mkzcw1niSfG7z3Qx+2ir9Gb4hdTkZe5p/I8x1aTIKQE0vY0tPAEUPhZm5tx6183gG3D/KwHG728UR0sIufA==}
+  /@vue/runtime-dom@3.4.15:
+    resolution: {integrity: sha512-EVW8D6vfFVq3V/yDKNPBFkZKGMFSvZrUQmx196o/v2tHKdwWdiZjYUBS+0Ez3+ohRyF8Njwy/6FH5gYJ75liUw==}
     dependencies:
-      '@vue/runtime-core': 3.4.18
-      '@vue/shared': 3.4.18
+      '@vue/runtime-core': 3.4.15
+      '@vue/shared': 3.4.15
       csstype: 3.1.3
 
-  /@vue/server-renderer@3.4.18(vue@3.4.18):
-    resolution: {integrity: sha512-YJd1wa7mzUN3NRqLEsrwEYWyO+PUBSROIGlCc3J/cvn7Zu6CxhNLgXa8Z4zZ5ja5/nviYO79J1InoPeXgwBTZA==}
+  /@vue/server-renderer@3.4.15(vue@3.4.15):
+    resolution: {integrity: sha512-3HYzaidu9cHjrT+qGUuDhFYvF/j643bHC6uUN9BgM11DVy+pM6ATsG6uPBLnkwOgs7BpJABReLmpL3ZPAsUaqw==}
     peerDependencies:
-      vue: 3.4.18
+      vue: 3.4.15
     dependencies:
-      '@vue/compiler-ssr': 3.4.18
-      '@vue/shared': 3.4.18
-      vue: 3.4.18(typescript@5.3.3)
-
-  /@vue/shared@3.3.12:
-    resolution: {integrity: sha512-6p0Yin0pclvnER7BLNOQuod9Z+cxSYh8pSh7CzHnWNjAIP6zrTlCdHRvSCb1aYEx6i3Q3kvfuWU7nG16CgG1ag==}
-    dev: true
+      '@vue/compiler-ssr': 3.4.15
+      '@vue/shared': 3.4.15
+      vue: 3.4.15(typescript@5.3.3)
 
   /@vue/shared@3.4.15:
     resolution: {integrity: sha512-KzfPTxVaWfB+eGcGdbSf4CWdaXcGDqckoeXUh7SB3fZdEtzPCK2Vq9B/lRRL3yutax/LWITz+SwvgyOxz5V75g==}
-    dev: true
 
   /@vue/shared@3.4.18:
     resolution: {integrity: sha512-CxouGFxxaW5r1WbrSmWwck3No58rApXgRSBxrqgnY1K+jk20F6DrXJkHdH9n4HVT+/B6G2CAn213Uq3npWiy8Q==}
+    dev: true
 
   /@vue/shared@3.4.3:
     resolution: {integrity: sha512-rIwlkkP1n4uKrRzivAKPZIEkHiuwY5mmhMJ2nZKCBLz8lTUlE73rQh4n1OnnMurXt1vcUNyH4ZPfdh8QweTjpQ==}
     dev: true
 
-  /@vue/test-utils@2.4.1(vue@3.4.18):
+  /@vue/test-utils@2.4.1(vue@3.4.15):
     resolution: {integrity: sha512-VO8nragneNzUZUah6kOjiFmD/gwRjUauG9DROh6oaOeFwX1cZRUNHhdeogE8635cISigXFTtGLUQWx5KCb0xeg==}
     peerDependencies:
       '@vue/server-renderer': ^3.0.1
@@ -8961,7 +8938,7 @@ packages:
         optional: true
     dependencies:
       js-beautify: 1.14.9
-      vue: 3.4.18(typescript@5.3.3)
+      vue: 3.4.15(typescript@5.3.3)
       vue-component-type-helpers: 1.8.4
     dev: true
 
@@ -9067,7 +9044,7 @@ packages:
     resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
     engines: {node: '>= 6.0.0'}
     dependencies:
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
     transitivePeerDependencies:
       - supports-color
 
@@ -9075,7 +9052,7 @@ packages:
     resolution: {integrity: sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==}
     engines: {node: '>= 14'}
     dependencies:
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
     transitivePeerDependencies:
       - supports-color
     dev: false
@@ -9471,7 +9448,7 @@ packages:
     resolution: {integrity: sha512-TAlMYvOuwGyLK3PfBb5WKBXZmXz2fVCgv23d6zZFdle/q3gPjmxBaeuC0pY0Dzs5PWMSgfqqEZkrye19GlDTgw==}
     dependencies:
       archy: 1.0.0
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       fastq: 1.15.0
     transitivePeerDependencies:
       - supports-color
@@ -10985,7 +10962,6 @@ packages:
     dependencies:
       ms: 2.1.2
       supports-color: 5.5.0
-    dev: true
 
   /debug@4.3.4(supports-color@8.1.1):
     resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
@@ -10998,6 +10974,7 @@ packages:
     dependencies:
       ms: 2.1.2
       supports-color: 8.1.1
+    dev: true
 
   /decamelize-keys@1.1.1:
     resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==}
@@ -11210,7 +11187,7 @@ packages:
     hasBin: true
     dependencies:
       address: 1.2.2
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
     transitivePeerDependencies:
       - supports-color
     dev: true
@@ -11527,7 +11504,7 @@ packages:
     peerDependencies:
       esbuild: '>=0.12 <1'
     dependencies:
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       esbuild: 0.18.20
     transitivePeerDependencies:
       - supports-color
@@ -11843,7 +11820,7 @@ packages:
       ajv: 6.12.6
       chalk: 4.1.2
       cross-spawn: 7.0.3
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       doctrine: 3.0.0
       escape-string-regexp: 4.0.0
       eslint-scope: 7.2.2
@@ -11890,7 +11867,7 @@ packages:
       ajv: 6.12.6
       chalk: 4.1.2
       cross-spawn: 7.0.3
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       doctrine: 3.0.0
       escape-string-regexp: 4.0.0
       eslint-scope: 7.2.2
@@ -11937,7 +11914,7 @@ packages:
       ajv: 6.12.6
       chalk: 4.1.2
       cross-spawn: 7.0.3
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       doctrine: 3.0.0
       escape-string-regexp: 4.0.0
       eslint-scope: 7.2.2
@@ -12589,7 +12566,7 @@ packages:
       debug:
         optional: true
     dependencies:
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
 
   /for-each@0.3.3:
     resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==}
@@ -13238,7 +13215,7 @@ packages:
     engines: {node: '>= 14'}
     dependencies:
       agent-base: 7.1.0
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
     transitivePeerDependencies:
       - supports-color
     dev: false
@@ -13277,7 +13254,7 @@ packages:
     engines: {node: '>= 6.0.0'}
     dependencies:
       agent-base: 5.1.1
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
     transitivePeerDependencies:
       - supports-color
     dev: true
@@ -13287,7 +13264,7 @@ packages:
     engines: {node: '>= 6'}
     dependencies:
       agent-base: 6.0.2
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
     transitivePeerDependencies:
       - supports-color
 
@@ -13296,7 +13273,7 @@ packages:
     engines: {node: '>= 14'}
     dependencies:
       agent-base: 7.1.0
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
     transitivePeerDependencies:
       - supports-color
     dev: false
@@ -13456,7 +13433,7 @@ packages:
     dependencies:
       '@ioredis/commands': 1.2.0
       cluster-key-slot: 1.1.2
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       denque: 2.1.0
       lodash.defaults: 4.2.0
       lodash.isarguments: 3.1.0
@@ -13880,7 +13857,7 @@ packages:
     resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==}
     engines: {node: '>=10'}
     dependencies:
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       istanbul-lib-coverage: 3.2.2
       source-map: 0.6.1
     transitivePeerDependencies:
@@ -17115,7 +17092,7 @@ packages:
     engines: {node: '>=8.16.0'}
     dependencies:
       '@types/mime-types': 2.1.4
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       extract-zip: 1.7.0
       https-proxy-agent: 4.0.0
       mime: 2.6.0
@@ -18084,7 +18061,7 @@ packages:
     dependencies:
       '@hapi/hoek': 10.0.1
       '@hapi/wreck': 18.0.1
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       joi: 17.7.0
     transitivePeerDependencies:
       - supports-color
@@ -18284,7 +18261,7 @@ packages:
     engines: {node: '>= 14'}
     dependencies:
       agent-base: 7.1.0
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       socks: 2.7.1
     transitivePeerDependencies:
       - supports-color
@@ -18437,7 +18414,7 @@ packages:
       arg: 5.0.2
       bluebird: 3.7.2
       check-more-types: 2.24.0
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       execa: 5.1.1
       lazy-ass: 1.6.0
       ps-tree: 1.2.0
@@ -19342,7 +19319,7 @@ packages:
       chalk: 4.1.2
       cli-highlight: 2.1.11
       dayjs: 1.11.10
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       dotenv: 16.0.3
       glob: 10.3.10
       ioredis: 5.3.2
@@ -19626,7 +19603,7 @@ packages:
     resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==}
     hasBin: true
 
-  /v-code-diff@1.7.2(vue@3.4.18):
+  /v-code-diff@1.7.2(vue@3.4.15):
     resolution: {integrity: sha512-y+q8ZHf8GfphYLhcZbjAKcId/h6vZujS71Ryq5u+dI6Jg4ZLTdLrBNVSzYpHywHSSFFfBMdilm6XvVryEaH4+A==}
     requiresBuild: true
     peerDependencies:
@@ -19639,8 +19616,8 @@ packages:
       diff: 5.1.0
       diff-match-patch: 1.0.5
       highlight.js: 11.8.0
-      vue: 3.4.18(typescript@5.3.3)
-      vue-demi: 0.13.11(vue@3.4.18)
+      vue: 3.4.15(typescript@5.3.3)
+      vue-demi: 0.13.11(vue@3.4.15)
     dev: false
 
   /v8-to-istanbul@9.2.0:
@@ -19682,7 +19659,7 @@ packages:
     hasBin: true
     dependencies:
       cac: 6.7.14
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       mlly: 1.5.0
       pathe: 1.1.2
       picocolors: 1.0.0
@@ -19794,7 +19771,7 @@ packages:
       acorn-walk: 8.3.2
       cac: 6.7.14
       chai: 4.3.10
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       happy-dom: 10.0.3
       local-pkg: 0.4.3
       magic-string: 0.30.5
@@ -19873,7 +19850,7 @@ packages:
     resolution: {integrity: sha512-6bnLkn8O0JJyiFSIF0EfCogzeqNXpnjJ0vW/SZzNHfe6sPx30lTtTXlE5TFs2qhJlAtDFybStVNpL73cPe3OMQ==}
     dev: true
 
-  /vue-demi@0.13.11(vue@3.4.18):
+  /vue-demi@0.13.11(vue@3.4.15):
     resolution: {integrity: sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==}
     engines: {node: '>=12'}
     hasBin: true
@@ -19885,23 +19862,23 @@ packages:
       '@vue/composition-api':
         optional: true
     dependencies:
-      vue: 3.4.18(typescript@5.3.3)
+      vue: 3.4.15(typescript@5.3.3)
     dev: false
 
-  /vue-docgen-api@4.64.1(vue@3.4.18):
+  /vue-docgen-api@4.64.1(vue@3.4.15):
     resolution: {integrity: sha512-jbOf7ByE3Zvtuk+429Jorl+eIeh2aB2Fx1GUo3xJd1aByJWE8KDlSEa6b11PB1ze8f0sRUBraRDinICCk0KY7g==}
     dependencies:
       '@babel/parser': 7.23.6
       '@babel/types': 7.23.4
       '@vue/compiler-dom': 3.4.15
-      '@vue/compiler-sfc': 3.4.18
+      '@vue/compiler-sfc': 3.4.15
       ast-types: 0.14.2
       hash-sum: 2.0.0
       lru-cache: 8.0.4
       pug: 3.0.2
       recast: 0.22.0
       ts-map: 1.0.3
-      vue-inbrowser-compiler-independent-utils: 4.64.1(vue@3.4.18)
+      vue-inbrowser-compiler-independent-utils: 4.64.1(vue@3.4.15)
     transitivePeerDependencies:
       - vue
     dev: true
@@ -19912,7 +19889,7 @@ packages:
     peerDependencies:
       eslint: '>=6.0.0'
     dependencies:
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4(supports-color@5.5.0)
       eslint: 8.56.0
       eslint-scope: 7.2.2
       eslint-visitor-keys: 3.4.3
@@ -19924,12 +19901,12 @@ packages:
       - supports-color
     dev: true
 
-  /vue-inbrowser-compiler-independent-utils@4.64.1(vue@3.4.18):
+  /vue-inbrowser-compiler-independent-utils@4.64.1(vue@3.4.15):
     resolution: {integrity: sha512-Hn32n07XZ8j9W8+fmOXPQL+i+W2e/8i6mkH4Ju3H6nR0+cfvmWM95GhczYi5B27+Y8JlCKgAo04IUiYce4mKAw==}
     peerDependencies:
       vue: '>=2'
     dependencies:
-      vue: 3.4.18(typescript@5.3.3)
+      vue: 3.4.15(typescript@5.3.3)
     dev: true
 
   /vue-template-compiler@2.7.14:
@@ -19951,28 +19928,28 @@ packages:
       typescript: 5.3.3
     dev: true
 
-  /vue@3.4.18(typescript@5.3.3):
-    resolution: {integrity: sha512-0zLRYamFRe0wF4q2L3O24KQzLyLpL64ye1RUToOgOxuWZsb/FhaNRdGmeozdtVYLz6tl94OXLaK7/WQIrVCw1A==}
+  /vue@3.4.15(typescript@5.3.3):
+    resolution: {integrity: sha512-jC0GH4KkWLWJOEQjOpkqU1bQsBwf4R1rsFtw5GQJbjHVKWDzO6P0nWWBTmjp1xSemAioDFj1jdaK1qa3DnMQoQ==}
     peerDependencies:
       typescript: '*'
     peerDependenciesMeta:
       typescript:
         optional: true
     dependencies:
-      '@vue/compiler-dom': 3.4.18
-      '@vue/compiler-sfc': 3.4.18
-      '@vue/runtime-dom': 3.4.18
-      '@vue/server-renderer': 3.4.18(vue@3.4.18)
-      '@vue/shared': 3.4.18
+      '@vue/compiler-dom': 3.4.15
+      '@vue/compiler-sfc': 3.4.15
+      '@vue/runtime-dom': 3.4.15
+      '@vue/server-renderer': 3.4.15(vue@3.4.15)
+      '@vue/shared': 3.4.15
       typescript: 5.3.3
 
-  /vuedraggable@4.1.0(vue@3.4.18):
+  /vuedraggable@4.1.0(vue@3.4.15):
     resolution: {integrity: sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==}
     peerDependencies:
       vue: ^3.0.1
     dependencies:
       sortablejs: 1.14.0
-      vue: 3.4.18(typescript@5.3.3)
+      vue: 3.4.15(typescript@5.3.3)
     dev: false
 
   /w3c-xmlserializer@5.0.0:
-- 
GitLab


From 2e7df3297c13826c4c37f1c06bd87bc7cfba9e4c Mon Sep 17 00:00:00 2001
From: Marie <marie@kaifa.ch>
Date: Fri, 9 Feb 2024 19:51:41 +0100
Subject: [PATCH 40/40] fix: diff between NoteCreateService and NoteEditService

---
 packages/backend/src/core/NoteCreateService.ts | 2 +-
 packages/backend/src/core/NoteEditService.ts   | 4 +++-
 2 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts
index 34f3736446..0d032011b6 100644
--- a/packages/backend/src/core/NoteCreateService.ts
+++ b/packages/backend/src/core/NoteCreateService.ts
@@ -259,7 +259,7 @@ export class NoteCreateService implements OnApplicationShutdown {
 
 		if (data.visibility === 'public' && data.channel == null) {
 			const sensitiveWords = meta.sensitiveWords;
-			if (this.utilityService.isSensitiveWordIncluded(data.cw ?? data.text ?? '', sensitiveWords)) {
+			if (this.utilityService.isKeyWordIncluded(data.cw ?? data.text ?? '', sensitiveWords)) {
 				data.visibility = 'home';
 			} else if ((await this.roleService.getUserPolicies(user.id)).canPublicNote === false) {
 				data.visibility = 'home';
diff --git a/packages/backend/src/core/NoteEditService.ts b/packages/backend/src/core/NoteEditService.ts
index a6f7edb05c..c0d17fc7ac 100644
--- a/packages/backend/src/core/NoteEditService.ts
+++ b/packages/backend/src/core/NoteEditService.ts
@@ -145,6 +145,8 @@ type Option = {
 export class NoteEditService implements OnApplicationShutdown {
 	#shutdownController = new AbortController();
 
+	public static ContainsProhibitedWordsError = class extends Error {};
+
 	constructor(
 		@Inject(DI.config)
 		private config: Config,
@@ -266,7 +268,7 @@ export class NoteEditService implements OnApplicationShutdown {
 
 		if (data.visibility === 'public' && data.channel == null) {
 			const sensitiveWords = meta.sensitiveWords;
-			if (this.utilityService.isSensitiveWordIncluded(data.cw ?? data.text ?? '', sensitiveWords)) {
+			if (this.utilityService.isKeyWordIncluded(data.cw ?? data.text ?? '', sensitiveWords)) {
 				data.visibility = 'home';
 			} else if ((await this.roleService.getUserPolicies(user.id)).canPublicNote === false) {
 				data.visibility = 'home';
-- 
GitLab