diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8f6cf2071b38846071561dea91c5f9072ce328d1..1d788e152258d0643be35c970ea10a19ea1d1f6a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -24,6 +24,8 @@
 - Fix: リモートユーザーのリアクション一覧がすべて見えてしまうのを修正
   * すべてのリモートユーザーのリアクション一覧を見えないようにします
 - Enhance: モデレーターはすべてのユーザーのリアクション一覧を見られるように
+- Fix: 特定のキーワードを含むノートが投稿された際、エラーに出来るような設定項目を追加 #13207
+  * デフォルトは空欄なので適用前と同等の動作になります
 
 ### Client
 - Feat: 新しいゲームを追加
@@ -49,6 +51,12 @@
 - Enhance: MFMの属性でオートコンプリートが使用できるように #12735
 - Enhance: 絵文字編集ダイアログをモーダルではなくウィンドウで表示するように
 - Enhance: リモートのユーザーはメニューから直接リモートで表示できるように
+- Enhance: リモートへの引用リノートと同一のリンクにはリンクプレビューを表示しないように
+- Enhance: コードのシンタックスハイライトにテーマを適用できるように
+- Enhance: リアクション権限がない場合、ハートにフォールバックするのではなくリアクションピッカーなどから打てないように
+  - リモートのユーザーにローカルのみのカスタム絵文字をリアクションしようとした場合
+  - センシティブなリアクションを認めていないユーザーにセンシティブなカスタム絵文字をリアクションしようとした場合
+  - ロールが必要な絵文字をリアクションしようとした場合
 - Fix: ネイティブモードの絵文字がモノクロにならないように
 - Fix: v2023.12.0で追加された「モデレーターがユーザーのアイコンもしくはバナー画像を未設定状態にできる機能」が管理画面上で正しく表示されていない問題を修正
 - Fix: AiScriptの`readline`関数が不正な値を返すことがある問題のv2023.12.0時点での修正がPlay以外に適用されていないのを修正
@@ -62,6 +70,13 @@
 - Enhance: ページ遷移時にPlayerを閉じるように
 - Fix: iOSで大きな画像を変換してアップロードできない問題を修正
 - Fix: 「アニメーション画像を再生しない」もしくは「データセーバー(アイコン)」を有効にしていても、アイコンデコレーションのアニメーションが停止されない問題を修正
+- Fix: 画像をクロップするとクロップ後の解像度が異様に低くなる問題の修正
+- Fix: 画像をクロップ時、正常に完了できない問題の修正
+- Fix: キャプションが空の画像をクロップするとキャプションにnullという文字列が入ってしまう問題の修正
+- Fix: プロフィールを編集してもリロードするまで反映されない問題を修正
+- Fix: エラー画像URLを設定した後解除すると,デフォルトの画像が表示されない問題の修正
+- Fix: MkCodeEditorで行がずれていってしまう問題の修正
+- Fix: Summaly proxy利用時にプレイヤーが動作しないことがあるのを修正 #13196
 
 ### Server
 - Enhance: 連合先のレートリミットに引っかかった際にリトライするようになりました
@@ -76,6 +91,7 @@
 - Fix: properly handle cc followers
 - Fix: ジョブに関する設定の名前を修正 relashionshipJobPerSec -> relationshipJobPerSec
 - Fix: コントロールパネル->モデレーション->「誰でも新規登録できるようにする」の初期値をONからOFFに変更 #13122
+- Enhance: 連合向けのノート配信を軽量化 #13192
 
 ### Service Worker
 - Enhance: オフライン表示のデザインを改善・多言語対応
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 7f6c1f4f82468f1f845c8dac5450a2a84a7bb463..ac0a1ba3c1c55ea085d0fb3f3e8d78ca70198959 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/locales/ca-ES.yml b/locales/ca-ES.yml
index eeafcafed24dc2e1144df72f22ead4e6fc6d559a..13bbb43f765554a6a6ad382584874cbc809a41b6 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."
@@ -1157,6 +1160,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 +1196,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 +1236,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 +1323,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,42 +1464,413 @@ _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."
+    _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 "
+  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"
+  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"
@@ -1401,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 241e4cfc7b7d247ec533896d1926626fe4c3ad6f..6f838d4880c5856f663c4044095ee11e22d04b8e 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 909ee5d45cfa3dfad490c23d5ec55860bd565793..67178924e35debe5b446ece0f7b94e432f116ee5 100644
--- a/locales/de-DE.yml
+++ b/locales/de-DE.yml
@@ -1037,6 +1037,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 4c859861f7dee9e1feb88d88d4dd3b7de6efcbee..8a32d8307cfd1a7ca3b6b6f275185129dc2b307f 100644
--- a/locales/en-US.yml
+++ b/locales/en-US.yml
@@ -1078,6 +1078,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."
@@ -2023,54 +2024,30 @@ _permissions:
   "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 account"
+  "write:admin:delete-account": "Delete user account"
   "write:admin:delete-all-files-of-a-user": "Delete all files of a user"
-  "read:admin:index-stats": "View information about database indexes"
-  "read:admin:table-stats": "View information about database tables"
-  "read:admin:user-ips": "View user IP address"
   "read:admin:meta": "View instance metadata"
-  "write:admin:reset-password": "Reset user passwords"
-  "write:admin:resolve-abuse-user-report": "Resolve user reports"
-  "write:admin:send-email": "Send Email"
+  "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 user information"
-  "read:admin:show-users": "View users"
+  "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 avatar from user"
-  "write:admin:unset-user-banner": "Remove banner from 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": "Edit instance metadata"
-  "write:admin:user-note": "Edit user note"
-  "write:admin:roles": "Edit roles"
+  "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": "Edit relays"
+  "write:admin:relays": "Manage relays"
   "read:admin:relays": "View relays"
-  "write:admin:invite-codes": "Edit invite codes"
+  "write:admin:invite-codes": "Manage invite codes"
   "read:admin:invite-codes": "View invite codes"
-  "write:admin:announcements": "Edit announcements"
+  "write:admin:announcements": "Manage announcements"
   "read:admin:announcements": "View announcements"
-  "write:admin:avatar-decorations": "Edit avatar decorations"
-  "read:admin:avatar-decorations": "View avatar decorations"
-  "write:admin:federation": "Edit remote instance information"
-  "write:admin:account": "Edit users"
-  "read:admin:account": "View information about user"
-  "write:admin:emoji": "Edit emojis"
-  "read:admin:emoji": "View emojis"
-  "write:admin:queue": "Edit queue"
-  "read:admin:queue": "View queue"
-  "write:admin:promo": "Edit promo"
-  "write:admin:drive": "Edit user drive"
-  "read:admin:drive": "View user drive"
-  "read:admin:stream": "Using the Websocket API for Admin"
-  "write:admin:ad": "Edit ads"
-  "read:admin:ad": "View ads"
-  "write:invite-codes": "Create Invitation Code"
-  "read:invite-codes": "View Invitation Code"
-  "write:clip-favorite": "Edit clips and likes"
-  "read:clip-favorite": "View clips and likes"
-  "read:federation": "View information about remote instance"
-  "write:report-abuse": "Report abuse"
+  "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 e8e9761d26e5685e77568d63a8f35e086fcf465b..96926dc38dbfdbd3ea025487d6d2b6c8f8a5aee0 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 4093dee6d3628b5cb682586ccbb8a52184434cf9..30a5955ff3a6ea7e242f61062505231b292cd4a1 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/index.d.ts b/locales/index.d.ts
index 686fa57cd5a8e372becb6f64c59d91c2762e2675..012cbe055a2d764bac31fac474b454ec34d663b7 100644
--- a/locales/index.d.ts
+++ b/locales/index.d.ts
@@ -4329,6 +4329,18 @@ export interface Locale extends ILocale {
      * スペースで区切るとAND指定になり、キーワードをスラッシュで囲むと正規表現になります。
      */
     "sensitiveWordsDescription2": string;
+    /**
+     * 禁止ワード
+     */
+    "prohibitedWords": string;
+    /**
+     * 設定したワードが含まれるノートを投稿しようとした際、エラーとなるようにします。改行で区切って複数設定できます。
+     */
+    "prohibitedWordsDescription": string;
+    /**
+     * スペースで区切るとAND指定になり、キーワードをスラッシュで囲むと正規表現になります。
+     */
+    "prohibitedWordsDescription2": string;
     /**
      * 非表示ハッシュタグ
      */
diff --git a/locales/it-IT.yml b/locales/it-IT.yml
index 0c033373432af3686abd925757f18fdf3f424f3f..69720871c978ba732b388b6f17566a7aee87f9bc 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."
@@ -1039,6 +1045,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."
@@ -1057,6 +1064,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 +1196,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 +1587,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 +1684,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 +1977,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/ja-JP.yml b/locales/ja-JP.yml
index f6879acda15840c50f3b4070e92d2f42d95ec8c4..9714f8f66845f9982828b8fa15dc04c82d67dda3 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -1078,6 +1078,9 @@ resetPasswordConfirm: "パスワードリセットしますか?"
 sensitiveWords: "センシティブワード"
 sensitiveWordsDescription: "設定したワードが含まれるノートの公開範囲をホームにします。改行で区切って複数設定できます。"
 sensitiveWordsDescription2: "スペースで区切るとAND指定になり、キーワードをスラッシュで囲むと正規表現になります。"
+prohibitedWords: "禁止ワード"
+prohibitedWordsDescription: "設定したワードが含まれるノートを投稿しようとした際、エラーとなるようにします。改行で区切って複数設定できます。"
+prohibitedWordsDescription2: "スペースで区切るとAND指定になり、キーワードをスラッシュで囲むと正規表現になります。"
 hiddenTags: "非表示ハッシュタグ"
 hiddenTagsDescription: "設定したタグをトレンドに表示させないようにします。改行で区切って複数設定できます。"
 notesSearchNotAvailable: "ノート検索は利用できません。"
diff --git a/locales/ja-KS.yml b/locales/ja-KS.yml
index 5d5b175e0c2844bb1b60f721f77bd7c60934cd8f..b4787c636f116bb78c73c8de4aaa49f8400b1927 100644
--- a/locales/ja-KS.yml
+++ b/locales/ja-KS.yml
@@ -1043,6 +1043,7 @@ resetPasswordConfirm: "パスワード作り直すんでええな?"
 sensitiveWords: "けったいな単語"
 sensitiveWordsDescription: "設定した単語が入っとるノートの公開範囲をホームにしたるわ。改行で区切ったら複数設定できるで。"
 sensitiveWordsDescription2: "スペースで区切るとAND指定、キーワードをスラッシュで囲んだら正規表現や。"
+prohibitedWordsDescription2: "スペースで区切るとAND指定、キーワードをスラッシュで囲んだら正規表現や。"
 hiddenTags: "見えてへんハッシュタグ"
 hiddenTagsDescription: "設定したタグを最近流行りのとこに見えんようにすんで。複数設定するときは改行で区切ってな。"
 notesSearchNotAvailable: "なんかノート探せへん。"
diff --git a/locales/ko-GS.yml b/locales/ko-GS.yml
index 29bfe5394d76a12c5f50f5449d4b26bf166d98cb..b1702114be09eb157effcf7088c1952803ab58c9 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 718d04caaeb267c0c7f5697f1db0d22c1005d32a..1231209b36911567dae3833fc3575a4b0c270568 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: "연합을 끄더라도 게시물이 비공개로 전환되는 것은 아닙니다. 대부분의 경우 연합을 비활성화할 필요가 없습니다."
@@ -1041,6 +1041,7 @@ resetPasswordConfirm: "비밀번호를 재설정하시겠습니까?"
 sensitiveWords: "민감한 단어"
 sensitiveWordsDescription: "설정한 단어가 포함된 노트의 공개 범위를 '홈'으로 강제합니다. 개행으로 구분하여 여러 개를 지정할 수 있습니다."
 sensitiveWordsDescription2: "공백으로 구분하면 AND 지정이 되며, 키워드를 슬래시로 둘러싸면 정규 표현식이 됩니다."
+prohibitedWordsDescription2: "공백으로 구분하면 AND 지정이 되며, 키워드를 슬래시로 둘러싸면 정규 표현식이 됩니다."
 hiddenTags: "숨긴 해시태그"
 hiddenTagsDescription: "설정한 태그를 트렌드에 표시하지 않도록 합니다. 줄 바꿈으로 하나씩 나눠서 설정할 수 있습니다."
 notesSearchNotAvailable: "노트 검색을 이용하실 수 없습니다."
@@ -1797,7 +1798,7 @@ _instanceMute:
   title: "지정한 서버의 노트를 숨깁니다."
   heading: "뮤트할 서버"
 _theme:
-  explore: "테마 찾아보기"
+  explore: "테마 둘러보기"
   install: "테마 설치"
   manage: "테마 관리"
   code: "테마 코드"
@@ -2022,7 +2023,7 @@ _permissions:
   "write:report-abuse": "위반 내용 신고하기"
 _auth:
   shareAccessTitle: "어플리케이션의 접근 허가"
-  shareAccess: "\"{name}\" 이 계정에 접근하는 것을 허용하시겠습니까?"
+  shareAccess: "‘{name}’에서 계정에 접근하는 것을 허용하시겠습니까?"
   shareAccessAsk: "이 애플리케이션이 계정에 접근하는 것을 허용하시겠습니까?"
   permission: "{name}에서 다음 권한을 요청하였습니다"
   permissionAsk: "이 앱은 다음의 권한을 요청합니다"
diff --git a/locales/ru-RU.yml b/locales/ru-RU.yml
index 60682fe961cfa80b00c500cf77de32ac3d1b1c3b..d014b7fc259e72c5093dedd201eb3ec668123890 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 b14d855566c61969fec1b1411d5a91a3a06627b8..cfba65685b641c1f4ab5bfc3372c7b96530635e1 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 09c210011c46369592fe9763bc205b18e7114f47..4a36e30db896ea715c1024fa8b7ca143a2305bfd 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 872a90bc6aa958fc25e36fca978c2404c2955d7d..ed2bd1cf3a29a7d29abc530fc40a5a79a77112a1 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: "無法使用搜尋貼文功能。"
diff --git a/package.json b/package.json
index 7af66b8e377dae8e22e49d862fe4ff8625ac1eef..fdd23f02c630744ee9e1685907bbb87374d89615 100644
--- a/package.json
+++ b/package.json
@@ -1,12 +1,12 @@
 {
 	"name": "sharkey",
-	"version": "2024.2.0-beta.9",
+	"version": "2024.2.0-beta.11",
 	"codename": "shonk",
 	"repository": {
 		"type": "git",
 		"url": "https://git.joinsharkey.org/Sharkey/Sharkey.git"
 	},
-	"packageManager": "pnpm@8.12.1",
+	"packageManager": "pnpm@8.15.1",
 	"workspaces": [
 		"packages/frontend",
 		"packages/backend",
diff --git a/packages/backend/migration/1707429690000-prohibited-words.js b/packages/backend/migration/1707429690000-prohibited-words.js
new file mode 100644
index 0000000000000000000000000000000000000000..2dd62d8ff88abd0f0772c997abc62dafa10b014c
--- /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/package.json b/packages/backend/package.json
index 296eddfe3ca73a7348667257bb1bf5871e570745..e5c70d120994a1d96d757fb8c68ae7d4a121ac98 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -84,7 +84,7 @@
 		"@nestjs/testing": "10.2.10",
 		"@peertube/http-signature": "1.7.0",
 		"@transfem-org/sfm-js": "0.24.4",
-		"@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",
@@ -205,7 +205,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/backend/src/core/CustomEmojiService.ts b/packages/backend/src/core/CustomEmojiService.ts
index 5a1fe3d0890335c5b7d35bf33d9d0e5605fa0dee..1de71212e9aff99a9ea427f6266b0c845e4c6ba1 100644
--- a/packages/backend/src/core/CustomEmojiService.ts
+++ b/packages/backend/src/core/CustomEmojiService.ts
@@ -408,7 +408,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/EmailService.ts b/packages/backend/src/core/EmailService.ts
index 8daee148eb8614f480db6eabe51ae1f0b94e0b28..89722965c11aae697f97c6abb1436fae5f69f7a5 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/HashtagService.ts b/packages/backend/src/core/HashtagService.ts
index 5a2417c9cdb4b8fa822cf2cbad3d7710314d870e..712530108e28429f17bb5c06ef9aa51460270825 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/InstanceActorService.ts b/packages/backend/src/core/InstanceActorService.ts
index b40fd46291ecdbc2f1dc4102a84d7dba4e9ff351..7ce8dc96a18102310e9d1e6e5ad391003b0c4272 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/MfmService.ts b/packages/backend/src/core/MfmService.ts
index 51c4252715a55bb8e9e842fd1ea89ae3afe084b3..5cfcd93a64511276a0774d85b4352bb2eb66c598 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/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts
index 2848be9fe1472075912021bed219e72642fe71c0..0d032011b66eb47bd2d6585fc2545e4e6af32d4f 100644
--- a/packages/backend/src/core/NoteCreateService.ts
+++ b/packages/backend/src/core/NoteCreateService.ts
@@ -153,6 +153,8 @@ type Option = {
 export class NoteCreateService implements OnApplicationShutdown {
 	#shutdownController = new AbortController();
 
+	public static ContainsProhibitedWordsError = class extends Error {};
+
 	constructor(
 		@Inject(DI.config)
 		private config: Config,
@@ -257,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';
@@ -429,13 +431,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) {
@@ -795,7 +803,7 @@ export class NoteCreateService implements OnApplicationShutdown {
 				});
 				// 通知
 				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,
@@ -830,7 +838,7 @@ export class NoteCreateService implements OnApplicationShutdown {
 
 				// Notify
 				if (data.renote.userHost === null) {
-					const isThreadMuted = await this.noteThreadMutingsRepository.exist({
+					const isThreadMuted = await this.noteThreadMutingsRepository.exists({
 						where: {
 							userId: data.renote.userId,
 							threadId: data.renote.threadId ?? data.renote.id,
@@ -1057,7 +1065,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/NoteEditService.ts b/packages/backend/src/core/NoteEditService.ts
index 98762f790e97b3bab177497f89f24e3d9bacc8ac..c0d17fc7acee5f97b53e821fecf18fdd8d8145f3 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';
@@ -612,7 +614,7 @@ export class NoteEditService 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,
@@ -647,7 +649,7 @@ export class NoteEditService implements OnApplicationShutdown {
 
 				// Notify
 				if (data.renote.userHost === null) {
-					const isThreadMuted = await this.noteThreadMutingsRepository.exist({
+					const isThreadMuted = await this.noteThreadMutingsRepository.exists({
 						where: {
 							userId: data.renote.userId,
 							threadId: data.renote.threadId ?? data.renote.id,
@@ -751,7 +753,7 @@ export class NoteEditService 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 c73cf765922bcb8a85d4c79366e669d95af08ba4..11791a44121c89e803bafd104b4ca694c0a5dc85 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/ReactionService.ts b/packages/backend/src/core/ReactionService.ts
index 11c972982e50ff6edb09f4d465943b2c62103566..6cce4a61ea4eb973f7e09ab1295352b111131639 100644
--- a/packages/backend/src/core/ReactionService.ts
+++ b/packages/backend/src/core/ReactionService.ts
@@ -248,7 +248,7 @@ export class ReactionService {
 
 		// リアクションされたユーザーがローカルユーザーなら通知を作成
 		if (note.userHost === null) {
-			const isThreadMuted = await this.noteThreadMutingsRepository.exist({
+			const isThreadMuted = await this.noteThreadMutingsRepository.exists({
 				where: {
 					userId: note.userId,
 					threadId: note.threadId ?? note.id,
diff --git a/packages/backend/src/core/SignupService.ts b/packages/backend/src/core/SignupService.ts
index 32e3dee937c58850d3ebdc0e8bf6319f5c7af9b7..5941944a20441ebe55c7b2a0efb3dad63492f594 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,
 	) {
 	}
@@ -75,16 +77,16 @@ 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');
 		}
 
-		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/UserFollowingService.ts b/packages/backend/src/core/UserFollowingService.ts
index 93e9fbbd70d7b7f564ad79286ab0b6b4674b1297..e82a7e06f9f338dc9c0a391a94c8c44964965a9c 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/UtilityService.ts b/packages/backend/src/core/UtilityService.ts
index 5dec36c89e31664ffcb332e2412fcca48031e81a..15b98abe635b0bd94b877facf7609d290b4876fc 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/core/activitypub/ApInboxService.ts b/packages/backend/src/core/activitypub/ApInboxService.ts
index 3f01c0289a2cfcb9e64c6054b82c1457633fa16e..acbe59b8bda15da646839a098efee415f3998aa7 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/ApMfmService.ts b/packages/backend/src/core/activitypub/ApMfmService.ts
index b900def2e450ad70bebb5adfd773e726010fb700..0716e4312358787921bd7942e9aad6f8b40a3aef 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), note.mentionedRemoteUsers ? 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 19899629c3bd1fb60f63b6727b52027ada5cab6d..e07bb8be7878eba72cf9d7b3e3607741a7bef907 100644
--- a/packages/backend/src/core/activitypub/ApRendererService.ts
+++ b/packages/backend/src/core/activitypub/ApRendererService.ts
@@ -345,7 +345,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) {
@@ -409,17 +409,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));
@@ -432,9 +430,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',
@@ -452,11 +447,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,
 			quoteUri: quote,
@@ -639,7 +636,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) {
@@ -796,6 +793,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 8507c6f9496a1da03f0add40fe3d8734cfdb1b95..a4d497a7ff2b50f95d60338b5bfb7388497d3879 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/ChannelEntityService.ts b/packages/backend/src/core/entities/ChannelEntityService.ts
index 305946b8a648b3e9b3d37baa2a2690c540299394..f358875f729d6eb41bc7107b94fea44081a68fb6 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 96422894fd47f4afe2369b253e994d9d41f4a2fd..2133f80f1af884222c3eef27b4aec930776dfb3c 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/EmojiEntityService.ts b/packages/backend/src/core/entities/EmojiEntityService.ts
index 5b97cfad5ee70f8684ecdd1f00f1ffefcd6ff5eb..655c4c5ada6df6854c20a8641a245c0f4b17fc11 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/core/entities/FlashEntityService.ts b/packages/backend/src/core/entities/FlashEntityService.ts
index 70faa2b38063abdcab4b0c135d766ae56ef5d3aa..c1b9f9a791ffbdc081517657a42cd9398438fda3 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 d7b960e0d9c0b6999cf91f8160128c7373d59224..2a615a9216255178bdade6a5f01f07c185f5c2b1 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 1cbd5cb70c182f36563b469dcecee75ba1e1843b..a59de4985c8e8e9fb8bf1bde1398361cc527f08c 100644
--- a/packages/backend/src/core/entities/NoteEntityService.ts
+++ b/packages/backend/src/core/entities/NoteEntityService.ts
@@ -114,7 +114,7 @@ export class NoteEntityService implements OnModuleInit {
 				hide = false;
 			} else {
 				if (packedNote.renote) {
-					const isFollowing = await this.followingsRepository.exist({
+					const isFollowing = await this.followingsRepository.exists({
 						where: {
 							followeeId: packedNote.renote.userId,
 							followerId: meId,
@@ -124,7 +124,7 @@ export class NoteEntityService implements OnModuleInit {
 					hide = !isFollowing;
 				} 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 bc26362aba9a059ded31a4f34edbb08ab4835e42..27c9011f6d53c4c10de2e0a435afd69927da9b54 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 6c1a02d9d8866bf204abcbbf24993fbb7036dc25..d24d97ed17d71791e93e978c9015c10a27b6e403 100644
--- a/packages/backend/src/core/entities/UserEntityService.ts
+++ b/packages/backend/src/core/entities/UserEntityService.ts
@@ -153,43 +153,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,
@@ -216,7 +216,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/models/Meta.ts b/packages/backend/src/models/Meta.ts
index 9629012c743a8759e9f2304a96cc3f4f8ba7e13d..149955aa5a90b371565288db0ab582be11760b13 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/models/json-schema/emoji.ts b/packages/backend/src/models/json-schema/emoji.ts
index 99a58f8773e320f76656aeee7071bb0e0e8d9679..954eb98d575dc6a97a1eb25e852fbc4e0b3380b4 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/SignupApiService.ts b/packages/backend/src/server/api/SignupApiService.ts
index d2e6185aa3a6ac55fe67de2af9177d376f7b41ad..584853c1f3e5a75ae5228aaaa63aae815430f064 100644
--- a/packages/backend/src/server/api/SignupApiService.ts
+++ b/packages/backend/src/server/api/SignupApiService.ts
@@ -176,12 +176,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/accounts/create.ts b/packages/backend/src/server/api/endpoints/admin/accounts/create.ts
index b18a7e0e412d7123b0c8107ed01e537c4e25cf61..14fd69a1a247de39744b40a408def32444433237 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/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts
index c6edd6c9a155f5fbc866983604f17995402c47db..6201d1554dbc3479d743ea2da9b2fc23c3006951 100644
--- a/packages/backend/src/server/api/endpoints/admin/meta.ts
+++ b/packages/backend/src/server/api/endpoints/admin/meta.ts
@@ -160,6 +160,13 @@ export const meta = {
 					type: 'string',
 				},
 			},
+			prohibitedWords: {
+				type: 'array',
+				optional: false, nullable: false,
+				items: {
+					type: 'string',
+				},
+			},
 			bannedEmailDomains: {
 				type: 'array',
 				optional: true, nullable: false,
@@ -549,6 +556,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,
 				bubbleInstances: instance.bubbleInstances,
 				hcaptchaSecretKey: instance.hcaptchaSecretKey,
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 ab69dfba9635493927eac23672a7c3021d569e49..339b7a8aa4d9a13e934b436fb0ee7df3121cc5ad 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/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts
index 8c0d2f8876b7bc6aa76f011c27f59ea2f13e1fc7..dce02532713ec81d696d82410ab3e43552c1c092 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 },
@@ -185,6 +190,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/auth/accept.ts b/packages/backend/src/server/api/endpoints/auth/accept.ts
index e0baeb35656ef48798d09198348471c2bb04c12a..602c34b1e63ee108bd764a76b512d185099cddfa 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 1dc456318033ddc5bd748170029b545a161ef375..ea7d2076b0f489513b0e0f7833cce4295d0c22ee 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 a6e6bcb5b3f7c7df581bf5a341438b72186e1cd4..b0d66fd05cce57172b18b5dc9af197b4a65aa8d7 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 015b2cfa859754a7673cb549d0bb90b3c65042b7..b4c6a4940ba302639b0c25413c8383df791fcdcd 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 85e6312b6abef733ca6055b68da6aa336c01e945..8c1f491f8d2d8c780459a3cffa65a464fb997456 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 1003249c0cd7d47803af1355ac3eaa51cfa6556a..5878200828f359727b0744f386039d8ce6f39a10 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 9037944ef9efb364e144a23f2d1f0b6d6cc1778a..1d0691407d9f4a4e2f051e8092882b72287583c8 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 f44692ba6d7e744e001688a3b4ac36af35942b57..f761968c90f97a990e463567d8c7d7c8536a34a7 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 cc424261b49c3317e90a1c79ee8f30f61b130f24..576cff4e9130f35c475dfd74d8ec3d2b0d45e580 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 71db8710af923e3455a85f8b2b3d0f30e19c91a3..771f3395ce00a4acc7bfac66b423bbba03e627cf 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 98d866f867d4fa653dde407197a1a159ab8b2e93..545d16082c42bebcdb12d4d42cfc5484379d06f1 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/meta.ts b/packages/backend/src/server/api/endpoints/meta.ts
index cb52bf6b516684bcba34f67f95fc901069176bfb..6d553e5d79e11426f73658df83073e926f2747d5 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/backend/src/server/api/endpoints/mute/create.ts b/packages/backend/src/server/api/endpoints/mute/create.ts
index 49c2b5707deb6f2b2ab41c146ecb43de13ec9fc0..1d931150e191e31e4804188353846502137e7ad5 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 44ca1b9c16dd2536c0c53a8b582a7366f4664c42..dbcea2932e737fd4326665614fdaa60a6de2ec23 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 = {
@@ -117,6 +119,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;
 
@@ -271,7 +279,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,
@@ -319,7 +327,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,
@@ -351,31 +359,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/src/server/api/endpoints/notes/edit.ts b/packages/backend/src/server/api/endpoints/notes/edit.ts
index 0c9c0d3baf91390ccc7e9dbdd8e97f92e52ff85a..44796d9290ea31175c380aad42ea874ba888a154 100644
--- a/packages/backend/src/server/api/endpoints/notes/edit.ts
+++ b/packages/backend/src/server/api/endpoints/notes/edit.ts
@@ -311,7 +311,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,
@@ -349,7 +349,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 ed3dce7f35433d56b0d9d3688186873ed34647c2..bfa621aa38f3346db112f8c42ae3feb33662e2cc 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 8c18982b50a70a487d2f9b9758bcddb10118f831..bee60f080d47268b977daf760f558decc527bc31 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 f427939a7a2d531134e71eb95b6309c01d17d77d..4899408ddd41412192a8c14eb86bf422b9e4cc85 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 5706e46b96216ca937853d1cf527d63466827f4d..314a45ed61eb66c9f5ea583c64091c3cf4c7d2da 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 794fb04f102b1f4f40b020cca47384c7c015aae0..86f55c5a12bfd29442f135c50e1793fcfdc8b385 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 fa2e3338b88e5f724c8a365c45e97e1e52dc076c..dd9b459a1f4916c3581ed19692bcf91ee61570fc 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 864cdc2ee0106823aab122e427db3a814d1c12ef..e5b3a73b556e741eee14067f7475ab021b9610f2 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 c4ceec575b92f880b1f78d1df999e4e3776582aa..0984270943b23df2da1be3415a55b88b0d347baa 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 df44870b041e22e8a5108d4c3ab0deaadc6705ae..10efbaafd8bed99105840d4f9886cecd455eab58 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 d51d57343e29c97c67ce0bc5664aac8905aaa5fe..0935584cbc6c17ef1f6bf0f1c106f22ecf6cbcca 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 e0245814c47a7aed8914a40a5a63466655eedf66..0434833df879e832b97888f10641bb6a4ea4307e 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,
diff --git a/packages/backend/test/e2e/note.ts b/packages/backend/test/e2e/note.ts
index 0280b051f5d2dc26fd986cba4e1d77efa3221b58..1bc8cb591c81b9dc7a0c8af048101321d93840e3 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/backend/test/unit/ApMfmService.ts b/packages/backend/test/unit/ApMfmService.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2b79041c86d7e6a3b6a58a3c35b9797500227959
--- /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 8f40ae493529f7572a8bbb7f69a701b8150e56e4..b714f2c0fbdf39f74770196afb662826f1167600 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', () => {
diff --git a/packages/frontend/.storybook/mocks.ts b/packages/frontend/.storybook/mocks.ts
index 80e5157c5a2d7a3d5150f8fcf8f06ed2006b1637..f0feff9f784c245a6ef5d6f68d8993856202b84d 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 707c15f5c7304437684b342fcf47abceadefd3f3..f5c968ec3c9383a917c1762ca211c44001034841 100644
--- a/packages/frontend/package.json
+++ b/packages/frontend/package.json
@@ -30,7 +30,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",
@@ -72,7 +72,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"
 	},
@@ -102,7 +102,7 @@
 		"@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",
@@ -115,7 +115,7 @@
 		"@vue/runtime-core": "3.4.15",
 		"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",
@@ -123,10 +123,10 @@
 		"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",
+		"prettier": "3.2.5",
 		"react": "18.2.0",
 		"react-dom": "18.2.0",
 		"start-server-and-test": "2.0.3",
diff --git a/packages/frontend/src/boot/common.ts b/packages/frontend/src/boot/common.ts
index f6a3e40305e1c88794ec9f3e17f80125f1fa4072..65bd7cf0f13c0f5a6fac165a63122e57ffcbb814 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 クライアントが更新されたかチェック
@@ -293,5 +287,10 @@ function removeSplash() {
 	if (splash) {
 		splash.style.opacity = '0';
 		splash.style.pointerEvents = 'none';
+
+		// transitionendイベントが発火しない場合があるため
+		window.setTimeout(() => {
+			splash.remove();
+		}, 1000);
 	}
 }
diff --git a/packages/frontend/src/components/MkAbuseReport.stories.impl.ts b/packages/frontend/src/components/MkAbuseReport.stories.impl.ts
index 77e7c84d5c9cf65fc7516bb25b281f990778bb65..dc2697f25c42fb5f0911dcde8ef9e1fae9311588 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 dc842b3d1bd2b81460c7272ceb754f6c193fcb82..771452cb5fde2eec5e444a6375618c14cb2fc5ec 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 6d972467b153d178f7262a7b97437a9874a86f7c..81e9529de21065f1413046b0df5e1da93373381e 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 969519386fc9733da1fd23a485330aaa728b820a..3ca8c5b8646a26e287f6d38cfec3c14543b52226 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 d41b64695fdcfb1fc14e0c364b26a7420096d310..a9b4540ca97596af422cec346c275c4f85de68f0 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/MkCode.core.vue b/packages/frontend/src/components/MkCode.core.vue
index b06bb70e99ed09eb4661d6b0fcc00eff011f6ad8..5ad5892b7ce06c51c9050cf0b6f6591b8e05bfc3 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,16 @@ watch(() => props.lang, (to) => {
 	margin: .5em 0;
 	overflow: auto;
 	border-radius: var(--radius-sm);
+	border: 1px solid var(--divider);
+	font-family: Consolas, Monaco, Andale Mono, Ubuntu Mono, monospace;
+
+	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 +109,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 +137,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 +149,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 05b5fe8da1a743800c13f28c238890fcb5915072..b34eb8aaf6792930c02007f898ee253590030009 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 7334fc4d0e4305ce27d6722beede37d6436f1afb..5ff6c801a5687119e46329b8123100a4bd748fe4 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 5340c1fd5fa30a713fa54e70dbfb096cae73276e..6a9d97ab5a3ee0eb0abe74ef30da48b5d6aaf554 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 0a1ddd3171db7ef9a349e9f50365f73e1c2a36a2..745453646c48085aa1b656ca92eb20dd48669d3f 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 1db03a5eb9ec4155a4bafca1791dda37167e660f..a4c5e07cd9cb40b95d7da9a0461d84ae899f2343 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 4068a79f085f280a9c2069c0245b2b84f4eb6d37..1c0f9a5a33ef36e8364917aaae6844f74dcdc746 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 1a2c55e785d569fc4a53d9130a657bd1546538ef..2a6828f24255c78955fe487ad4bd1238bf475a10 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/MkHorizontalSwipe.vue b/packages/frontend/src/components/MkHorizontalSwipe.vue
index 67d32c505adb0fd903703b281f1c4b2aba4e491e..bf7d43fd43dd944039fc779db8aaff29b6962ffc 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/MkInviteCode.stories.impl.ts b/packages/frontend/src/components/MkInviteCode.stories.impl.ts
index 2ea32dd3b67a5afb31e6ec374bc1dc95d25275c8..2abe1a8770524f7055d019dd4109fb1161be9da9 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/MkLaunchPad.vue b/packages/frontend/src/components/MkLaunchPad.vue
index 79b2b91154f6074910ec635ca8495bd8272d7cde..02bf987bde39a3dd9952adbb873a59ebc901c5e5 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 0abad81d97c64d6e2ed37c379a960490653897da..ff9bf3c395074297eeb1a261dea68cc18b9ae1b8 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 d31f77bbc28d9b6985646ad349944fbc3cf40f6d..a2e3a747d9e93beebd2db879343dd393b164d4e8 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 cb2db0b6a5e0cca06af1eead945e8b7b728a8341..ee0963a9b502709b84dd39384a259196366b85a6 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/MkPullToRefresh.vue b/packages/frontend/src/components/MkPullToRefresh.vue
index e96369799798fd5312cd01d757dd2fca29ae8a6f..3898f40ca6c716443b2add96b78564f26e61b333 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/components/MkReactionsViewer.reaction.vue b/packages/frontend/src/components/MkReactionsViewer.reaction.vue
index e43841e5c9e4d564613654daa71ef6cb38aeac2d..356020a0de23959cc0b1c01c32f3f1cf762e5a0f 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 6ac8297c0285a9d71bd6d6be1ced3efbd0b2a4c7..1ed6c0d822596ae4c9dc374d112bfa13d370df4f 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/MkTimeline.vue b/packages/frontend/src/components/MkTimeline.vue
index 95bbe71f9689dc41a6ca95986ada2c6fa8ebd363..e998b081eece91d797fb192eac69e2e77f700a8a 100644
--- a/packages/frontend/src/components/MkTimeline.vue
+++ b/packages/frontend/src/components/MkTimeline.vue
@@ -19,7 +19,6 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts" setup>
 import { computed, watch, onUnmounted, provide, ref, shallowRef } from 'vue';
 import * as Misskey from 'misskey-js';
-import { ChannelConnection as Connection } from 'misskey-js';
 import MkNotes from '@/components/MkNotes.vue';
 import MkPullToRefresh from '@/components/MkPullToRefresh.vue';
 import { useStream } from '@/stream.js';
@@ -90,8 +89,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();
@@ -163,7 +162,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/MkUrlPreview.vue b/packages/frontend/src/components/MkUrlPreview.vue
index 440dcd84fa34fce9e4fb0c5119898d9227cef376..9fa6fb1ab6ded4c6a6bf865e2cd53c69071c7b9b 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/src/components/MkUserSetupDialog.Follow.stories.impl.ts b/packages/frontend/src/components/MkUserSetupDialog.Follow.stories.impl.ts
index 45c7da40ce2e51171e07aa0cd00ae0a571a7dcdf..c1b380bd1ba35f1a2b5d5469aa6a2a4d9868a32e 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.Follow.vue b/packages/frontend/src/components/MkUserSetupDialog.Follow.vue
index 46459df6a663584ea7abd3d884f985329b7920b9..86a5c812bdf769fe9754b3cfa6bc93d719675701 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';
diff --git a/packages/frontend/src/components/MkUserSetupDialog.stories.impl.ts b/packages/frontend/src/components/MkUserSetupDialog.stories.impl.ts
index 5182db12b2e9f20996434ca41d9ca0bc15c9fae0..7177d256e188fff2975093936fd0df8e4d073d3a 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/SkNote.vue b/packages/frontend/src/components/SkNote.vue
index 84bcd3886d29f7cee746ffb7fbc51ec608b41015..dc74c4928d68bc7389d76321b9007cd2c8143079 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);
@@ -625,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 ca3ea09b2a7290b4a1f853b2860ddde3e5d8f2ca..ef17a2e8a6bf4559aa34d555d2dd4d38904a5d72 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);
@@ -621,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', {
diff --git a/packages/frontend/src/components/global/MkUrl.stories.impl.ts b/packages/frontend/src/components/global/MkUrl.stories.impl.ts
index b35b6114fdd4e3e4af4050a687720a562ae04495..d05306908789ddd726d749d5144d7e319140b260 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/components/global/RouterView.vue b/packages/frontend/src/components/global/RouterView.vue
index dc7474835ded2d24e844be3e65dac3b587fb5298..aeb87e659b6a93419530b2687f96cf950f02b4c8 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 90d5f6eede42d9f2da81da34c7b2587e43d274bb..46faec8d3e4cfa01ed2a13c230ac270685ca4901 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 7cce77cdf7d2ee9c4ec139f2442afc4085e7e741..010fdbb6d84d8b6434399de0455da34306350700 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 080a81767b5f8e6f0bff31661279d7595e473310..b39c82515236cac322a98957cac8c24a3d311eb4 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 4238589f188e2a4c31b9ed7b36fb268364597f10..d0fce6407b5d07da1053170571031b077afaaecf 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/moderation.vue b/packages/frontend/src/pages/admin/moderation.vue
index e8b0e306fcbda5110b0b1155a6d272739d01883a..965793bf4f18c7943b198e91059aa3d93ea49688 100644
--- a/packages/frontend/src/pages/admin/moderation.vue
+++ b/packages/frontend/src/pages/admin/moderation.vue
@@ -49,6 +49,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>
@@ -87,6 +92,7 @@ const emailRequiredForSignup = ref<boolean>(false);
 const approvalRequiredForSignup = ref<boolean>(false);
 const bubbleTimelineEnabled = ref<boolean>(false);
 const sensitiveWords = ref<string>('');
+const prohibitedWords = ref<string>('');
 const hiddenTags = ref<string>('');
 const preservedUsernames = ref<string>('');
 const bubbleTimeline = ref<string>('');
@@ -99,6 +105,7 @@ async function init() {
 	emailRequiredForSignup.value = meta.emailRequiredForSignup;
 	approvalRequiredForSignup.value = meta.approvalRequiredForSignup;
 	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;
@@ -115,6 +122,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'),
 		bubbleInstances: bubbleTimeline.value.split('\n'),
diff --git a/packages/frontend/src/pages/admin/security.vue b/packages/frontend/src/pages/admin/security.vue
index 7190dd436da090f848a1ea35f0cf657cf08d4c18..b1083636db8de5be92a987df3b8504b939129d17 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 6d805862e2a16f9e8b93e92d81fb6e94b06ee4e6..513b6560974261331ab8090820e7e6736dc99ead 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 4a4953bbc296664c367b7950e13115ebda47d936..e8b29e9ba35a30c445f45568ca110111eb18abe1 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 fb812589b4cf84f4d60d49bd310cd6aa4caba129..634a9503916f31e75cdcbfafe138d428e12dc2cc 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 40bb823ac65be67b6c01f1fadd92f898e1f5b5c3..383841d02fac2381b245caac06feb842325f8a7c 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 abf929e30b5c9880562d79fd186d55352951156b..37fca4f8ae88a49ab12fa0e16f31d32fe504e2b6 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 cb9c714441a55d21b56ecb98ffe913adb04b4da9..f01ebc1377726a142e22f2f37d2af3259ae80ade 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/pages/user/home.stories.impl.ts b/packages/frontend/src/pages/user/home.stories.impl.ts
index a2ef5d50d1ef8dd2e7c3c1150f612176e22c8db0..1e67d96c71c95dc08656e1627442ec5dae6aa43d 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/packages/frontend/src/pizzax.ts b/packages/frontend/src/pizzax.ts
index 68c36ca1b4b6d63f036c4a762042b56f6014e91c..199addaefd5cbedb90ac52f89ea19d350a2b829b 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 5adb3f606f4356070c67a8b031e08c2f4391bfff..c6a520e9132bb76aa1d0e0b8edccc788c942466c 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 0000000000000000000000000000000000000000..c9d2a5bfc6bff9b5e10481ecd66368fbdc206ef6
--- /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 ac38faefaa272d0d14d146a21ef62d7d26a1ed74..6d3a1c8c79765b587433d79b31f5fee6e95e63e5 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 bc05ec94d54cc83c346aacdcc6e066f6dc9b8fcf..2733897bab109620214a26f4757e31f4af418a70 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();
@@ -16,17 +58,34 @@ export async function initHighlighter() {
 
 	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'),
-			{
-				aliases: ['is', 'ais'],
-				...aiScriptGrammar.default,
-			} as unknown as LanguageRegistration,
+			aiScriptGrammar.default as unknown as LanguageRegistration,
 		],
 	});
 
+	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 0000000000000000000000000000000000000000..60097051fab66e003c00e86e90c5164eeea75575
--- /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 a13351b53656ee0d59396cb89725fb3f83147264..193ac838a2583d4d364a7906b296f71b21640984 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 a174f51756481b148991856eeef3f9b9a4163beb..05ccd3dc380895c70fc7290d8a8d42ba1b7d7c69 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/scripts/touch.ts b/packages/frontend/src/scripts/touch.ts
index 05f379e4aa602ab80a9b58a479b737b2cda83e06..4fd7d500c4eecbcfa5e0f225e6be71e921cd4366 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);
diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts
index 4dad6ce406049ae1a8d504974e27073b48266db6..d695caa95f0275ce1496f6559a4eb283007a111d 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 8544572718f6faa53e9ee75323e7c15369c43793..7b70aa1e0941cb7e5184e7b8f7a6c142c55f14ea 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 2f3783310fa276c873c17385ddc0fc72b0cd1ba6..d797aec734954d47773f8f782ca1b9169064e15b 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/frontend/test/url-preview.test.ts b/packages/frontend/test/url-preview.test.ts
index 6cf8317c07c75263c9e3b1ce6844fdfadad24604..b7587754c65f7955603c0fe7b365ffd84735d898 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',
diff --git a/packages/misskey-js/generator/src/generator.ts b/packages/misskey-js/generator/src/generator.ts
index 7e72359167e1a0e9236e5dec0e745ef1509fa4a7..f091e599a921e9088a136926d43e6c945c5fa6d0 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/package.json b/packages/misskey-js/package.json
index 03952c7ac150b4326fabca3bb32662c22db53ff6..26b682f1c4e7340bde685adcb076ff73aeeeb8f7 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.11",
 	"description": "Misskey SDK for JavaScript",
 	"types": "./built/dts/index.d.ts",
 	"exports": {
@@ -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/packages/misskey-js/src/autogen/apiClientJSDoc.ts b/packages/misskey-js/src/autogen/apiClientJSDoc.ts
index 205fba69a7a4c031cf4ec39f6fe38e53eddf230d..2d9d78228ed4d860b4d3ec617ccb4039e77ab433 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 28fc82c60981069289635f6bff7669733e8d7e7d..3f95d3cf4be0dd2cbff4a93d050252fd04c6f87f 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 17471a27f0de6a4c37449c2a75551a0073877df6..3e96fcc61c5d2110a47c2943120227142045ba18 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 5d87df588cee046f1c8a9e0a14e6d18591957651..8d594e61bb45cce026b617b93b2dd0b2b1b8eb16 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 54765e86f25ab1a6ec734ea478834a28940bce4b..dee7caba99225f0a26bb3d3cb9b79c7e2b9f60aa 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[];
     };
@@ -4797,6 +4793,7 @@ export type operations = {
             hiddenTags: string[];
             blockedHosts: string[];
             sensitiveWords: string[];
+            prohibitedWords: string[];
             bannedEmailDomains?: string[];
             preservedUsernames: string[];
             bubbleInstances: string[];
@@ -8818,6 +8815,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;
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 913f0ec2e1b1fe758ab4ef5fa78a29417e14e8bb..2c15690f933eea74b39bdf22c7379af1841385d7 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -116,8 +116,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
@@ -161,14 +161,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
@@ -559,8 +559,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
@@ -656,7 +656,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
@@ -707,13 +707,13 @@ 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
       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
@@ -838,8 +838,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.17)(sass@1.70.0)(terser@5.27.0)
       vue:
         specifier: 3.4.15
         version: 3.4.15(typescript@5.3.3)
@@ -891,7 +891,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
@@ -906,7 +906,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)
@@ -923,8 +923,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
@@ -962,8 +962,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
@@ -986,17 +986,17 @@ 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
       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
@@ -1105,7 +1105,7 @@ importers:
         version: 9.0.0(eslint@8.54.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-worker:
         specifier: ^29.7.0
         version: 29.7.0
@@ -1185,7 +1185,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)
@@ -1196,8 +1196,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)
@@ -1209,7 +1209,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
@@ -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:
@@ -2281,6 +2281,14 @@ packages:
     hasBin: true
     dependencies:
       '@babel/types': 7.23.4
+    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.4
 
   /@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.23.3(@babel/core@7.23.3):
     resolution: {integrity: sha512-iRkKcCqb7iGnq9+3G6rZ+Ciz5VywC4XNRHe57lKM+jOeYAoR0lVqdeeDRfh0tQcTfw/+vBhHn926FmQhLtlFLQ==}
@@ -3261,13 +3269,6 @@ packages:
     dependencies:
       regenerator-runtime: 0.13.11
 
-  /@babel/runtime@7.23.2:
-    resolution: {integrity: sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==}
-    engines: {node: '>=6.9.0'}
-    dependencies:
-      regenerator-runtime: 0.14.0
-    dev: true
-
   /@babel/runtime@7.23.4:
     resolution: {integrity: sha512-2Yv65nlWnWlSpe3fXEyX5i7fx5kIKo4Qbcj+hMO0odwaneFjfXw5fdum+4yL20O0QiaHpia0cYQ9xpNMqrBwHg==}
     engines: {node: '>=6.9.0'}
@@ -3295,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
@@ -3356,12 +3357,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:
@@ -4003,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
@@ -4020,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
@@ -4261,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
@@ -4317,7 +4312,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
@@ -4338,14 +4333,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.9.0
       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
@@ -4380,7 +4375,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
 
@@ -4406,7 +4401,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
@@ -4439,7 +4434,7 @@ packages:
       '@jest/transform': 29.7.0
       '@jest/types': 29.6.3
       '@jridgewell/trace-mapping': 0.3.20
-      '@types/node': 20.11.10
+      '@types/node': 20.11.17
       chalk: 4.1.2
       collect-v8-coverage: 1.0.2
       exit: 0.1.2
@@ -4532,7 +4527,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
@@ -4544,11 +4539,11 @@ packages:
       '@jest/schemas': 29.6.3
       '@types/istanbul-lib-coverage': 2.0.6
       '@types/istanbul-reports': 3.0.4
-      '@types/node': 20.11.10
+      '@types/node': 20.11.17
       '@types/yargs': 17.0.32
       chalk: 4.1.2
 
-  /@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'
@@ -4562,7 +4557,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.17)(sass@1.70.0)(terser@5.27.0)
     dev: true
 
   /@jridgewell/gen-mapping@0.3.3:
@@ -4655,24 +4650,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
@@ -4817,8 +4812,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
@@ -5751,7 +5746,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': '*'
@@ -5759,7 +5754,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
@@ -5803,8 +5798,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
@@ -5813,15 +5808,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:
@@ -6579,7 +6574,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': '*'
@@ -6611,7 +6606,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.17)(sass@1.70.0)(terser@5.27.0)
     transitivePeerDependencies:
       - encoding
       - supports-color
@@ -6968,7 +6963,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:
@@ -6976,16 +6971,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.17)(sass@1.70.0)(terser@5.27.0)
     transitivePeerDependencies:
       - '@preact/preset-vite'
       - encoding
@@ -7100,18 +7095,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.17)(sass@1.70.0)(terser@5.27.0)
       vue-docgen-api: 4.64.1(vue@3.4.15)
     transitivePeerDependencies:
       - '@preact/preset-vite'
@@ -7581,7 +7576,7 @@ packages:
     engines: {node: '>=14'}
     dependencies:
       '@babel/code-frame': 7.23.4
-      '@babel/runtime': 7.23.2
+      '@babel/runtime': 7.23.4
       '@types/aria-query': 5.0.1
       aria-query: 5.1.3
       chalk: 4.1.2
@@ -7609,7 +7604,7 @@ packages:
         optional: true
     dependencies:
       '@adobe/css-tools': 4.3.1
-      '@babel/runtime': 7.23.2
+      '@babel/runtime': 7.23.4
       '@types/jest': 28.1.3
       aria-query: 5.1.3
       chalk: 3.0.0
@@ -7636,7 +7631,7 @@ packages:
       '@vue/compiler-sfc': '>= 3'
       vue: '>= 3'
     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)
@@ -7672,7 +7667,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:
@@ -7726,7 +7721,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:
@@ -7738,14 +7733,14 @@ packages:
     dependencies:
       '@types/http-cache-semantics': 4.0.4
       '@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:
@@ -7771,7 +7766,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:
@@ -7789,7 +7784,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:
@@ -7841,7 +7836,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
@@ -7862,7 +7857,7 @@ 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/form-data@2.5.0:
@@ -7876,13 +7871,13 @@ packages:
     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.9:
     resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==}
     dependencies:
-      '@types/node': 20.11.10
+      '@types/node': 20.11.17
     dev: true
 
   /@types/http-cache-semantics@4.0.4:
@@ -7891,7 +7886,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:
@@ -7944,10 +7939,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
@@ -7955,7 +7946,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
@@ -7979,7 +7970,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:
@@ -8023,7 +8014,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
     dev: true
 
@@ -8037,8 +8028,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
 
@@ -8057,7 +8048,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:
@@ -8074,13 +8065,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
 
   /@types/object-assign-deep@0.4.3:
     resolution: {integrity: sha512-d9Gxaj5j1hzrxJ61EFEg13B4g4FgrT/DYtcDWFXPehR8DF2SUZbVMFtZIs8exkVRiqrqBpdTc/lUUZjncsPpMw==}
@@ -8093,7 +8084,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
@@ -8117,7 +8108,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:
@@ -8147,7 +8138,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:
@@ -8161,7 +8152,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:
@@ -8186,7 +8177,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:
@@ -8264,19 +8255,19 @@ 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/ws@8.5.10:
     resolution: {integrity: sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==}
     dependencies:
-      '@types/node': 20.11.10
+      '@types/node': 20.11.17
 
   /@types/yargs-parser@21.0.0:
     resolution: {integrity: sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==}
@@ -8300,7 +8291,7 @@ packages:
     resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==}
     requiresBuild: true
     dependencies:
-      '@types/node': 20.11.10
+      '@types/node': 20.11.17
     dev: true
     optional: true
 
@@ -8321,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
@@ -8350,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
@@ -8379,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
@@ -8405,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:
@@ -8426,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:
@@ -8447,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:
@@ -8490,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
@@ -8510,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
@@ -8530,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
@@ -8564,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
@@ -8585,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
@@ -8606,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
@@ -8702,7 +8693,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:
@@ -8713,30 +8704,30 @@ packages:
       '@babel/plugin-transform-react-jsx-source': 7.19.6(@babel/core@7.23.3)
       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.17)(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.17)(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.17)(sass@1.70.0)(terser@5.27.0)
       vue: 3.4.15(typescript@5.3.3)
     dev: false
 
@@ -8818,23 +8809,24 @@ packages:
       path-browserify: 1.0.1
     dev: true
 
-  /@vue/compiler-core@3.3.12:
-    resolution: {integrity: sha512-qAtjyG3GBLG0chzp5xGCyRLLe6wFCHmjI82aGzwuGKyznNP+GJJMxjc0wOYWDB2YKfho7niJFdoFpo0CZZQg9w==}
+  /@vue/compiler-core@3.4.15:
+    resolution: {integrity: sha512-XcJQVOaxTKCnth1vCxEChteGuwG6wqnUHxAm1DO3gCz0+uXKaJNx8/digSz4dLALCy8n2lKq24jSUs8segoqIw==}
     dependencies:
-      '@babel/parser': 7.23.6
-      '@vue/shared': 3.3.12
+      '@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.15:
-    resolution: {integrity: sha512-XcJQVOaxTKCnth1vCxEChteGuwG6wqnUHxAm1DO3gCz0+uXKaJNx8/digSz4dLALCy8n2lKq24jSUs8segoqIw==}
+  /@vue/compiler-core@3.4.18:
+    resolution: {integrity: sha512-F7YK8lMK0iv6b9/Gdk15A67wM0KKZvxDxed0RR60C1z9tIJTKta+urs4j0RTN5XqHISzI3etN3mX0uHhjmoqjQ==}
     dependencies:
-      '@babel/parser': 7.23.6
-      '@vue/shared': 3.4.15
+      '@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
+    dev: true
 
   /@vue/compiler-core@3.4.3:
     resolution: {integrity: sha512-u8jzgFg0EDtSrb/hG53Wwh1bAOQFtc1ZCegBpA/glyvTlgHl+tq13o1zvRfLbegYUw/E4mSTGOiCnAJ9SJ+lsg==}
@@ -8846,29 +8838,29 @@ 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
 
+  /@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.15:
     resolution: {integrity: sha512-LCn5M6QpkpFsh3GQvs2mJUOAlBQcCco8D60Bcqmf3O3w5a+KWS5GvYbrrJBkgvL1BDnTp+e8q0lXCLgHhKguBA==}
     dependencies:
-      '@babel/parser': 7.23.6
+      '@babel/parser': 7.23.9
       '@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.5
+      magic-string: 0.30.7
       postcss: 8.4.33
       source-map-js: 1.0.2
 
@@ -8888,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
@@ -8925,13 +8917,13 @@ packages:
       '@vue/shared': 3.4.15
       vue: 3.4.15(typescript@5.3.3)
 
-  /@vue/shared@3.3.12:
-    resolution: {integrity: sha512-6p0Yin0pclvnER7BLNOQuod9Z+cxSYh8pSh7CzHnWNjAIP6zrTlCdHRvSCb1aYEx6i3Q3kvfuWU7nG16CgG1ag==}
-    dev: true
-
   /@vue/shared@3.4.15:
     resolution: {integrity: sha512-KzfPTxVaWfB+eGcGdbSf4CWdaXcGDqckoeXUh7SB3fZdEtzPCK2Vq9B/lRRL3yutax/LWITz+SwvgyOxz5V75g==}
 
+  /@vue/shared@3.4.18:
+    resolution: {integrity: sha512-CxouGFxxaW5r1WbrSmWwck3No58rApXgRSBxrqgnY1K+jk20F6DrXJkHdH9n4HVT+/B6G2CAn213Uq3npWiy8Q==}
+    dev: true
+
   /@vue/shared@3.4.3:
     resolution: {integrity: sha512-rIwlkkP1n4uKrRzivAKPZIEkHiuwY5mmhMJ2nZKCBLz8lTUlE73rQh4n1OnnMurXt1vcUNyH4ZPfdh8QweTjpQ==}
     dev: true
@@ -9052,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
 
@@ -9060,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
@@ -9456,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
@@ -9880,8 +9872,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
@@ -10060,8 +10052,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
@@ -10600,7 +10592,7 @@ packages:
       readable-stream: 3.6.2
     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
@@ -10609,7 +10601,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:
@@ -10856,6 +10848,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'}
@@ -10920,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==}
@@ -10933,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==}
@@ -11145,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
@@ -11462,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
@@ -11778,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
@@ -11825,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
@@ -11872,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
@@ -12524,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==}
@@ -13173,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
@@ -13212,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
@@ -13222,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
 
@@ -13231,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
@@ -13391,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
@@ -13815,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:
@@ -13869,7 +13911,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.5.1
@@ -13890,7 +13932,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
@@ -13904,10 +13946,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
@@ -13918,7 +13960,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:
@@ -13933,7 +13975,7 @@ packages:
       '@babel/core': 7.23.3
       '@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.23.3)
       chalk: 4.1.2
       ci-info: 3.9.0
@@ -14012,7 +14054,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
@@ -14041,7 +14083,7 @@ packages:
     dependencies:
       '@jest/types': 29.6.3
       '@types/graceful-fs': 4.1.9
-      '@types/node': 20.11.10
+      '@types/node': 20.11.17
       anymatch: 3.1.3
       fb-watchman: 2.0.2
       graceful-fs: 4.2.11
@@ -14100,7 +14142,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:
@@ -14108,7 +14150,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
 
@@ -14163,7 +14205,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
@@ -14194,7 +14236,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
@@ -14246,7 +14288,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.9.0
       graceful-fs: 4.2.11
@@ -14270,7 +14312,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
@@ -14289,13 +14331,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
@@ -14308,7 +14350,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
@@ -14355,11 +14397,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==}
 
@@ -14880,6 +14917,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
@@ -15298,17 +15341,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
@@ -15319,13 +15362,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
@@ -15333,7 +15374,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
@@ -16680,6 +16720,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'}
@@ -16763,8 +16811,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
@@ -17044,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
@@ -18013,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
@@ -18213,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
@@ -18366,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
@@ -19008,7 +19056,7 @@ packages:
       '@babel/core': 7.23.3
       bs-logger: 0.2.6
       fast-json-stable-stringify: 2.1.0
-      jest: 29.7.0(@types/node@20.11.10)
+      jest: 29.7.0(@types/node@20.11.17)
       jest-util: 29.7.0
       json5: 2.2.3
       lodash.memoize: 4.1.2
@@ -19271,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
@@ -19605,17 +19653,17 @@ 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
     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
-      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.17)(sass@1.70.0)(terser@5.27.0)
     transitivePeerDependencies:
       - '@types/node'
       - less
@@ -19631,8 +19679,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.17)(sass@1.70.0)(terser@5.27.0):
+    resolution: {integrity: sha512-STmSFzhY4ljuhz14bg9LkMTk3d98IO6DIArnTY6MeBwiD1Za2StcQtz7fzOUnRCqrHSD5+OS2reg4HOz1eoLnw==}
     engines: {node: ^18.0.0 || >=20.0.0}
     hasBin: true
     peerDependencies:
@@ -19659,9 +19707,9 @@ packages:
       terser:
         optional: true
     dependencies:
-      '@types/node': 20.11.10
+      '@types/node': 20.11.17
       esbuild: 0.19.11
-      postcss: 8.4.33
+      postcss: 8.4.35
       rollup: 4.9.6
       sass: 1.70.0
       terser: 5.27.0
@@ -19713,7 +19761,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
@@ -19723,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
@@ -19733,8 +19781,8 @@ 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-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
@@ -19750,6 +19798,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.6
+      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
+
   /vscode-oniguruma@1.7.0:
     resolution: {integrity: sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==}
     dev: true
@@ -19805,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
@@ -20329,11 +20413,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):